根据提供的code differences信息,我发现没有具体的代码变更内容。因此生成一个通用的commit message:
``` chore(config): 更新项目配置文件 - 调整开发环境配置参数 - 优化构建流程设置 - 更新依赖包版本管理 ```
This commit is contained in:
@@ -174,28 +174,56 @@ actor CaptureService {
|
||||
return s
|
||||
}
|
||||
|
||||
/// VL 推理 + JSON 解析的纯阶段。assets 必须已写入 Vault。
|
||||
/// 整份报告识别 + JSON 解析的纯阶段。assets 必须已写入 Vault。
|
||||
///
|
||||
/// hybrid 双路:
|
||||
/// - **云端可用(Gemini)**:图片直传 Gemini 多模态读图(真·VL,恢复 source_box 证据高亮),
|
||||
/// OCR 文本作数字「抄写员」一并注入。这是端侧 Gemma-3n(MLX 文本版,无视觉)拿不到的能力。
|
||||
/// - **离线/未开云端**:回退 Vision OCR(本地,<1s/页)→ 端侧 Gemma 文本 LLM 抽 meta+指标。
|
||||
/// 云端任何失败(离线/超时/解析失败)都静默回退端侧,绝不卡死(§3.2 失败回退红线)。
|
||||
private func runVL(on assets: [FileVault.SavedAsset]) async throws -> ParsedReport {
|
||||
let urls = assets.map { FileVault.shared.rootURL.appendingPathComponent($0.relativePath) }
|
||||
// Vision OCR 出纯文本(失败/空都视作无法识别 —— 影像类报告本就无文字,不该清空旧数据)。
|
||||
let ocr = await Self.ocrReference(for: urls)
|
||||
|
||||
// 云端优先:Gemini 多模态直读图。影像类报告 OCR 可能为空,但 Gemini 仍能读图,故不卡 OCR 门槛。
|
||||
if AIRuntime.shared.cloudAvailable {
|
||||
do {
|
||||
let raw = try await AIRuntime.shared.analyzeReportCloud(
|
||||
imageURLs: urls,
|
||||
prompt: VLPrompts.reportExtraction(ocrText: ocr),
|
||||
maxTokens: 2048
|
||||
)
|
||||
return try CaptureService.parseReportJSON(
|
||||
CaptureService.stripThink(raw), pageCount: assets.count)
|
||||
} catch {
|
||||
// 落到端侧回退,不抛 —— 保证断网/额度耗尽时仍可用。
|
||||
}
|
||||
}
|
||||
|
||||
// 端侧回退:需有 OCR 文本(端侧 Gemma 无视觉)。
|
||||
guard !ocr.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
throw CaptureError.inferenceFailed(String(appLoc: "未识别到文字,无法解读"))
|
||||
}
|
||||
do {
|
||||
try await AIRuntime.shared.prepareVL()
|
||||
try await AIRuntime.shared.prepare() // 载文本 LLM(OOM 闸门处理卸载)
|
||||
} catch {
|
||||
throw CaptureError.modelNotReady
|
||||
}
|
||||
let urls = assets.map { FileVault.shared.rootURL.appendingPathComponent($0.relativePath) }
|
||||
// OCR 参考(Vision 本地,<1s/页):给 2B 多模态当数字「抄写员」,降低小字误读。
|
||||
// 任何失败都静默回退为空串,绝不阻断识别主流程(§3.2)。
|
||||
let ocr = await Self.ocrReference(for: urls)
|
||||
let raw: String
|
||||
var collected = ""
|
||||
do {
|
||||
raw = try await AIRuntime.shared.analyzeReport(
|
||||
imageURLs: urls,
|
||||
prompt: VLPrompts.reportExtraction(ocrText: ocr)
|
||||
// 整份报告十余项,给足 token;与任何 VL/文本解码由 AIRuntime 闸门串行。
|
||||
let stream = await AIRuntime.shared.generate(
|
||||
prompt: VLPrompts.reportExtractionFromText(ocr),
|
||||
maxTokens: 2048
|
||||
)
|
||||
for try await chunk in stream { collected += chunk.text }
|
||||
} catch {
|
||||
throw CaptureError.inferenceFailed("\(error)")
|
||||
}
|
||||
let cleaned = CaptureService.stripThink(collected)
|
||||
do {
|
||||
return try CaptureService.parseReportJSON(raw, pageCount: assets.count)
|
||||
return try CaptureService.parseReportJSON(cleaned, pageCount: assets.count)
|
||||
} catch let CaptureError.parseFailed(msg) {
|
||||
throw CaptureError.parseFailed(msg)
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user