fix(polish): 对话感知上下文——按风格包隔离 + 翻译会话参与润色(不串味)#583
Conversation
对话感知 polish 回看最近历史作为 LLM 上下文时,之前复用所有成功会话,导致两类串味: 1. 切换风格包后,旧风格包的历史仍被带进新提示词,把当前风格带偏; 2. 翻译会话的译文/指令污染下一段普通润色(如中文输入被带成英文)。 给 DictationSession 增加 style_pack_id / translation_active 两字段(serde default 向后兼容旧历史),抽出 eligible_polish_context_turns 过滤:仅复用同一 active 风格包、且非翻译的成功会话;旧历史无 style_pack_id 时保守排除。前后端类型同步,补两条单测。
PR Reviewer Guide 🔍(Review updated until commit 4c31448)Here are some key observations to aid the review process:
|
接续上一条「按风格包隔离」,把「翻译会话直接排除出上下文」改成「保留但按需剔除」:翻译会话也参与对话连续性,又不让外语译文污染普通润色。 - 翻译改为单次 LLM 调用同时润色源文 + 翻译:polish_and_translate_or_passthrough 用合成系统提示词(翻译规则 + 追加输出润色后源文 + 两段哨兵)走现成 polish() 通道,复用对话上下文;解析失败 / 译文为空则退回专用 translate_text 拿干净译文(不丢字)。 - DictationSession 加 polish_source(serde default 兼容旧历史),存润色后的源语言文本。 - eligible_polish_context_turns 加 current_translation_active 分流:翻译历史在普通轮喂 polish_source(无则跳过=剔除译文),翻译轮喂 final_text;普通历史始终喂 final_text。 - 前后端类型同步(polishSource);补解析器 + 上下文分流单测,cargo test --lib 371 全过。
|
Persistent review updated to latest commit bff4bd7 |
|
@claude 请审核这个 PR(目标合并到 beta)。重点:
|
|
Claude finished @appergb's task in 3m 52s —— View job PR #583 代码审核审核任务
审核结论🟢 可安全合并到 beta。无 CRITICAL / HIGH 问题。 1. UTF-8 安全性:
|
| 历史类型 | 当前轮 | 喂入 | 结果 |
|---|---|---|---|
| 翻译 | 普通 | polish_source(缺失则跳过) |
✅ |
| 翻译 | 翻译 | final_text(译文) |
✅ |
| 普通 | 普通 | final_text(润色结果) |
✅ |
| 普通 | 翻译 | final_text(源语言润色结果) |
✅ |
filter(|s| s.error_code.is_none()) 同时排除了 fallback 路径下解析失败 + 退回专用翻译但仍成功的会话中 polish_source == None 的情况(这类会话 error_code 为空但 polish_source 缺失——但该会话仍通过前两层 filter,进入 Case 1 后 polish_source 为 None → 整条 filter_map 返回 None 跳过)。逻辑正确。
LOW:Case 4(普通历史 + 翻译轮)缺少单测。当前测试只覆盖 Case 1/2/3。建议补一个
fn translation_turn_keeps_normal_history_final_text()固化此行为。Fix this →
3. 解析失败兜底的健壮性(coordinator.rs:2980)
结论:完全健壮,满足「不丢字」约定,无重复/死循环风险。
polish_text (combined LLM call)
├─ Ok(out) → split_polish_translate_output(out)
│ ├─ Some(source, translation) → ✅ 返回译文 + 源文
│ └─ None (格式不符) → translate_text (独立翻译)
│ ├─ Ok(translation) → ✅ 返回译文(polish_source = None)
│ └─ Err → ✅ 返回 raw.text + polish_error(报 polishFailed)
└─ Err → ✅ 返回 raw.text + polish_error(报 polishFailed)
各路径分析:
- Happy path:
translation来自解析结果,split_polish_translate_output已保证非空(空时返回None),不会把空串插入光标 ✓ - Fallback 路径:
translate_text是一次无prior_turns的独立调用,正确(combined call 格式失败时不应相信上下文一致性)✓ - Fallback 双重失败:返回
raw.text+Some(reason),写入历史时error_code = Some("polishFailed"),前端显示"润色失败,已插入原文" ✓ - 无递归/无循环:两层顺序调用,完全有界 ✓
LOW(设计权衡):Fallback 路径下
translate_text不携带prior_turns,翻译质量可能略低于正常路径。这是有意设计(格式混乱时不信任上下文),可接受。
LOW(极低概率):解析失败触发 Fallback 时会发起第二次 LLM 调用,双倍 token 消耗。在 LLM 严格遵守格式提示词的情况下,此路径极少触发。
4. DictationSession 构造点完整性
结论:全部 4 处构造点已补齐新字段。
| 文件 | 行 | 场景 | style_pack_id |
translation_active |
polish_source |
|---|---|---|---|---|---|
dictation.rs:1470 |
空转写 | None |
false |
None |
✅ |
dictation.rs:1793 |
正常会话 | Some(pack.id) |
translation_active |
polish_source |
✅ |
coordinator.rs:3656 |
QA placeholder | None |
false |
None |
✅ |
dictation.rs:1977 |
测试辅助函数 | 参数传入 | 参数传入 | 参数传入 | ✅ |
5. 前后端 camelCase 一致性
结论:完全一致。
// types.ts
export interface DictationSession {
stylePackId: string | null; // ← Rust style_pack_id ✓
translationActive: boolean; // ← Rust translation_active ✓
polishSource: string | null; // ← Rust polish_source ✓
}// ipc.ts mock
polishSource: null, // ✓ 新字段已加入 mock 历史数据
translationActive: false, // ✓
stylePackId: "builtin.structured", // ✓Rust 侧 #[serde(rename_all = "camelCase")] + #[serde(default)] 保证新字段序列化正确且向后兼容旧 JSON。✅
分级汇总
| 级别 | 发现 |
|---|---|
| CRITICAL | 无 |
| HIGH | 无 |
| MEDIUM | 无 |
| LOW | ① Case 4 缺测试 ② Fallback 无 prior_turns(有意设计)③ 哨兵字面量碰撞(概率极低) |
总体评价
三个核心审核点全部通过:UTF-8 安全、上下文分流逻辑正确、fallback 无漏洞。新字段在所有构造点已补齐,前后端类型一致。370 个测试覆盖主要路径。建议合并。
|
Persistent review updated to latest commit 4c31448 |
|
✅ 第二轮复审(rust-reviewer agent):无 CRITICAL/HIGH。UTF-8 切片安全、四象限分流无译文泄漏、不丢字兜底无重入、构造点字段齐全、serde camelCase 与前端一致。仅 2 个 MEDIUM(可维护性,后续跟进)。批准合并。 |
User description
本 PR 两个 commit,围绕「对话感知 polish」的上下文复用做隔离与扩展。
commit 1 · 按风格包隔离 + (初版)排除翻译
对话感知 polish 回看最近历史当上下文时,原先复用所有成功会话,导致串味。第一版:
style_pack_id保守排除)。DictationSession加style_pack_id/translation_active(serde(default)兼容旧历史)。commit 2 · 翻译会话参与润色(取代「直接排除」)
按需求把「翻译会话直接排除」改成「参与上下文但按轮剔除译文」:
polish()通道(复用 prior_turns 上下文);解析两段。解析失败 → 退回专用翻译拿干净译文(不丢字、不插畸形文本)。polish_source(翻译会话润色后的源语言文本,serde(default))。polish_source(剔除译文;源文缺失则跳过),在翻译轮喂final_text(译文);普通历史始终喂final_text。polishSource)。验证
cargo test --lib全过(370 通过,含 9 个新单测:解析器、上下文分流、风格包重置等)。tsc --noEmit通过;cargo clippy无新增告警。行为权衡
升级后存量历史缺
style_pack_id会被排除在上下文外,直到新历史积累——对话感知短暂退化为单轮,是有意的保守策略。PR Type
Enhancement, Bug fix
Description
Isolate polish context by style pack to prevent cross-pack leakage
Include translation sessions with source-only text in normal polish turns
Combine polish and translation into single LLM call with fallback to plain translation
Add new fields and frontend types for context persistence
Diagram Walkthrough
flowchart LR A[Dictation Session] --> B{Style pack match?} B -- yes --> C{Translation active?} B -- no --> D[Skip session] C -- yes (next also translation) --> E[Use final_text (translation)] C -- yes (next is normal polish) --> F{polish_source exists?} F -- yes --> G[Use polish_source] F -- no --> H[Skip session] C -- no (normal history) --> I[Use final_text (polished source)]File Walkthrough
coordinator.rs
Add combined polish+translate LLM call and marker parsingopenless-all/app/src-tauri/src/coordinator.rs
polish_and_translate_or_passthroughwith fallback to plaintranslate
translate_or_passthroughfunctiondictation.rs
Filter context turns by style pack and translation statusopenless-all/app/src-tauri/src/coordinator/dictation.rs
eligible_polish_context_turnsto filter turns by stylepack and translation mode
polish_sourcefor future context usetypes.rs
Add polish context fields to DictationSessionopenless-all/app/src-tauri/src/types.rs
style_pack_id,translation_active,polish_sourcefields toDictationSession#[serde(default)]for backward compatibilitydefault_style_system_prompt_for_modetypes.ts
Add context fields to TypeScript typesopenless-all/app/src/lib/types.ts
stylePackId,translationActive,polishSourcefields toDictationSessioninterfaceipc.ts
Update mock history with new context fieldsopenless-all/app/src/lib/ipc.ts
stylePackId,translationActive,polishSourceinmock history