INC-20260927-01:Qwen 聊天持续串接屏幕评论
Historical snapshot / 历史取证记录,记录日期:2026-09-27。本文是本次排查结论,不是已上线修复或现行产品行为承诺。仅提供中文版本。
事故状态与评级
| 项目 | 结论 |
|---|---|
| 状态 | 未关闭:本地已实现限定范围修复并验证;未部署,用户恢复情况未验证 |
| 建议严重度 | 暂定 SEV-2(核心功能明显降级) |
| 建议修复优先级 | P1,与事故严重度分开表达 |
| 已知影响 | 报告用户的聊天回复异常变长,夹带重复或虚构的屏幕评论;文本与语音体验受影响 |
| 频率 | 用户报告每天发生;尚无全量发生率或受影响用户数统计 |
| 现场模型 | 配置日志确认聊天、视觉模型均为 qwen3.7-plus |
| 源码核对基线 | main,419264971b45d23b5dd0bbc564b0821feb05c651 |
| 现场版本限制 | Steam 打包运行环境,证据未包含可核对的 commit;不可把当前源码行号视为现场版本证明 |
| 修复负责人、跟踪单、修复版本 | 尚未指定或创建;本记录不自动分派任务或批准方案 |
| 修复分支 | codex/fix-qwen-screen-history-repetition;合并与发布状态以平台记录为准,未部署 |
仓库检索未发现统一事故分级规范,因此本次 SEV-2 是评估建议,不冒充项目正式等级。本文采用的口径是:SEV-1 表示广泛不可用或严重安全、数据完整性后果;SEV-2 表示核心功能对部分用户持续明显降级;SEV-3 表示影响较轻或局限的缺陷。现有证据支持 SEV-2:核心聊天受损、用户报告高频发生、真实 API 复现了传播机制;但没有广泛服务中断、数据泄露或数据损坏证据。若后续确认影响范围扩大,应重新评级。
摘要:问题怎样产生、怎样延续
不能把反馈中最早的一段当成污染首次发生。它可能已经是第 N 次异常,后续是 N+1、N+2 次;“每天发生”支持持续排查的必要性,但不能单独证明跨天继承了同一份历史。
已验证的触发条件是:聊天历史里存在一段“正常回答+多条带标签屏幕评论”的异常 assistant 回复。真实 Qwen 在随后普通用户输入下,会沿用这个格式编出多条新场景;在重建的话题回调下,还会逐字重放旧评论。实验没有要求模型复述历史,也没有本地追加屏幕评论。
当前源码的历史保存和读取方式允许这类异常继续传播:异常回答进入 assistant 历史,后续普通聊天或话题回调再次读取,新的异常回答又被保存。防重复检查未在输出送达前阻止这种行为,且普通聊天与话题回调的检测路径不一致。
持续传播机制已有真实模型阳性证据,且与现场时间线吻合;最早污染的入口尚未确定。后者不应成为修复持续传播问题的前置条件。
现场时间线
以下时间采用日志原文,不自行校正时区。23 是用户提供文本中的标签总次数,不是 23 个已证实的请求。
| 时间 | 证据与事件 | 判读 |
|---|---|---|
| 16:36:05 | 普通用户输入,附带一张暂存屏幕截图;随后另一主动搭话任务因用户互动而取消投递 | 第一段长回复是普通聊天,不是同时不断追加新主动搭话的证据 |
| 16:36:08—18 | 用户记录显示正常回答后接七条屏幕评论;后端 16:36:14 记录完成 | 展示/播放时间不能当作 API 调用次数 |
| 16:38:41 | 一条独立主动搭话成功投递 | 两次用户输入之间存在正常主动搭话,但没有原文可确定它是哪条评论 |
| 16:41:20 | 第二次普通用户输入;暂存截图过期被丢弃 | 不代表请求历史里所有旧图片均已删除 |
| 16:41:23—35 | 用户记录显示原七条再次出现,再加第八条 | 第二段共八个标签 |
| 16:41:31 | 长回复摘要成功:尾段 52 字变为 23 字;后台开始准备待切换会话 | 摘要后的历史与后续短尾句有强对应,尚无现场摘要原文作逐字证明 |
| 16:42:19 | 主进程等待播放结束信号超时,释放等待;进入 trigger_agent_callbacks,pending=1;插件投递批次 n=1 | 第三段由一条积压话题回调触发,不是八条新屏幕回调 |
| 16:42:22—34 | 第三段再出现八个标签,尾句缩短;后端标记 had_input=False、agent_callback_turn=True | 话题内容与该段开头吻合;重复延续到了话题回调路径 |
| 16:47:07 | 实际会话热切换完成 | 晚于这三段异常,不能把这一轮热切换回填当成其发生原因 |
现场合计 7+8+8=23。可见用户输入为两次,另有一次系统话题回调。
代码依据与防护缺口
行号仅针对上述源码基线。
| 位置 | 行为及与本事故的关系 |
|---|---|
main_logic/core/proactive.py::finish_proactive_delivery(),约 542—574 行 | 已投递的主动搭话正文作为独立 AIMessage 写入离线会话历史;不是每轮主动把所有旧评论拼进响应 |
main_logic/omni_offline_client/_streaming.py::stream_text(),1108 行 | 普通聊天向模型发送当前完整会话历史 |
| 同文件 1785、1805 行 | 摘要成功时历史保存“原前文+摘要尾段”;否则按相应路径保存回复。因此旧异常有再次被读取的机会 |
同文件 _check_repetition(),276 行;正常调用点 1809 行 | 需要与至少两条旧回复高度相似才触发;检测发生在输出之后,不是发送前拦截器。换词但沿用异常格式也可能不满足整段相似条件 |
main_logic/core/proactive.py,1833 行 | 文本会话的话题回调调用 prompt_ephemeral() |
main_logic/omni_offline_client/_lifecycle.py::prompt_ephemeral(),468、751 行 | 完整历史加临时指令进行生成,并保存回复;未调用上述 _check_repetition(),不能假设第三段会命中普通聊天的第三轮检测 |
日志中的 BM25 拦截针对另一批主动搭话候选;它们未阻止本次普通聊天和话题回调已经发出的长回复。当前问题不能仅归为“模型问题”,也不能只靠删除标签处理:应用需要防止异常内容持续进入后续生成链路,同时避免误删正常讨论。
真实 API 验证
使用仓库配置的阿里百炼官方接口,请求及返回模型均为 qwen3.7-plus。enable_thinking=false,不下发 temperature,流式读取 delta.content;普通请求上限 3000 tokens,话题回调 520 tokens;SDK 自动重试关闭。
共尝试 8 次,7 次成功、1 次 APIConnectionError。失败请求没有可见输出且未重试,不计入阳性或阴性样本。成功请求返回的 total_tokens 合计 5,061;失败请求无 usage,不能据此认定零计费。
| 条件 | 成功次数 | 结果 |
|---|---|---|
| 无屏幕历史 | 1 | 未串接,无标签、无旧评论逐字重复;已有坦克游戏角色扮演续写(见后文基线归因复核) |
| 七条独立 assistant 屏幕评论,无标签 | 1 | 无标签、无旧评论逐字重复 |
| 相同独立评论,仅增加标签 | 1 | 无标签、无旧评论逐字重复 |
| 第一段异常长回复放入历史,再发送第二句用户输入 | 2 | 分别生成 3、7 段带标签的新编场景,不是旧七条逐字重放 |
| 同样的长回复,仅删除标签 | 1 | 无标签、无旧句逐字匹配,但仍虚构一段游戏画面 |
| 两段异常长回复放入历史,再加话题回调 | 1 | 八个标签;旧七条完整评论逐字重现,短第八条也重现;正常 stop 结束 |
最强阳性是单次回调重放八条,不是“现场 23 条全过程原样复现”。诊断脚本没有运行前端、TTS 或应用生命周期,响应只来自真实 API 流式增量。因此实验中的重放可以明确归于模型输出,而不是本地显示或语音拼接。
验证限制:
- 未取得现场完整人设、记忆、工具声明、出站请求和截图。实验使用简化合成人设与仓库会话/回调模板,不是生产请求原样回放。
- 七条独立评论由异常回答拆出,是重建假设,不是已取得此前七次屏幕搭话原文。
- 污染传播实验主动放入了已有异常回复,只证明该条件下可以延续异常,不能证明首次污染来源。
- 一次阴性不能排除随机触发;一次删标签对照不能作为修复验收。没有截图的实验不能排除现场截图读回聊天内容的可能。
- 每天复发是用户报告,不能外推全体用户发生率,也不能推定跨天历史传播已经被证实。
处理日志与判断修正
以下是调查阶段顺序,不为未记录的排查操作补造精确时间。
- 收集用户提供的两次输入、三段回复,明确统计对象是 23 个标签,而非 23 次 API 调用。
- 检查普通聊天、主动搭话、回调、历史写入与前端分段路径;已有模拟测试验证请求序列化与本地输出没有自动拼接这串旧评论,但不能预测真实 Qwen 行为。
- 早期发现并以测试验证了热切换相关风险;取得现场日志后,按实际切换时刻修正判断,不再将该风险当成本次三段异常的发生原因。
- 只读核对主进程、记忆服务、插件日志及插件日志压缩包,识别第三段为单条话题回调。错误日志中其他时段的网络、TTS 错误不作为本次原因。
- 现场摘要长度与第三段短尾句相互印证;代码说明摘要结果会保存到历史。由于缺少现场 payload,保留证据边界。
- 用户授权使用 Qwen API key 后,执行上述八次受控请求。探针不导入应用,避免初始化日志和本地运行状态的副作用;记录认证外的请求正文、响应、usage 和匹配指标。
- 真实 API 复现普通聊天的异常格式续写,以及话题回调的旧评论逐字重放;独立复核确认响应不是脚本拼接。
- 根据用户补充修正调查范围:反馈可能已经是第 N、N+1 次异常;不再把首次污染复现作为处理持续故障的门槛。
- 取证阶段生成脱敏事故记录;当时未改生产逻辑,未提交、推送或部署。先前未跟踪测试文件保持原状。
- 用户随后授权新建分支并修复,实施下述请求视图与共享流式防护。原始证据、密钥配置、先前测试文件均保留;随后授权提交上游 PR,尚未部署。
记忆服务同期 cache 记录表示落盘,不能直接当成反复注入证据;16:45 才发生的事实提取不能倒推为 16:36—16:42 的原因。插件压缩包只包含日志,未运行其中内容,也未找到每条屏幕评论对应一次插件新调用的证据。负面检索不等于证明运行数据里绝不存在相关内容。
本次处理结局及待验收事项
本地修复验证不等于用户现场恢复。本事故保持未关闭。 目前没有部署修复、执行用户会话清理或收到恢复确认;首次污染入口仍待查。无需等其完全查明,才能着手阻断已有污染的传播。
后续验收方向(不代表已有任务分派或上线承诺):
- 在普通聊天和话题回调上明确一致的输出及历史提交规则,处理旧回复成串重放和异常格式延续;避免仅在正常回合结束后才检测。
- 兼顾已污染会话的恢复,不能只防止新标签进入。不得无差别清空用户记忆或删除正常正文。
- 若需要补充观测,记录可脱敏的回合 ID、来源、历史条数及重复判定,原文采集须单独控制;不得记录密钥。
关闭前至少验证:本事故已复现的带标签污染历史下,普通回复、话题回调、摘要历史三条路径不再持续放大;正常引用和用户主动讨论旧画面不能被静默误删;分片边界变化不改变结果;取消、重试、结束与 TTS 收尾保持正确。无标签历史识别另列覆盖缺口,不声称本次规则已经支持;基线游戏化续写/内容真实性不作为本次串接修复的失败判据。随后以少量真实模型重复实验及用户观察验证,记录覆盖范围和未覆盖条件,而非把单次阴性当作彻底修复。
2026-09-27 本地修复进展
先尝试了不删内容、只增加历史边界提示的方案。真实 API 的普通聊天仍接出三条,话题回调仍重放八条;无标签历史的用例也编出了多段画面。因此该提示词候选未作为生产修复保留。
实际实现:
utils/screen_comment_guard.py识别连续的来源标记叙述,而非全局删除词语。至少需要两个未被引用/代码保护的标记,各自后接达到最小长度的完整叙述;普通的“屏幕内容:”字段不按内部标记处理,描述性来源名须有斜杠分隔。模糊情形保留原文。- 请求侧只在发送给模型的 assistant 消息副本中移除已识别的异常后缀,保留正常前文。原始会话历史、用户消息、系统/记忆内容、图片及工具元数据不被改写;用户明确请求引用等历史处理时保留原始内容。
- 输出侧在
_astream_visible_with_tools()共用的过滤器内处理。审阅后改为第一条正常流出,仅暂存疑似连续的后续条目,确认后截断第二条及后续内容,先于 UI/TTS 输出及新回复历史写入。候选缓冲最多 256 个字符;超限的模糊内容保留并继续后续检测。已经播放的首条不尝试撤回,请求投影仍可移除完整历史串。 - OpenAI-compatible、原生 Gemini 的正常工具迭代和强制最终回复使用相同请求投影。工具轮次边界先释放缓冲再发持久化标记,避免把 pre-tool 文字二次写入 final 回复。
- 引用豁免在本轮内固定,不受后来追加的工具图片改变。话题通知不是用户的引用请求,不因通知里出现“分析”等字样而关闭防护;用户要求“不要重复”也不会被当成允许复述。
首版修复(9926d7fa4)的真实 API 回归:相同污染样本,普通用户输入、话题回调各两次,四次成功结果均无屏幕标签串接、无原七条逐字重放。这四次是模型原始响应本身未再出现该串接,并非只在展示层隐藏;返回 total_tokens 合计 1,798。另用首版过滤器逐字回放五份先前阳性响应,均保留正常前文并拦住屏幕评论后缀,不额外调用 API。该结果不是下述审阅修正后的新一轮真实 API 验收。
确定性验证覆盖:每个分割位置及逐字符输入、前后斜杠和全角分隔符、普通字段、单条评论、引用/代码/思考块、工具调用及强制最终回复、请求副本不修改原件、普通聊天/话题回调的输出和历史一致性,以及已有截图、取消、摘要、工具泄漏过滤相关回归。取证阶段九个相关测试文件合并执行为 298 passed;提交前排除不公开的本地诊断文件,重新执行八个可在 PR 中复现的测试文件:294 passed,0 failed,仅有七条现有依赖弃用警告;未声称运行全仓测试。
覆盖边界: 无标记的纯语义重复、短于确认阈值的内容、系统记忆内嵌的旧污染,以及不满足结构规则的变体未被本补丁全面处理。回归中单段游戏场景和寒暄的观察保留,但零屏幕历史基线已有游戏化角色扮演,因此不能将这些现象直接作为残留污染或本次串接修复失败的证据;内容真实性另行评估。事故未关闭的依据是尚无现场部署/恢复确认及产品链路验收,不是未消除通用幻觉。
PR 审阅后的规则与集成修正
PR #3185 的外部评论与独立复核发现:引用豁免过宽、尺寸引号遮蔽后文、单条评论等待流结束、全量重扫、Unicode 分片重复、合法代码误删,以及屏幕过滤抢先截断 Qwen 推理闭标签和正式答案。首版 294 项通过并未覆盖这些交互,不能作为合并安全性证明。
- 将引用豁免限定到明确的历史处理或词语含义讨论;普通屏幕问题、翻译菜单不关闭防护。真实用户意图在整个回合内固定,包括工具图追加后的重试。
- 以同一个增量词法状态处理完整和分片文本,记录引号、转义、代码分隔符长度、缩进代码与引用段落边界;已输出正文不再保留或重扫。对于不确定的引用语法保守保留,不声称实现完整 Markdown 解析器。
- 采用上述“首条流出、后续候选有界等待”规则,明确放弃流式阶段撤回整串首条的承诺。256 字符是缓冲长度上限,不是网络等待时间上限,也不是最终输出长度上限。
- Qwen 凝神模式的推理剥离移入共享过滤器,顺序为工具标记过滤、推理剥离、屏幕规则,覆盖普通输出、工具历史和强制最终回答。正常完成但没有推理闭标签时保留既有正文兜底;异常或取消不释放未确认的推理。工具边界的短正文在清空前缀缓冲前走既有输出规则。
- 新增确定性测试覆盖本轮发现及相邻反例;未新增真实 API 调用或现场部署。审阅修正阶段的测试结果以本次交付及 PR 后续更新为准,不覆盖首次污染入口与通用幻觉问题。
后续完整审阅修正
- 工具执行期间取消时,已持久化标记可能在正常摘要重置前退出循环。补齐循环外摘要状态和尾段缓冲清理,避免取消后继续摘要、续读尾段或额外追加 assistant 历史。测试同时覆盖摘要阈值两侧、前缀缓冲开启/关闭,以及未取消时正常释放工具前尾段的对照。
- 引用豁免不再接受孤立的时间词或泛指“历史/原文”。要求明确的聊天对象(回答、回复、消息、聊天记录或原话等),因此普通的上一局复盘、之前截图翻译保持防护;明确引用上一条回答仍保留原文。对象不明确的“分析刚才的内容”不关闭防护;这一规则仍是有限的文本启发式,不是完整语义理解,原始持久化记录不被改写。
- 新增集成用例在修复前确认四个普通复盘/翻译输入会放行污染,四个取消变体会继续摘要或输出;四个正常结束对照通过。修复后 13 个相关测试文件合并执行为 453 passed,7 条现有依赖弃用警告;未运行全仓测试、未调用真实 API、未部署,事故仍未关闭。
- 外部审阅另指出工具边界可能释放未闭合的推理缓冲;基线已存在该行为。本次不改变该既有边界语义,不能据此宣称所有推理泄漏风险已解决。
引用对象规则的再次复核
后续三条审阅意见均可复现:明确的“刚才那段对话”和“刚才那条回复”被误拦,而“截图里的原话”被误放行。上一轮的引用规则仍不完整,453 项通过不能证明这些未覆盖边界正确。
本轮将指代、指示词/数量词和聊天对象按相邻短语匹配,不再单独凭“原话”豁免,也不跨越其他对象的描述寻找它。对“截图里的那段对话”这类带外层来源的嵌套引用,保持防护;“你上一条回复里的原话”则由明确聊天引用确定来源。不改写持久化原件,明确引用时仍保留请求与输出的原文。
验证包括三条意见在整块及逐字流中的六个修复前失败用例,以及时间词、指示词、数量词、聊天对象与外层来源的成组正反例;同时检查请求投影、输出和新回复保存。该规则仍是有限的文本启发式,不能保证识别任意自然语言指代。未新增真实 API 调用或用户现场部署,事故保持未关闭;最终相关测试结果记录于 PR。
局部隔离候选的实施前验证(未进入生产改造)
用户授权执行“先验证局部隔离,再决定生产改造”的计划后,以 545532d36 为基线复核了当前全局引用豁免及历史回填。当前实现的一次引用判断同时恢复全部历史并关闭整轮输出过滤,不能通过增加引用关键词解决“引用最近一条正常回复却恢复更早污染”的范围错误。
本轮编写了独立的本地设计探针,不导入应用、不修改现场会话。候选只在请求副本中隔离异常后缀,再把人工明确选定的一条原文作为当前请求的临时数据传入,不恢复其他污染历史;共享输出过滤保持开启。该探针没有实现生产引用目标解析、稳定消息标识、UI 引用入口或归档恢复,因此不能视为已完成 A 方案。
确定性验证结果:
- 当前引用规则会恢复无关的早期污染;明确选定最近正常消息的候选不会恢复它,历史原件保持不变。
- 合法要求“不增加引用格式、完整复述选定原文”时,518 字原文被始终开启的过滤器截成 167 字。对原文全部 519 个二段切分位置重复检查,结果一致,排除了此例仅由分片位置导致的解释。
- 将原文包装为 Markdown 引用块不能单独作为修复:当前输出过滤和历史投影都会保留引用块,它仍会完整进入下一轮请求。这里只证明请求暴露,不据此声称模型必然复读。
随后完成 6 次真实 API 请求,6 次成功、均正常 stop,无自动重试。请求模型为 qwen3.7-plus,使用此前验证的官方端点;禁用 thinking、不设置 temperature,普通请求上限 3000 tokens、回调 520 tokens。成功请求返回的 total_tokens 合计 3,154。
| 用例 | 结果及证据边界 |
|---|---|
| 当前实现,引用最近正常回复 | 原样返回该正常回复;此次没有复读,不能将请求暴露风险说成此次实际触发 |
| 局部候选,引用同一正常回复 | 原样返回,未重新提供无关污染原文 |
| 局部候选,普通聊天 | 无标签串、无原评论逐字命中;仍出现没有图像依据的游戏场景 |
| 局部候选,话题回调 | 无标签串、无原评论逐字命中;仍出现没有图像依据的游戏场景 |
| 局部候选,完整复述选定异常原文 | 模型原始输出逐字正确,含七条评论;过滤后只剩第一条。合法引用完整性验收失败 |
| 上述引用后的普通聊天 | 无标签串、无原评论逐字命中;仍编造场景。保存的是被截断的可见回复,不能证明“完整引用后的恢复”已通过 |
首个正文增量耗时约 0.844–1.438 秒,整次请求约 0.937–7.047 秒;这是探针的网络/生成时间,不是产品 TTS 延迟。本轮没有调用前端、TTS 或工具执行器,没有验证实际 UI 选定、跨服务元数据传递、历史回填或用户现场恢复。各场景仅一次,合成人设和重建历史的既有限制继续适用。
实施门槛未通过,暂停替换生产策略。 不能直接删除全局豁免后宣布修复;也不能用“引用时加引号/代码框”绕开当轮过滤,却不处理下一轮上下文传播。下一步需要明确局部引用在产品中的定位、交付和后续上下文保留契约,再决定是否扩展请求/引用协议。完整保留自然语言原文处理能力,与不增加可信局部引用边界的纯文本流式启发式,不能在本轮已验证的方案中同时得到保证。
本轮只新增本地诊断探针与本记录,不修改生产逻辑、不清理用户数据、不提交、不推送、不更新远端 PR。事故继续保持未关闭。
2026-09-28:输入投影对真实模型接话行为的同期对照
按用户要求,不再停留于离线 payload 检查,复用既有同类污染历史探针,执行 3 种输入策略 × 2 个入口,共 6 次真实请求:原始污染历史、现有前缀投影、拟议 0A 整条占位;分别配普通输入和话题回调。每格一次,不追加抽样寻找成功结果。
请求与返回模型均为 qwen3.7-plus,使用既有官方端点;enable_thinking=false、不设置 temperature、SDK 自动重试关闭。普通请求上限 3000 tokens,回调 520 tokens,同入口不同策略的生成参数完全相同,仅历史视图不同。六次均成功、正常 stop,返回 total_tokens 合计 3,826。
整条占位仅替换命中异常串的 assistant 正文,采用设计中的固定非空说明。投影后的四份请求都确认不含被隔离的原七条完整评论;原来独立存在、未被隔离的第八条评论仍保留。未添加截图,未修改人设以诱导结果。请求对象、模型原始响应与 usage 保存在本地证据中。
| 输入策略 | 普通输入的模型原始输出 | 话题回调的模型原始输出 |
|---|---|---|
| 原始污染历史 | 579 字、7 个标签,连续生成七段新场景;不是原七条逐字重放 | 558 字、8 个标签;原七条完整评论及短第八条重放;移除输出新增换行后,整段等于最后一条异常 assistant 历史 |
| 现有投影:保留链前前缀 | 215 字、0 标签、0 旧完整评论命中;不再成串,但仍无图描述草丛动静、履带声并声称能报点 | 168 字、0 标签、0 旧完整评论命中;不再成串,但仍无图描述敌方重型坦克绕后并建议开火 |
| 0A 候选:命中消息整条占位 | 125 字、0 标签、0 旧完整评论命中;不再成串,但仍无图推测草丛中的敌人 | 159 字、0 标签、0 旧完整评论命中;不再成串,但仍声称草丛有动静并要求转炮塔 |
这里的“字”按 Python len(str) 计数,包含标点、换行等,不是汉字数或 token。投影后的四次模型原始响应与输出过滤后的响应完全相同,因此本轮的长串消失发生在模型生成阶段,不是显示/TTS 过滤隐藏了后半段。
本次可支持的结论:
- 同期未投影组再次出现了异常格式续写和逐字重放;两种投影都在本组样本中抑制了原事故的长串传播,且明显缩短了回答。输入侧隔离具备独立价值,不需要先完成完整引用系统才能测试这一效果。
- 四次投影后的单段场景描述/操作建议作为内容真实性观察保留,不再据此认定污染修复残留失败:零屏幕历史基线也有游戏化续写。当前证据不能把这类现象单独归因于污染,也不足以证明具体实时画面臆测完全由人设造成。详见下一节。
- 不能据每格一次判断占位优于保留前缀,或估计每天复发概率。整条占位同时移除了正常前文,其上下文损失代价也没有被这六次实验完整评估。
- 本轮不是生产 payload 原样回放;继续沿用简化人设、重建历史、无截图的限制。未运行产品 UI/TTS、工具执行、跨会话恢复或生产拒绝路由,没有部署修复,也没有现场恢复确认。
本轮只调整本地诊断脚本并记录实验结果,不修改生产逻辑、不提交、不推送。完整性/拒绝与恢复的发布门槛仍需独立验证,事故保持未关闭。
2026-09-28:零屏幕历史基线与无标签识别边界的归因复核
根据用户指出的基线遗漏,重新读取既有原始实验文件,未新增 API 请求。
基线证据 E13: baseline 的请求只有一条 system 和一条 user,没有 assistant 历史或图像。system 明确设定正在陪伴用户玩坦克游戏;175 字输出仍谈及保护坦克和一起击败对手,但没有连续屏幕评论或旧句重放。因此游戏化角色扮演并非污染历史出现后才存在的现象,不能把投影后仍谈游戏计为本次修复失败。
同时收紧推断:该基线是“下一局保护坦克”的角色扮演邀请,不等于已经复现“左侧草丛现在有敌人”等具体视觉判断;它与后续实验的用户输入、历史长度也不完全一致。可确认“游戏化续写是基线已有现象、不能据此归因污染”,不能确认“所有具体无图判断都与污染/投影完全无关,且只由人设导致”。 后一个结论需要控制用户输入及其他上下文的消融实验,本轮不为扩大本事故范围而追加调用。
修订后的验收归类:
| 现象 | 当前结论 | 对本次验收的作用 |
|---|---|---|
| 连续追加多条屏幕评论、异常变长 | 同期对照中两种投影均未再出现,原始历史组出现七段新场景 | 是目标症状,本组真实 API 实验有效 |
| 旧评论逐字重放 | 回调原始组重放整段;两种投影组旧评论均零命中 | 是目标症状,本组真实 API 实验有效 |
| 普通游戏化续写及单段无图场景 | 游戏化续写在零屏幕历史基线已存在;具体画面判断的独立归因未完成 | 不作为本次污染修复失败或“只覆盖一半”的依据;内容真实性另列 |
上述是已测带标签污染传播两类症状均被抑制,不是“所有现场变体/每日复发都已解决”。每格一次、合成上下文、未部署和未确认用户恢复的限制不变。缺少新场景串与缺少旧句重放都需要查看原始输出,不能把新场景串也一概归为基线角色扮演。
无标签证据分层:
- E14
seeded_user_no_labels保留异常正文、只移除标签。那一次输出为 248 字、零标签、零旧完整评论命中,有一段新场景,但未重现七八段串接或逐字重放。单凭这段场景不能证明污染传播失败,也不能证明已修复。 - 本轮离线强制启用当前投影,确认 E14 所有 assistant 正文的
screen_chain_start()都返回None,投影返回原消息列表且内容不变。“无标签输入不被现有规则隔离”是确定性事实,不需要用模型的新场景来间接推断。 - 另有 E15:给同类无标签历史加历史边界提示的旧实验,模型在正常开头后接出四段无标签场景,仍无旧完整评论命中。它说明无标签多段续写已有观察样本;但 system 多了边界提示,不是上述六次实验的同条件对照,不能混入其成功率或单独完成归因。
来源元数据能够让未来已标注记录的处理不依赖正文标签,是阶段 1A 的直接工程动机;但必须结合明确的上下文使用策略并贯通归档/回填。仅增加“该轮有截图”字段不会自动识别所有污染,更不能倒推出旧无标签记录或无屏幕普通聊天生成内容的来源。旧数据仍需保留文本识别/人工恢复路径和明确的未覆盖说明,不能宣称 1A 完全替代历史检测。
修复生效前提与失效条件。 上面的“投影有效”不是通用能力,它有一条已实测的适用边界。输入投影的作用方式是:screen_chain_start() 命中 → 该条 assistant 正文不进请求。命中是前提,不是结果:
| 历史形态 | 本修复的行为 | 依据 |
|---|---|---|
带标签、评论达到 _MIN_PROSE 长度且成串(现场形态) | 命中,投影生效 | 六次真实 API 对照;出站 payload 检查 |
| 无标签(E14 形态) | 检测返回 None,投影静默无操作,现象不变 | E14 离线强制启用投影的确定性检查 |
| 评论短于长度阈值或不成串 | 同上 | 阈值测量;no_labels 对照 |
对上述未覆盖历史形态的实际影响:这个修复不会报错、不会降级、不会有任何提示,只是不生效。此处描述的是检测边界,不代表已确认另一个受影响用户。部署后若现场历史属于后两行形态,请求历史正文仍原样发送;不能仅凭这一点断言模型每次生成的现象完全相同。因此发布说明与用户沟通不能把“输入投影”表述为通用能力,必须同时说明其适用边界;边界外的形态当前没有兜底。
输入投影不拦本轮新生成的串接。 它只作用于请求历史,不观察本轮输出。若模型在没有受污染历史的情况下自行写出多条屏幕评论,用户会完整看到。本组同期对照里未观察到这种情况(拿掉刺激源后模型不再生成串接),但那是本组观测结果,不是承诺。这一条与上一条(无标签/短评论静默透传)是同一个交付说明里必须并列的两项限制。
消除这个边界的工程路径是阶段 1A 的来源标记(不依赖正文长度与措辞),但它只覆盖未来轮次;旧记录仍需保留文本识别与人工恢复路径。
本轮修正的是归因及验收范围,保留原始请求/响应不变。未修改生产代码、未提交或推送。
2026-09-28:assistant 段的位置是多消息传播的判别因子
后续探针修正了此前对“多条 assistant 消息各含一条评论”的判断,也修正了本记录上一版给出的规则方向。判别式是位置,且需要两个条件同时成立:
- 该段是最后一次
user/human消息之前连续的 assistant/ai 消息; - 该段之前也是一条
user/human消息——即它是在回答一个用户轮。
| 布局 | 续接 |
|---|---|
[u, a, a, ask]、[u, a×7, ask] | 5/5、2/2;后者每次生成 7 条标签 |
[a×7, u](应用自身独立主动搭话的形态) | 0/3 |
[a×7, u1, ask] | 0/2 |
[u, a, tool, a, ask](工具结果隔开) | 0/3 |
[u, a(单条消息内两条评论), ask] | 2/3(原规则即可命中) |
早先的负向对照仍然成立,而且现在有了明确解释:test_screen_chat_history_payload.py 钉的正是 [a×7, u]——段前没有用户轮,实测 0/3 不续接,必须原样进入 provider。第 2 个条件就是它成立的根据。实现时先只按第 1 个条件写,该负向对照立即转红;据此补测 [a×7, u] 才定位到第 2 个条件,这是本轮由既有测试挡下的过度隔离。
工具边界对照不支持另一条假设:工具前一条评论、工具后一条评论的 3 次请求均回到正常对话(0/3 续接)。因此不把工具边界视为需要跨段合并的传播缺陷,实现中以“非 assistant 消息打断该段”复现了这一行为。
全组 21 次请求、9,183 tokens。结果来自单一模型、合成人设、每种形态 2–5 次请求;工具结果为伪造数据,没有真实执行工具。产物保存在本地 .codex-tmp/tool-boundary-*.json,已断言不含密钥。结论应作为后续测试和实现约束,不应表述为跨模型的统计保证。
据此,project_screen_history 已实现跨消息尾部合并(随阶段 0A-1 交付)。
PR #3201 审查:位置边界与来源缺口
[a×N, u] 在没有 system 前缀时,原实现会在 start == 0 读取 messages[-1],误把末尾用户当成段前用户。本次增加显式边界检查,并覆盖有/无 system 的负向对照。
位置规则不能证明 assistant 段是在回答前一用户。正常应用也能产生 [user, proactive×N, user]。修复后,成功主动交付写入内部 AIMessage.additional_kwargs.dialog_source="proactive",投影以该标记分段,独立交付不再与邻接消息合并判定;单条消息内部的异常链仍会隔离。真实交付 payload 测试覆盖有/无前置用户、标签和截图,确认正常正文保留且内部标记不进入 provider 消息。
anti_repeat_response_id 继续只作反重复关联,不作为来源豁免依据。新增标记只表达主动交付来源,不表示有截图或内容可信。现有 AIMessage 文件序列化/恢复已保留 additional_kwargs;SQL 保存仅新增允许值为 proactive 的 dialog_source,未扩大其他元数据的保存范围。测试覆盖实际 SQLite 写入与恢复后的投影。
缺失标记的旧记录、未知来源及已丢失元数据的回填仍采用既有跨消息规则,保留已证实传播形态的防护;这类旧主动交付仍有误隔离可能。不会从正文或反重复 ID 反推来源,也不迁移旧数据。本次仅完成成功主动交付至本地历史的最小来源区分,不宣称阶段 1A 的屏幕来源事实或所有跨服务回填已贯通。
2026-09-28:本地修复分支的处置
本记录此前各节描述的本地修复实现(分支 codex/fix-qwen-screen-history-repetition,核对基线 545532d3)未合并、未上线,且已被取代。该分支的 PR #3185 已关闭,理由是它的实现方向被同一事故的第一次独立交付取代:
| 该分支的做法 | 取代它的做法 |
|---|---|
| 自然语言引用措辞授予全局豁免(恢复全部历史、关闭整轮过滤) | 删除。权限范围错误无法靠补关键词修正:已确认它会为无关的更早消息一起开锁 |
| 请求投影截断到链起点、保留前缀 | #3201:整条替换为占位,理由是保留的截断前缀正是该防护要挡的畸形形状。拆分单独交付时改为保留首条评论(去标签、截至句末),见下节 |
流式输出过滤器 ScreenCommentChainFilter | 尚未交付,需要换设计(跨分片不漏计、不误伤合法长文本) |
_streaming.py 工具边界重构 | 不要。它引入了取消工具轮时摘要状态不清理的缺陷;main 原本的结构不可跳过该清理 |
本记录的证据与结论不因此失效。 各节的真实 API 结果(E3–E15)、归因复核与适用边界分析描述的是模型行为与检测能力,与哪一份实现落地无关;「修复生效前提与失效条件」是取代实现同样适用的边界说明。
取代实现最初见 PR #3201;其屏幕历史部分后来拆出单独交付(阶段 0A-1,设计文档 docs/design/screen-history-local-reference.md)。
维护提示:上文引用的核对基线
545532d3只存在于该分支上,分支保留未删除。
2026-10-01:请求视图改为保留首条评论
0A-1 从 #3201 拆出单独交付时,命中链的改写方式由"整条替换为固定占位"改为"保留首条评论":去掉来源标签,截至首条评论的句末,第二条评论起移出请求视图。合同见设计文档 §7.1.2。
改动理由:
- 占位写在 assistant 消息里,等于让角色"自己说过"一句系统说明,还提到历史面板,出戏;方括号格式本身可被模仿。
- 跨消息命中时,N 条消息变成 N 条相同占位,本身就是重复格式样本。
- 整条占位丢掉了全部内容,用户追问刚才的评论时模型无从接话。
与本记录"截断前缀"结论的关系:被拒绝的是截到链起点、留下半句的做法。新规则的切点落在首条完整评论的句末;没有句末标点的残段整段不保留。
2026-10-02 补充:无条件去标签。 标签由模型生成,应用代码从不输出,单靠成链判定会放过单条带标签评论、过短的首条评论和引号里的标签。请求视图因此对所有 assistant 消息无条件删除来源标签,成链截断保留为兜底。依据是上文 E14:只删标签、正文全留的普通输入为 248 字、0 标签、0 旧评论命中。本记录早先"不能只靠删除标签处理"的判断针对的是仅删标签而不阻止异常内容进入后续链路,E14 本身并不反对删标签。尚缺"只删标签、正文全留 + 话题回调"的对照。
证据缺口: 上文同期对照里,"保留链前前缀"(无标签、无占位)为 0 标签、0 旧评论命中,与新规则最接近,但新规则还额外保留了一条去标签的评论,这个视图没有做过真实模型对照。在补测单条消息内成链与 [u, a×7, ask] 两种布局之前,不能宣称新规则与整条占位效果相同。运维开关 NEKO_SCREEN_HISTORY_GUARD 不变。
证据索引与保密
原始资料保留在本地调查材料中,不随本记录公开、上传或提交。本文不包含密钥、用户聊天原文或个人机器路径。API 结果已检查不含提供的密钥。证据编号、行号和 SHA-256 用于向持有原件的处理人员核验;不是公开下载链接。
| 编号 | 材料与定位 | SHA-256 |
|---|---|---|
| E1 | 2026-09-27 主进程日志:1854—1868、1910—1930、1974 行 | 1ec8f0eac02303d9695c6b9e61ffe6da410bcc0cb6c7ad8990d22b8a16de2f3f |
| E2 | 同日总插件日志副本:2856—2857 行,watchdog 与单条释放批次 | 6ba750a4b1f18c67c464b9eb2f33a0d8649c6ce4120e5aab375b30c7e08e0e8d |
| E3 | API 用例 seeded_callback,运行批次 20260927T102608Z,序号 1 | a3f7318e2f5236efd35a0760207d607471657a99d069fb15890fc864ff89d346 |
| E4 | API 用例 seeded_user,运行批次 20260927T102608Z,序号 0 | 0a1f0e0bd0de83dedc337a884ffeb442a9aa5a71e25ae6f78eda5c7ac2f873b8 |
| E5 | API 用例 seeded_user,运行批次 20260927T102653Z,序号 0 | c686a220856bcec134ea61e6fef1065ce7540f8349df8b49ac3b5e02681b7716 |
| E6 | 局部引用候选六次 API 请求与响应,运行批次 20260927T140650Z | 362fc418fa619f0f06f4a290b3977c376b2445b57a05fd4b3c6ca30eb0e7c8d1 |
| E7 | 原始污染历史+普通输入,批次 20260928T071036Z,序号 0 | 5601a79f25a3fdc18c397adb0a4b67b4d55133ecb9333b6609a32cbea0d7c258 |
| E8 | 原始污染历史+回调,同批次序号 1 | 2ae63f137f3e4c18854f90bf1912d421e08d00e4d056c918fc071ca8fe72171b |
| E9 | 前缀投影+普通输入,批次 20260928T071108Z,序号 0 | 85951d68c99d7f3a4bc5b347afaa6ad57ad0e7df7e1657a8c5eaf1666253c7a8 |
| E10 | 前缀投影+回调,同批次序号 1 | c56a747e40a59814b8f7e7963fa119c28c15bdf25a07632f04aeedcf724cd08e |
| E11 | 整条占位+普通输入,批次 20260928T071129Z,序号 0 | c3f1b273cb3c13390119d52ba991412251bf2a2deb5d591986ec6dff42327b8c |
| E12 | 整条占位+回调,同批次序号 1 | dd3180fc9a5f516859011b1a8acf4ab34f0b35b6e8d5a6c5f190f3e44fbfa74b |
| E13 | 零屏幕历史基线,批次 20260927T102503Z,序号 0 | 552a72e3a39c1edf275ef78cc4282b374b58f62992f6c37e49016a277bb53ac3 |
| E14 | 无标签异常历史+普通输入,批次 20260927T102653Z,序号 1 | 69d3d7114c633cc73cd756238ffcf1c87649850ceaa77bc3dddaaedde12b5cf9 |
| E15 | 无标签异常历史+边界提示,批次 20260927T104553Z,序号 2 | 031858894a70aac886fde4fe94bc2b31c7ffe4b85eb1821ccd3a1fdf193aeeea |
| E16 | 尾部位置对照首轮:single_message / tool_split / two_messages,批次 20260928T102002Z | 1e010fc63f1fbbf33e1cfc113cb2a9b1cce979552c4b6b8dc9b83931c08eca74 |
| E17 | 同上第 2 轮,批次 20260928T102028Z | 461c2d08f3db2116b7eaa9b6ee44d608319d45bc80fb0734ae5caf5f22b4a58e |
| E18 | 同上第 3 轮,批次 20260928T102038Z | b9ad9d9fa2a56d5b061acf159f3a9f93ea9e324f60e547e9128aea64f39f0d32 |
| E19 | 尾部位置对照新增项:seven_before / seven_after / two_messages,批次 20260928T102107Z | cd1293aa07a1c89af32096d61425d849dc9373c5c28fc9d66ba489c39e176a7e |
| E20 | 同上第 2 轮,批次 20260928T102122Z | 3a64efe411c3f3161e567e236b5214897845c9b90d7a78479f8196cfded1a623 |
| E21 | 段前用户轮对照第 1 轮:seven_then_user / seven_before,批次 20260928T103606Z | b628d6490e2ef0fdcbadeb5c31cbbf872478b41cd294c3b0ebd172844d5e244d |
| E22 | 同上第 2 轮,批次 20260928T103616Z | 919fd06718370056df9c4970875c6a2bde54f718b4f5a09c30ad9316c143b858 |
| E23 | 同上第 3 轮,批次 20260928T103626Z | e53520708cd6b8c0a955aafc22d2fa2ff3ac5d1012471a3136f1297f25a67f69 |
E16–E20 为每组批次内 3 个产物的合并 SHA-256(按文件名排序,逐个文件内容哈希后串联再哈希);产物内含请求正文、模型原始响应与 usage,密钥不在其中。
另已核对主进程错误日志、记忆服务普通/错误日志、插件错误日志及插件日志压缩包。原始证据未改写;本文仅形成新的脱敏记录。
