审查回填(均 ✅ PASS):
- CR-22 摘要不改 updated_at / CR-23 P2 批次A 读 / CR-24 批次B 写 / CR-25 跑题P0 /
CR-26 跑题P1 / CR-27 跑题P2(6维度全过,topic 无破坏 + 双高置信保守)
跨端 AI Chat 设计(F-260620-01):
- 三层架构 df-tunnel/df-relay/df-miniapp + Rust 云后端选型(代码复用/类型一致/团队版演进)
- todo 规划跨端 P1-P4 + 跑题修复 P0-P2
跑题改进试验记录:
- db 基线(3 长对话:assistant 文本仅 4-5%,95% tool_result,47% echo)
- 5 类跑题现象与 P0-P2 五改进精准对应 + 四层测试计划 + 迭代过程
14 KiB
AI Chat 跑题改进试验记录
创建:2026-06-20 范围:AI Chat 长对话「抓不住重点/跑题」根因治 — 测试计划 + 迭代过程 + 改进前基线 + 遗留 关联提交:P0
102d398/ P1013ce21/ P2a2db5c7关联设计:意图识别层论证-2026-06-19.md/多主题上下文管理愿景-2026-06-19.md三原则对齐:优雅 / 可靠 / 易迭代
一、测试计划
四层覆盖,从机制单测到实跑场景逐层外扩。机制层已闭环,实跑层待人工验证。
① 单测(机制正确性)— 189 测,全过
| 模块 | 测数 | 覆盖点 |
|---|---|---|
intent.rs 意图识别 + filter |
43 | 空 subset 命中 / 置信三重 fallback(<0.7、过滤<3、空 subset→全量)/ 顺序漂移 / 工具收敛 29→5-10 |
context_helpers.rs 压缩+工具压缩+tokenize |
18 | extract_keyword_summary(全停用词、全漂移、top-10)/ should_summarize_tool_result(2KB 边界、占比 40% 边界)/ extract_key_info(错误行 error/panic/失败、首尾 5 行、空/短直回)/ 2-gram 汉字滑窗("压缩架构"→"压缩/缩架/架构"、不跨边界) |
context.rs 上下文+主题检测 |
32 | TrackedMessage.topic 推断 / pending_topic_marker 双高置信置位 / 任一 None 不标防误报 / take 消费 |
prompt.rs 系统提示 |
6 | 中文「聚焦准则」段 / 英文「Focus」段 / 兜底 lang 回落 / 压缩 prompt 未波及回归保护 |
df-ai crate 共 189 测全过(commit a2db5c7 自验:cargo test -p df-ai EXIT 0)。
② 苛刻测(边界/对抗,agent 在跑)
针对每条机制的极端输入,验证不崩溃、不误报、有兜底:
- 全漂移(user 消息全是无关内容 → 意图 Unknown → fallback 全量工具,不卡死)
- 全停用词(
extract_keyword_summary返回空字符串,不 panic) - 全错误行(
extract_key_info命中所有 error/panic 行,不超长) - 2KB 边界(
should_summarize_tool_result恰好 2000/2001 字节,阈值判定稳定) - 连续主题切换(连续两条 user topic 不同 →
pending_topic_marker只标末两条,不累积)
状态:部分已并入单测(见
context_helpers.rs对抗段),其余 agent 审查阶段补跑。
③ db 基线分析(改进前跑题现象)— 本文 §三
挑 3 个长对话(均 600KB 量级,glm-5.2),解析 messages JSON,归类跑题现象,作为改进前基线。 结论:跑题主因是「工具结果挤占 + 全量工具分散 + 压缩丢主题」三类,P0-P2 改进恰好对症。
④ 实跑场景(重启 dev 对话验证)— 待人工执行
改进机制已测过,LLM 实际响应效果需实跑验证。建议场景:
| 场景 | 验证改进 | 预期 |
|---|---|---|
| 单主题长对话(连续 5+ 轮同一主题) | 改进3 压缩增强保留主题锚点 | 续接时不丢主线 |
| 主题切换(话题 A→B) | 改进5 主题标记软提示 | 切换后不把旧话题带进新回答 |
| 多主题交织(A/B 轮番) | 改进1 聚焦准则 + 改进5 | 始终围绕当前轮核心 |
| 大工具结果(>2KB / >10KB) | 改进4 工具结果压缩 | tool_result 不挤占上下文 |
| 工具并发(4-12 个并行) | 改进2 意图收敛 29→5-10 | LLM 可见工具收敛,不被工具列表稀释意图 |
二、迭代过程(P0 → P1 → P2)
三批改进,每批:根因 → 改进 → 单测 → 审查 → 效果。常量开关均可回退(调参退回接入前行为)。
P0(102d398)— 系统提示聚焦 + 意图接入 loop 收敛工具
根因:
- 系统 prompt 把「聚焦」混进「行为准则」段被稀释,LLM 无独立抓手
- loop 每轮把全量 29 个工具定义塞给 LLM,工具列表本身稀释意图(LLM 在 29 个工具里挑,而非围绕 user 目标)
改进:
- 改进1
prompt.rs加## 聚焦准则(中)/## Focus(英)独立段 4 条,机制描述("始终围绕当前请求核心目标""切换话题以最新为准""先结论后解释""不主动展开无关上下文"),不混进行为准则 - 改进2
intent.rsfilter_tool_defs按意图收敛工具 29→5-10;agentic:487三重 fallback:置信<0.7 / 过滤后<3 / 空 subset → 回全量;执行路径不变(audit/execute 走完整 registry,filter 只改 LLM 可见tool_defs)
单测:prompt 4(中/英/兜底 lang/压缩未波及)+ intent filter 7(空 subset/命中/漂移/顺序)= 11
审查:CR-25,🟡2(Debug 加 Data 域 + threshold 注释失准)→ 已修 ✅ PASS
常量开关:INTENT_CONF_THRESHOLD = 0.7(调 1.0 关闭收敛)
P1(013ce21)— 压缩增强 + 工具结果压缩
根因:
3. compress_old_messages 压缩时丢主题词(用户反复提及的实体/技术名词是续接锚点),续接时 AI 找不回主线
4. tool_result 原样塞回上下文,>2KB 的搜索/读文件结果直接挤占 token(基线:95% 内容是 tool_result)
改进:
- 改进3
context_helpers.rs压缩 prompt 加「主题/关键词保留段」;失败兜底从「裸裁剪」改extract_keyword_summary(user 消息词素 top-10 去停用词),首位插摘要作锚点 - 改进4
should_summarize_tool_result(>2KB 或占比>40% 触发)+extract_key_info(错误行 error/panic/失败 + 首尾各 5 行中间省略);view-only 不改持久化(原始 tool_result 仍在 DB,build_for_requestclone 视图)
单测:compress/keyword 2 + should_summarize + extract_key_info 14 = 16
审查:CR-26,🟡1(tokenize 中文锚点弱:每汉字单成词素致"压缩/架构"2 字词被拆滤)→ P2 修 ✅ PASS
常量开关:KEYWORD_FALLBACK_ENABLED / TOOL_RESULT_COMPRESS_ENABLED
P2(a2db5c7)— 主题检测保守 + tokenize 2-gram 汉字
根因: 5. 主题切换无感知,切换后旧话题被带进新回答(基线 d6614e0b:用户问"写文件为什么总写错日期",AI 仍在追旧话题"授权体验"的尾巴) 6. CR-26 🟡1:汉字单字词素导致 2 字中文锚点("压缩""架构""审批")被 tokenize 拆滤,关键词摘要抓不住中文主题
改进:
- 改进5
context.rsTrackedMessage.topic 字段(push 推断 topic:意图置信>=0.7 且非 Unknown)+pending_topic_marker(末两条 user topic 双高置信且不同才标,任一 None 不标防误报)+ loop 保守标记(agentic:571顶部读 marker → insert system 软提示,不参与裁剪/压缩) - tokenize 2-gram 汉字滑窗(
context_helpers.rs:294):每汉字既单成词素又产出相邻 2-gram("压缩架构"→"压缩/缩架/架构"),非汉字边界重置窗口不跨边界组词
单测:topic 检测 6 + tokenize 2-gram(含不跨边界)= 测并入 context 32 + context_helpers 增量
审查:CR-26 🟡1 已闭环
常量开关:TOPIC_MARKER_ENABLED = true(false 跳过标记,排障/对比用)
保守性:topic 不参与裁剪/压缩、TrackedMessage 不 Serialize(只 ChatMessage 落库)、软提示非强制
三原则对齐
| 原则 | 落地 |
|---|---|
| 优雅 | filter 只改 LLM 可见 tool_defs 不动执行路径;view-only 不改持久化;topic 软提示不参与裁剪;常量开关可回退 |
| 可靠 | 三重 fallback(意图置信/过滤数/空 subset);压缩失败兜底关键词摘要;view-only clone 视图;topic 双高置信才标防误报 |
| 易迭代 | 4 个常量开关(INTENT_CONF_THRESHOLD/KEYWORD_FALLBACK_ENABLED/TOOL_RESULT_COMPRESS_ENABLED/TOPIC_MARKER_ENABLED)可独立开关排障;每批独立 commit + 审查回填;tokenize 2-gram 是纯函数增量无破坏 |
三、改进前基线(db 对话分析)
数据源:
C:/Users/23780/AppData/Roaming/top.1216.devflow/devflow-dev.db,ai_conversations.messagesJSON 挑样:raw_len>500K 的长对话 3 个(均为 glm-5.2,改进前数据)
量化指标(3 对话均值)
| 指标 | f89d129a | a95f5d6d | d6614e0b | 均值 |
|---|---|---|---|---|
| 总消息数 | 251 | 213 | 211 | 225 |
| user / assistant / tool | 22 / 111 / 118 | 22 / 88 / 103 | 22 / 96 / 93 | 22 / 98 / 105 |
| tool_calls 总数 | 118 | 103 | 93 | 105 |
| 单轮 ≥3 工具并发 | 6 | 4 | 3 | 4 |
| tool_result >2KB | 39 | 34 | 44 | 39 |
| tool_result >10KB | 24 | 14 | 14 | 17 |
| 空 assistant(纯工具 echo) | 69/111 (62%) | 32/88 (36%) | 41/96 (43%) | 47% |
| user 「继续」 | 5 | 2 | 0 | 2.3 |
| user ≤12 字短问 | 14/22 | 7/22 | 13/22 | 52% |
| asst 文本 / tool 文本 比 | 0.05 | 0.04 | 0.05 | 0.05 |
关键信号:
- asst 文本仅占 4-5%,95% 上下文是 tool_result → 工具结果挤占严重(改进4 对症)
- 47% assistant 轮次是纯工具 echo(content 空、只发 tool_calls)→ LLM 不产出围绕 user 目标的文本,被工具流程推着走(改进1+2 对症)
- 单轮 4-12 工具并发 → 工具列表稀释意图(改进2 收敛对症)
跑题现象归类(5 类,含实例)
类型1:工具结果挤占(最高频,3/3 对话)
- 现象:tool_result 占 95% token,asst 文本仅 4-5%。AI 回复被大块搜索/读文件结果淹没,user 要的「结论」淹没在工具回显里。
- 实例(f89d129a [58]):用户问"我做哪些决策,多角度论证",AI 先并发 12 个工具读 4 个文件([9]-[49] 占 50+ 条消息),最后才在 [58] 一次性吐 6760 字决策分析。论证正确但被工具流程拖到 50 条消息后。
- 对症改进:P1 改进4 工具结果压缩(>2KB 触发 extract_key_info 保留错误行+首尾 5 行)。
类型2:全量工具分散意图
- 现象:每轮把 29 个工具全塞 LLM,AI 在工具列表里挑而非围绕 user 目标,表现为"读一堆无关文件试探"。
- 实例(d6614e0b [92]-[116]):用户问"消息发送交付功能",AI 并发 3 工具读 useAiEvents/useAiStream/useAiSend/useAiConversations/ai.ts/stream_recv/commands.rs/agentic.rs/persistence 9 个文件,最后才产出分析。
- 对症改进:P0 改进2 意图 filter_tool_defs 收敛 29→5-10。
类型3:压缩丢主题(长对话续接跑题)
- 现象:
compress_old_messages裁掉主题词后,续接时 AI 找不回主线,绕旧话题。 - 实例(d6614e0b [174]→[181]):用户问"最新 aichat 权限",AI 先读旧路径
2025-07-14.md(不存在),再搜索才发现文件已被手工改名为07-15,绕了一圈。主题锚点("授权方案文件")在压缩中被丢。 - 对症改进:P1 改进3 压缩增强 + extract_keyword_summary 保留主题锚点。
类型4:多主题交织(切换不干净)
- 现象:用户切换话题,AI 仍带旧话题尾巴进新回答。
- 实例(d6614e0b [140]→[153]):用户从"消息交付功能不足"切到"写文件为什么写错日期",AI 在 [141]-[152] 还在读 useAiSend/ai_tools 等旧话题文件,[153] 才答到日期根因。
- 对症改进:P2 改进5 主题检测 + pending_topic_marker 软提示。
类型5:无聚焦(系统提示无独立抓手)
- 现象:系统 prompt 把「聚焦」混在「行为准则」被稀释,AI 无明确"围绕当前请求核心"的指令约束。
- 实例(3 对话通用):47% assistant 轮次纯工具 echo 不产出围绕 user 目标的文本。
- 对症改进:P0 改进1 系统提示独立聚焦段(中「聚焦准则」/ 英「Focus」4 条)。
基线小结
5 类跑题现象与 P0-P2 五条改进一一对应,改进设计精准对症基线,无遗漏主因。改进后预期:
| 基线现象 | 改进 | 机制测过 | 实跑预期 |
|---|---|---|---|
| 工具结果挤占 | 改进4 | ✅ 14 测 | tool_result 压缩释放 token |
| 全量工具分散 | 改进2 | ✅ 7 测 | 工具收敛 29→5-10 |
| 压缩丢主题 | 改进3 | ✅ 2 测 | 主题锚点保留 |
| 多主题交织 | 改进5 | ✅ 6 测 | 切换软提示 |
| 无聚焦 | 改进1 | ✅ 4 测 | 独立聚焦段 |
四、遗留与风险
已知遗留
- LLM 实际响应效果需实跑验证 — 单测覆盖机制正确性(filter/压缩/tokenize/topic 标记),但 LLM 在改进后 prompt + 收敛工具下的实际「抓重点」表现,需重启 dev 对话按 §一④ 场景实跑。机制正确 ≠ LLM 行为改善。
- 主题检测 F1 天花板 — topic 推断依赖意图置信>=0.7(非 Unknown),意图识别本身的准确率决定 topic 检测上限。短问(基线 52% user ≤12 字)意图置信常 <0.7,这类 topic 推断为 None,不参与标记(保守防误报的代价)。
- 中文 tokenize 2-gram 仍有边界 — 3+ 字词("状态机""工作流")能由 2-gram 组合近似,但语义级词("推进链"3 字)仍可能被切;已优于单字方案,非完美。
未覆盖的跑题场景(建议补改进,基于基线)
基线 5 类已被 P0-P2 覆盖,未发现遗漏的主因。但有两类次生现象改进未直接覆盖,建议观察实跑后决定是否补:
- 「继续」疲劳(f89d129a 5 次"继续")— AI 被工具流程拖到长尾,user 不得不催。根因是工具流程过长(类型1+2 的次生),P0-P2 收敛工具+压缩后应缓解,但若实跑仍高频出现,可考虑加「单轮工具调用数硬上限」或「长工具流程中途主动给中间结论」。
- 空 assistant 纯工具 echo(47%)— P0 改进1 聚焦段是软约束,不强制 assistant 每轮产出文本。若实跑 echo 率仍高,可考虑 prompt 加「工具调用前用一句话说明意图与当前请求的关系」的硬约束(当前基线已有"执行操作前简要说明你的意图",但未要求与当前请求关联)。
两项均为次生/可选,不阻塞当前迭代,建议实跑验证后再定。
附:数据可复现
db 分析脚本核心逻辑(python sqlite3,标准库):
import sqlite3, json
conn = sqlite3.connect(r'C:/Users/23780/AppData/Roaming/top.1216.devflow/devflow-dev.db')
# 按长度排长对话
conn.execute("SELECT id,title,LENGTH(messages) FROM ai_conversations ORDER BY 3 DESC LIMIT 5")
# 解析单对话
msgs = json.loads(row['messages'])
# 量化:空 assistant(content 空但 tool_calls 非空)/ asst 文本 vs tool 文本比 / 单轮工具并发数
复现命令:见本任务分析脚本(已离线保存于 /tmp/analyze_chat.py + /tmp/metrics.py)。