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

Abierto
#1,030 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Tranquilo
Stack tecnológico
ollama, rust
Área
backend

Línea de trabajo

Start with openless-all/app/src-tauri/src/coordinator/dictation.rs and openless-all/app/src-tauri/src/polish.rs, especially build_polish_history_messages; the issue also identifies build_polish_history_contents as a related Gemini path. Reproduce with the described system + history + current-user request using lfm2.5-voice-8k, then assess the proposed provider-specific controls and result guard. Done means the stated 10 history-bearing tests produce no historical content and cloud-channel behavior still works.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

现象

相关背景:#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.rs 的 build_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
  • #678:历史上下文无上限导致润色延迟,已由 #693 修复为有限轮数;本 issue 是该机制的结果正确性后续问题。
  • #1011:润色后相邻近似重复文本,现象相近但根因和复现路径不同。

影响

  • 数据正确性:默认配置下,本地小参数模型用户会静默拿到错误的润色结果——当前记录被写入上一条的内容,无任何提示或标记。对以转写正确性为核心卖点的语音输入产品,属于核心功能产出错误数据。
  • 排查成本极高:现象酷似「模型幻觉 / 服务串线」,用户第一反应是换模型、查 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 请求/响应日志、完整排查报告可按需提供。
Lenguaje dominante
Rust
Estrellas
3.7k
Forks
349
Merge medio
2 d 14 h
PR fusionados (30 d)
97

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de Open-Less/openless

Todos los issues de Open-Less/openless

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.