Skip to content

[polish] 对话感知润色在本地小参数模型上会将上一轮历史内容输出为当前结果 #1030

Description

@agent-labs1998

现象

相关背景:#678 解决了历史上下文无上限导致的润色延迟问题,#693 合并后当前 beta 代码最多保留 2 轮历史。本 issue 关注的是:即使只有 1 轮历史,部分本地小参数模型仍会把历史内容输出为当前结果,属于结果正确性问题。

环境与测试模型

  • OpenLess 1.3.18-Beta.7(Windows),Ollama 0.33.2(localhost,模型常驻)
  • 本地前后共测试 9 个模型,串线现象跨模型出现,形态随模型不同:
模型 参数量 证据来源 观察到的形态
lfm2.5-voice-8k:latest 1.2B 受控复现 3/3 + 生产日志 复读上一轮结果
LiquidAI/lfm2.5-1.2b-instruct:q8_0 1.2B 受控对照 0/11 未触发(对照组)
qwen2.5:1.5b 1.5B 生产日志 把转写当聊天回答
qwen2.5:3b 3B 生产日志 对话式跑题 / 评论润色任务本身
hf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_M 2.6B 日常使用观察 串线/混合
hf.co/tencent/Youtu-LLM-2B-GGUF:F16 2B 日常使用观察 串线/混合
qwen3.5:2b-q8_0 2.3B 受控测试 非流式响应 content 为空(独立问题,见 TODO)
qwen3.5:4b 4.7B 日常使用观察 串线/混合
gemma4:12b 11.9B 日常使用观察 偶发,程度较轻

受控复现数据目前覆盖前 4 行;其余为日常使用观察,未逐一做受控量化。

触发条件

默认配置即可满足,共三个:

  1. 润色走 custom 渠道本地模型(Ollama 等);
  2. polishContextWindowMinutes > 0(默认值 5);
  3. 窗口内存在上一条成功润色的会话(日志字段 prior_turns >= 1)。

当前表现

当前这段转写的润色结果被上一轮会话内容替换,三种形态:

  • 复读上一轮:当前输入 104 字(直播带货内容),返回的是上一条 16 字录音(买机票)的润色结果;
  • 把转写当聊天回答:输出「你去搜索」「帮我解决」等对话式内容,完全脱离润色任务;
  • A/B 混合:当前内容与上一轮内容拼合输出。

同一文本点击「重新润色」立即恢复正常——重润色路径不携带历史,可稳定反证。

证据链(排查全程)

  1. 排除 Ollama 本体:绕过客户端直连 localhost:11434/api/chat/v1/chat/completions 双端点,串行 24 轮 + 最高 8 路并发,45+ 次请求),messages 仅 system + 当前文本时零串线。keep-alive、模型常驻、KV 缓存、并发请求映射全部排除。

  2. 定位客户端机制:源码确认润色请求默认携带历史——openless-all/app/src-tauri/src/coordinator/dictation.rs 拉取最近 N 分钟会话(当前上限 2 轮),openless-all/app/src-tauri/src/polish.rsbuild_polish_history_messages 将历史展开为真实 user/assistant 消息对。polish.rs 中虽有「不要复读历史」的 system 指令,但小参数模型服从不了。

  3. API 层稳定复现:按源码 1:1 模拟请求形态(system + 历史对 + 当前 user),lfm2.5-voice-8k 3/3 稳定输出历史轮内容、完全无视当前输入;其母模型 0/11 未触发。证实触发概率取决于「模型权重 × 历史内容组合」——与「跨模型出现、时有时无」的实际体验一致。

  4. 生产日志实锤%LOCALAPPDATA%\OpenLess\Logs\openless.log):

    时间 (UTC) 场景 raw_chars prior_turns 响应开头 判定
    09-04 10:19:16 记录 B 自动润色 16 0 正常 B 本身是该主题
    09-04 10:20:41 记录 A 自动润色 104 1 「去…」 输出上一条 B 的结果
    09-04 10:33:47 记录 A 手动重润色 104 0 「有…」 恢复正常(A 原文开头)

    三行中同模型、同模式、A 两行同输入文本,唯一实质差异是 prior_turns;prompt 长度差 4028-3840=188 字符,与一轮历史(原文 16 字 + 润色结果 + 信封包装)体量吻合。

  5. 污染面量化:该日志共 362 次 LLM 润色调用,其中 76 次(21%)prior_turns>=1,观察到的串线事故全部落在其中。

临时解决(已执行,已止血)

已在「设置 → 隐私 → 数据存储 → 对话上下文窗口(分钟)」将默认 5 改为 0。改后所有润色请求恢复 stateless(messages 仅 system + 当前转写),经连续口述验证串线不再出现。

这是配置层面的规避:历史面板、手动重润色等本地功能不受影响,代价是连续口述时代词指代无法看到前文。该参数无渠道粒度,对所有 provider(含云端)一刀切关闭,这正是希望官方改进的部分。

相关 issue

影响

  • 数据正确性:默认配置下,本地小参数模型用户会静默拿到错误的润色结果——当前记录被写入上一条的内容,无任何提示或标记。对以转写正确性为核心卖点的语音输入产品,属于核心功能产出错误数据。
  • 排查成本极高:现象酷似「模型幻觉 / 服务串线」,用户第一反应是换模型、查 Ollama(本次排查先后测试 9 个模型、排查过 keep-alive 与 KV 缓存后才锁定客户端),且该场景发生在产品明确支持的 custom + Ollama 主路径上。
  • 风险不对称无提示:携带历史对强云端模型是正向功能,对小参数本地模型是污染源;设置项描述(「把最近 N 分钟内已润色的转写作为多轮上下文,0 = 关闭」)无任何风险说明,用户无从判断该不该开。

建议接受标准

运维侧目前用「全局窗口 = 0」规避,但这是临时方案——一刀切放弃了所有渠道的上下文能力。希望官方针对**自定义模型渠道(尤其 Ollama 这类本地推理)**做系统性优化,按优先级:

  • 渠道粒度上下文开关polishContextWindowMinutes 支持按 provider / 渠道维度配置。本地 custom 渠道(Ollama 等)可独立关闭或调小,不影响内置云渠道;UI 上对本地渠道给出风险提示(「小参数模型可能将历史内容带入当前结果」)。
  • 默认值调整:默认窗口 5 → 0(上下文显式 opt-in),或至少 custom 渠道默认 0、内置云渠道维持 5。
  • 结果守卫:润色返回后校验——若输出与任一历史消息(assistant 润色结果或 user 原始转写,两者实测均可被复读)高度相似、而与当前输入差异大,自动去历史重发一次单轮请求。仅坏 case 触发,正常路径零额外延迟。
  • 结构性改写(治本,可选):历史不再展开为 user/assistant 消息对,改为前文纯文本参考块并入 system prompt,chat 结构只保留一条 user 消息——消除「上一条 assistant 消息」这一复读目标,同时保留语境能力。

验证方式:lfm2.5-voice-8k + 受控复现请求(历史对 + 当前转写),连续 10 次含历史润色,0 次输出历史内容;同时云渠道功能回归正常。

TODO / 不确定项

  • 触发率与「模型 × 历史内容」组合强相关:同一模型对特定历史组 3/3 稳定复现,另一模型 0/11 未触发;受控量化目前覆盖 1.2B~3B 四个模型,其余为日常观察,未逐一量化。
  • 复读对象有两种形态:历史 assistant 润色结果、历史 user 原始转写(受控复现中均实测到),守卫比对需同时覆盖。
  • 划词语音问答的多轮历史为独立机制,未纳入本次排查。
  • Gemini 路径存在同款历史注入逻辑(build_polish_history_contents),未实测。
  • thinking 类模型非流式响应 content 为空(正文在 reasoning 字段)是另一个独立问题,建议另开 issue。
  • 复现脚本、raw 请求/响应日志、完整排查报告可按需提供。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions