Skip to content

fix: 护栏拒绝时保留原始转写,不再输出空串 - #97

Merged
IchenDEV merged 1 commit into
mainfrom
agent/qa/guard-fallback-preserve-source
Sep 18, 2026
Merged

IchenDEV merged 1 commit into
mainfrom
agent/qa/guard-fallback-preserve-source

Conversation

@IchenDEV

Copy link
Copy Markdown
Owner

背景

TextProcessor.process 在 TranscriptFidelityGuard.violation(...) 命中时调用 rejectedOutputFallback(cleanedText, allowsGuardFallback:);当 allowsGuardFallback == false 时原实现返回空串(TextProcessor+Output.swift:95-100)。

可达性确认

可达。 唯一传 allowsGuardFallback: false 的调用点是延迟智能整理路径 VoicePipeline+Replacement.swift:234-236(即时插入后后台重排)。该路径先通过 immediateInsertText 插入了 prepareForFormatting(raw) 的结果,再由后台 LLM 重排;fidelityPolicy 默认为 .faithfulCorrection,护栏启用完整语义校验。以下拒绝原因在正常使用中都能触发(测试逐条复现):

触发条件 violation
LLM 把口述压缩成一句话(source 单元 ≥ 6,candidate < 55%) excessive_deletion
LLM 补出原文没有的内容(candidate > source×1.35+3) excessive_expansion
LLM 改了原文中的受保护 token(数字/URL/路径/邮箱) protected_token_change
LLM 概括改写导致语义漂移 content_drift

原行为:process 返回 "" → finishDeferredSmartFormat 的 guard !formattedText.isEmpty 判定为失败,标记 pipeline.formatting_failed,用户看到「整理失败」。转写本身完好,但结果被整段清空/判失败。

改动

Sources/Processing/TextProcessor+Output.swift:rejectedOutputFallback 不再返回空串,恒定返回 cleanedText(已做词典替换与规范化的原始转写)。

  • 被拒绝的 candidate 仍然被丢弃,不会引入幻觉;来源文本不可能是模型补出的内容。
  • 该路径下 cleanedText 与即时插入的 quickText 同为 prepareForFormatting(raw),因此延迟替换是内容保持的(把同一段文字标记为已整理,而不是清空)。
  • allowsGuardFallback 参数保留,用于表达调用点意图;文档注明它不再抑制来源回退。
  • TranscriptFidelityGuard 的所有启发式与判定完全未改。仅把拒绝日志从单行扩展为记录原因 + source/candidate 字数(只做记录)。
  • LLM 调用失败(非护栏拒绝)路径不受影响,仍按 allowsPreparedFallback 处理。

测试

Tests/OpenTypeTests/TextProcessorFallbackTests.swift:

  • 原 testRejectedOutputFallbackRespectsPreparedFallbackPolicy(断言 false → 空串)按新语义改写为 testRejectedOutputFallbackAlwaysKeepsSourceTranscript。
  • 新增:excessive_deletion、protected_token_change、excessive_expansion 三种护栏拒绝下,allowsGuardFallback: false 的输出非空、等于来源转写、且不含被拒候选的内容(不引入幻觉)。

结果:

  • swift build → Build complete! (3.72 secs)
  • swift test --filter 'TextProcessorFallbackTests' → 14 passed / 0 failures
  • swift test --filter 'Processing|Prompt|Fidelity|Sanitizer|Dictionary|Lexicon|Text|Translation|QualityProbe|Guard|Output|Replacement' → 327 passed / 2 skipped / 0 failures

范围

仅 3 个文件;未改护栏判定、未改阈值、未改其它回退语义。

The deferred instant-insert path calls process(allowsGuardFallback: false), and a guard rejection then returned an empty string, so the background Smart Format was marked failed even though the dictation was valid. Guard rejections are reachable in normal use via protected_token_change, excessive_deletion, excessive_expansion and content_drift.

rejectedOutputFallback now always returns the prepared source transcript. The rejected candidate is still discarded, so no hallucinated content can enter, and the source equals the text already inserted by instant insert, so the deferred replacement is content-preserving. Guard heuristics and their decisions are unchanged; the rejection log now records the reason and both lengths.

Co-authored-by: multica-agent <github@multica.ai>
@IchenDEV
IchenDEV merged commit f50c488 into main Sep 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant