Files
DevFlow/docs/05-代码审查/aichat-会话实测分析-2026-06-22.md
2026-06-24 00:29:02 +08:00

186 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI Chat 真实会话实测分析(上下文 / 工具 / Agent 行为)
> 日期:2026-06-22 | 方法:从生产 DB(`%APPDATA%/top.1216.devflow/devflow-dev.db`)实测会话 `84dc36c8` 全量 86 条消息反推,Python 脚本统计(`.temp/analyze_session.py`)
> 基线:会话 prompt **1,320,713** / completion **7,578** tokens(completion/prompt 比 0.57%)
> 产物:**3 P0 / 3 P1 / 2 P2**
> 性质:**实测数据反推**(非静态代码审查)。finding 的源码根因为"假设",须独立 grep/read 核验后再定论
> 关联:[[devflow-compress-ui-no-fold]] [[devflow-generating-statemachine]] [[aichat-techdebt-audit-2026-06-21]] [[code-review-anti-contamination]] [[devflow-aichat-streaming-md]]
---
## 会话概况
| 项 | 值 |
|---|---|
| conversation_id | `84dc36c8-b416-44bc-a7c6-c4e00de82b6f` |
| 标题 | `D:\Downloads\kms` |
| 模型 | glm-5.2 |
| 跨度 | 2026-06-22 01:29 ~ 12:23(中间大间隔,有效工作段 ~11:5712:23) |
| 消息数 | 86 |
| prompt / completion tokens | 1,320,713 / 7,578 |
| completion/prompt 比 | **0.57%**(极端输入重) |
| 工具调用 | patch_file **16** / read_file **10** / list_directory 3 / grep 1 |
| tool 返回总量 | **194KB 跨 41 条** |
| `parts` 字段 | 0/86(全 null) |
| 用户催促次数 | 7(继续×2 / 完成了吗×2 / 仔细检查 / 核对 收尾 / 核对 内容) |
| 任务 | 整理 `D:\Downloads\kms` 下 README/INDEX + 三知识库 HTML 头尾修复 |
---
## P0(3 项)
### [P0-1] 上下文管理实质失效:压缩标记 `compressed` 未降 prompt,token 累积至 1.32M
- **现象**:seq 065 `status=compressed`(仅个别 active),但最终 `prompt_tokens=1,320,713`。压缩"生效"了,prompt 却没降。
- **db 证据**:全量 86 条中 66 条 `status='compressed'`;conv 元数据 `prompt_tokens=1320713 / completion_tokens=7578`
- **根因假设**:`compressed` 仅压历史消息的展示态/落库态,**未真正削减送入 LLM 的 prompt**。与 [[devflow-compress-ui-no-fold]](2026-06-18 决策"后端省 token 不前端藏")冲突——决策要求后端真省,实测后端没省。
- **影响**:1.32M 接近模型上下文上限,长会话必爆;成本几乎全烧在重复搬运文件内容。
- **待核验源码点**:compress 模块(`src-tauri/src/commands/ai/` 下 compress 相关)——压缩后重组 messages 时是否真替换为摘要,还是仅打标记原样回灌。
- **关联**:印证 [[aichat-techdebt-audit-2026-06-21]] 3-compress 单元的 2 个 smell。
### [P0-2] 工具返回值协议不一致:`patch_file` / `read_file` 部分路径裸吐文件全文
- **现象**:同一工具返回结构两副面孔:
- seq 16 patch_file → `{"changed":true,"diff":...}` ✅ JSON 正常
- seq 37/40/42/46/49/51/53/60/63/65/75/76 patch_file → `raw 6204c` ❌ 裸文本(parse 失败)
- seq 6/7 read_file → `{"content":...}` ✅ 正常
- seq 11/29/31/33 read_file → `raw 6204c` ❌ 裸文本
- **db 证据**:41 条 tool 返回总计 194KB;`6204` 字符数反复出现(≈被操作 HTML 文件大小)。
- **根因假设**:`patch_file` 成功路径本应只回 `changed`+`diff`,却把整个文件内容回灌;或某分支(超长 / 特定路径)跳过 JSON 包装直接返回文件串。`6204c` 即文件实际长度被全文塞回 context。
- **影响**:**这是 prompt 1.32M 的直接元凶**。每次 patch 一次 = 文件全文再进一遍 context。16 次 patch × ~6KB ≈ 100KB 仅 patch 回灌。
- **待核验源码点**:`tool_registry.rs` patch_file handler(~1247 区,[[devflow-patch-file-design]] 落地点)与 read_file handler(~892-976)的返回序列化分支——确认哪条路径丢 JSON 包装。
- **关联**:与 [[aichat-techdebt-audit-2026-06-21]] P1「expected_hash schema 契约破裂」同源(工具契约失配家族)。
### [P0-3] 重复 tool 结果:同一结果多次持久化,去重缺失
- **现象**:
- seq 11/12/13/14 → 连续 4 条 `raw 6204c`,同一 read 结果存 4 份
- seq 69/70/71 → 3×6204c;seq 83/84/85 → 3×6204c
- seq 75/76 → 2×6204c
- **db 证据**:上述 seq 的 content 长度完全一致(6204),且时间戳相同(同一秒)。
- **根因假设**:一次 assistant 回合并行触发多个 read(读 mysql+redis+linux 三文件),结果落库时按 tool_call 重复写,或同一 `tool_call_id` 结果被多次持久化;前端流式接收去重逻辑缺失。
- **影响**:context 进一步虚胖;且会污染后续压缩/摘要(同一内容被当作多条独立事实)。
- **待核验源码点**:tool 结果落库路径(`ai_messages` 写入)——按 `tool_call_id` 是否做唯一约束/去重;流式 patch 合并逻辑。
---
## P1(3 项)
> **总纲(用户归纳)——编排规划能力弱**:P1-1(无自检)与 P1-3(无并行调度)同源。agent 无任务分解、无并行 fan-out、无宣称完成前的自检规划,单链 ReAct 逐工具串行推进,识别不出"N 个独立子任务可并行"。这是 agent 自主性/可靠性的核心缺口,印证 [[aichat-decision-capability]] 与 [[aichat-b-route-parallel-multiround]]。归 B 路线立项。
### [P1-1] Agent 过早宣称完成,无 checklist 自检,用户被迫 7 次催促
- **现象**:assistant 3 次宣称完成,3 次被用户推翻:
| seq | 时刻 | 宣称 | 推翻证据 |
|---|---|---|---|
| #26 | 12:01 | "整理完成" | #27 用户"文档内容需要按要求整理" |
| #66 | 12:10 | "**全部完成了!**" | #67"仔细检查" → #72 发现 `theme.js` vs `claude-artifact-theme.js` 引用名错(漏改 2 个文件) |
| #80 | 12:19 | "全部核对完毕,7 个标记全部对齐无遗漏" | #81"核对 内容"(用户不信,要求再核)→ 会话中断于此 |
- **db 证据**:`theme.js` 引用名错误本可一次 grep 抓出,却要用户肉眼挑(实际 #78 才首次用 grep,且是用户第三次催后)。
- **根因假设**:无宣称完成前的强制自检机制(如 grep 引用一致性 / 闭合标记核对)。Agent 倾向于 patch 完即宣布成功,不验证。
- **影响**:用户对 agent 完成度零信任,交互成本极高;本质是 agent 自主性/可靠性的核心缺口。
- **待核验/落点**:属 agent 行为策略层(prompt / loop 收尾钩子),非单点代码 bug。建议与 [[aichat-b-route-parallel-multiround]] B 路线(决策能力)合并立项。
- **关联**:[[aichat-decision-capability]](单链 ReAct,无规划式智能)的现实印证。
### [P1-2] 末尾会话中断:tool 全部返回后无 assistant 回复(generating 状态机卡死)
- **现象**:#82 assistant 触发读三文件 → #83/84/85 tool 全部正常返回(`status=active`)→ **之后再无 assistant 消息**,会话 12:23 停尸。
- **db 证据**:seq 85 是最后一条,role=tool;无 seq 86 的 assistant 总结回复。`active` 状态说明 tool 执行完成,但后续生成未发生。
- **根因假设**:tool 执行完但 `generating` 状态未复位,后续回复被吞。高度吻合 [[devflow-generating-statemachine]] 记录的"generating 复位散布 + 无 panic 兜底"卡死模式。
- **影响**:用户看到"读完了"就没下文,必须手动重发;且该会话 token 已 1.32M,重发极可能直接触上限。
- **待核验源码点**:agentic loop 的 tool-后-续生成衔接(`mod.rs` 收尾 / `try_continue` / per-conv generating 复位)。
### [P1-3] 三个独立文件整理任务严格串行,无并行 fan-out 调度
- **现象**:`mysql_guide` / `redis_guide` / `linux_ubuntu_guide` 三个 HTML 整理是独立子任务(无依赖),本可并行。但 agent 严格串行:mysql(12:0212:06)→ redis(12:0712:08)→ linux(12:0812:10),**时段完全不重叠**;每个文件内还 read→patch→read→patch 多轮。assistant 从未在一个 turn 内对三文件 fan-out 工具调用。
- **db 证据**:时间线三文件处理时段分明不重叠;seq 68"仔细检查"后只 read mysql 一个,未并行触发 redis/linux 的核对。
- **根因假设**:单链 ReAct 无任务分解层。缺"识别 N 个独立子任务 → 同 turn 并行发起 N 路工具调用"的编排能力。
- **影响**:耗时线性叠加;同一指令在 context 里反复加载三遍;用户等待感差。**直接印证 [[aichat-b-route-parallel-multiround]]**(用户 2026-06-20 已决策"单对话并行多轮必须做")。
- **归并**:与 P1-1 同属"编排规划能力弱"(见 P1 区总纲),归 B 路线。
---
## P2(2 项)
### [P2-1] system 消息污染 + 时序错乱
- **现象**:
- seq 0 `role=system`,content 却是 assistant 语义残留:"我需要查看 mysql\_guide.html 的最后几行来确认尾部内容..."
- seq 0 timestamp `12:08:02`,seq 1 却是 `01:29:35`——seq 0 比 seq 1 晚 10 小时。
- **根因假设**:system/上下文注入消息的 role 标记错位;timestamp 用"插入时刻"而非"会话时刻"。
- **影响**:排序、压缩、计费全受污染;system 消息混入 assistant 内容会误导模型。
- **待核验源码点**:system prompt 注入路径(`ai_messages` 写入 system 行的位置)+ timestamp 赋值逻辑。
### [P2-2] 同文件反复 read,无缓存 / 无 diff 感知
- **现象**:`mysql_guide.html` read **4×**`linux_ubuntu_guide.html` read **4×**;patch_file 共 16× 改 3 个文件。read→patch→read→patch 循环。
- **根因假设**:每次 patch 后又全文 read 确认(叠加 P0-2 全文回灌);无 session 级文件缓存或"patch 后不重读"约定。
- **影响**:context 雪崩放大器;与 P0-2 叠加是 prompt 爆炸的复合原因。
- **待核验/落点**:工具侧可加"会话内文件指纹缓存,LLM 重读同文件时返回缓存命中提示";或 prompt 侧约束 patch 后不重读。
---
## 附录:`parts` 字段全空(待观察项)
- **现象**:`msgs_with_parts=0/86`。[[devflow-aichat-streaming-md]] 记录块级 memo(memo=mapped.lexer+v-memo)已落地(ARC-260615-08)。
- **不定论**:本会话可能早于改造,或 `parts` 存于别处 / 未回填。需对照**新产生**的会话核验,不能据此断定流式渲染回退。
---
## 落点建议(未实施,遵守 [[session-role-diagnose-only]])
| 项 | 落点 |
|---|---|
| P0-1 / P0-2 / P0-3 | 并入 [[aichat-techdebt-audit-2026-06-21]] P1 主线,或单列"上下文/工具返回"专项 |
| P1-2 中断 | 印证 [[devflow-generating-statemachine]] 待落地链(已有详案) |
| P1-1 自检 + P1-3 并行调度 | 同属「编排规划能力弱」总纲,归 [[aichat-b-route-parallel-multiround]] B 路线(任务分解 + 并行 fan-out + 宣称前自检) |
| P2-1 / P2-2 | 低优登记 todo |
**最高杠杆**:P0-2(工具返回全文回灌)——改一处(工具序列化返回 diff/changed 而非全文),prompt 直接降一个量级。
---
## 复现方法
```bash
# 分析脚本(已留存)
python E:/wk-lab/devflow/.temp/analyze_session.py
# DB 只读连接
sqlite3 "file:%APPDATA%/top.1216.devflow/devflow-dev.db?mode=ro" -uri
# 表:ai_conversations / ai_messages / ai_tool_executions
```
数据快照:2026-06-22 12:36 抓取,会话可能仍在变化(WAL 活跃)。
---
## 2026-06-23 修正与落地(自主推进,290 src-tauri + 324 df-ai 测试全过)
**核验推翻**(实测 DB tool_call_id + timestamp 独立核验,[[code-review-anti-contamination]]):
- **P0-3 撤销**:两会话(4ae73423/84dc36c8)"重复 tool 结果"实测 tool_call_id **全不同** + timestamp 间隔数分钟/小时 → LLM 不同时刻重调,非落库去重 bug。原 P0-3 误判(06-22 报告 + 核验 agent 都错,实测数据证伪)。
- **P0-2 性质修正**:非"prompt 爆炸元凶"(prompt 用内存完整态,非落库截断版)。真因是 `conversation.rs:72` truncate_for_persist 对**序列化 JSON 中间截断**插裸 `\n\n[...省略 N 字符...]\n\n`,破坏 JSON 致**落库坏 JSON**(reload/审计 parse FAIL,pos3072=TRUNCATE_HEAD)。
- **中断A 非bug**:续轮机制完整(mod.rs:1362-1478),84dc36c8 seq85 后无回复疑达 max_iterations 或自然收敛。
- **中断B 真缺陷**:4ae73423 末尾 advance_task 审批 `__PENDING__` 卡死(mod.rs:1464 return 无超时),非 generating 卡死。
**本次修复落地**:
| 项 | 位置 | 修复 |
|---|---|---|
| intent 收敛误杀(4ae73423 主因) | intent.rs | L0 strip_mention_tags 剥离 mention 前缀 + L1 Project/Task/Idea 加 File domain |
| write_file parent 误拒(P0,授权内文件被拒) | tool_registry.rs:1114/1421/1559 | 改 allowed_dirs.is_authorized(脱 workspace_root) |
| read_file limit 忽略 | tool_registry.rs:1023 | 无 offset 尊重 LLM limit(原固定 500) |
| truncate 破坏 JSON(P0-2) | conversation.rs:175 | 跳 tool role 截断 |
| 压缩丢末条消息 | agentic/mod.rs:1225 | 全 provider 失败 return 前补 save_conversation |
| run_command && PS5 失败 | df-execute/shell.rs | pwsh(PS7)优先探测 + PS5 兜底(OnceLock 缓存) |
| remote_bridge args default | remote_bridge.rs:97 | custom default 返空 Object(#[serde(default)] 给 Null) |
| remote_bridge r1 双轨测试 | remote_bridge.rs:872 | 设态对齐双轨(conv_state=Generating) |
**写 docs/待决策.md**(非确定性,需决策):
- BUG-260623-03 审批 pending 无超时(中断B,推荐 b 前端倒计时)
- ARC-260623-01 workspace_root 写死残留(delete_file trash/resolve/run_command working_dir,分发适配)
**生效**:需重启 devflow app(cargo tauri dev / 重新 build),新会话才用新二进制。