现象
相关背景:#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 行;其余为日常使用观察,未逐一做受控量化。
触发条件
默认配置即可满足,共三个:
- 润色走 custom 渠道本地模型(Ollama 等);
polishContextWindowMinutes > 0(默认值 5);
- 窗口内存在上一条成功润色的会话(日志字段
prior_turns >= 1)。
当前表现
当前这段转写的润色结果被上一轮会话内容替换,三种形态:
- 复读上一轮:当前输入 104 字(直播带货内容),返回的是上一条 16 字录音(买机票)的润色结果;
- 把转写当聊天回答:输出「你去搜索」「帮我解决」等对话式内容,完全脱离润色任务;
- A/B 混合:当前内容与上一轮内容拼合输出。
同一文本点击「重新润色」立即恢复正常——重润色路径不携带历史,可稳定反证。
证据链(排查全程)
-
排除 Ollama 本体:绕过客户端直连 localhost:11434(/api/chat 与 /v1/chat/completions 双端点,串行 24 轮 + 最高 8 路并发,45+ 次请求),messages 仅 system + 当前文本时零串线。keep-alive、模型常驻、KV 缓存、并发请求映射全部排除。
-
定位客户端机制:源码确认润色请求默认携带历史——openless-all/app/src-tauri/src/coordinator/dictation.rs 拉取最近 N 分钟会话(当前上限 2 轮),openless-all/app/src-tauri/src/polish.rs 的 build_polish_history_messages 将历史展开为真实 user/assistant 消息对。polish.rs 中虽有「不要复读历史」的 system 指令,但小参数模型服从不了。
-
API 层稳定复现:按源码 1:1 模拟请求形态(system + 历史对 + 当前 user),lfm2.5-voice-8k 3/3 稳定输出历史轮内容、完全无视当前输入;其母模型 0/11 未触发。证实触发概率取决于「模型权重 × 历史内容组合」——与「跨模型出现、时有时无」的实际体验一致。
-
生产日志实锤(%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 字 + 润色结果 + 信封包装)体量吻合。
-
污染面量化:该日志共 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 这类本地推理)**做系统性优化,按优先级:
验证方式: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 请求/响应日志、完整排查报告可按需提供。
现象
环境与测试模型
lfm2.5-voice-8k:latestLiquidAI/lfm2.5-1.2b-instruct:q8_0qwen2.5:1.5bqwen2.5:3bhf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_Mhf.co/tencent/Youtu-LLM-2B-GGUF:F16qwen3.5:2b-q8_0content为空(独立问题,见 TODO)qwen3.5:4bgemma4:12b触发条件
默认配置即可满足,共三个:
polishContextWindowMinutes> 0(默认值 5);prior_turns >= 1)。当前表现
当前这段转写的润色结果被上一轮会话内容替换,三种形态:
同一文本点击「重新润色」立即恢复正常——重润色路径不携带历史,可稳定反证。
证据链(排查全程)
排除 Ollama 本体:绕过客户端直连
localhost:11434(/api/chat与/v1/chat/completions双端点,串行 24 轮 + 最高 8 路并发,45+ 次请求),messages仅 system + 当前文本时零串线。keep-alive、模型常驻、KV 缓存、并发请求映射全部排除。定位客户端机制:源码确认润色请求默认携带历史——
openless-all/app/src-tauri/src/coordinator/dictation.rs拉取最近 N 分钟会话(当前上限 2 轮),openless-all/app/src-tauri/src/polish.rs的build_polish_history_messages将历史展开为真实 user/assistant 消息对。polish.rs中虽有「不要复读历史」的 system 指令,但小参数模型服从不了。API 层稳定复现:按源码 1:1 模拟请求形态(system + 历史对 + 当前 user),
lfm2.5-voice-8k3/3 稳定输出历史轮内容、完全无视当前输入;其母模型 0/11 未触发。证实触发概率取决于「模型权重 × 历史内容组合」——与「跨模型出现、时有时无」的实际体验一致。生产日志实锤(
%LOCALAPPDATA%\OpenLess\Logs\openless.log):三行中同模型、同模式、A 两行同输入文本,唯一实质差异是
prior_turns;prompt 长度差4028-3840=188字符,与一轮历史(原文 16 字 + 润色结果 + 信封包装)体量吻合。污染面量化:该日志共 362 次 LLM 润色调用,其中 76 次(21%)
prior_turns>=1,观察到的串线事故全部落在其中。临时解决(已执行,已止血)
已在「设置 → 隐私 → 数据存储 → 对话上下文窗口(分钟)」将默认 5 改为 0。改后所有润色请求恢复 stateless(
messages仅 system + 当前转写),经连续口述验证串线不再出现。这是配置层面的规避:历史面板、手动重润色等本地功能不受影响,代价是连续口述时代词指代无法看到前文。该参数无渠道粒度,对所有 provider(含云端)一刀切关闭,这正是希望官方改进的部分。
相关 issue
影响
建议接受标准
运维侧目前用「全局窗口 = 0」规避,但这是临时方案——一刀切放弃了所有渠道的上下文能力。希望官方针对**自定义模型渠道(尤其 Ollama 这类本地推理)**做系统性优化,按优先级:
polishContextWindowMinutes支持按 provider / 渠道维度配置。本地 custom 渠道(Ollama 等)可独立关闭或调小,不影响内置云渠道;UI 上对本地渠道给出风险提示(「小参数模型可能将历史内容带入当前结果」)。验证方式:
lfm2.5-voice-8k+ 受控复现请求(历史对 + 当前转写),连续 10 次含历史润色,0 次输出历史内容;同时云渠道功能回归正常。TODO / 不确定项
build_polish_history_contents),未实测。content为空(正文在 reasoning 字段)是另一个独立问题,建议另开 issue。