From b1d7deece19095e7f3e404f0614add2643f37a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Mon, 20 Jul 2026 00:53:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87=E7=AE=A1=E7=90=86=E6=BC=94=E8=BF=9B=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=88=E5=8F=91=E6=95=A3=E6=80=9D=E8=80=83?= =?UTF-8?q?=20+=20=E4=BB=BB=E5=8A=A1=E6=8A=80=E6=9C=AF=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发散思考文档涵盖: - ContextManager 三维评估(效率/合理性/成本) - 业界方案对标(Claude Code/OpenAI SDK/Mem0/LangGraph 等) - 融合设计: 结构化分层上下文引擎(L1常驻/L2历史/L3摘要) - 9 种方法交叉论证(决策矩阵/ROI/帕累托/Kano/风险矩阵等) - 7 个发散方向与冲突分析,推荐派系 A 路线 任务技术设计文档涵盖: - T2 工具命名空间: 大工具结果不进主队列,NamespaceStore 详细设计 - T4 工作流 DAG 注入: 活跃路径裁剪,结构化 DAG 进 system prompt - T3 L3 结构化摘要: JSON+NL 双格式压缩摘要 - T1 动态压缩阈值: 精确到行的 3 处改动 - 4 种边界情况推演,AI coding 多 agent 并行策略 --- .../上下文管理演进-任务技术设计-2026-07-20.md | 732 +++++++++++++++ .../上下文管理演进与发散思考-2026-07-20.md | 882 ++++++++++++++++++ docs/INDEX.md | 2 + 3 files changed, 1616 insertions(+) create mode 100644 docs/02-架构设计/专项设计/上下文管理演进-任务技术设计-2026-07-20.md create mode 100644 docs/02-架构设计/专项设计/上下文管理演进与发散思考-2026-07-20.md diff --git a/docs/02-架构设计/专项设计/上下文管理演进-任务技术设计-2026-07-20.md b/docs/02-架构设计/专项设计/上下文管理演进-任务技术设计-2026-07-20.md new file mode 100644 index 0000000..f2ef001 --- /dev/null +++ b/docs/02-架构设计/专项设计/上下文管理演进-任务技术设计-2026-07-20.md @@ -0,0 +1,732 @@ +# 上下文管理演进 — 任务技术设计 + +> 创建: 2026-07-20 | 状态: 设计阶段 | 关联文档: `上下文管理演进与发散思考-2026-07-20.md` + +--- + +## 目录 + +- [T2 工具命名空间(详细设计)](#t2-工具命名空间详细设计) +- [T4 工作流 DAG 注入(详细设计)](#t4-工作流-dag-注入详细设计) +- [T3 L3 结构化摘要(设计概要)](#t3-l3-结构化摘要设计概要) + +--- + +## T2 工具命名空间(详细设计) + +### 现有流程 vs 新流程 + +``` +现有流程: + AiToolRegistry.execute(name, args) + → 工具执行 → 全量结果 String + → push(ChatMessage::tool_result(全量内容)) ← 不管大小,全塞进消息队列 + → build_for_request 时,大 tool_result 撑爆预算 + → 压缩/裁剪 → 丢失细节 + +新流程: + AiToolRegistry.execute(name, args) + → 工具执行 → 全量结果 String + → if should_use_namespace(全量内容) + → store_to_namespace(key, 全量内容) ← 存入独立存储 + → push(ChatMessage::tool_result(引用路径)) ← 主队列只插引用 + else + → push(ChatMessage::tool_result(全量内容)) ← 小结果不动 + → build_for_request 时,引用只占 ~20 tokens + → 压缩不影响 namespace 内容 + → 模型通过引用路径按需读取(或系统自动补全) +``` + +### 数据结构 + +```rust +// crates/df-ai/src/namespace_store.rs + +/// namespace 引用路径格式: "namespace://tool_name/args_hash" +/// 例: "namespace://read_file/a1b2c3d4" +pub const NAMESPACE_REF_PREFIX: &str = "namespace://"; + +/// 落入 namespace 的字节阈值(> 2048 bytes) +pub const NAMESPACE_BYTE_THRESHOLD: usize = 2048; + +/// 落入 namespace 的行数阈值(> 50 行) +pub const NAMESPACE_LINE_THRESHOLD: usize = 50; + +/// namespace 存储 +/// +/// 内存中为 HashMap,提供常规 CRUD; +/// 外部可注入持久化实现(接 SQLite / 内存 fallback)。 +/// 设计为独立于 ContextManager 的结构——不被压缩/裁剪影响。 +pub struct NamespaceStore { + /// key = 引用路径 hash, value = 原始工具结果 + entries: HashMap, + /// 总字节数上限(防内存爆炸,超限淘汰最旧条目) + max_bytes: usize, + current_bytes: usize, +} + +pub struct NamespaceEntry { + pub key: String, // 完整引用路径 + pub tool_name: String, + pub args: serde_json::Value, + pub content: String, // 原始工具执行结果 + pub content_summary: String, // extract_key_info 后的摘要(供 LLM 预览) + pub created_at: Instant, + pub access_count: u64, +} +``` + +### 关键决策点 + +#### 决策 1:何时进 namespace + +```rust +pub fn should_use_namespace(content: &str, tool_name: &str) -> bool { + // 1. 明确标记为"大结果"的工具(read_file / list_directory 几乎总是大) + if is_always_large_tool(tool_name) { + return true; + } + // 2. 按大小阈值判定 + content.len() > NAMESPACE_BYTE_THRESHOLD + || content.lines().count() > NAMESPACE_LINE_THRESHOLD +} + +fn is_always_large_tool(tool_name: &str) -> bool { + matches!(tool_name, "read_file" | "list_directory" | "grep" | "diff_files") +} +``` + +#### 决策 2:模型看到什么 + +LLM 在 tool_result 消息中看到的不是全量内容,而是: + +``` +当前(小结果原样,大结果原样): + [tool_result] 342 行文件内容...(占 2000 tokens) + +新流程(引用路径 + 摘要预览): + [tool_result] + [tool_result] + 文件: config.rs + 行数: 342 + 关键节点: timeout:15 (行15), pool_size:10 (行42) +``` + +**LLM 的理解能力验证**:如果模型需要读取完整内容,system prompt 需说明: + +``` +[上下文说明] +当工具结果以 格式返回时,表示完整内容已存储。 +如果你需要查看完整内容,回复 /read_namespace path +系统将自动拉取完整内容替换当前引用。 +``` + +或自动更激进——当 LLM 的回复引用某 namespace 路径时,系统在下一轮自动拉取: + +``` +LLM: "config.rs 第 15 行的 timeout 需要改" +系统检测: /read_namespace 触发 → 从 namespace 拉取 config.rs 完整内容 +→ 下轮 tool_result 消息中替换为完整内容 +→ LLM 看到原文,精确引用行号 +``` + +#### 决策 3:持久化与生命周期 + +```rust +impl NamespaceStore { + /// 存入 namespace + pub fn store(&mut self, key: &str, tool_name: &str, args: &Value, content: &str) -> String { + let summary = extract_key_info(content); // 复用现有 extract_key_info + let entry = NamespaceEntry { + key: key.to_string(), + tool_name: tool_name.to_string(), + args: args.clone(), + content: content.to_string(), + content_summary: summary, + created_at: Instant::now(), + access_count: 0, + }; + self.current_bytes += content.len(); + // 超限淘汰:从最旧开始删 + while self.current_bytes > self.max_bytes { + // ... LRU 淘汰 + } + format!("{}{}/{}", NAMESPACE_REF_PREFIX, tool_name, key) + } + + /// 读取完整内容 + pub fn read(&mut self, full_path: &str) -> Option<&str> { + let key = self.parse_key(full_path)?; + let entry = self.entries.get_mut(&key)?; + entry.access_count += 1; + Some(entry.content.as_str()) + } + + /// 读取摘要(用于自动补全判断) + pub fn read_summary(&self, full_path: &str) -> Option<&str> { + let key = self.parse_key(full_path)?; + self.entries.get(&key).map(|e| e.content_summary.as_str()) + } +} +``` + +### 接线点:process_tool_calls + +改动点位于 `audit/mod.rs::process_tool_calls`,工具执行完成后的 push 路径: + +```rust +// current (audit/mod.rs ~380行附近) +pub(crate) async fn process_tool_calls(..., conv_id: &str) -> usize { + // ... 现有审批/执行逻辑 ... + for (index, draft) in tc_list { + // ... 执行工具 ... + let result = tools_arc.execute(&draft.name, &draft.args).await; + + // ★ 新增:namespace 判断 + let content = if should_use_namespace(&result, &draft.name) { + let session = session_arc.lock().await; + let ns = &mut session.namespace_store; // AiSession 新增字段 + let ref_path = ns.store(&draft.name, &draft.args, &result); + ref_path // ← 主队列只推引用路径 + } else { + result // ← 小结果原样推 + }; + + // 后续 push 不变 + let msg = ChatMessage::tool_result(draft.tool_call_id.clone(), &content); + session.conv(conv_id).messages.push(msg); + } +} +``` + +### AiSession 新增字段 + +```rust +// src-tauri/src/commands/ai/mod.rs 或 state.rs + +pub struct AiSession { + pub conversations: HashMap, + pub current_conv: Option, + pub generating: HashSet, + + // ★ 新增 + pub namespace_store: NamespaceStore, +} +``` + +### 边界情况推演 + +#### 情况 1:模型不理解为 namespace 引用 + +``` +LLM 收到 [tool_result] +LLM 回复: "读了 config.rs,但我不知道内容是什么" +→ 用户体验差 +``` + +**对策**:引用路径不是结束。在 `process_tool_calls` push 引用时,额外 push 一条 assistant 消息或修改 tool_result 内容格式: + +``` +[tool_result] 文件 config.rs (342 行) +| 关键行: timeout:15 (行15), pool_size:10 (行42) +| 完整内容: +``` + +即:`extract_key_info(已有)+ 引用路径` 混合格式。LLM 可以靠摘要感知文件内容,只有需要精确行号时才触发 `/read_namespace`。 + +#### 情况 2:同一文件在 2 轮内被多次引用 + +``` +第 3 轮: read_file("config.rs") → namespace 存 342 行 → 引用 +第 5 轮: 模型需要第 15 行 → /read_namespace → 系统拉取 +``` + +第 5 轮拉取后,第 6 轮应该做什么? + +**方案 A(推荐)**:拉取后仅在**当轮** `tool_result` 替换为完整内容。下一轮重新压缩时,如果内容大再次进入 namespace。 + +``` +第 5 轮: [tool_result] config.rs 342 行全文(/read_namespace 触发的) +第 6 轮: 压缩 → config.rs 内容被压缩掉 → 正常 +第 7 轮: 模型再次需要 → 再次 /read_namespace → namespace 还在 +``` + +**方案 B**:拉取后一直保留在消息队列中。 + +→ 不推荐,回到了 tool_result 膨胀的老路。 + +#### 情况 3:namespace 内存爆炸 + +``` +最大对话: 500 轮,平均每轮存 2 个 namespace 条目,每条约 3k bytes +→ 500 × 2 × 3k = 3MB +``` + +设 `max_bytes = 10MB`(宽松上限),最旧条目自动淘汰。淘汰后如果有模型再次引用: + +``` +LLM: /read_namespace namespace://read_file/a1b2c3 +系统: 条目已淘汰 → 重新执行工具 → 重新 namespace 存储 → 返回内容 +``` + +等价于「cache miss」,对用户透明。 + +#### 情况 4:DB 持久化需不需要存 namespace + +**不需要**。namespace 是运行时缓存优化,不是持久化真相源。全量消息已在 `save_conversation` 写入 `ai_messages` 表(uncompressed),压缩后的摘要也在 system 消息中。namespace 淘汰后,可通过 DB 重新构建,但需要保证: + +```rust +// save_conversation 中 +if msg.content.starts_with(NAMESPACE_REF_PREFIX) { + // 写 DB 前将引用替换回原始内容(从 namespace 中取) + // 替代方案:DB 也存引用路径,恢复时从 namespace 重建 +} +``` + +**建议**:DB 存引用路径。恢复时 namespace 可能已淘汰,此时: + +1. 重新执行工具(不可行——工具可能有副作用) +2. DB 保留原文(`save_conversation` 时展开引用) + +**结论**:DB 存原文。save 时 namespace 条目肯定存在(刚执行完),展开引用写入 DB。恢复时直接读 DB 原文,namespace 只服务于运行时。 + +```rust +// save_conversation 中展开引用 +let content = if msg.content.starts_with(NAMESPACE_REF_PREFIX) { + namespace_store.read(&msg.content) + .unwrap_or(&msg.content) // 兜底:用引用路径自身(可能性低) +} else { + &msg.content +}; +record.content = content.to_string(); +``` + +### 推演:T2 在 T-heavy 场景中的行为 + +``` +第 3 轮: read_file("config.rs") → 342 行 + 应进 namespace ✓ + 主队列: [tool_result] 文件 config.rs (342行), 行15:timeout, 行42:pool + 主队列 token: ~80(→原来 2000) + 节省: 1920 tokens + +第 4-7 轮: 另有 3 次大工具结果 → 每次省 ~1500-2000 tokens + +第 8 轮: 压缩触发 + 当前: 主队列总 history_tokens ≈ 5000(含 4 条引用) + → 未达压缩阈值(原应为 20000+) + 压缩不触发 ✓ 用户无感知 + +第 15 轮: 用户回来 "config.rs 的 timeout 在第几行?" + LLM 在压缩摘要中仍能找到"行15:timeout" ← 摘要来自 extract_key_info + → 精确回答 ✓ 不需要 /read_namespace + +第 16 轮: "删掉那行,改成 30" + LLM 需要完整文件结构来做 diff + → /read_namespace namespace://read_file/xxx + → 系统拉取完整 342 行 + → 第 16 轮 tool_result 出现完整内容 + → 第 17 轮压缩 → 完整内容又被 namespace 吸收 → 回到摘要+引用 +``` + +**总节省**:15 轮对话中,原本平均每轮 4000 tokens 的大 tool_result 占用 → 现在每轮 ~100 tokens 引用 + 摘要。**累积节省 ~60k tokens,约 $0.18(Sonnet)。** + +--- + +## T4 工作流 DAG 注入(详细设计) + +### 现有流程 vs 新流程 + +``` +现有: + run_agentic_loop system_prompt 拼接: + [系统指令] + [工具定义] + [pinned_goals] + [知识注入] + → build_for_request + → LLM 只能从消息历史中推断"当前在做什么" + +新流程: + run_agentic_loop system_prompt 拼接前: + if conv.workflow_id != None: + dag = df_workflow::Dag::load(conv.workflow_id) + dag_block = render_dag_to_system_block(dag) + system_prompt = dag_block + system_prompt_原有内容 + → build_for_request(system_prompt + ...) + → LLM 看到结构化 DAG,"任务进展"一目了然 +``` + +### 数据结构 + +```rust +// src-tauri/src/commands/ai/workflow_context.rs + +/// 工作流上下文块 — 注入 system prompt 的 DAG 摘要 +pub struct WorkflowContextBlock { + pub workflow_id: String, + pub workflow_name: String, + pub total_nodes: usize, + pub completed_nodes: usize, + pub current_node: Option, + pub next_nodes: Vec, +} + +/// DAG 节点摘要 — 不进完整 DAG,只进关键上下文 +pub struct WorkflowNodeSummary { + pub node_id: String, + pub node_type: String, // "AINode" | "ScriptNode" | "HumanNode" | ... + pub label: String, // 用户或 LLM 设定的节点名称 + pub status: String, // "completed" | "running" | "pending" | "blocked" + pub output_summary: Option, // 节点输出的摘要(关键产出) +} +``` + +### 关键决策点 + +#### 决策 1:DAG 全量注入还是摘要注入 + +``` +DAG 可能很大(50+ 节点)。全量注入 ≈ 2000+ tokens,不可接受。 +``` + +**方案**:只注入「当前活跃路径」。从 DAG 的起始节点到当前节点 + 后续 2 层子节点。其余节点不注入。 + +```mermaid +graph LR + subgraph "DAG 全量(50 节点)" + N1["N1 ✅"] --> N2["N2 ✅"] + N1 --> N3["N3 ✅"] + N2 --> N4["N4 🚧 当前"] + N2 --> N5["N5 pending"] + N3 --> N6["N6 pending"] + N4 --> N7["N7 pending"] + N5 --> N8["N8 pending"] + N6 --> N9["N9 pending"] + N3 --> N10["N10 pending"] + N4 --> N11["N11 pending"] + end + + subgraph "注入内容(当前活跃路径,5 节点)" + I1["N1: 读源码 ✅"] + I2["N2: 分析依赖 ✅"] + I3["N4: 改配置 🚧"] + I4["N5: 验证配置 pending"] + I5["N7: 提交变更 pending"] + end +``` + +```rust +pub fn build_active_path(dag: &Dag, current_node_id: &str) -> WorkflowContextBlock { + let path = dag.path_to_root(current_node_id); // 到根的全路径 + let next = dag.children(current_node_id, 2); // 后续 2 层 + + WorkflowContextBlock { + workflow_id: dag.id.clone(), + workflow_name: dag.name.clone(), + total_nodes: dag.nodes.len(), + completed_nodes: dag.nodes.iter().filter(|n| n.status == "completed").count(), + current_node: summarize_node(dag.get_node(current_node_id)), + next_nodes: next.into_iter().map(|n| summarize_node(n)).collect(), + } +} +``` + +#### 决策 2:注入位置 + +注入 system prompt 的最前方(优先级高于工具定义): + +``` +[system] +[工作流] 审批模块改造 (ID: wf-abc) + ✅ 1/3 读 config.rs 源码 — 产出: timeout:15 位于行15 + 🚧 2/3 改 timeout 配置 — 当前步骤 + ⬜ 3/3 验证配置生效 +────────────────────────────────────── +[工具定义] 30 个工具的 JSON schema... +[目标提示] ... +[system prompt 原有内容] +``` + +**理由**:工作流状态是会话的最上层语境。模型先看到"我们在做什么",再看到"有什么工具可用"。 + +#### 决策 3:无工作流时的行为 + +```rust +if let Some(wf_id) = &conv.workflow_id { + if let Ok(dag) = df_workflow::Dag::load(db, wf_id) { + let block = build_active_path(&dag, &conv.current_node_id); + system_prompt = format!("{}\n{}", block.to_system_text(), system_prompt); + } + // Dag::load 失败 → 静默跳过(workflow 可能被删了) +} +// workflow_id == None → 行为完全不变 +``` + +### 推演:T4 在 T-resume 场景中的行为 + +``` +上午: + 用户在工作流 wf-abc 中: + N1: 读源码 ✅ + N2: 分析依赖 ✅ + N3: 改配置 🚧(当前) + 最后操作: 改了一半配置,离开 + +下午回来: + 现有: 消息历史被压缩 → 摘要"用户讨论过配置修改" + → LLM 答:"你讨论了配置,要继续吗?" + → 用户需要重新说明 + + T4: system prompt 注入: + [工作流] 审批模块改造 + ✅ 1/3 读 config.rs — timeout:15 + ✅ 2/3 分析 db.rs 依赖 — 连接池:10 + 🚧 3/3 改 timeout 配置 — 已设为30,未验证 + → LLM: "你上午在改 timeout 配置,已经改了还没验证, + 要继续验证还是改其他?" + → 用户直接继续,不需要重新说明 +``` + +**精度依赖**:`WorkflowNodeSummary.output_summary` 是关键。如果工作流节点在完成时已有结构化的产出记录(`read_file → 行号:timeout:15`),LLM 就能精确回溯。这需要 df-workflow 的节点在完成时主动记录产出摘要——目前不一定有。 + +**缺口**:如果工作流节点跑完但没有产出摘要(`output_summary: None`),LLM 只能看到"节点已完成",不知道完成了什么。`output_summary` 需要在 `AiNode::execute` 完成时自动产生: + +```rust +// df-nodes/src/ai_node.rs: 执行完成后 +if let Some(workflow_node_id) = current_workflow_node { + let summary = extract_key_info(&result); // 复用 + workflow::update_node_output(db, workflow_node_id, summary); +} +``` + +这个联动在 T4 之前需要确保。 + +### T4 + T2 组合推演 + +``` +T2 提供了 namespace 精确行号回溯 +T4 提供了工作流节点结构化映射 + +组合: + LLM: "config.rs 的 timeout 在哪一行?" + T4 DAG 节点: "N1: 读 config.rs" + T2 namespace: 关联 namespace://read_file/xxx 到 N1 的产出 + → LLM 从 DAG 节点取到 "timeout:15 位于行15" + → 不需要调用 /read_namespace + → 精确行号回答,零额外 token + +如果 T2 没有、T4 没有: + LLM: 从压缩摘要猜 → 可能错 + +如果 T2 有、T4 没有: + LLM: 从 DAG 节点取出 "读 config.rs",但需要/read_namespace 拿行号 + → 多一次 tool round + +如果 T2 没有、T4 有: + LLM: DAG 节点只有 "读源码 ✅",没有行号细节 + → 重新 read_file + +组合后: 最大精度,最小 token 开销。 +``` + +--- + +## T3 L3 结构化摘要(设计概要) + +### 改 compress_via_llm 返回类型 + +```rust +// current +pub(crate) async fn compress_via_llm(...) -> Result; + +// new +pub struct CompressedSummary { + /// JSON 卡片序列化字符串 + pub json_card: String, + /// 自然语言摘要(向前兼容) + pub nl_summary: String, +} + +pub(crate) async fn compress_via_llm(...) -> Result; +``` + +### 改 compress_prompt + +```rust +pub(crate) fn compress_prompt(lang: &str) -> &'static str { + match lang { + "en" => "You are a conversation summarizer. Compress the following conversation \ + into a structured summary that preserves the essential context for \ + continuing the work. \ + \n\ + **You MUST output TWO parts separated by a delimiter:**\n\ + \n\ + Part 1 — JSON (between <<>> and <<>>):\n\ + {\n\ + \"topics\": [\"topic1\", \"topic2\"],\n\ + \"decisions\": [{\"what\": \"...\", \"why\": \"...\"}],\n\ + \"unresolved\": [\"question1\"],\n\ + \"key_files\": [{\"path\": \"...\", \"change\": \"...\"}],\n\ + \"token_saved\": \n\ + }\n\ + \n\ + Part 2 — Natural language summary (between <<>> and <<>>):\n\ + A concise paragraph summarizing the conversation.\n\ + \n\ + Rules:\n\ + - JSON must be valid.\n\ + - Keep the natural language summary concise; prefer bullet points.\n\ + - Preserve file paths, identifiers, and error messages verbatim in both parts.\n\ + - Drop small talk; keep only technically load-bearing facts.\n\ + - Do NOT invent facts.\n", + _ => { + // 中文版本同上,翻译为中文 + } + } +} +``` + +### insert_at 处的消费 + +```rust +// context_lifecycle.rs: 压缩成功后 +match compress_outcome { + Ok(Some(summary)) => { + // ★ 新格式:JSON + NL 双格式嵌入 system 消息 + let system_text = format!( + "{}<<>>\n{}\n<<>>\n{}", + SUMMARY_MARKER, + summary.json_card, + summary.nl_summary, + ); + conv.messages.insert_at(0, ChatMessage::system(&system_text)); + // ... + } + Ok(None) => { /* noop */ } + Err(e) => { + // LLM 压缩失败 → 关键词摘要兜底(同前) + // 关键词摘要是纯文本格式,不需要 JSON 结构 + // → insert_at(0, system(keyword_fallback)) ← 与旧行为一致 + } +} +``` + +### 退化检测适配 + +当前 `clean_summary` 函数只处理纯文本退化。新格式引入后,退化检测需增加 JSON 解析验证: + +```rust +fn clean_summary(raw: &str) -> Result { + // 提取 JSON 段 + let json = extract_between(raw, "<<>>", "<<>>")?; + let nl = extract_between(raw, "<<>>", "<<>>")?; + + // 验证 JSON 合法性 + let card: SummaryCard = serde_json::from_str(&json) + .map_err(|e| format!("JSON 解析失败: {}", e))?; + + // 退化检测 NL 段 + if is_degenerated_repetition(&nl) { + return Err("NL 段退化重复".to_string()); + } + + Ok(CompressedSummary { + json_card: json, + nl_summary: nl, + }) +} +``` + +### 推演:T3 在检索场景中的行为 + +``` +无 T3(当前): + 压缩摘要: "用户讨论了审批配置,把 timeout 改成了 30" + LLM 读取后知道"改过",但具体决策原因不清晰 + → 需要推测或 ask user + +有 T3: + system 消息中嵌入: + <<>> + {"decisions": [{"what": "timeout: 15→30", "why": "用户反馈15min太短"}]} + <<>> + 自然语言: 用户讨论审批配置,决定超时改为 30 分钟 + + LLM 直接从 JSON 读取决策原因: + → "15→30,原因是用户反馈太短" + → 精确回答 ✓ + → 不需要 ask user +``` + +**收益量化**:每次压缩后,JSON 卡片占 ~300 tokens,自然语言占 ~200 tokens。比纯自然语言的 ~400 tokens 多了 ~100 tokens。但 JSON 的结构化让 LLM 的检索精度从「需要推理」变为「可以直接读」,减少了后续追问的轮次。 + +--- + +## T1 动态压缩阈值(设计概要) + +代码改动精确到行: + +```rust +// src-tauri/src/commands/ai/agentic/mod.rs: 调用处(line ~1117) +if maybe_auto_compress( + &session_arc, + &conv_id, + &app_handle, + &provider, + &provider_config, + &llm_concurrency, + iteration, + sys_tokens, // ★ 新增参数 +).await { +``` + +```rust +// src-tauri/src/commands/ai/agentic/context_lifecycle.rs: 函数签名(line ~72) +pub(super) async fn maybe_auto_compress( + session_arc: &Arc>, + conv_id: &str, + app_handle: &AppHandle, + provider: &Box, + provider_config: &AiProviderRecord, + llm_concurrency: &LlmConcurrency, + iteration: usize, + sys_tokens: u32, // ★ 新增 +) -> bool { +``` + +```rust +// 同上,line ~88-92 触发条件 +let budget = mgr.budget_limit(); +let available = budget.saturating_sub(sys_tokens); // ★ 新增 +let should = protect_start > 0 + && (available as u64) * 6 / 10 < history_tokens as u64 // ★ 改 budget→available + && mgr.has_compressible_messages(protect_start); +``` + +--- + +## 实施顺序验证(依赖关系) + +```mermaid +graph LR + T1["T1: 动态阈值
~4 行改动"] --> T2["T2: 命名空间
~300 行"] + T1 -.->|可选前置| T3["T3: 结构化摘要
~100 行"] + T2 --> T4["T4: 工作流DAG
~500 行"] + T3 --> T5["T5: WorkingContext
~500 行"] + + T2 -.-> T4 + T3 -.-> T4 + + style T1 fill:#c8e6c9 + style T2 fill:#c8e6c9 + style T3 fill:#fff9c4 + style T4 fill:#fff9c4 + style T5 fill:#ffccbc +``` + +**并发策略(AI coding 多 agent 并行)**: + +| 并行流 | 任务 | 前置 | 可独立启动? | +|--------|------|------|------------| +| 流 A | T1 + T2 | T1 无前置,T2 无前置 | ✅ 立即,T1 与 T2 可并行编码 | +| 流 B | T3 | 无(与 T2 独立) | ✅ 可同时启动 | +| 流 C | T4 | 依赖 T2 的 namespace 接口签名(非实现) | ⚠️ 接口定义后即可启动 | +| 流 D | T5 | 依赖 T3 的 CompressedSummary 结构 | ⚠️ T3 验收后 | diff --git a/docs/02-架构设计/专项设计/上下文管理演进与发散思考-2026-07-20.md b/docs/02-架构设计/专项设计/上下文管理演进与发散思考-2026-07-20.md new file mode 100644 index 0000000..c843dec --- /dev/null +++ b/docs/02-架构设计/专项设计/上下文管理演进与发散思考-2026-07-20.md @@ -0,0 +1,882 @@ +# 上下文管理:现状评估、业界对标与发散演进 + +> 创建: 2026-07-20 | 状态: 构想审查 | 关联: F-15 上下文管理增强设计 + +--- + +## 目录 + +- [一、当前 ContextManager 评估](#一当前-contextmanager-评估) +- [二、业界方案对标](#二业界方案对标) +- [三、融合设计:结构化分层上下文引擎](#三融合设计结构化分层上下文引擎) +- [四、3 个近期改进的推演验证](#四3-个近期改进的推演验证) +- [五、7 个发散方向](#五7-个发散方向) +- [六、发散组合分析与冲突矩阵](#六发散组合分析与冲突矩阵) +- [七、推荐路线](#七推荐路线) + +--- + +## 一、当前 ContextManager 评估 + +### 核心架构 + +``` +build_for_request → 返回裁剪后视图(影响 LLM 上下文) +all_messages_clone → 返回全量(影响 DB 存储, save_conversation) +``` + +| 层级 | 组件 | 文件 | +|------|------|------| +| Token 估算 | `TokenEstimator` — chars×0.35 + 消息开销 | `context_helpers.rs:28` | +| 窗口配置 | `ContextConfig` — max_tokens/output_reserve/safety_ratio | `context_helpers.rs:105` | +| 消息管理 | `ContextManager` — 消息队列 + token 缓存 | `context/mod.rs:44` | +| 自愈 | `sanitize模块` — 畸形配对过滤 + 序列合法性修复 | `context/sanitize.rs` | +| 自动压缩 | `maybe_auto_compress` — F-15 自动触发 | `context_lifecycle.rs:64` | + +### 三维评分 + +| 维度 | 评分 | 核心论据 | +|------|------|---------| +| **效率** | ⭐⭐⭐⭐ (8/10) | 核心路径零拷贝裁剪,O(n) 反向扫描查找,幂等压缩。瓶颈在 TokenEstimator 精度和压缩同步等待 | +| **合理性** | ⭐⭐⭐⭐⭐ (9/10) | 三元组原子性、保护区、视图/持久化分离、畸形自愈——近乎工业级完整。略缺跨会话上下文 | +| **成本** | ⭐⭐⭐⭐ (8/10) | 预算池 + 流式记录 + 关键词兜底形成三级防线。缺乏自动模型降级链和长时间会话自动分段 | + +### 已知问题 + +1. **TokenEstimator 对所有字符一视同仁** — 中文(~1.8-2.5 tok/char)被低估,英文(~0.2-0.3)被高估。当前靠 `safety_ratio: 0.85` 和 `output_reserve: 8192` 补误差 +2. **压缩阈值硬编码 0.6** — 不考 system prompt 大小,大量工具结果时可能误判 +3. **无自动会话分段** — `archived_segment` 状态和 IPC 已就绪,但只能手动触发 + +--- + +## 二、业界方案对标 + +### 2.1 与 7 条行业公认合理标准对标 + +| # | 行业标准 | DevFlow 现状 | 差距 | +|---|---------|-------------|------| +| 1 | **运行时/LLM 上下文双层隔离** | 有部分隔离但不彻底。无类型层面的强制边界 | ⚠️ 概念上有但无强制 | +| 2 | **分层记忆(瞬时→短期→长期)** | 只有「内存窗口 ↔ DB」两层,缺短期缓存层和向量检索层 | ⚠️ 有分层但缺中间层 | +| 3 | **Token 预算管控** | `TokenEstimator` + `budget_limit` + 0.6 水位 + 降级关键词兜底 | ✅ 业界中上 | +| 4 | **信息加权裁剪** | `PROTECT_COUNT=6` 保近期,但裁剪是纯位置滑动,不是按价值加权 | ⚠️ 机制粗糙 | +| 5 | **命名空间隔离** | `conv_id` 做 Session 级隔离,无 User/Agent 维度 | ⚠️ 缺维度 | +| 6 | **结构化沉淀** | 压缩摘要为纯文本,非结构化数据 | ❌ 文本摘要非结构化 | +| 7 | **可持久快照** | DB 保留全量,但无版本概念,不可回滚 | ❌ 有持久无快照 | + +### 2.2 具体方案比较 + +| 方案 | 核心优势 | 对 DevFlow 的参考价值 | +|------|---------|---------------------| +| **Claude Code 三层加载** | 常驻索引 + 按需主题 + 离线归档 | 当前 `pinned_goals` → 扩展为更丰富的 WorkingContext | +| **OpenAI Agents SDK RunContext** | 运行时状态不进 prompt | 当前 AiSession 概念上有但无强制边界 | +| **Mem0** | User/Session/Agent 三维命名空间 | 未来多租户/跨会话复用的架构参考 | +| **Letta (MemGPT)** | 虚拟内存分页,模型自主调度 | 哲学不同:Letta 模型自主换页,DevFlow 系统强制压缩 | +| **TencentDB Agent Memory** | 结构化任务画布替代文本历史 | **最有参考价值** — 与 df-workflow + df-nodes 天然契合 | +| **LangGraph** | 可持久化 State + Checkpoint | 版本化快照模式参考 | +| **AutoContextMemory** | 6 档阶梯式上下文管控 | 信息加权裁剪参考 | + +### 2.3 核心启发 + +``` +TencentDB 任务画布思路 → 结构化卡片替代纯文本摘要 + ↓ +WorkingContext 常驻 → 结构化目标/决策/步骤替代纯文本 pinned_goals + ↓ +MemoryAdapter trait → 插件化记忆层,不绑定 Vec + ↓ +版本化快照 → 从覆盖写改为追加 checkpoint +``` + +--- + +## 三、融合设计:结构化分层上下文引擎 + +### 3.1 架构总览 + +```mermaid +graph TB + subgraph LLM 上下文窗口 (build_for_request 输出) + L1["L1 常驻上下文
(永不裁剪,结构化 system 消息)"] + L2["L2 活跃历史
(窗口滑动淘汰,user/assistant/tool)"] + L3["L3 结构化摘要
(压缩/归档后锚点,JSON 卡片)"] + end + + subgraph 本地运行时状态 (不进 LLM) + R1["原始消息全量 (SQLite DB)"] + R2["工具执行日志 (分级缓存)"] + R3["向量记忆索引 (df-knowledge)"] + R4["工作流 DAG (df-workflow)"] + end + + L1 -->|注入 system| LLM["LLM 请求"] + L2 -->|阈值超限| L3 + L3 -->|检索回溯| R1 + L3 -->|语义检索| R3 + + style L1 fill:#c8e6c9 + style L2 fill:#fff9c4 + style L3 fill:#bbdefb + style R1 fill:#f5f5f5 + style R2 fill:#f5f5f5 + style R3 fill:#f5f5f5 + style R4 fill:#f5f5f5 +``` + +### 3.2 L1 常驻上下文:WorkingContext + +融合 Claude Code 常驻索引 + DevFlow 现有 `pinned_goals` + Decision Journal: + +```rust +pub struct WorkingContext { + pub goals: Vec, + pub decisions: Vec, + pub unresolved: Vec, + pub current_step: Option, + pub key_artifacts: Vec, +} +``` + +### 3.3 L1 注入策略:分层注入(非全量常驻) + +`dirty_window_turns` 控制脏标记的可见窗口——只有最近 N 轮内有变更的字段才注入 system prompt。 + +```mermaid +graph LR + subgraph "L1a 高频常驻 (~200 tokens)" + A1["当前步骤 current_step"] + end + subgraph "L1b 条件注入 (~200-800 tokens)" + B1["活跃目标 — dirty_window 内变更才注入"] + B2["未决项 — dirty_window 内变更才注入"] + end + subgraph "L1c 工具按需" + C1["完整决策历史 → read_context 工具"] + end + + A1 -->|每轮必带| LLM + B1 -->|变更在窗口内| LLM + B2 -->|变更在窗口内| LLM + C1 -->|模型主动调用| LLM +``` + +### 3.4 压缩联动(关键补丁) + +压缩事件发生后,`reset_all_with_summary` 将所有脏标记重置到当前轮次,保证压缩后下轮 L1b 全量注入,补偿被压缩丢失的历史信息: + +```rust +// context_lifecycle.rs: 压缩成功后 +conv.messages.compress_old_messages(protect_start); +conv.messages.insert_at(0, ChatMessage::system(&summary)); + +// 压缩后重置脏窗口 +conv.working_context.version.reset_all_with_summary(&summary, current_turn); +``` + +### 3.5 L3 结构化摘要卡片 + +将压缩输出的纯文本摘要改为 JSON + NL 双格式: + +``` +当前: system: "用户讨论了审批流程,确认了超时参数改为 30 分钟" + +改进后: system: <<>> +{ + "type": "conversation_segment", + "topics": ["审批超时设置"], + "decisions": [ + {"what": "超时改为 30 分钟", "why": "用户反馈 15 分钟太短"} + ], + "unresolved": ["推送方式待定"], + "token_saved": 45200 +} +<<>> +自然语言摘要: 用户讨论了审批流程,确认了超时参数改为 30 分钟 +``` + +### 3.6 信息加权裁剪 + +当前 `build_eviction_units` 按消息位置淘汰,改为按消息类型赋予保留权重: + +| 消息角色 | 保留权重 | 理由 | +|---------|---------|------| +| User(短指令) | 100 | 高价值,优先保留 | +| User(长消息) | 70 | 中等 | +| Assistant(含 tool_calls) | 50 | 工具调用链需要保留 | +| Assistant(纯文本) | 40 | 一般 | +| Tool(工具结果) | 20 | 低价值,优先淘汰 | + +--- + +## 四、3 个近期改进的推演验证 + +### 4.1 改进 1:TokenEstimator 按字符类型加权 + +**初始提议**:中文×1.8、英文×0.25、数字×0.20 等混合加权。 + +**推演结论**:**不做。** 理由: + +1. 当前 `safety_ratio: 0.85` + `output_reserve: 8192` 提供了约 27k tokens 的余量,中英混合对话要超界需要极端场景 +2. 真正该修的入口不在 TokenEstimator:tool_result 膨胀应由 `extract_key_info` 在工具执行阶段削减,不是靠 TokenEstimator 精度来兜底 +3. 改加权逻辑有回归风险(content / parts / tool_calls / tool_call_id 多处调用了 `chars_ratio`) + +### 4.2 改进 2:动态压缩阈值 + +**初始提议**:将 `budget * 6 / 10` 改为 `available * 6 / 10`(available = budget - sys_tokens)。 + +**推演结论**:**做。** 3 行改动,效果明确: + +```rust +// 当前 +let should = (budget as u64) * 6 / 10 < history_tokens as u64; + +// 改为 +let available = mgr.budget_limit().saturating_sub(sys_tokens); +let should = (available as u64) * 6 / 10 < history_tokens as u64; +``` + +`sys_tokens` 在 `maybe_auto_compress` 调用处已由调用方算好(loop 顶部传给 `build_for_request`),透传即可。 + +### 4.3 改进 3:长时间会话自动存档 + +**初始提议**:达到压缩次数或消息总量阈值后自动触发 `archived_segment`。 + +**推演结论**:**暂缓。** 基础设施已就绪(`archived_segment` 状态、IPC `ai_chat_clear_context`、前端 `AiContextCleared` 事件),但: + +1. 极长对话(>300 条)才需要,日常场景用不着 +2. 归档后压缩摘要与归档摘要的关系需要先理清(归档时会把之前的压缩摘要也一并标记 archived_segment,确保不产生孤立 system 消息) +3. 建议 Phase 4/5 再做 + +### 4.4 优先级 + +| 排序 | 项 | 改动量 | 收益 | 建议 | +|------|----|--------|------|------| +| 🥇 | **2a 动态阈值** | 3 行 | 减少误压缩和漏压缩 | **立即做** | +| 🥈 | 1 TokenEstimator | 多文件 | 被 safety_ratio 兜住 | **不做** | +| 🥉 | 3 自动存档 | ~50 行 | 场景不迫切 | **Phase 4/5** | + +--- + +## 五、7 个发散方向 + +### 发散①:画布导航 — 模型自主控制视野 + +**核心思想**:不再把上下文拼成平铺消息队列丢给模型,而是组织成多维画布,模型通过 `look_at` 工具自主导航: + +```json +{ + "tool": "look_at", + "args": { "zone": "L1/decisions", "expand": true } +} +``` + +**差异**:窗口永远不会满(不需要装全部),压缩不存在(没有"全量传输"的概念)。模型控制视野范围。 + +**代价**:需要模型主动使用 `look_at`,模型不用时需降级回传统模式。新协议,高实现成本。 + +### 发散②:上下文版本分支 — 决策回溯可导航 + +**核心思想**:支持分支与合并上下文。`ContextManager` 加 `parent: Option` 和 `fork_point: usize`: + +```mermaid +graph LR + A[方案 A] --> B[继续 A] + A --> C[方案 B] + C --> D[发现 B 不可行] + D --> E[合并回到 A] + B --> E +``` + +**差异**:放弃的方案上下文不会被新会话污染,可随时回溯精确内容。 + +**代价**:存储复杂度——N 个分支 = N× 内存。 + +### 发散③:主动参与 — 系统在对话中插话 + +**核心思想**:上下文管理器在 agentic loop 中拥有系统消息通道,可以在检测到模式时主动"说话": + +``` +触发条件示例: + 连续 N 轮使用同一工具 → 建议创建快捷方式 + 修改 A 后 5 轮内又修改 B → 建议关联工作流 + 目标活跃但 N 轮无推进 → 提醒调整目标 +``` + +**差异**:从"你问它答"到"AI 主动观察并建议"——人机交互范式变化。 + +**代价**:冷却期管理和负面反馈机制是必需的,否则必然变成骚扰。 + +### 发散④:时间轴衰减 — 不裁不压,让信息连续"模糊" + +**核心思想**:每条消息的精度随轮次连续衰减,而非在某一轮突然被压缩: + +```rust +pub struct TrackedMessage { + pub message: ChatMessage, + pub precision: f32, // 1.0=精确 → 0.0=完全模糊 + pub last_accessed: u32, // 最后被 LLM 读取的轮次 +} +``` + +**差异**:压缩不是在某一轮突然发生的,而是每轮都在发生——精度衰减是连续的。 + +**代价**:需要新的衰减算法和精度阈值判断。与当前离散三态模型不兼容。 + +### 发散⑤:元控制器 — 上下文管理策略自感知 + +**核心思想**:把 `safety_ratio`、压缩阈值、`PROTECT_COUNT` 等硬编码常量变为动态自适应参数: + +```rust +pub struct MetaContextController { + features: VecDeque, // 会话特征滑动窗口 + recommended: ContextConfig, // 当前推荐配置 +} +``` + +**差异**:无需手调参数,系统在 5-10 轮后自动收敛到适合当前会话的策略。 + +**代价**:元控制器自己的参数(特征窗口大小、步长)也需要调——递归问题。 + +### 发散⑥:工作流即上下文 — DAG 作为上下文主干 + +**核心思想**:上下文就是当前工作流的 DAG 视图,消息按工作流节点组织,而非按时间排列: + +``` +当前: [user][asst][tool][user][asst]... → 时间线 +改进: [节点1(完成)] [节点2(当前)] [节点3(待办)] → DAG +``` + +**差异**:DevFlow 是唯一同时拥有工作流引擎和 AI 对话的产品,这种融合是**差异化最大的方向**。 + +**代价**:需要工作流与对话关联,自由对话不适用。 + +### 发散⑦:工具命名空间 — 工具结果不进主队列 + +**核心思想**:工具的执行结果不返回主消息队列,而是存入专属的 namespace: + +``` +主队列: + [asst] 正在读取 config.rs... + [tool_result(namespace://read/config.rs)] ← 仅 20 tokens 的引用 + +工具工作区: + namespace://read/config.rs → 342 行代码(按需读取,不占窗口) +``` + +**差异**:大工具结果膨胀是当前压缩触发的主因,这直接消除膨胀源。 + +**代价**:需要 namespace 存储引擎 + 模型引用协议。工具执行路径不变,路由从 push messages 改为 push reference。 + +--- + +## 六、发散组合分析与冲突矩阵 + +### 6.1 内部不可调和冲突 + +#### 冲突 1:画布(①) vs 批传输(当前 Design + ②③④⑤⑥⑦) + +``` +画布模式要求: 模型自主注视 → 增量读取 → 窗口永不装满 +批传输模式要求: 系统全量拼装 → 一次发给模型 → 压缩/裁剪管理窗口 + +当①启用时,压缩、裁剪、budget_limit 的假设前提不再成立—— +窗口装的不是全量消息,而是画布坐标系。 +``` + +#### 冲突 2:主动参与(③) vs 画布导航(①) + +``` +①: 模型是自主的,系统是服务者 +③: 系统有更高优先级的信息需要模型注意 + +模型正在注视 L2,系统突然插话——注意力被打破。 +``` + +#### 冲突 3:时间轴衰减(④) vs 分支(②) vs 工作流(⑥) + +三者对"什么是上下文的主要信息载体"的回答不同: + +``` +④: 消息是主要载体 → 精度衰减作用于消息 +⑥: 工作流节点是主要载体 → 消息附着在节点上 +②: 分支是主要载体 → 消息在分支的线性时间线上 + +一条消息同时有精度值(④)+ 分支ID(②)+ 工作流节点ID(⑥)→ +三个矛盾的淘汰决策信号同时作用 → 需要额外仲裁逻辑 +``` + +### 6.2 组合爆炸 + +7 个发散引入至少 26 个新参数;状态空间约 3^7 ≈ 2187 种(无法穷举测试)。 + +### 6.3 三个互斥派系 + +```mermaid +graph TB + subgraph "派系 A: 工作流结构化 (推荐首做)" + A6["⑥工作流即上下文"] + A7["⑦工具命名空间"] + A5["⑤元控制器 (精简版)"] + A_RESULT["输出: 工作流DAG + 命名空间引用 替代平铺消息"] + end + + subgraph "派系 B: 记忆系统" + B2["②上下文分支"] + B4["④时间轴衰减"] + B5["⑤元控制器"] + B_RESULT["输出: 分支记忆 + 连续遗忘 替代离散压缩"] + end + + subgraph "派系 C: 交互范式" + C1["①画布导航"] + C3["③主动参与"] + C5["⑤元控制器"] + C_RESULT["输出: 自主模型 + 系统辅助 替代命令式对话"] + end + + A6 ---|不兼容| B2 + B4 ---|兼容| C1 + C3 ---|不兼容| C1 +``` + +| 派系 | 核心哲学 | 适合场景 | 与当前 Design 衔接成本 | +|------|---------|---------|----------------------| +| **A 工作流结构化** | 把上下文组织成 DAG | 有明确工作流的任务 | **低**(复用 df-workflow) | +| **B 记忆系统** | 让上下文更像人脑记忆 | 超长对话、研究型任务 | 中(新算法 + 新状态) | +| **C 交互范式** | 重新定义人机交互 | 高级用户、复杂决策 | 高(改变协议) | + +### 6.4 双发散的乘数效应 + +| 组合 | 收益等级 | 价值 | +|------|---------|------| +| **⑥+⑦** | 🔥🔥🔥 | 工作流节点引用 namespace 路径,精确回溯无需重查 | +| **①+②** | 🔥🔥 | 画布让 N 个分支的内存成本从 O(N) 降到 O(1) | +| **③+⑤** | 🔥🔥🔥 | 没有⑤,③是骚扰;没有③,⑤的优化用户感觉不到 | +| **④+⑦** | 🔥 | namespace 引用增加置信度信号 | +| **①+②+⑥+⑦** | 🏆 | 最完整的中断恢复路径 | +| **③+⑤+⑥** | 🔥🔥 | 工作流阻塞时智能主动提示 | + +### 6.5 对「全组合」的结论 + +> **技术上可以,但架构上存在 3 对不可调和冲突、26+ 新参数、2187+ 状态空间。不可控。** 不推荐全组合。 + +| 维度 | 答案 | +|------|------| +| **技术上可行吗** | 可以,但需要全新架构,复杂度膨胀约 10 倍 | +| **测试覆盖可行吗** | 不可行。2187 种组合状态 → 实际覆盖不到 5% | +| **维护成本可接受吗** | 不可接受。团队需同时理解 7 个系统的交互 | +| **价值可叠加吗** | 边际递减。②+④ 已覆盖 T-fork+T-resume 主要痛点 | +| **更优路径** | **选派系 A 做深,接口预留 B 和 C** | + +--- + +## 七、推荐路线 + +### 选派系 A(工作流结构化)为主干,预留接口 + +```mermaid +graph TB + subgraph "Phase 4-5" + A6["⑥ 工作流即上下文"] + A7["⑦ 工具命名空间"] + A5["⑤ 元控制器 (精简版: 仅调 3-5 个参数)"] + COMBINED["A 组构成新 ContextManager"] + end + + subgraph "预留接口 (不实现)" + IF1["Trait: ContextNavigation
(供①画布)"] + IF2["Trait: ContextFork
(供②分支)"] + IF3["Trait: ProactiveHint
(供③主动参与)"] + IF4["Trait: PrecisionDecay
(供④衰减)"] + end + + A6 --> COMBINED + A7 --> COMBINED + A5 --> COMBINED + COMBINED --> IF1 + COMBINED --> IF2 + COMBINED --> IF3 + COMBINED --> IF4 +``` + +### 实施步骤 + +| 步 | 内容 | 改造成本 | 用户可见收益 | 阶段 | +|----|------|---------|------------|------| +| 0 | **动态压缩阈值** — 将 sys_tokens 纳入 | 3 行 | 减少误/漏压缩 | 立即 | +| 1 | **L3 结构化摘要** — JSON 卡片替代纯文本 | ~100 行 | 压缩后上下文质量可测提升 | Phase 4 | +| 2 | **⑦ 工具命名空间** — 大结果不进主队列 | ~300 行 | tool_result 膨胀消除 | Phase 4 | +| 3 | **⑥ 工作流 DAG 注入** — 消息按节点组织 | ~500 行 | 工作流感知的精确回溯 | Phase 4 | +| 4 | **L1 WorkingContext + 分层注入** — 结构化常驻 | ~400 行 | 长对话目标保持 | Phase 5 | +| 5 | **MemoryAdapter trait 提取** — 重构 | ~200 行 | 零(纯重构) | Phase 5 | +| 6 | **版本化快照** — checkpoint 追加写 | ~150 行 | 断点恢复 | Phase 5 | + +**接口预留**(不实现,仅定义 trait): + +```rust +#[async_trait] +pub trait ContextNavigation { /* 供①画布实现 */ } +#[async_trait] +pub trait ContextFork { /* 供②分支实现 */ } +#[async_trait] +pub trait ProactiveHint { /* 供③主动参与实现 */ } +#[async_trait] +pub trait PrecisionDecay { /* 供④衰减实现 */ } +``` + +### 一句话总结 + +> **做 ⑥+⑦ 结构化工作流上下文,留接口给记忆和交互范式,不做全组合。** + +--- + +## 八、多方法交叉论证 + +> 本章用 7 种独立方法对同一组选项交叉验证。结论收敛到同一方向则信心高,出现分歧则标注并分析原因。 + +### 8.1 方法概览 + +| # | 方法 | 分析对象 | 输出 | 与已有分析的关系 | +|---|------|---------|------|-----------------| +| A | **决策矩阵** | 3 个派系 | 加权总分排序 | 把第 6 章定性结论量化 | +| B | **成本收益分析** | 7 个发散 | 成本/收益散点图 | 补充第 5 章缺失的量化维度 | +| C | **ROI 测算** | 派系 A 实施步 | 美元/月节省 vs 开发改动单元 | 把第 7 章路线图加上财务回报 | +| D | **影响范围分析** | 7 个发散 | 波及文件 / 子系统热力图 | 补充实现成本的具体依据 | +| E | **依赖图 & 拓扑排序** | 派系 A 实施步 | 前置条件图 | 验证第 7 章步骤顺序的合理性 | +| F | **风险矩阵** | 7 个发散 | 概率×影响热力图 | 补充第 6 章冲突分析的失败模式 | +| G | **根本原因回溯** | 7 个发散 → 原始痛点 | 覆盖度矩阵 | 从问题端验证发散是否对症 | +| H | **帕累托分析** | 派系 A 实施步 | 累积收益曲线 | 找出 20% 努力得 80% 收益的关键步 | +| I | **Kano 模型** | 7 个发散 | 用户满意度分类 | 从用户感知角度验证优先级 | + +--- + +### 8.2 方法 A:决策矩阵 + +对 3 个派系在 6 个加权维度上打分(1-5)。权重表示该维度对 DevFlow 当前阶段的重要性。 + +| 维度 | 权重 | 派系 A(工作流结构化) | 派系 B(记忆系统) | 派系 C(交互范式) | 权重理由 | +|------|------|---------------------|------------------|------------------|---------| +| 贴合现有资产 | **5** | 5 — 复用 df-workflow + ToolRegistry | 2 — 全新记忆层 | 1 — 需改协议 | Phase 4 优先利用已有代码 | +| 用户可见收益 | **4** | 5 — 压缩后仍可回溯精确行号 | 4 — 长对话不失忆 | 3 — 高级用户才感知 | 需要让用户感受到变化 | +| 实现成本 | **4** | 4 — 增量改造(~900 行) | 2 — 新状态+新算法 | 1 — 新协议+新交互 | 资源有限,成本敏感 | +| 测试覆盖度 | **3** | 4 — 可在现有单测基础上加 | 2 — 新算法需要全新测试 | 2 — 新协议需要集成测试 | 质量保障成本 | +| 长期扩展性 | **3** | 5 — 为 B/C 预留接口 | 3 — 自成体系 | 3 — 自成体系 | 不堵死未来路径 | +| 风险可控度 | **4** | 5 — 增量上线,可回退 | 3 — 新状态机,迁移风险 | 2 — 模型配合度不确定 | 线上稳定性 | + +**加权总分**: + +``` +派系 A = 5×5 + 4×5 + 4×4 + 3×4 + 3×5 + 4×5 = 25 + 20 + 16 + 12 + 15 + 20 = 108 +派系 B = 5×2 + 4×4 + 4×2 + 3×2 + 3×3 + 4×3 = 10 + 16 + 8 + 6 + 9 + 12 = 61 +派系 C = 5×1 + 4×3 + 4×1 + 3×2 + 3×3 + 4×2 = 5 + 12 + 4 + 6 + 9 + 8 = 44 +``` + +**结论**:派系 A 以 108 分大幅领先。差距主要在「贴合现有资产」(权重最高)和「实现成本」两个维度——这正是 Phase 4 阶段的核心约束。 + +--- + +### 8.3 方法 B:成本收益散点图 + +对 7 个发散方向估算实施成本(改动单元)和预期收益(用户可见改善度 1-10): + +| 发散 | 实施成本(改动单元) | 收益(1-10) | 性价比 | 说明 | +|------|----------------|-------------|--------|------| +| ①画布导航 | 25-35 | 6 | 低 | 新传输协议 + 模型配合训练 | +| ②分支 | 15-20 | 7 | 中 | 新数据结构 + 状态机 | +| ③主动参与 | 10-15 | 5 | 中 | 需要用户行为研究 | +| ④时间轴衰减 | 12-18 | 4 | 低 | 与现有三态模型不兼容 | +| ⑤元控制器 | 8-12 | 6 | **高** | 改配置参数即可,收益面广 | +| ⑥工作流即上下文 | 10-15 | 9 | **最高** | 复用 df-workflow,DAG 注入 | +| ⑦工具命名空间 | 5-8 | 8 | **最高** | 直接消除 tool_result 膨胀根因 | + +``` +收益 + 10│ 🚩⑥ + 9│ 🚩⑦ + 8│ + 7│ ② + 6│ ① ⑤ + 5│ ③ + 4│ ④ + 3│ + 2│ + 1│ + └──────────────────────────▶ 成本(改动单元) + 5 10 15 20 25 30 +``` + +**结论**:⑥+⑦ 落在「低成本、高收益」象限(左上),⑤ 居中,②③ 在中成本区,①④ 在右下象限(不推荐)。 + +--- + +### 8.4 方法 C:ROI 测算 + +以典型高频用户(日均 30 会话,每会话 20 轮,使用 Claude Sonnet $3/M input tokens)为基准,测算派系 A 实施后的成本节省: + +| 实施步 | 节省机制 | 单会话节省 | 月节省(30 日) | 实施改动单元 | ROI(月节省/改动) | +|--------|---------|-----------|---------------|---------|-------------------| +| **步 0:动态压缩阈值** | 减少误压缩 ≈ 10% 不必要的压缩 LLM 调用 | ~$0.01 | **~$9** | 0.5 | **$18/改动单元** | +| **步 1:L3 结构化摘要** | LLM 压缩时产出 JSON 摘要,后续检索少一轮 tool call | ~$0.02 | **~$18** | 2 | **$9/改动单元** | +| **步 2:工具命名空间** | 大 tool_result 不进主队列,输入减少 30-50% | ~$0.08 | **~$72** | 5 | **$14.4/改动单元** | +| **步 3:工作流 DAG 注入** | 结构化信息替代重复消息,压缩效率提升 | ~$0.03 | **~$27** | 8 | **$3.4/改动单元** | +| **步 4:WorkingContext 分层注入** | 条件注入比全量常驻省 90% L1 开销 | ~$0.01 | **~$9** | 6 | **$1.5/改动单元** | + +**累计 ROI 曲线**: + +``` +月节省 +$140│ 🟢 步2后爆发 +$120│ 🟢 +$100│ 🟢 + $80│ 🟢 + $60│ 🟢 + $40│ 🟢 + $20│ 🟢 + 0└──────────────────────────────▶ 累计改动单元 + 0.5 2.5 7.5 15.5 21.5 +``` + +**关键发现**:步 2(工具命名空间)贡献了 ~50% 的总节省。如果资源只能做一件事,做步 2。 + +--- + +### 8.5 方法 D:影响范围分析 + +对 7 个发散方向统计影响的 Rust 文件数(含新增和修改): + +| 发散 | 新增文件 | 修改文件 | 涉及 crate | 核心改动点 | +|------|---------|---------|-----------|----------| +| ①画布 | 5-7 | 8-12 | df-ai, src-tauri, df-storage | `look_at` 工具注册、画布坐标系构建、导航状态机 | +| ②分支 | 3-4 | 6-8 | df-ai, df-storage | `ContextFork` 分支存储、`parent_id`/`fork_point`、分支索引 | +| ③主动参与 | 2-3 | 4-6 | df-ai, src-tauri | `ScheduledMessage` 队列、冷却期管理器、模式识别 | +| ④衰减 | 3-4 | 5-7 | df-ai, df-ai-core | `precision` 字段、衰减函数、刷新策略 | +| ⑤元控制器 | 2-3 | 3-5 | df-ai, src-tauri | `MetaContextController` 结构体、特征窗口、参数调节器 | +| ⑥工作流 | 3-5 | 5-8 | df-ai, df-workflow, src-tauri | 工作流 DAG 序列化、节点↔消息关联、`workflow_id` | +| ⑦命名空间 | 2-3 | 4-6 | df-ai, src-tauri, df-storage | `NamespaceStore`、引用路径编码、`extract_key_info` 接线 | + +**子系统热力图**: + +``` + ①画布 ②分支 ③主动 ④衰减 ⑤元控 ⑥工作流 ⑦命名空间 +df-ai-core · · · ███ · · · +df-ai ███ ███ ██ ██ ██ ███ ███ +df-workflow · · · · · ███ · +df-storage ██ ██ · · · · ██ +src-tauri ███ ██ ███ · ███ ███ ██ +前端 ██ · ██ · · ██ · + +███=大量改动 ██=中等 █=少量 ·=无 +``` + +**结论**: +- ⑤(元控)的影响面最小却收益面广——这是高性价比的信号 +- ⑥+⑦ 共同覆盖 df-ai + src-tauri 重叠区——可以合并实施,共享改造成本 +- ① 涉及前端 + df-storage + df-ai + src-tauri——全栈改动,风险最大 + +--- + +### 8.6 方法 E:依赖图 & 拓扑排序 + +对派系 A 的实施步进行前置依赖分析: + +```mermaid +graph LR + S0[步0: 动态压缩阈值] --> S1[步1: L3结构化摘要] + S1 --> S2[步2: 工具命名空间] + S2 --> S3[步3: 工作流DAG注入] + S3 --> S4[步4: WorkingContext分层注入] + S4 --> S5[步5: MemoryAdapter trait] + S5 --> S6[步6: 版本化快照] +``` + +**拓扑排序分析**: + +| 步 | 前置依赖 | 可并行? | 关键路径? | +|----|---------|---------|-----------| +| 步 0 | 无 | ✅ 可独立上线 | 否 | +| 步 1 | 步 0(需要压缩触发确定) | ❌ 依赖 | 否(压缩已存在,只改输出格式) | +| 步 2 | 无 | ✅ 可与任何步并行 | **是**(收益最高) | +| 步 3 | 步 2(命名空间为 DAG 节点提供数据源) | ⚠️ 弱依赖 | **是**(差异化核心) | +| 步 4 | 步 1(需要结构化摘要格式经验) | ⚠️ 推荐步 1 后 | 否 | +| 步 5 | 步 0-4(重构现有逻辑) | ❌ 必须最后 | 否 | +| 步 6 | 步 2(命名空间需要持久化) | ⚠️ 弱依赖 | 否 | + +**关键路径**:步 2 → 步 3 → 步 5,这是派系 A 的核心价值链。步 0 和步 1 可以在任何时间插入。 + +**并行窗口**:步 2 与步 1 完全并行(互不依赖),可分配给两个独立 AI agent 同时编码。步 3 和步 4 可以部分重叠(共享 WorkingContext 的数据结构定义,但注入逻辑不同)。 + +--- + +### 8.7 方法 F:风险矩阵 + +对 7 个发散方向评估失败概率(1-5)和失败影响(1-5),风险值 = 概率 × 影响: + +| 发散 | 失败模式 | 概率 | 影响 | 风险值 | 缓解措施 | +|------|---------|------|------|--------|---------| +| ①画布 | 模型不主动使用 `look_at`,降级回传统模式 | 4 | 3 | **12** 🔴 | 需 prompt 工程 + 用户教育,不可强制 | +| ②分支 | 分支数量失控,内存爆炸 | 3 | 4 | **12** 🔴 | 设分支上限(如 5 个),超限自动合并 | +| ③主动参与 | 用户觉得烦,关掉整个功能 | 4 | 3 | **12** 🔴 | 冷却期+负面反馈自愈(依赖⑤) | +| ④衰减 | 衰减函数不匹配真实 tokenizer,精度信号无意义 | 3 | 3 | **9** 🟡 | 离线对比验证衰减曲线 vs 真实 token 分布 | +| ⑤元控制器 | 参数震荡——元控自己的参数也需要调 | 3 | 2 | **6** 🟢 | 固定多数参数,只自适应 3 个核心参数 | +| ⑥工作流 | 自由对话无工作流关联,收益为零 | 2 | 3 | **6** 🟢 | 检测 `workflow_id`,没有则跳过 DAG 注入 | +| ⑦命名空间 | 模型不理解引用语法,从不读取 namespace | 2 | 2 | **4** 🟢 | 透明降级:模型读引用时自动补全内容 | + +**风险热力图**: + +``` +影响 + 5│ · + 4│ ② ① + 3│ ③ · ④ + 2│ ⑦ ⑤ ⑥ + 1│ · + └────────────────────────▶ 概率 + 1 2 3 4 5 + +🟢 低风险(⑤⑥⑦)🟡 中风险(④)🔴 高风险(①②③) +``` + +**重要发现**: +- 派系 A 的 ⑥+⑦+⑤ 全部落在 🟢 低风险区域 +- 派系 B 的 ② 落在 🔴 高风险区域(分支爆炸) +- 派系 C 的 ①③ 都落在 🔴 高风险区域(模型不配合 + 用户反感) +- 风险分布与决策矩阵的得分分布高度一致——交叉验证通过 ✅ + +--- + +### 8.8 方法 G:根本原因回溯 + +从 DevFlow 中已确认的上下文管理痛点出发,看每个发散方向是否直接对症: + +| 痛点 | 严重度 | 根因 | 对症的发散 | 不对症的发散 | +|------|--------|------|-----------|-------------| +| **P1: tool_result 膨胀撑爆窗口** | 🔴 P0 | `read_file` 等工具返回大量文本全部挤入主队列 | **⑦**命名空间(直接消除)| ①画布 ②分支 ③主动 ④衰减 ⑤元控 ⑥工作流 | +| **P2: 压缩后目标丢失** | 🔴 P0 | `compressed` 消息不进 LLM,目标信息消失 | **⑥**工作流DAG保留结构化产出 | ①画布 ②分支 ③主动 ④衰减 ⑤元控 ⑦命名空间 | +| **P3: 中断恢复模糊** | 🟡 P1 | `build_for_request` 只恢复窗口内消息,不恢复会话状态 | **⑥+⑦**(DAG+namespace 双重恢复) | ①画布 ②分支 ③主动 ④衰减 | +| **P4: 触发时机不当** | 🟡 P1 | 压缩阈值硬编码,不考 system prompt 大小 | **⑤**元控制器(动态调参)| ①画布 ②分支 ③主动 ④衰减 ⑥ ⑦ | +| **P5: 多方案对比困难** | 🟢 P2 | 线性历史无法表达多方案岔路,需切换分支 | **②**分支(直接解决) | ①画布 ③主动 ④衰减 ⑤元控 ⑥ ⑦ | + +**覆盖度统计**: + +| 发散方向 | 覆盖痛点数 | 覆盖的痛点 | 错过的痛点 | +|---------|-----------|-----------|-----------| +| ⑥工作流 | 2(P2, P3) | 目标丢失、中断恢复 | P1 tool膨胀、P4触发时机、P5多方案 | +| ⑦命名空间 | 2(P1, P3) | tool膨胀、中断恢复 | P2目标丢失、P4触发时机、P5多方案 | +| ⑤元控制器 | 1(P4) | 触发时机 | 其他 4 个 | +| ②分支 | 1(P5) | 多方案对比 | 其他 4 个 | +| ①画布 | 0 | — | 全部 5 个 | +| ③主动参与 | 0 | — | 全部 5 个 | +| ④时间轴衰减 | 0 | — | 全部 5 个 | + +**结论**:⑥+⑦ 覆盖了 P0 和 P1 级的 3 个痛点(P1, P2, P3),合计覆盖 3/5 = 60% 的已知痛点。加上⑤覆盖 P4 后达到 4/5 = 80%。②覆盖 P5 后达到 100%。路径清晰:⑥+⑦ → +⑤ → +②。 + +--- + +### 8.9 方法 H:帕累托分析 + +对派系 A 的各实施步,计算累计收益占总收益的百分比: + +| 实施步 | 累计改动单元 | 月节省 | 累计节省 | 累计节省占比 | 累计改动单元占比 | +|--------|---------|--------|---------|------------|------------| +| 步 0:动态阈值 | 0.5 | $9 | $9 | 6.7% | 2.3% | +| 步 1:结构化摘要 | 2.5 | $18 | $27 | 20.0% | 11.6% | +| **步 2:工具命名空间** | 7.5 | $72 | $99 | **73.3%** 🔥 | 34.9% | +| 步 3:工作流 DAG | 15.5 | $27 | $126 | 93.3% | 72.1% | +| 步 4:WorkingContext | 21.5 | $9 | $135 | 100% | 100% | + +``` +累计节省占比 +100%│ ● + 75%│ ● ← 步2: 20%努力→73%收益 + 50%│ + 25│ ● + 0%│──●────●───────────────────────────▶ 累计改动单元占比 + 0% 2.3% 11.6% 34.9% 72.1% +``` + +**帕累托结论**:步 0-2(前 35% 努力)贡献 73% 收益。步 2(工具命名空间)是唯一的"低挂果实"——5 改动单元工作量,月节省 $72,占全部收益的 53%。**建议立即启动步 2,步 3-4 视资源情况决策。** + +--- + +### 8.10 方法 I:Kano 模型 + +从用户感知角度将 7 个发散方向分类: + +| 发散方向 | Kano 分类 | 判断依据 | +|---------|----------|---------| +| ⑤元控制器 | **基本型** | 用户不直接感知,但参数不当会导致对话中断(负面体验) | +| ⑦工具命名空间 | **基本型 → 性能型** | 当前 tool 膨胀可接受但痛苦(Sprint 6 P1),改善后用户不说但感觉流畅 | +| ⑥工作流即上下文 | **性能型** | 用户使用工作流时体验飞跃,自由对话时完全无感 | +| ②上下文分支 | **性能型** | 多方案探索用户明显受益,单线用户不关心 | +| ④时间轴衰减 | **无差异型** | 精度衰减在对话中无法被用户察觉(除非看日志) | +| ①画布导航 | **魅力型** | 高级用户会惊叹"AI 自己知道要看哪里",但普通用户不敏感 | +| ③主动参与 | **魅力型 → 反向型** | 做得好是惊喜,做不好是骚扰。双刃剑 | + +**Kano 对实施顺序的指导**: + +```mermaid +graph LR + subgraph "先做(基本型→性能型)" + A["⑤元控制器
(基本型,不做会出问题)"] + B["⑦工具命名空间
(性能型,做了用户觉得流畅)"] + end + subgraph "再做(性能型)" + C["⑥工作流即上下文
(差异化竞争力)"] + D["②分支
(特定场景加分)"] + end + subgraph "可选" + E["①画布
(魅力型,成本高)"] + F["③主动参与
(双刃剑,需谨慎)"] + G["④衰减
(无差异,不做)"] + end + + A --> B + B --> C + C --> D +``` + +**结论**:Kano 模型给出的优先级顺序与决策矩阵、ROI、帕累托的分析高度一致——⑤→⑦→⑥→②→①③可选→④跳过。这是第 9 种独立方法得出同一结论,信心再度增强。 + +--- + +## 九、论证收敛总表 + +### 9.1 各方法结论汇合 + +| 方法 | 收敛结论 | 与主线是否一致 | +|------|---------|--------------| +| 推演法(第 4-6 章) | 派系 A > B > C,⑥+⑦ 为最优起点 | —(主线基准) | +| **A 决策矩阵** | 派系 A 108 > B 61 > C 44 | ✅ 一致 | +| **B 成本收益散点** | ⑥+⑦ 在低成本高收益象限 | ✅ 一致 | +| **C ROI 测算** | 步 2 月节省 $72,5 改动单元回收 | ✅ 一致 | +| **D 影响范围** | ⑥+⑦ 影响面可控,集中在 df-ai | ✅ 一致 | +| **E 拓扑排序** | 步 2 和步 1 可并行,关键路径清晰 | ✅ 一致 | +| **F 风险矩阵** | ⑥+⑦+⑤ 全 🟢,①②③ 全 🔴 | ✅ 一致 | +| **G 根本原因回溯** | ⑥+⑦ 覆盖 3/5 痛点,② 补 1 个 | ✅ 一致 | +| **H 帕累托分析** | 35% 努力得 73% 收益,步 2 为低挂果实 | ✅ 一致 | +| **I Kano 模型** | ⑤基本→⑦性能→⑥性能→②性能 | ✅ 一致 | + +**9 种独立方法结论全部收敛,无分歧。** 这是强信号。 + +### 9.2 最终建议 + +``` +立即启动(步 0 + 步 2): + 动态压缩阈值(3 行) + 工具命名空间(5 改动单元) + → 消除 tool 膨胀主因,月省 $81 + +Phase 4 追加(步 1 + 步 3): + L3 结构化摘要(2 改动单元) + 工作流 DAG 注入(8 改动单元) + → 差异化竞争力,累计月省 $135 + +Phase 5 完善(步 4 + 步 5 + 步 6): + WorkingContext 分层注入 + MemoryAdapter trait + 版本化快照 + → 架构完善,累计月省 $135+ + +不做: + 画布导航(①)、时间轴衰减(④)—— 收益成本比低 + 主动参与(③)、分支(②)—— Kano 双刃剑,待用户需求信号出现后再评估 + +预留接口: + ContextNavigation / ContextFork / ProactiveHint / PrecisionDecay + → 4 个 trait,仅定义不实现(实现零行) +``` diff --git a/docs/INDEX.md b/docs/INDEX.md index 0f9b64c..e0db8e4 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -60,6 +60,8 @@ docs/ │ ├── secret下沉与provider注入方案-2026-06-16.md # secret 纯密钥下沉 df-storage 方案 │ ├── 推进链阶段2实施路径-2026-06-16.md # advance_task 走 df-nodes 实施路径 │ ├── 意图识别层论证-2026-06-19.md # 通用前置意图识别层 8 维度论证 +│ ├── 上下文管理演进与发散思考-2026-07-20.md # 上下文管理现状评估、业界对标、7个发散方向与组合分析 +│ ├── 上下文管理演进-任务技术设计-2026-07-20.md # T2命名空间/T4工作流DAG/T3结构化摘要技术设计与推演 │ ├── 多主题上下文管理愿景-2026-06-19.md # 多主题多摘要愿景(远期方向,关联 F-15/意图识别) │ └── 查询效率优化方案-2026-06-19.md # 查询链路优化(SQL 下推/精确拉取/缓存/字段投影) ├── 03-模块文档/