VoiceInput/Sources/Core/LLMRefiner.swift
JOJO 1d9c9d5fa0 feat: macOS 菜单栏语音输入应用初始版本
- CGEvent cghidEventTap 全局监听 Fn 键
- Apple Speech 流式语音识别(zh-CN 默认)
- 实时音频 RMS 电平驱动波形动画
- NSPanel 胶囊浮动窗口
- 剪贴板 + Cmd+V 文本注入,CJK 输入法自动切换
- OpenAI 兼容 LLM 纠错(DeepSeek 预设)
- LSUIElement 菜单栏运行
- SPM + Makefile 构建
2026-04-24 16:17:57 +08:00

98 lines
4.2 KiB
Swift

import Foundation
/// OpenAI API
final class LLMRefiner {
private let session: URLSession = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15
config.timeoutIntervalForResource = 20
return URLSession(configuration: config)
}()
///
struct RefinementResult {
let refinedText: String
let wasChanged: Bool
}
///
/// - Parameters:
/// - text:
/// - config: LLM
/// - Returns:
func refine(text: String, config: Config) async throws -> RefinementResult {
let baseURL = config.llmBaseURL
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
let url = URL(string: "\(baseURL)/v1/chat/completions")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("Bearer \(config.llmAPIKey)", forHTTPHeaderField: "Authorization")
let systemPrompt = """
你是一个语音识别后处理工具。你唯一的任务是修正转录文本中的明显识别错误。规则:
1. 修正中文同音字/词错误。语音识别经常把技术词汇识别为同音的日常词汇,例如:
「变异」→「编译」、「程序圆」→「程序员」、「配森」→「Python」。
请根据上下文判断修正方向,将同音错误还原为正确的词汇。
2. 修正被错误转录为中文的英文技术术语。
3. 修正极其明显的中文和英文拼写错误(包括错别字、漏字、多字)。
4. 修正明显的乱码或无意义字符序列。
5. 绝不重写、润色、改进或修改任何看起来正确的内容。
6. 如果输入本身正确,请原样返回,不做任何修改。
7. 保留所有标点符号、空格、大小写和格式。
8. 只返回修正后的文本,不要添加任何解释、前缀、后缀或 Markdown 标记。
"""
let messages: [[String: String]] = [
["role": "system", "content": systemPrompt],
["role": "user", "content": text]
]
let body: [String: Any] = [
"model": config.llmModel,
"messages": messages,
"max_tokens": 4096,
"thinking": ["type": "disabled"]
]
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await session.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw NSError(domain: "LLMRefiner", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
}
guard httpResponse.statusCode == 200 else {
let body = String(data: data, encoding: .utf8) ?? "Unknown"
throw NSError(domain: "LLMRefiner", code: httpResponse.statusCode,
userInfo: [NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode): \(body)"])
}
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let firstChoice = choices.first,
let message = firstChoice["message"] as? [String: Any],
let refined = message["content"] as? String else {
throw NSError(domain: "LLMRefiner", code: -2,
userInfo: [NSLocalizedDescriptionKey: "Failed to parse response"])
}
let trimmed = refined.trimmingCharacters(in: .whitespacesAndNewlines)
let wasChanged = trimmed != text.trimmingCharacters(in: .whitespacesAndNewlines)
print("[LLM] Refinement \(wasChanged ? "changed" : "unchanged"): \"\(trimmed.prefix(80))...\"")
return RefinementResult(refinedText: trimmed, wasChanged: wasChanged)
}
///
func testConnection(config: Config) async throws -> String {
let testText = "测试连接"
let result = try await refine(text: testText, config: config)
return result.refinedText
}
}