Files
openless/Sources/OpenLessPolish/PolishOutputCleaner.swift
T
baiqing d8fb309feb feat(persistence): generalize providers.llm + multi-provider vault API
将 v1 schema 中 `providers.llm` 从 Ark 专用结构推广为通用
`CredentialsProviderLLMEntry`(containing displayName / temperature / extraHeaders),
为 OpenAI / DeepSeek / DashScope / Moonshot 等多 LLM provider 共用一套字段。

旧 `CredentialsProviderLLMArk` 保留为 typealias,已迁移过的 v1 文件无需再次迁移即可继续解码。

新增公开 API:
- `CredentialsVault.activeLLMProviderId` getter/setter
- `CredentialsVault.configuredLLMProviderIds`
- `CredentialsVault.llmProviderConfig(for:)`
- `CredentialsVault.setLLMProviderConfig(_:)`
- `CredentialsVault.removeLLMProvider(_:)`,禁删 active provider

`OpenLessCore` 新增 `LLMProviderRegistry`,集中维护 5 家 OpenAI 兼容供应商
预设(baseURL / model / 帮助文案 / docs URL),UI 与 vault 兜底逻辑共用。

`PolishOutputCleaner` 从 DoubaoPolishClient 内部提升为独立 public 类型,
让 `OpenAICompatibleLLMProvider` 输出统一过 boilerplate / markdown 栅栏清理。

DoubaoPolishClient.swift 已无调用方,整个文件删除。

Tests:
- LLMProviderRegistryTests:5 家预设健康检查 + 唯一性 / URL 合法性
- CredentialsVaultMultiProviderTests:active 默认 / 增删 / 往返 /
  老 `ark.*` 账号 key 路由到 active provider
2026-04-29 11:54:11 +08:00

49 lines
2.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// 清理 LLM 返回内容里常见的"引导套话"和 markdown 代码栅栏。
///
/// 即使我们在 prompt 里明确禁止"根据您给的内容,整理如下"等开场白,仍有部分模型会顽固加上;
/// 在客户端再扫一遍确保插入到光标位置的就是干净正文。
///
/// 这是公共类型——之前藏在 DoubaoPolishClient 内部,B-3 抽成独立文件后被
/// `OpenAICompatibleLLMProvider` 复用,并保留 `PolishOutputCleanerTests` 直接测试入口。
public enum PolishOutputCleaner {
/// 已知会被加在正文最前面的引导套话正则集合(顺序无关,循环匹配)。
private static let leadingBoilerplatePatterns: [String] = [
#"(?s)^根据[你您]?(?:给的|提供的)?内容[,,\s]*(?:我)?(?:已经|已)?(?:整理|优化)(?:好|完成)?(?:如下)?[::\s]*"#,
#"(?s)^以下(?:是|为)?(?:整理|优化|结构化整理)后?的?内容(?:如下)?[::\s]*"#,
#"(?s)^(?:整理|优化|结构化整理)(?:后?的?内容)?(?:如下)?[::\s]*"#,
]
public static func clean(_ content: String) -> String {
var output = content.trimmingCharacters(in: .whitespacesAndNewlines)
output = stripMarkdownFence(from: output)
// 多模型可能叠加多次套话;循环到不再变化为止。
var changed = true
while changed {
changed = false
for pattern in leadingBoilerplatePatterns {
if let range = output.range(of: pattern, options: .regularExpression),
range.lowerBound == output.startIndex {
output.removeSubrange(range)
output = output.trimmingCharacters(in: .whitespacesAndNewlines)
changed = true
}
}
}
return output
}
/// 去掉首尾的 ```...``` 围栏(含语言标记行如 ```text)。
private static func stripMarkdownFence(from text: String) -> String {
guard text.hasPrefix("```"), text.hasSuffix("```") else { return text }
var lines = text.components(separatedBy: .newlines)
guard lines.count >= 2 else { return text }
lines.removeFirst()
lines.removeLast()
return lines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
}
}