新增: AI 对话目标钉扎+探索熔断+话题标记降级

治 AI Chat 目标丢失/意图丢失(实测会话 480万token,目标消息被压缩出局,LLM 尾部盲搜死循环):
- G1 目标钉扎:首条 user 目标提取存 PerConvState.pinned_goal,run_agentic_loop 入口拼进
  system_prompt 尾部(loop 不变量,免疫 F-15 压缩/裁剪/sanitize,治目标物理出局)
- G2 探索熔断:连续 N 次空结果无进展 → 两段式警示 + AiHelpRequired 熔断(治游荡死循环)
- G4 话题标记降级:pinned_goal 存在时跳过 topic marker insert(治反向误导+双锚点稀释)
- G3 衔接:pinned_goal 内容态与 ConvState 生命周期态正交,不进 enum
各改进配开关 flag(GOAL_PIN_ENABLED/STALL_BREAKER_*/TOPIC_MARKER_GOAL_AWARE)可单点回退。
诊断报告见 docs/02-架构设计/专项设计/AI对话目标丢失诊断-2026-06-26.md。
This commit is contained in:
2026-06-26 20:01:28 +08:00
parent c011f864fd
commit 8ce18cb482
6 changed files with 571 additions and 2 deletions

View File

@@ -0,0 +1,106 @@
# AI 对话「目标丢失 / 意图丢失」诊断报告
> 日期2026-06-26
> 触发AI Chat 使用中,用户发送 Bug / 分析需求目标后,分析过程中目标丢失,系统不知道做什么、完全跑偏。
> 方法:直读对话数据库(`AppData/Roaming/top.1216.devflow/devflow-dev.db`)实测会话 + 源码机制走查。
> 性质:基于代码 + 数据库实证的机制级诊断(非模型分析猜测)。
---
## 一、现场铁证(会话 `54c8b5bd`
| 指标 | 实测值 | 含义 |
|---|---|---|
| `prompt_tokens` | **4,809,714**(约 480 万) | 远超 GLM 1M 窗口,反复触发压缩 / 裁剪 |
| 消息数 | 308 条 | 长 ReAct 链,工具结果堆积 |
| 原始目标 `[seq5]` | `分析一下 灵感模块 存在的不足 [项目: DevFlow]``status=compressed` | **目标消息已被踢出 LLM 上下文**`sanitize_messages` step0 过滤 `is_active` |
| 话题标记 `[seq0]` | `task → project`active | 系统判定「当前 project 话题」,但真实目标是 idea 域(灵感模块) |
| 尾部 `[seq287307]` | 连续 10+ 次 `search_files`,关键词 `auto-execute-mode / auto_execute / 全部接管 / autoExecute / execute-mode / execute_mode / auto-exec / 接管 / df-ai-auto`**全部 `total:0` 空结果** | 失去目标后的游荡死循环 |
**结论**原始目标是「分析灵感模块存在的不足」LLM 最终在反复搜索「自动执行模式 / 全部接管」——与目标毫无关系。目标彻底丢失。
---
## 二、根因(机制级)
### 根因 1上下文爆炸 → 目标消息物理出局(核心)
- `df-ai/src/context.rs``sanitize_messages` step0 按 `is_active()` 过滤发送视图。目标 user 消息被 F-15 压缩标 `status=compressed` 后,**不再进入 LLM 上下文**。
- 目标仅靠**压缩摘要 system 消息**「续命」。`prompt.rs::compress_prompt` 四段式(意图 / 决策 / 文件 / 约束)+ 主题词锚点保留——但摘要是 **LLM 二次生成,信息降级严重**。「分析灵感模块不足」这类具体目标,在数次压缩后语义稀释到无法指导后续动作。
- 480 万 token 的会话,目标消息经历多轮压缩,保真度无法保证。
### 根因 2话题切换标记误判反向强化错误方向
- `agentic/mod.rs:735-772``pending_topic_marker` 基于末两条 user 消息的 `IntentRecognizer` 关键词推断(双高置信 ≥0.7 才标),`insert_at(0)` 软提示 system「请以新话题为准」。
- 失效点:
- 长链里 user 消息早被压缩出局topic 推断基于**已失真历史**。
- 本例最后 active 标记是 `task→project`,与真实目标(灵感模块 / idea 域)**背离**——软提示反而告诉 LLM「现在是 project 话题」,帮倒忙。
- marker 只在 push **user 消息**时检测,工具轮次间不参与。
### 根因 3单链 ReAct无目标重锚定架构本质
- `df-ai/src/coordinator.rs`**纯空壳 TODO**B 路线占位,`AgentCoordinator::run` 直接返 `"TODO: Agent 协作结果"`)。
- `df-ai/src/planner.rs`Plan DAG + Kahn 分层 + validate 齐全,但文件头明确「纯函数零 IO 零状态」「不接入 agentic loop待 Phase B+C」。`PLANNING_ENABLED=true` 只门控**工具排序**`filter_tool_defs_planned`**不做多步规划执行**。
- agentic loop 每轮只在 push 新 user 消息时检测话题切换,**工具轮次间不重申目标**。没有「当前目标 + 进度」结构化状态LLM 每轮自行决定下一步,长链必然游荡(`[seq287307]` 实证)。
### 根因 4无探索熔断
- `[seq287307]` 连续 10+ 次空结果不中断。系统不跟踪「已搜过什么 / 连续 N 次无进展」LLM 无限换关键词。
- 这是目标丢失的**放大器**:不知道目标 → 不知道该停 → 无限游荡token 失控480 万)。
### 根因 5system prompt「聚焦准则」是文字说教非机制
- `prompt.rs:104-108` 有「聚焦准则:始终围绕用户当前请求的核心目标」——纯 prompt 文字,靠 LLM 自觉。
- 目标一旦从上下文(消息流)消失,文字准则**无锚点可抓**。没有「每轮把目标钉在 system 顶部」的机制。
---
## 三、为什么现有设计压不住
| 现有机制 | 性质 | 为什么压不住 |
|---|---|---|
| 压缩摘要F-15 | **被动**信息降级 | 摘要保真靠 LLM多轮压缩后目标语义稀释 |
| 话题切换标记 | 软提示 + 关键词推断 | 基于失真历史,本例反向误导 |
| 聚焦准则 prompt | 文字说教 | 目标消息出局后无锚点 |
| `insert_at(0)` system 锚点 | 多个堆首位 | 话题标记 + 压缩摘要 + 关键词兜底三个锚点互相稀释 |
三个 `insert_at(0)`(话题标记 `agentic/mod.rs:762`、压缩摘要 `:918`、关键词兜底 `:969`)堆在首位,互相稀释,没有一个是「权威目标」。
---
## 四、关联现状
- 当前分支 `conv-state-convergence` + `agentic/conv_state.rs`452 行状态机)已在尝试收敛对话状态,**但目标作为一等状态尚未固化进状态机**——这是未完成的工作。
- memory `devflow-aichat-session-analysis-2026-06-22` 已标的「最高杠杆 P0-2 编排规划能力弱」即此问题的上层概括。本报告是其**数据库实证版**。
---
## 五、方向建议(仅诊断,未实施)
最高杠杆:**把「用户当前目标」从消息流提升为一等结构化状态**。
1. **目标钉扎**:首轮提取用户目标为结构化字段(不依赖压缩摘要),每轮 agentic loop 顶部重注入 system。目标消息压缩出局后结构化目标仍在。
2. **探索熔断**:跟踪连续无进展 / 空结果次数,超阈值强制「回顾目标」或停止,防游荡死循环 + token 失控。
3. **状态机收敛**:把目标 + 进度纳入 `conv_state.rs` 状态机(当前分支已在做),目标作为状态而非消息。
4. **弱化 / 修正话题标记**:话题标记基于失真历史且可能反向误导,宜降级或改为「目标漂移检测」而非 topic 关键词切换。
---
## 附:待查 Bug
- `[seq1]` 一条 LLM 内部独白("等等,`process_tool_calls` 是从 `super::audit` 导入的。让我检查一下…")被存为 `system` 消息(`status=active`)。疑似 reasoning / 思考内容泄漏进 system 消息(`stream_recv` 归位问题),进一步污染上下文。待独立排查。
---
## 证据定位(源码)
| 机制 | 位置 |
|---|---|
| 上下文压缩 / sanitize / topic 字段 | `crates/df-ai/src/context.rs` |
| 话题切换 marker 消费 | `src-tauri/src/commands/ai/agentic/mod.rs:735-772` |
| 压缩 insert_at 摘要 | `src-tauri/src/commands/ai/agentic/mod.rs:820, 918, 969` |
| 多 Agent 协调(空壳) | `crates/df-ai/src/coordinator.rs` |
| Plan 规划(未接入 loop | `crates/df-ai/src/planner.rs` |
| 意图识别(关键词) | `crates/df-ai/src/intent.rs` |
| system prompt 聚焦准则 / 压缩 prompt | `src-tauri/src/commands/ai/prompt.rs:104, 233` |
| 对话状态机(进行中) | `src-tauri/src/commands/ai/agentic/conv_state.rs` |

View File

@@ -1009,3 +1009,23 @@
- [x] ✅(2026-06-25) **Task#1 自动压缩成功路径残留**(BUG-260624-05 衍生)— **AiCompressed 单事件被自动压缩路径误用 → 桌面端每次发送误弹 toast + 误刷整会话**。治法:`AiCompressed``AiManualCompressed`(手动 IPC,3 处 emit 点,前端弹 toast)+ `AiAutoCompressed`(loop 自动,桌面静默仅复位 isCompressing 防按钮卡死,miniapp 仍插摘要气泡)。按语义拆事件而非按字段。— `src-tauri/src/commands/ai/mod.rs:207/212`
- [x] ✅(2026-06-25) **Task#2 双轨状态机收口**`generating: bool` 字段(顶层单例 + PerConvState)与 `CONV_STATE_ENABLED` 灰度开关双轨全部删除,`ConvState` enum(`conv_state.rs`,5 态)成唯一真相源:写侧经 `transition_to` 守卫(非法转换拒绝),`GeneratingGuard` new/reset/drop 无条件迁移 + emit;读侧 `can_accept_request()` 接入 chat 域 4 入口(ai_chat_send/ai_regenerate/ai_chat_edit/ai_is_generating),比 `is_active()` 更严谨(覆盖 Stopping 态竞态)。开关常量已删,代码内 `CONV_STATE_ENABLED off 回退分支` 注释为说明性遗留(实际不可触发,待清理)。详案见 [generating状态机加固-2026-06-15.md §收口记录](./02-架构设计/专项设计/generating状态机加固-2026-06-15.md)。— `src-tauri/src/commands/ai/agentic/{conv_state.rs,guard.rs}` + `commands/ai/{mod.rs,commands/chat.rs}`
---
### 🔍 2026-06-26 灵感模块诊断待办(合并推进·单任务)
> 全面审查灵感模块df-storage / df-ideas / commands/idea.rs / Ideas.vue / IdeaDetail.vue后发现的 P0-P2 问题。
> 排除项:~~#3 status 无状态机~~(暂保留状态可跳跃迁移,不处理)。
> 合并为单个任务推进,不拆子任务。
- [ ] **IDEA-FIX-01 [P0🔴]****ideas 表无软删除**`delete_idea` 是硬删除(直接 `delete()`),无 `deleted_at` 列、无 restore API。误删不可恢复。需对标 tasks/projects 的软删除全套模板(`deleted_at` 列 + V28 迁移 + `list_active` 过滤 + restore IPC。— `commands/idea.rs:104` + `df-storage/src/crud/idea_repo.rs` + `migrations.rs`
- [ ] **IDEA-FIX-02 [P0🔴]****ideas 白名单含 id/created_at**`settings.rs:122` ideas 白名单包含 `id``created_at`,可通过 `update_idea` 篡改主键或创建时间。需移除(对标 B-260616-16 tasks 白名单修复)。— `commands/settings.rs:122`
- [ ] **IDEA-FIX-03 [P0🔴]****priority 无值域校验**`create_idea` / `update_idea` 不校验 priority 范围,传入 -5 或 999 静默降级为 Medium。需加 `parse::<i32>() ∈ 0..=3` 校验(对标 B-260615-15 tasks priority 修复)。— `commands/idea.rs:73,88`
- [ ] **IDEA-FIX-04 [P1🟡]****record_to_idea 有损转换**`record_to_idea()` 硬编码 `status=Draft`(丢弃 DB 真实 status+ `related_ids=空 Vec`(丢弃已保存关联)。评估时上下文不完整。需从 IdeaRecord 读真实值。— `commands/idea.rs:298-312`
- [ ] **IDEA-FIX-05 [P1🟡]****评分关键词硬编码**`scoring.rs` 关键词全写死中文("复用"/"重构"/"迁移"…),不可配置,英文/非技术项目评分失真。需提取为配置文件或 DB 表。— `df-ideas/src/scoring.rs`
- [ ] **IDEA-FIX-06 [P1🟡]****promote 补偿用 purge不可恢复**`promote_idea` 回写失败时 `purge_with_descendants`(永久删除项目)。应改为 `delete`(软删除,可恢复)。— `commands/idea.rs:179`
- [ ] **IDEA-FIX-07 [P1🟡]****关联单向**。A 关联 B 只写 A 的 `related_ids`B 不自动补 A。需双向同步关联时补入对方解绑时同步移除。— `commands/idea.rs` + 前端 `IdeaDetail.vue`
- [ ] **IDEA-FIX-08 [P1🟡]****创建表单无 tags 输入**。后端 `CreateIdeaInput` 支持 tags但前端捕捉表单Ideas.vue capture modal只有 title + descriptiontags 恒为 None。需加 tags 输入(逗号分隔 → JSON 数组)。— `Ideas.vue:108-119` + `confirmCapture()`
- [ ] **IDEA-FIX-09 [P2🟠]****创建表单缺 priority/source**。捕捉模态框无 priority 下拉和 source 输入。需补全。— `Ideas.vue:108-119`
- [ ] **IDEA-FIX-10 [P2🟠]****前端 filter + 后端分页漏数据**`hot`score≥80`pending`(多状态 OR在前端 filter但后端已分页返回子集可能漏掉符合条件的灵感。需下沉后端 IdeaQuery。— `Ideas.vue:221-226`
- [ ] **IDEA-FIX-11 [P2🟠]****假雷达图**。i18n key 和 CSS class 叫 "radar",但实际是水平进度条。需正名为 "score-bar" 或引入真正的雷达图组件。— `IdeaDetail.vue` + `i18n/zh-CN/ideas.ts` + `i18n/en/ideas.ts`