diff --git a/docs/todo.md b/docs/todo.md index da3bbe4..b4be5b1 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -703,9 +703,9 @@ 939. [ ] **上下文管理瓶颈清单(2026-10-10 五维只读走查,源码取证)** — 按收益排序,⚠️ `crates/df-ai/**` 被并行写者占用(工具结果外置 WIP)→ 带 ★ 的可先在 src-tauri 侧落,其余待解禁: - **HIGH ①** `history_tokens` 与真实请求体量**脱钩**:工具结果经 namespace 引用化入内存(`audit/mod.rs:1183-1190` + `namespace_store.rs:40-55` ALWAYS_LARGE_TOOLS 命中即引用,与体积无关),估算器只数字符(`context_helpers.rs:62-105`)→ 单条上万 token 的大结果只记 ~15 token;**预算/裁剪门(`context/mod.rs:185-205`)/压缩门(`context_lifecycle.rs:103-108`)三重门槛形同虚设**,每轮实际重发展开后的全量历史(223:1 结构浪费根因)。修:push/外置时按原文体量入账,或估算器识别 ref 取真实大小。 - **HIGH ②** 每轮 O(n) 全量深克隆 + sanitize + namespace 展开 + 摘要 + 估算(`context/mod.rs:198-205` / `agentic/mod.rs:1904-1921`,`1945-1957`,`1981-1984`)→ 会话内 O(n²) 累积(全量重发本体);治本=设计档 P2 分层组装。 - - **★HIGH ③** 预算上限**恒 128K**不随模型窗口(`commands/ai/mod.rs:1239` `ContextManager::new(ContextConfig::default())`;`context_helpers.rs:131/140-142`;子代理同 `sub_agent.rs:117-127`)→ 1M 窗口模型被当 128K 裁(能力降级),小窗口模型预算高于实际窗口。修:`ContextConfig` 字段 pub,可在 src-tauri 侧按 resolved_model 窗口构造(无需改 df-ai)。 + - **★HIGH ③ ✅ 已修(两批)**:主会话=model_context.rs 按 resolved_model 窗口构造(b37279f,未知窗口保守回退 128K 零回归);子代理=sub_agent_context_config 继承父会话预算(10-11,同模型同窗口单一事实来源,恒 128K 缺口收口)。 - **HIGH ④(压缩)** 压缩**单向不可逆**:被压原文对 LLM 永久不可见(`context/mod.rs:664-699` 标 Compressed + `sanitize.rs:42` is_active 过滤出局),设计 C-r1「压缩不丢/可召回」未落地 → 方向:复用 P1 句柄模式,压缩写 pointer 可回读。 - - MED ⑤ `chars_ratio=0.35` 中文低估 2-3 倍、base64 高估(`context_helpers.rs:29-44/73-74`);⑥ 保护区固定 6 条 + 补偿仅 300 字(`:648/:655`);⑦ 分层组装 P2 未落地(窗口外历史无检索召回);⑧ **★** system 可变段(日期+项目/任务清单)内嵌稳定段致**前缀缓存失效**(`prompt.rs:611-635` 缓存拆分开关默认关;清单描述不截断 `:565/:584`);⑨ 滑动裁剪致前缀缓存整体失配(`context/mod.rs:239-246`);⑩ 一次压尽(windows 式分批未做);⑪ **后台压缩无 epoch/内容版本守卫**——`clear()` 保留 per_conv 条目且复位 is_compressing → 窗口内旧摘要可注入已清空的新上下文(`context_lifecycle.rs:206-323`,loop 侧有 F1 epoch,此 task 侧无);⑫ 后台压缩**无 panic 兜底** → is_compressing 永真致压缩永久停摆。 + - MED ⑤ `chars_ratio=0.35` 中文低估 2-3 倍、base64 高估(`context_helpers.rs:29-44/73-74`);⑥ 保护区固定 6 条 + 补偿仅 300 字(`:648/:655`);⑦ 分层组装 P2 未落地(窗口外历史无检索召回);⑧ **★** system 可变段(日期+项目/任务清单)内嵌稳定段致**前缀缓存失效**(`prompt.rs:611-635` 缓存拆分开关默认关;清单描述不截断 `:565/:584`);⑨ 滑动裁剪致前缀缓存整体失配(`context/mod.rs:239-246`);⑩ 一次压尽(windows 式分批未做);⑪ ✅ 已修(内容版本守卫,context_lifecycle 提交前比对快照);⑫ ✅ 已修(reset_compressing_after_panic)。 - LOW ⑬ 估值双轨(estimated_prompt 准确用于路由 vs history_tokens 失真用于预算)从不互相校准;⑭ sys_tokens 未计工具 schema。 - **已核良好**:裁剪三元组原子性正确(`context/mod.rs:612-642`);909 两处修复(锚点避让/单锁复位)现状有效,未见旧窗口复现。 diff --git a/src-tauri/src/commands/ai/agentic/sub_agent.rs b/src-tauri/src/commands/ai/agentic/sub_agent.rs index 44eb445..56344c1 100644 --- a/src-tauri/src/commands/ai/agentic/sub_agent.rs +++ b/src-tauri/src/commands/ai/agentic/sub_agent.rs @@ -113,16 +113,13 @@ pub(crate) const SUB_AGENT_WALL_TIMEOUT_SECS: u64 = 900; /// 自达统一出口。 pub(crate) const SUB_AGENT_TOOL_BATCH_HEADROOM_SECS: u64 = 65; -/// 子代理上下文窗口预算上限(token)。自动压缩触发阈值与 build_for_request 裁剪预算 -/// 的数据源,口径同 [`ContextConfig::max_tokens`]。默认 128_000 对齐 ContextConfig::default -/// ——接入压缩层不改动既有预算(零行为变更);值单点集中于此,后续按需分档只改本常量。 -const SUB_AGENT_CONTEXT_MAX_TOKENS: u32 = 128_000; - -/// 子代理会话的上下文配置(压缩/裁剪预算来源)。仅覆写 max_tokens 为子代理常量, +/// 子代理会话的上下文配置(压缩/裁剪预算来源)。max_tokens **继承父会话预算**—— +/// 子代理与父同模型同窗口(939③ 收口:原恒 128K,1M 窗口模型被子代理裁半是同款缺口), +/// 父侧已按 model_context 按窗口构造,直接取单一事实来源,子侧不再自持口径。 /// 输出预留与安全系数沿用 ContextConfig::default,与主会话同一压缩链路口径一致。 -fn sub_agent_context_config() -> ContextConfig { +fn sub_agent_context_config(parent_max_tokens: u32) -> ContextConfig { ContextConfig { - max_tokens: SUB_AGENT_CONTEXT_MAX_TOKENS, + max_tokens: parent_max_tokens, ..ContextConfig::default() } } @@ -755,10 +752,16 @@ async fn run_sub_agent_loop_inner( // 先于 conv() 调用(insert 需可变借 session,避免与 conv 借用冲突) session.deleted_convs.insert(child_conv_id.clone()); session.conv_origins.insert(child_conv_id.clone(), SUB_AGENT_ORIGIN.to_string()); + // 父预算先取(可变借用 conv 之前):子代理与父同模型,窗口预算单一事实来源在父侧 + let parent_max_tokens = session + .conv(&parent_conv_id) + .messages + .config() + .max_tokens; let conv = session.conv(&child_conv_id); - // 上下文预算单点接入:子会话 ContextManager 用子代理配置(默认值与父会话一致), + // 上下文预算单点接入:子会话 ContextManager 继承父预算(939③ 子代理半边), // 供压缩触发阈值与 build_for_request 裁剪读取;新条目消息为空,直接替换无损。 - conv.messages = ContextManager::new(sub_agent_context_config()); + conv.messages = ContextManager::new(sub_agent_context_config(parent_max_tokens)); conv.messages.push(ChatMessage::user(&task_prompt)); conv.stop_flag.store(false, Ordering::SeqCst); conv.iteration_used = 0;