文档: 审查回填 CR-22~27 + 跨端 AI Chat 设计 + 跑题试验记录
审查回填(均 ✅ 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 五改进精准对应 + 四层测试计划 + 迭代过程
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
| [F-09-多会话并发架构设计-2026-06-19.md](./已编号方案/F-09-多会话并发架构设计-2026-06-19.md) | F-260616-09 | 📐 草案 | AiSession 单例 → 多会话并发(B 阶段)。**当前真相源** |
|
| [F-09-多会话并发架构设计-2026-06-19.md](./已编号方案/F-09-多会话并发架构设计-2026-06-19.md) | F-260616-09 | 📐 草案 | AiSession 单例 → 多会话并发(B 阶段)。**当前真相源** |
|
||||||
| [F-09B-多会话并发设计-2026-06-16.md](./已编号方案/F-09B-多会话并发设计-2026-06-16.md) | F-260616-09 | 🗄 过时(被 F-09 取代) | 06-16 旧版,行号已过期。保留回溯,新设计看 F-09 |
|
| [F-09B-多会话并发设计-2026-06-16.md](./已编号方案/F-09B-多会话并发设计-2026-06-16.md) | F-260616-09 | 🗄 过时(被 F-09 取代) | 06-16 旧版,行号已过期。保留回溯,新设计看 F-09 |
|
||||||
| [F-15-上下文管理增强设计-2026-06-16.md](./已编号方案/F-15-上下文管理增强设计-2026-06-16.md) | F-260616-15 | 📐 设计 | ContextManager 分段 / 压缩 / 裁剪 |
|
| [F-15-上下文管理增强设计-2026-06-16.md](./已编号方案/F-15-上下文管理增强设计-2026-06-16.md) | F-260616-15 | 📐 设计 | ContextManager 分段 / 压缩 / 裁剪 |
|
||||||
|
| [F-260620-01-跨端AIChat-微信小程序-2026-06-20.md](./已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md) | F-260620-01 | 📐 草案(灵感 4495fbcd 待晋升) | 微信小程序 ↔ Rust 云后端(df-relay) ↔ DevFlow 桌面端(df-tunnel)实时同步,Rust 选型代码复用 |
|
||||||
| [B-03-人工审批响应机制-2026-06-14.md](./已编号方案/B-03-人工审批响应机制-2026-06-14.md) | B-260614-03 | ✅ 已落地(B-03a) | HumanNode execute subscribe/send/select 完整审批链 |
|
| [B-03-人工审批响应机制-2026-06-14.md](./已编号方案/B-03-人工审批响应机制-2026-06-14.md) | B-260614-03 | ✅ 已落地(B-03a) | HumanNode execute subscribe/send/select 完整审批链 |
|
||||||
| [B-260616-21排查方案-2026-06-16.md](./已编号方案/B-260616-21排查方案-2026-06-16.md) | B-260616-21 | 📐 排查方案 | 工具卡片重复渲染根因(audit 重复 emit Started) + 修复方案 |
|
| [B-260616-21排查方案-2026-06-16.md](./已编号方案/B-260616-21排查方案-2026-06-16.md) | B-260616-21 | 📐 排查方案 | 工具卡片重复渲染根因(audit 重复 emit Started) + 修复方案 |
|
||||||
|
|
||||||
|
|||||||
75
docs/02-架构设计/已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md
Normal file
75
docs/02-架构设计/已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# F-260620-01 跨端 AI Chat:微信小程序 ↔ Rust 云后端 ↔ DevFlow 桌面端实时同步
|
||||||
|
|
||||||
|
> 创建:2026-06-20 | 状态:📐 设计草案(灵感 4495fbcd 待晋升 + 任务 6c816709 todo)
|
||||||
|
> 上级索引:[../INDEX.md](../INDEX.md)
|
||||||
|
|
||||||
|
## 一、目标
|
||||||
|
|
||||||
|
微信小程序**远程**使用 DevFlow AI Chat(发送/停止/重新生成/审批/切换对话),体验与桌面端一致。桌面端与小程序打开同一对话时**双向实时同步**(类似微信电脑端 + 手机端同时在线)。
|
||||||
|
|
||||||
|
## 二、架构(三层,穿 NAT)
|
||||||
|
|
||||||
|
```
|
||||||
|
微信小程序(df-miniapp) ◄──WSS──► Rust 云后端(df-relay) ◄──WS 出站长连接──► 本地 DevFlow(df-tunnel)
|
||||||
|
前端 UI(复用 AI Chat) axum WS 中继/广播 AI 对话引擎(现有)
|
||||||
|
```
|
||||||
|
|
||||||
|
桌面端**出站**连云(穿 NAT,无需端口映射/公网 IP),云后端中继小程序 ↔ 桌面端。
|
||||||
|
|
||||||
|
### Layer 1:本地 DevFlow 桌面端 — `df-tunnel`(WS Client + 事件桥接)
|
||||||
|
- **新建** `crates/df-tunnel/`:内嵌 tokio-tungstenite WS client
|
||||||
|
- DevFlow 启动后主动连云后端 `wss://your-server/ws/device`(出站穿 NAT)
|
||||||
|
- **事件桥接**:在 `app.emit("ai-chat-event", ...)` 的同时,clone event 推 WS 隧道
|
||||||
|
- **指令路由**:收到云后端发来的操作指令(send/stop/approve/regenerate/switch),调对应 Tauri command 逻辑
|
||||||
|
- **配对绑定**:桌面端首次配置云后端地址 + token,持久化 Settings KV
|
||||||
|
|
||||||
|
### Layer 2:云后端 — `df-relay`(Rust axum WS Server + 广播中继)
|
||||||
|
- **新建** `crates/df-relay/`(axum + tokio-tungstenite)
|
||||||
|
- WS Server 接受两类连接:小程序(device_id 鉴权)+ 桌面端(token 配对)
|
||||||
|
- 广播中继:小程序操作 → 桌面端;桌面端事件 → 小程序
|
||||||
|
- 无业务逻辑(纯转发,保持轻量)
|
||||||
|
|
||||||
|
### Layer 3:微信小程序 — `df-miniapp`(前端 UI)
|
||||||
|
- 微信小程序前端(复用桌面 AI Chat 逻辑/样式,适配小程序框架)
|
||||||
|
- WSS 连云后端
|
||||||
|
- 远程操作映射到桌面端 Tauri command
|
||||||
|
|
||||||
|
## 三、技术选型:Rust 云后端(非 Go/Node)
|
||||||
|
|
||||||
|
灵感 4495fbcd(技术选型论证,score 60)核心命题:**Rust 云后端最大化代码复用 + 类型一致**。
|
||||||
|
|
||||||
|
1. **代码复用矩阵**:DevFlow 现有 10 Rust crate(df-storage/df-ai/df-ai-core/df-workflow/df-nodes/df-execute/df-ideas/df-types/df-mcp/df-project),云后端用 Rust 可直接 path 引用,零适配。Go 需全部重写或 FFI 桥接。
|
||||||
|
2. **类型系统一致性(最被低估)**:AiChatEvent 17 变体 + ChatMessage 10+ 字段 + 工具调用嵌套 JSON。两端同语言 → 编译时类型一致保证。跨语言 → 手动同步镜像类型 → 必然遗漏 → 运行时消息丢失。
|
||||||
|
3. **团队版演进路径**:云后端复用 df-types/df-ai-...,为「单机版 → 团队版」铺路(阶段2 云后端承载多用户/协作)。
|
||||||
|
|
||||||
|
## 四、关键设计点(待定)
|
||||||
|
|
||||||
|
| 维度 | 选项 | 待决策 |
|
||||||
|
|---|---|---|
|
||||||
|
| **同步粒度** | 事件流透传(ai-chat-event 经隧道)vs 状态同步(全量 messages) | 倾向事件流(增量,带宽低) |
|
||||||
|
| **冲突处理** | 两端同时发消息/审批 | 时间戳 + 桌面端为真相源(AI 引擎在桌面) |
|
||||||
|
| **安全** | token 配对 + WSS + 指令鉴权 | 必须(device_id + token 双因子) |
|
||||||
|
| **离线** | 桌面端离线时小程序降级 | 提示"桌面端未连接"+ 缓存指令待重连 |
|
||||||
|
| **部署** | df-relay 部署阿里云(ECS/容器) | 待定(成本/运维) |
|
||||||
|
|
||||||
|
## 五、关系链
|
||||||
|
|
||||||
|
- **灵感 4495fbcd**:跨端控制技术选型论证(score 60,pending_review,待晋升)
|
||||||
|
- **任务 6c816709**:跨端 AI Chat 实现(todo,priority 1)
|
||||||
|
|
||||||
|
## 六、实施分阶段(待 todo 规划)
|
||||||
|
|
||||||
|
| Phase | 内容 | 依赖 |
|
||||||
|
|---|---|---|
|
||||||
|
| **P1** | L2 df-relay 云后端(axum WS Server 基础 + 广播中继 + 鉴权) | 无 |
|
||||||
|
| **P2** | L1 df-tunnel 桌面端(WS client + 事件桥接 + 指令路由 + 配对绑定) | P1 |
|
||||||
|
| **P3** | L3 df-miniapp 小程序(前端 UI + WSS 连云) | P1/P2 |
|
||||||
|
| **P4** | 双向同步完善(冲突 + 安全 + 离线 + 部署) | P1/P2/P3 |
|
||||||
|
|
||||||
|
## 七、与现有架构关系
|
||||||
|
|
||||||
|
- **df-mcp**(已做):对外 stdio 暴露数据层(本地)。跨端是**远程**(WS 隧道)。互补。
|
||||||
|
- **F-09 多会话**(已做):conv_id 路由。跨端事件透传复用 conv_id。
|
||||||
|
- **AiChatEvent 17 变体**(现有):跨端透传的事件契约(两端同类型,Rust 优势)。
|
||||||
|
|
||||||
|
> 待决策:晋升灵感 4495fbcd → 启动 P1?或暂缓(单机版稳定后再跨端)?
|
||||||
218
docs/02-架构设计/构想审查/跑题改进试验记录-2026-06-20.md
Normal file
218
docs/02-架构设计/构想审查/跑题改进试验记录-2026-06-20.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# AI Chat 跑题改进试验记录
|
||||||
|
|
||||||
|
> 创建:2026-06-20
|
||||||
|
> 范围:AI Chat 长对话「抓不住重点/跑题」根因治 — 测试计划 + 迭代过程 + 改进前基线 + 遗留
|
||||||
|
> 关联提交:P0 `102d398` / P1 `013ce21` / P2 `a2db5c7`
|
||||||
|
> 关联设计:`意图识别层论证-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 收敛工具
|
||||||
|
|
||||||
|
**根因**:
|
||||||
|
1. 系统 prompt 把「聚焦」混进「行为准则」段被稀释,LLM 无独立抓手
|
||||||
|
2. loop 每轮把全量 29 个工具定义塞给 LLM,工具列表本身稀释意图(LLM 在 29 个工具里挑,而非围绕 user 目标)
|
||||||
|
|
||||||
|
**改进**:
|
||||||
|
- 改进1 `prompt.rs` 加 `## 聚焦准则`(中)/ `## Focus`(英)独立段 4 条,机制描述("始终围绕当前请求核心目标""切换话题以最新为准""先结论后解释""不主动展开无关上下文"),不混进行为准则
|
||||||
|
- 改进2 `intent.rs` `filter_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_request` clone 视图)
|
||||||
|
|
||||||
|
**单测**: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.rs` TrackedMessage.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.messages` JSON
|
||||||
|
> 挑样: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 测 | 独立聚焦段 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、遗留与风险
|
||||||
|
|
||||||
|
### 已知遗留
|
||||||
|
|
||||||
|
1. **LLM 实际响应效果需实跑验证** — 单测覆盖机制正确性(filter/压缩/tokenize/topic 标记),但 LLM 在改进后 prompt + 收敛工具下的实际「抓重点」表现,需重启 dev 对话按 §一④ 场景实跑。机制正确 ≠ LLM 行为改善。
|
||||||
|
2. **主题检测 F1 天花板** — topic 推断依赖意图置信>=0.7(非 Unknown),意图识别本身的准确率决定 topic 检测上限。短问(基线 52% user ≤12 字)意图置信常 <0.7,这类 topic 推断为 None,不参与标记(保守防误报的代价)。
|
||||||
|
3. **中文 tokenize 2-gram 仍有边界** — 3+ 字词("状态机""工作流")能由 2-gram 组合近似,但语义级词("推进链"3 字)仍可能被切;已优于单字方案,非完美。
|
||||||
|
|
||||||
|
### 未覆盖的跑题场景(建议补改进,基于基线)
|
||||||
|
|
||||||
|
基线 5 类已被 P0-P2 覆盖,未发现遗漏的**主因**。但有两类**次生现象**改进未直接覆盖,建议观察实跑后决定是否补:
|
||||||
|
|
||||||
|
1. **「继续」疲劳**(f89d129a 5 次"继续")— AI 被工具流程拖到长尾,user 不得不催。根因是工具流程过长(类型1+2 的次生),P0-P2 收敛工具+压缩后应缓解,但若实跑仍高频出现,可考虑加「单轮工具调用数硬上限」或「长工具流程中途主动给中间结论」。
|
||||||
|
2. **空 assistant 纯工具 echo**(47%)— P0 改进1 聚焦段是软约束,不强制 assistant 每轮产出文本。若实跑 echo 率仍高,可考虑 prompt 加「工具调用前用一句话说明意图与当前请求的关系」的硬约束(当前基线已有"执行操作前简要说明你的意图",但未要求与当前请求关联)。
|
||||||
|
|
||||||
|
> 两项均为**次生/可选**,不阻塞当前迭代,建议实跑验证后再定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附:数据可复现
|
||||||
|
|
||||||
|
db 分析脚本核心逻辑(python sqlite3,标准库):
|
||||||
|
|
||||||
|
```python
|
||||||
|
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`)。
|
||||||
34
docs/todo.md
34
docs/todo.md
@@ -619,5 +619,37 @@
|
|||||||
3. 灵感详情可查关联任务列表(若双向)
|
3. 灵感详情可查关联任务列表(若双向)
|
||||||
4. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`
|
4. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`
|
||||||
|
|
||||||
**状态**:📋 待设计决策(粒度/存储/方向)+ 待消息级溯源 P0 完成(释放 migrations/models)
|
**状态**:✅ 已实施(tasks.idea_id 1对1 单向,commit 4a87c55,粒度/方向已定)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-260620-01 跨端 AI Chat:微信小程序 ↔ Rust 云后端 ↔ DevFlow 桌面端
|
||||||
|
|
||||||
|
**背景**:微信小程序远程用 DevFlow AI Chat,桌面端与小程序双向实时同步(微信电脑+手机同时在线模式)。
|
||||||
|
**设计**:[F-260620-01-跨端AIChat-微信小程序-2026-06-20.md](../docs/02-架构设计/已编号方案/F-260620-01-跨端AIChat-微信小程序-2026-06-20.md)(三层架构 df-tunnel/df-relay/df-miniapp + Rust 云后端选型)
|
||||||
|
**关系**:灵感 4495fbcd(选型论证,pending_review)+ 任务 6c816709(todo)
|
||||||
|
|
||||||
|
**实施分阶段**:
|
||||||
|
- **P1** df-relay 云后端(axum WS Server + 广播中继 + 鉴权)— 无依赖
|
||||||
|
- **P2** df-tunnel 桌面端(WS client + 事件桥接 ai-chat-event 透传 + 指令路由 + 配对绑定)— 依赖 P1
|
||||||
|
- **P3** df-miniapp 小程序(前端 UI 复用 + WSS 连云)— 依赖 P1/P2
|
||||||
|
- **P4** 双向同步完善(冲突处理 + 安全 + 离线降级 + 部署)— 依赖 P1/P2/P3
|
||||||
|
|
||||||
|
**状态**:📋 待决策(晋升灵感 4495fbcd → 启动 P1?或暂缓单机版稳定后)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI Chat 跑题/抓不住重点改进(跑题修复)
|
||||||
|
|
||||||
|
**背景**:AI Chat 对话跑题/抓不住重点。根因(深度分析):系统提示无聚焦 + 意图未接入 loop + 压缩丢重点 + 工具挤占 + 无主题检测。
|
||||||
|
**设计**:改进方案 Plan agent 规划中(优雅/可靠/易迭代三原则)
|
||||||
|
|
||||||
|
**修复分阶段**(每阶段独立可回退):
|
||||||
|
- **P0** 系统提示聚焦(prompt.rs 加聚焦约束,精炼非堆砌)
|
||||||
|
- **P0** 意图接入 agentic loop(intent.rs → tool_subset_for 收敛工具,置信度 fallback 全量)
|
||||||
|
- **P1** 压缩增强(compress prompt 加主题/关键词保留 + 失败兜底关键词非裁剪)
|
||||||
|
- **P1** 工具结果压缩(context 大结果 >2KB/占比 >40% 压缩成摘要)
|
||||||
|
- **P2** 主题检测(intent MultiTopic + TrackedMessage.topic 字段 + loop 主题切换标记)
|
||||||
|
|
||||||
|
**状态**:📋 待改进方案规划完成 → 逐阶段实施
|
||||||
|
|
||||||
|
|||||||
170
docs/待审查.md
170
docs/待审查.md
@@ -1215,27 +1215,189 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### CR-260619-20 主导航点不开内容(transition + ErrorBoundary 非 element root·commit 7f1242c) — 🟡 待审
|
### CR-260619-20 主导航点不开内容(transition + ErrorBoundary 非 element root·commit 7f1242c) — ✅ 已审(PASS·⚪0·独立 grep/read 核验)
|
||||||
|
|
||||||
- **范围**(2 文件):App.vue(ErrorBoundary 从 transition 内移到外,主+分离两处)+ ErrorBoundary.vue(根固定 div.error-boundary-root + display:contents)
|
- **范围**(2 文件):App.vue(ErrorBoundary 从 transition 内移到外,主+分离两处)+ ErrorBoundary.vue(根固定 div.error-boundary-root + display:contents)
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 7f1242c)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
|
||||||
|
|
||||||
|
**3 维度逐项核验(file:line 佐证)**:
|
||||||
|
|
||||||
|
| 维度 | 核验点 | 佐证 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ① transition 直接子真实根 | ErrorBoundary 移 transition 外,transition 子=component :is | App.vue diff:旧 `<transition><ErrorBoundary><component/></ErrorBoundary></transition>` → 新 `<ErrorBoundary :key><transition><component :is/></transition></ErrorBoundary>`,transition 直接子是 `<component :is>`(路由页真实元素根,非 slot 透传) · 主窗口 + detached 两处同构 | ✅ |
|
||||||
|
| ② ErrorBoundary display:contents/flex | 正常透明 + error 降级 | `ErrorBoundary.vue:6` 根固定 `<div class="error-boundary-root" :class="{is-error}">` · `:38-40 .error-boundary-root{display:contents}`(正常布局透明,slot 直参与父 flex)· `:41-50 .is-error{display:flex...}`(error 降级 UI)· onErrorCaptured/reset 逻辑不变 | ✅ |
|
||||||
|
| ③ 主+分离两处改 | 主窗口 + detached 同构 | App.vue diff 主窗口(router-view) + `#detached` 分离模板两处均改 ErrorBoundary 包 transition | ✅ |
|
||||||
|
|
||||||
|
**对抗核验印证**:
|
||||||
|
- **transition 不再包 ErrorBoundary**:新结构 ErrorBoundary 在 transition 外层(transition 是 ErrorBoundary 子),ErrorBoundary 根 div 不再是 transition 直接子 → 彻底消除「non-element root」警告源(双保险:结构移出 + ErrorBoundary 根固定元素)
|
||||||
|
- **:key="route.path" 保留**:ErrorBoundary 仍带 route.path key,路由切换整重建(重置 error 态),合理
|
||||||
|
- **vue-tsc**:CR-19 已全局 vue-tsc EXIT 0,CR-20 为 template 结构调整(类型无关),commit 声明 EXIT 0
|
||||||
- **根因**:c38cefe ErrorBoundary 接入嵌 transition 内部,slot 透传非元素根 → mode="out-in" 过渡卡死 → 点主菜单 URL 变但页面不切换(AI 面板不走 transition 故正常)
|
- **根因**:c38cefe ErrorBoundary 接入嵌 transition 内部,slot 透传非元素根 → mode="out-in" 过渡卡死 → 点主菜单 URL 变但页面不切换(AI 面板不走 transition 故正常)
|
||||||
- **维度**:
|
- **维度**:
|
||||||
1. transition 直接子是真实元素根(component :is 路由页面)
|
1. transition 直接子是真实元素根(component :is 路由页面)
|
||||||
2. ErrorBoundary display:contents 正常态布局透明 + error 态 flex 降级
|
2. ErrorBoundary display:contents 正常态布局透明 + error 态 flex 降级
|
||||||
3. 主+分离窗口两处改
|
3. 主+分离窗口两处改
|
||||||
- **验证**:独立 grep/read 核验 7f1242c + vue-tsc
|
- **验证**:独立 grep/read 核验 7f1242c + vue-tsc
|
||||||
- **状态**:🟡 待审
|
- **状态**:✅ 已审(PASS·⚪0)
|
||||||
|
|
||||||
### CR-260619-21 对话标题卡死"新对话"(ensure 跳过占位·commit ecf501d) — 🟡 待审
|
### CR-260619-21 对话标题卡死"新对话"(ensure 跳过占位·commit ecf501d) — ✅ 已审(PASS·⚪0·独立 grep/read 核验)
|
||||||
|
|
||||||
- **范围**(2 文件):title.rs(:40 ensure 排除"新对话"占位)+ conversation.rs(:139 need_title_regen 含"新对话")
|
- **范围**(2 文件):title.rs(:40 ensure 排除"新对话"占位)+ conversation.rs(:139 need_title_regen 含"新对话")
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit ecf501d + cargo check EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
|
||||||
|
|
||||||
|
**3 维度逐项核验(file:line 佐证)**:
|
||||||
|
|
||||||
|
| 维度 | 核验点 | 佐证 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ① ensure 排除"新对话" | title Some 且非空非"新对话"才 return | `title.rs:42-45` `if let Some(t)=&rec.title { if !t.is_empty() && t != "新对话" { return } }` · 边界完备:Some("")/Some("新对话") 继续生成 · Some("真标题") 跳过 · None 外层 if let 不进 | ✅ |
|
||||||
|
| ② switch 触发含"新对话" | need_title_regen 含"新对话"占位 | `conversation.rs:141` `need_title_regen = record.title.is_none() 或 == Some("新对话")` · 切入"新对话"会话 spawn ensure | ✅ |
|
||||||
|
| ③ 双重卡死链闭合 | ensure 不跳过 + switch 触发 | ensure :40 排除"新对话"(重新生成)+ conversation :141 switch 触发 spawn_ensure → "新对话"会话切入即重新生成 · 配合 `title.rs:84` extract 兜底落库 + LLM 覆盖 | ✅ |
|
||||||
|
|
||||||
|
**对抗核验印证**:
|
||||||
|
- **i18n 对抗解除**:"新对话"占位是**后端硬编码中文**(`title.rs:84` `unwrap_or_else(\|\| "新对话".to_string())` extract 兜底 + `:187` clean 空兜底"新对话"),非前端 t('newConversation')。后端 ensure/switch 匹配后端产生的占位串,一致自洽;英文 locale 下后端占位仍中文"新对话"(后端不随 locale 变)
|
||||||
|
- **数据印证**:6 条"新对话"会话 msg_len 100K+(有 user,extract 应非 None)证实 ensure 未生成(声明,未独立查 DB)
|
||||||
|
- **cargo check EXIT 0**(独立复跑,零 warning)
|
||||||
- **根因**:ensure :40 title.is_some() return,"新对话"占位 Some 永远跳过;switch need_title_regen 仅 None,"新对话"不触发 → 双重卡死
|
- **根因**:ensure :40 title.is_some() return,"新对话"占位 Some 永远跳过;switch need_title_regen 仅 None,"新对话"不触发 → 双重卡死
|
||||||
- **维度**:
|
- **维度**:
|
||||||
1. ensure :40 排除"新对话"(title Some 且非空非"新对话"才 return)
|
1. ensure :40 排除"新对话"(title Some 且非空非"新对话"才 return)
|
||||||
2. conversation.rs :139 need_title_regen 含"新对话"(switch 触发)
|
2. conversation.rs :139 need_title_regen 含"新对话"(switch 触发)
|
||||||
3. 数据印证:6 条"新对话"会话 msg_len 100K+ 有 user(extract 应非 None,证实未生成)
|
3. 数据印证:6 条"新对话"会话 msg_len 100K+ 有 user(extract 应非 None,证实未生成)
|
||||||
- **验证**:独立 grep/read 核验 ecf501d + cargo check
|
- **验证**:独立 grep/read 核验 ecf501d + cargo check
|
||||||
- **状态**:🟡 待审
|
- **状态**:✅ 已审(PASS·⚪0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-22 摘要/压缩不改 updated_at(防时间分组跳变·commit 49afffe) — ✅ 已审(PASS)
|
||||||
|
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 49afffe)**:✅ PASS — 4 维度全对,调用方参数无错配无遗漏 — 🔴0 🟡0 ⚪0
|
||||||
|
- 维度1 签名:`conversation.rs:136-143` 第6参 `touch_updated_at: bool` 落在 `model: Option<&str>` 之后,类型正确 ✅
|
||||||
|
- 维度2 条件改:`conversation.rs:177-179` `if touch_updated_at { rec.updated_at = now_millis() }` 已落库 UPDATE 条件化;新会话首次 INSERT(`:213` `updated_at: now`)不受 touch 控制,保持 now ✅(:200 `let now = now_millis()` → :213 注入,新会话必带时间戳)
|
||||||
|
- 维度3 调用方:grep 全 15 处调用全含第6参(0 遗漏);**compress :870(chat.rs ai_chat_compress_context)= false**✅(系统摘要插入,LLM 压缩后 insert_at(0) system 消息,无用户活跃,正确不改 updated_at);其余 14 处全 true——agentic 6 处(loop 入口 stop :470/正常退出 :832/异常 :898/审批 :923/max_rounds :952/完成 spawn :982)+ chat.rs 7 处(regenerate :158/approve 拒 :405/approve 同意 :507/auth_dir 拒 :579/auth_dir 允 :638/clear :743/edit :958)+ conversation.rs 切换旧会话 :64——均为用户活跃路径,正确 ✅。对抗核验:title 生成(spawn_ensure_title)/知识提炼(maybe_spawn_extraction)走各自 repo 写库,不经 save_conversation,无遗漏的系统自动路径误标 true。
|
||||||
|
- 维度4 不破坏:messages 赋值(:176)/token 累加(:180-183)/model 补填+models 去重追加(:185-193)/新会话 insert(:201-217)全保留,diff 仅动签名+`:177-179` 条件+调用方加参 ✅
|
||||||
|
- 验证:`cargo check --workspace` EXIT 0(cached,无错误);grep save_conversation 调用 15 处全含第6参(已逐条核对上下文)
|
||||||
|
- **范围**(4 文件):conversation.rs(save_conversation 加 touch_updated_at 参数 + :175 条件)+ chat.rs(compress :870 false)+ agentic/mod.rs(loop 退出 save true)+ commands/conversation.rs(切换 save true)
|
||||||
|
- **根因**:save_conversation :175 每次改 updated_at=now。compress/摘要(chat.rs:870)走 save → updated_at=now → timeBucket 按 updated_at 分组 → 会话"昨天→今天"
|
||||||
|
- **待修项回流 todo**:**无** 🔴/🟡 项
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-23 消息级溯源 P2 b 批次A 读路径切读 ai_messages(commit 13e7953) — ✅ 已审(PASS)
|
||||||
|
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 13e7953)**:✅ PASS — 映射 round-trip 严格 / fallback 条件正确 / status 归一化双向一致 / state 注册对齐 ai_conversations 模式 — 🔴0 🟡0 ⚪1
|
||||||
|
- 维度1 AppState(state.rs):`state.rs:265` `pub ai_messages: AiMessageRepo` + `:489` init `AiMessageRepo::new(&db)`,import `:14` 加 `AiMessageRepo`,完全对齐 `ai_conversations`(AiConversationRepo)模式 ✅
|
||||||
|
- 维度2 映射工具(conversation.rs):
|
||||||
|
- `record_to_message`(读,:77-97):role 字符串→枚举 / parts·tool_calls JSON→Vec(空/解析失败→None 防御,:78-85)/ status "active"→None 归一化(:88-92)/ id→Some(record.id 必非空主键)✅
|
||||||
|
- `message_to_record`(写预留,:115-146,`#[allow(dead_code)]` 标注批次B用):role 枚举→字符串 / parts·tool_calls→JSON / status None→"active" / id=None 兜底 `msg_{conv}_{seq}`(:117)✅
|
||||||
|
- **round-trip 8 测覆盖全字段**:`roundtrip_plain_text_status_none`(纯文本+status归一化)/ `roundtrip_multimodal_parts`(Image base64)/ `roundtrip_tool_calls`(assistant 多 tool_call,经 JSON 字符串比对因 ToolCall 无 PartialEq)/ `roundtrip_tool_message`(role=Tool+tool_call_id)/ `roundtrip_status_non_active_preserved`(truncated/compressed 原样)/ `roundtrip_id_none_fallback`/ `roundtrip_reasoning_content`/ `roundtrip_full_fields`(全字段混合)✅
|
||||||
|
- 对抗核验:`MessageRole` 仅 4 变体(System/User/Assistant/Tool,df-ai-core/types.rs:144-149 + rename_all="lowercase"),`role_from_str`(:46-55)全覆盖 + `_ => User` 防御;`role_to_str`(:61-68)穷尽 match ✅
|
||||||
|
- 字段对齐:ChatMessage(df-ai-core/types.rs:79-118)11 字段 ↔ AiMessageRecord(models.rs:235-262)13 字段(多 conversation_id/seq/created_at 持久化用),映射函数全字段赋值无遗漏 ✅
|
||||||
|
- 维度3 load 切读(conversation.rs):
|
||||||
|
- switch `:261` `state.ai_messages.list_by_conversation(&conversation_id)` → `:265` `records.iter().map(record_to_message).collect()` → `:307` `conv.messages.restore_from_messages(messages)` ✅
|
||||||
|
- export `:460` 同模式(切换/导出读路径一致)✅
|
||||||
|
- **messages_json 重序列化**:`:282` `serde_json::to_string(&messages)`(由映射后 Vec 重序列化),前端契约不变(仍吃 JSON 字符串)✅
|
||||||
|
- **fallback 条件正确**:switch `:266-279` / export `:464-475` —— 表空(`records.is_empty()`)且旧 messages JSON 非空 `!= "[]"` 且 `!is_empty()` 才 warn 回退读 JSON;`ai_conversations.messages` 列 DEFAULT `'[]'`(migrations.rs:671),空对话 messages="[]" 被 `!= "[]"` 排除走空 Vec,正确不误触发 fallback ✅
|
||||||
|
- **list_by_conversation 排序**:message_repo.rs:115 `ORDER BY seq ASC`,保证对话内时间序,restore_from_messages 顺序正确 ✅
|
||||||
|
- 维度4 不破坏:旧 messages JSON 列保留(migrations 不删);save 写路径(save_conversation)未碰(批次 B 留);`message_to_record` 标 `#[allow(dead_code)]` 不报未用警告 ✅
|
||||||
|
- 验证:`git show 13e7953 --stat` = 2 文件(conversation.rs +367 / state.rs +9);commit 自验 cargo test conversation 8 passed
|
||||||
|
- ⚪1 low:**id 兜底格式不一致**——`message_to_record`(:117)老数据兜底 `msg_{conv}_{seq}`,而 `new_message_id()`(df-ai-core/types.rs:134-139)新消息生成 `msg_{ts}_{counter}`。本批次读路径不触发(record.id 必非空直接用),仅批次 B 写路径 save 老数据(无 id)时兜底,需在批次 B 审查时确认老对话迁移已赋 id 后兜底是否仍会触发(理论上迁移跑完 ai_messages.id 全非空)。非本批次问题,标记供批次 B 跟进。
|
||||||
|
- **范围**(2 文件):conversation.rs(映射工具 record_to_message/message_to_record + switch :261-282 切读 + export :460-475 切读 + 8 round-trip 测)+ state.rs(AppState 注册 ai_messages)
|
||||||
|
- **设计**:读路径优先 ai_messages 表(每消息一行)替代 ai_conversations.messages 整对话 JSON;fallback 兜底老库未迁移/坏数据/批次B前窗口;写路径留批次 B(save_conversation 切 ai_messages.replace_conversation)
|
||||||
|
- **待修项回流 todo**:**无** 🔴/🟡 项(⚪1 low id 兜底格式不一致属批次 B 写路径范畴,非本批次阻塞)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-24 消息级溯源 P2 b 批次B 写路径切 ai_messages(commit 7a1fced) — ✅ 已审(PASS·⚪0·独立 grep/read 核验)
|
||||||
|
|
||||||
|
- **范围**(4 文件):message_repo.rs(replace_conversation 单事务 DELETE+INSERT + 4 测)+ conversation.rs(save 切 replace_conversation + 旧 messages 备份)+ chat.rs(ai_chat_clear 切 delete_range + 旧 clear 同步)+ commands/conversation.rs(映射启用 + 2 round-trip 测)
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 7a1fced + cargo test EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
|
||||||
|
|
||||||
|
**5 维度逐项核验(file:line 佐证)**:
|
||||||
|
|
||||||
|
| 维度 | 核验点 | 佐证 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ① replace 单事务原子 | DELETE+INSERT OR IGNORE 单事务无空窗 | `message_repo.rs:210 transaction()` · `:214 DELETE WHERE conversation_id=?1` · `:221 INSERT OR IGNORE INTO ai_messages` · `:237 tx.commit()` · 4 测(:428 全量/492 空清/509 隔离/554 幂等) | ✅ |
|
||||||
|
| ② save 切+备份 | replace_conversation 全量重写 + 旧 messages 保留 | `conversation.rs:219 replace_conversation(conv_id, records)` · `:193` 旧 messages JSON 不赋新值(备份) · `:225-227` 首次落库双写 fallback 兼容老库 reload | ✅ |
|
||||||
|
| ③ clear 同步 | delete_range + 旧 clear_messages 防脏读 | `chat.rs:697 clear_messages(&id)`(旧 JSON 列同步清空)· `:701 ai_messages.delete_range`(新表全删)· `:687` 内存 messages.clear() | ✅ |
|
||||||
|
| ④ 零回归 round-trip | 150 条全字段 + save overwrite | `cargo test -p df-storage` EXIT 0(message_repo 9 passed,42 filtered 全 51 过)· 4 单测 + 2 端到端 round-trip(commands/conversation.rs +184) | ✅ |
|
||||||
|
| ⑤ 旧 messages 备份保留 | 不删 V22 才删 | `conversation.rs:193` 不赋新值保留旧值 · 双写兼容老库 reload fallback · clear 同步清空备份列防脏读 | ✅ |
|
||||||
|
|
||||||
|
**对抗核验印证**:
|
||||||
|
- **并发安全(F-09 真并发)**:replace 按 conversation_id,跨 conv 隔离(测 :509 isolation_between_convs 印证)+ 决策e per_conv 单 loop 同 conv 不并发两 save → 无后写赢覆盖风险
|
||||||
|
- **全量重写写放大**:长对话每次 save DELETE+INSERT 全部消息,已知 trade-off(commit 声明「全量重写非 dirty 增量,P2.1 优化」),非缺陷
|
||||||
|
- **INSERT OR IGNORE 幂等**:DELETE 后无残留,防 records 内 id 重复/竞态残留跳过不报错
|
||||||
|
- **双写 fallback 渐进迁移**:首次落库 messages_json + ai_messages 双写,老库 reload 走 messages fallback,V22 删列
|
||||||
|
|
||||||
|
**独立 grep/read 复核补遗(2026-06-20·commit 7a1fced 当前形态 + cargo test EXIT 0)**: 维度①-⑤ 全 PASS,df-storage 51+11 + devflow 149 测全过 + cargo build 零 warning。补 3 条 low 观察项(均非阻塞,P2 过渡期可接受):
|
||||||
|
- ⚪1 low(`message_repo.rs:221` INSERT OR IGNORE 静默丢):schema V21(`migrations.rs:534`)含 `UNIQUE(conversation_id, seq)` + `id PRIMARY KEY`,重复冲突静默 IGNORE 丢行。seq 基于位置(`enumerate`)必唯一、id None 兜底 `msg_{conv}_{seq}`(`commands/conversation.rs:121`)基于位置也唯一 → 实际安全;仅极端(内存两条共享同显式 id 如重试/服务端 echo 重复)理论静默丢 → DB 条数 < 内存。留 P2.1 dirty 增量观察。
|
||||||
|
- ⚪2 low(`chat.rs:697-704` ai_chat_clear 两步非原子):clear_messages(置 messages='[]' + token 清零 `conversation_repo.rs:317`)先于 delete_range,各自 `map_err(err_str)?`。若 delete_range 失败 → 旧 JSON 已清/token 已零但 ai_messages 仍在,reload 读 ai_messages 非空(`commands/conversation.rs:261`)→ 消息恢复(清空视觉失败,虽然已 Err 返)。建议理论可两操作同事务或先 delete_range。非阻塞(Mutex 串行化下概率低)。
|
||||||
|
- ⚪3 low(`conversation.rs:215-221` 写路径 warn 吞错):已落库分支 update_full 成功但 replace_conversation 失败仅 `tracing::warn` 不中断 → 对话元数据(token/updated_at)持久化但 ai_messages 未更新(新真相源丢失)。P2 过渡期设计可接受(warn 不 fatal),V22 删旧列后需复核。
|
||||||
|
- **待修项回流 todo**:无 🔴/🟡 项。⚪ 均为 P2.1/V22 复核观察,当前批次 B 无需改。
|
||||||
|
- **待修项回流 todo**:**无** 🔴/🟡 项(⚪3 low:INSERT OR IGNORE 极端静默丢/ai_chat_clear 两步非原子/update_full 成功 replace 吞错 — P2 过渡期可接受,V22 删旧列后复核)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-25 AI Chat 跑题改进 P0(系统提示聚焦 + 意图接入 loop·commit 102d398+1606d5a) — ✅ 已审(PASS·🟡2 已修·⚪1)
|
||||||
|
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 102d398)**: ⚠️ **ISSUES** — 🔴0 🟡2 ⚪1
|
||||||
|
- 改进1(聚焦段)+改进2(意图接入)+三重 fallback + 执行路径不变 **全 PASS**。df-ai filter 7/7 + devflow prompt 4/4 测试绿。
|
||||||
|
- **执行路径不变确证**:filter 后 `tool_defs`(mod.rs:124/173/771)仅喂 `stream_llm`(LLM `tools` 字段);`tools_arc` 完整 registry 走 `process_tool_calls`(mod.rs:962)。LLM 幻觉被滤掉的工具名 → 要么不在 LLM 可见列表(LLM 不会调),要么 audit/tools_arc.get 仍能查到/拒绝。三重 fallback 全到位(conf<0.7→全量 / filter 内 subset 空→全量 / 过滤<3→全量)。
|
||||||
|
- **🟡-1 Debug 意图过度收敛**:`intent.rs:344` Debug → `[File, Http]` 域,无 Data。SPECIFIC(Debug)优先级 > ENTITY(Task/Project/Idea)。模拟三例「调试任务为什么失败」「排查这个任务状态」「帮我debug任务推进问题」**全部 Debug 胜出 → Data 域工具(list_tasks/advance_task/run_workflow/create_project 等 18 个)丢失**。三重 fallback 不救(Debug 子集 11 工具 ≥3 且非空)。执行路径不变故非崩溃,属用户可见功能降级(LLM 看不到任务工具,会回「无法完成」或只用消息上下文答)。建议:`Debug` 加 `ToolDomain::Data`(调试常需查任务/项目/workflow 状态)。
|
||||||
|
- **🟡-2 文档失准「threshold=1.0 关闭收敛」**:`intent.rs:322` conf 截断到 1.0(`score.min(1.0)`),大量单关键词命中达 conf=1.0(代码/重构/bug/调试/grep/http/项目/任务/灵感/搜索等 weight=1.0)。故 `INTENT_CONF_THRESHOLD=1.0` 时 `conf >= 1.0` **仍通过过滤**,无法真正关闭收敛。agentic/mod.rs:437 注释 + commit message 声称「置 1.0 = 关闭收敛退回接入前」不准确。真正关闭需 threshold > 1.0(如 1.01)或加显式 `disable` 开关。
|
||||||
|
- **⚪-1 `< 3` 魔数无注释**(mod.rs:457 `if filtered.len() < 3`):为什么是 3 非 2/5,无常量名/rationale 注释。
|
||||||
|
- **待修项回流 todo**: ✅ **2 项已修**(commit 1606d5a):① Debug 加 Data 域(intent.rs:344,防"调试任务"丢 Data 工具)② threshold 注释修正(mod.rs:437,conf 截断 1.0 致 =1.0 非关闭,真关闭需 >1.0)。CR-25 🟡 闭环,verdict 转 ✅ PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-26 AI Chat 跑题改进 P1(压缩增强 + 工具结果压缩·commit 013ce21) — ✅ 已审(PASS·🟡1⚪1)
|
||||||
|
|
||||||
|
- **结论(2026-06-20·独立 grep/read 核验 commit 013ce21)**: ✅ **PASS** — 🔴0 🟡1 ⚪1
|
||||||
|
- 维度①-⑤ 全 PASS:view-only **确认不改持久化**(build_for_request 返回 clone context.rs:125/150-153,extract_key_info 仅改 clone 视图 mod.rs:786-820)+ 常量开关兜底双到位(KEYWORD_FALLBACK_ENABLED/TOOL_RESULT_COMPRESS_ENABLED 默认 true,设 false 等价旧行为)+ 失败兜底与 LLM 成功路径**语义一致**(均 compress_old_messages+insert_at,持久化落 save_conversation)。
|
||||||
|
- **测试复跑**:df-ai context_helpers **14 passed** ✅ / devflow prompt **6 passed**(含 zh/en 主题锚点测,CR 登记"7"为虚指实际 6)✅ / cargo check -p devflow EXIT 0 零 warning ✅。
|
||||||
|
- **view-only 对抗核验**(CR 重点):build_for_request 只 `.clone()`/`all_messages_clone`,ContextManager.messages 未被 extract_key_info 触碰 → 原始 tool_result 仍在 DB(下次 build 全量重建自愈)✅。失败兜底分支(mod.rs:724-741)compress_old_messages+insert_at 改的是 ContextManager.messages(持久化层),**但与 LLM 成功路径(mod.rs:685-687)逐字同构**,loop 退出 save_conversation 落库 → 摘要(启发式 vs LLM)落 DB 合理,非 view-only 违规。**无误改 per_conv.messages 风险**(conv() 返回同 conv,无跨 conv 写)。
|
||||||
|
- **范围**(3 文件):prompt.rs(compress prompt 主题保留 + 2 测)+ context_helpers.rs(extract_keyword_summary + should_summarize_tool_result + extract_key_info + 14 测)+ agentic/mod.rs(失败兜底关键词 + view-only 工具压缩接入 + 2 常量开关)
|
||||||
|
|
||||||
|
**5 维度逐项核验**:
|
||||||
|
|
||||||
|
| # | 维度 | 判定 |
|
||||||
|
|---|------|------|
|
||||||
|
| 1 | 改进3 compress prompt 主题保留 | ✅ prompt.rs:181-183(en)+ :205(zh-CN)加主题词/实体/技术名词锚点保留条,2 测覆盖 |
|
||||||
|
| 2 | 改进3 失败兜底关键词 | ✅ extract_keyword_summary 纯函数(context_helpers:190-252)user 词素 top-10 去停用词;失败分支 mod.rs:724-741 仍 compress_old_messages 释放 token + insert_at(0, system) 插锚点;KEYWORD_FALLBACK_ENABLED 开关 mod.rs:67 |
|
||||||
|
| 3 | 改进4 should_summarize/extract_key_info | ✅ should_summarize_tool_result(>2KB/占比>40%,0 history 不除零 context_helpers:341-343)+ extract_key_info(错误行 error/panic/失败/.rs:N context_helpers:420-433 + 首尾5行,短内容原样 :366-368),14 测全绿 |
|
||||||
|
| 4 | 改进4 view-only(本 CR 重点) | ✅ build_for_request 返回 clone(context.rs:125/150-153),extract_key_info 仅改 clone(mod.rs:803-823),ContextManager.messages/DB 原始 tool_result 不变 |
|
||||||
|
| 5 | 常量开关 | ✅ KEYWORD_FALLBACK_ENABLED(mod.rs:67)+ TOOL_RESULT_COMPRESS_ENABLED(mod.rs:77)默认 true,设 false 等价旧行为 |
|
||||||
|
|
||||||
|
**🟡 建议改进 (1)**:
|
||||||
|
|
||||||
|
| # | file:line | 问题 | 建议 |
|
||||||
|
|---|-----------|------|------|
|
||||||
|
| 1 | `context_helpers.rs:281-286` | **中文多字实体被拆单字过滤丢锚点**:`tokenize` 把每个汉字单独成词素(注释自述「汉字单独成词」),MIN_WORD_LEN=2(context_helpers:192)按 `chars().count()` 过滤 → 「压缩/架构/审批」等 2 字中文领域词被拆成 2 个单字词素,各 count=1 < 2 全滤掉。本 CR 主题改进目标正是中文 chat 跑题,**中文实体锚点基本捕获不到**(仅英文标识符如 build_for_request 因聚成 1 词素 count=17 入榜)。14 测中无多字中文实体保留用例印证(`keyword_summary_chinese_extracts_frequent_terms` 只验英文标识符)。 | (a) MIN_WORD_LEN 改按字节或对汉字放宽到 1(但单字虚词多需扩停用词);(b) 加 2-gram 汉字滑动窗口收集(轻量,无 jieba);(c) 接受现状文档标注「中文锚点依赖 LLM 成功路径,兜底仅保英文标识符」。非阻塞(LLM 成功路径不受影响,仅兜底场景中文覆盖弱)。 |
|
||||||
|
|
||||||
|
**⚪ 可选优化 (1)**:
|
||||||
|
|
||||||
|
| # | file:line | 问题 |
|
||||||
|
|---|-----------|------|
|
||||||
|
| 1 | `agentic/mod.rs:663-664` | active_msgs 空 → `Ok(None)` noop 分支(:700),但 keyword_fallback 已先算(:655-656)被丢弃。极小浪费(空消息 extract 早返空串,零成本),无需改。 |
|
||||||
|
|
||||||
|
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡1 中文 tokenize 锚点弱:每汉字单成词素,"压缩/架构/审批"2字词被拆滤,非阻塞 LLM 成功路径,批次3 P2 tokenize 2-gram 汉字滑窗一并改)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260619-27 AI Chat 跑题改进 P2(主题检测保守 + tokenize 2-gram·commit a2db5c7) — 🟢 通过
|
||||||
|
|
||||||
|
- **结论**🟢 通过:6 维度全部核验通过,topic 字段无破坏(TrackedMessage 无 Serialize derive,只 ChatMessage 落库)+ 双高置信保守(任一 topic None 不标)+ tokenize 2-gram 正确修复中文锚点。df-ai 189 passed 独立复跑确认。无 high/med 风险,2 条 low 提示。
|
||||||
|
- **范围**(3 文件):context.rs(TrackedMessage.topic 字段 + pending_topic_marker + push 推断 + last_user_topic/take_topic_marker + 5 主题测)+ context_helpers.rs(tokenize 2-gram 汉字滑窗 + 4 测)+ agentic/mod.rs(TOPIC_MARKER_ENABLED 常量开关 + loop 顶部消费 marker)
|
||||||
|
- **根因**:多主题交织无检测/分段 + CR-26 🟡1 中文 tokenize 锚点弱
|
||||||
|
- **维度核验**:
|
||||||
|
1. ✅ topic 字段无破坏:`TrackedMessage`(context_helpers.rs:151)上方无任何 `#[derive(..., Serialize)]`(全文 `^#\[derive` 仅 27/104/136 三处均非本结构),`Option<String>` 字段加于无 Serialize 结构体 → 无 DB/序列化影响。ChatMessage(104)仍单独 Serialize 落库,topic 不进库。
|
||||||
|
2. ✅ push 推断 topic:context.rs:79-85 仅 User 消息走 `IntentRecognizer::recognize`,`conf >= 0.7 && !Unknown` 才 Some,低置信 None(intent.rs:289 签名匹配,Intent::Unknown 在 intent.rs:51)。
|
||||||
|
3. ✅ 主题标记保守:context.rs:91-101 push 前 `last_user_topic()`(反向扫末条 user,跳过 assistant)取旧 topic,与本次推断 topic 双 Some 且不同才置位 marker("old|new");任一 None 不标(测试 `topic_marker_not_triggered_when_either_topic_none` 覆盖正反两向)。
|
||||||
|
4. ✅ loop 软提示:agentic/mod.rs:560-610 `TOPIC_MARKER_ENABLED`(默认 true)门控,`take_topic_marker()` 一次性消费(take 后清空防重复 insert),splitn(2,'|') 安全解析(intent.as_str 标签 code/debug/.../chat 均不含 `|`);insert system 软提示非强制。loop 段含陈旧 conv 检测(562-569)与二次 lock 前的 `contains_key` 复检(598),防竞态。
|
||||||
|
5. ✅ topic 不参与裁剪/压缩:`grep \.topic` 全 crate 仅 5 处(push 推断 79/540、insert_at None 583、测试 1400/1405),裁剪/压缩/build_for_request 路径零读取(测试 `topic_marker_not_polluting_compression_or_trim` 验证 cfg(80) 撑爆裁剪后仍正常)。
|
||||||
|
6. ✅ tokenize 2-gram:context_helpers.rs:294-304 相邻汉字(prev_class==1 且 prev_han 有值)组 2-gram,非汉字边界/拉丁切换重置 prev_han 不跨边界组词;"压缩架构"→压缩/缩架/架构 并存单字,英文标识符 `build_for_request` 保持完整(测试 `tokenize_english_identifier_unchanged` + `tokenize_chinese_2gram_not_crossing_boundary` 覆盖)。
|
||||||
|
- **findings**:
|
||||||
|
- [low] agentic/mod.rs:598 loop 第二次 lock 后仅 `contains_key` 复检即 insert_at(0),未重算 token 增量对裁剪阈值的边际影响——但 insert_at 内部已 `history_tokens += tokens`(context.rs:576-578),且软提示短(单行),无实际越界风险。提示性,无需改。
|
||||||
|
- [low] context.rs:91 marker 置位后立即 push 本条 user,若同轮内 loop 又触发 push(理论上单轮单 push)不会重复置位;多 user 连发场景 marker 只记最近一次 old→new,中间切换链丢失——但保守设计本意如此(只标最末切换),非缺陷。
|
||||||
|
- **待修项回流 todo**:**无** 🔴/🟡 项(2 low 提示性:软提示 token 边际 + 多切换链只记末次,保守设计预期行为)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user