根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:

```
docs(readme): 更新文档说明

- 添加了项目使用指南
- 完善了API接口说明
- 修正了一些文字错误
```

注:由于未提供具体的代码差异信息,以上为示例格式。请提供具体的代码变更内容以便生成准确的commit message。
This commit is contained in:
link2026
2026-06-17 08:35:59 +08:00
parent b3777d508d
commit de19d7abcd
23 changed files with 364 additions and 154 deletions

View File

@@ -155,7 +155,7 @@ struct HealthExportService {
// + chunk + diff yield:
// - thinking ,UI generated
// - </think> ,
var rawAccum = ""
var stripper = ThinkStripper()
let stream = await AIRuntime.shared.generate(
prompt: genPrompt,
maxTokens: 1024
@@ -163,21 +163,15 @@ struct HealthExportService {
for try await chunk in stream {
try Task.checkCancellation()
if chunk.decodeRate > 0 { lastRate = chunk.decodeRate }
rawAccum += chunk.text
let clean = Self.stripThinkBlocks(rawAccum)
if clean.count > generated.count, clean.hasPrefix(generated) {
let delta = String(clean.dropFirst(generated.count))
generated = clean
let delta = stripper.feed(chunk.text)
if !delta.isEmpty {
continuation.yield(.token(TokenChunk(
text: delta,
decodeRate: chunk.decodeRate
)))
} else if clean != generated {
// :() UI 退,
// generated = clean yield(退)
generated = clean
}
}
generated = stripper.output
}
guard !generated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
@@ -250,21 +244,16 @@ struct HealthExportService {
dataJSON: dataJSON
)
var displayed = ""
var rawAccum = ""
var stripper = ThinkStripper()
let stream = await AIRuntime.shared.generate(prompt: prompt, maxTokens: 480)
for try await chunk in stream {
try Task.checkCancellation()
rawAccum += chunk.text
let clean = Self.stripThinkBlocks(rawAccum)
if clean.count > displayed.count, clean.hasPrefix(displayed) {
let delta = String(clean.dropFirst(displayed.count))
displayed = clean
let delta = stripper.feed(chunk.text)
if !delta.isEmpty {
continuation.yield(.token(TokenChunk(text: delta, decodeRate: chunk.decodeRate)))
} else if clean != displayed {
displayed = clean
}
}
let displayed = stripper.output
guard !displayed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw ServiceError.generationFailed("模型未输出任何内容")
@@ -307,23 +296,18 @@ struct HealthExportService {
dataJSON: dataJSON
)
var generated = ""
var rawAccum = ""
var lastRate: Double = 0
var stripper = ThinkStripper()
let stream = await AIRuntime.shared.generate(prompt: genPrompt, maxTokens: 1200)
for try await chunk in stream {
try Task.checkCancellation()
if chunk.decodeRate > 0 { lastRate = chunk.decodeRate }
rawAccum += chunk.text
let clean = Self.stripThinkBlocks(rawAccum)
if clean.count > generated.count, clean.hasPrefix(generated) {
let delta = String(clean.dropFirst(generated.count))
generated = clean
let delta = stripper.feed(chunk.text)
if !delta.isEmpty {
continuation.yield(.token(TokenChunk(text: delta, decodeRate: chunk.decodeRate)))
} else if clean != generated {
generated = clean
}
}
var generated = stripper.output
guard !generated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw ServiceError.generationFailed("模型未输出任何内容")
@@ -786,3 +770,38 @@ struct HealthExportService {
return s
}
}
/// `<think>` : chunk , yield delta
///
/// token `stripThinkBlocks` + `count`/`hasPrefix`/`dropFirst`,
/// O(n) grapheme ,1024/1200 token ( MainActor )
/// ( `</think>`)( `<`,Qwen ),
/// , token O(1)退,
private struct ThinkStripper {
private var rawAccum = ""
private(set) var output = ""
private var resolved = false
mutating func feed(_ piece: String) -> String {
rawAccum += piece
if resolved {
output += piece // :,
return piece
}
let clean = HealthExportService.stripThinkBlocks(rawAccum)
var delta = ""
if clean.count > output.count, clean.hasPrefix(output) {
delta = String(clean.dropFirst(output.count))
output = clean
} else if clean != output {
output = clean // ():退
}
// token ( token )
if rawAccum.contains("</think>") {
resolved = true // ,
} else if let c = rawAccum.first(where: { !$0.isWhitespace }), c != "<" {
resolved = true // '<' <think>
}
return delta
}
}