diff --git a/crates/df-ai/src/context_helpers.rs b/crates/df-ai/src/context_helpers.rs index 278066f..f54afcf 100644 --- a/crates/df-ai/src/context_helpers.rs +++ b/crates/df-ai/src/context_helpers.rs @@ -660,8 +660,10 @@ pub const PROTECT_COUNT: usize = 6; /// /// build_for_request 超预算裁剪时,把被淘汰文本提炼成一短段 system 摘要插回发送视图 /// 头部(补偿无声丢弃的早期主题,对齐压缩路径的"裁剪有补偿"语义)。摘要过长会吃掉 -/// 本次裁剪腾出的预算,故硬截断到此上限(≈百 token 量级,相对淘汰收益可忽略)。 -pub const EVICTION_SUMMARY_MAX_CHARS: usize = 300; +/// 本次裁剪腾出的预算,故硬截断到此上限。原 300 字(≈百 token)对几十轮历史的 +/// 摘要形同虚设,放大到 1000(≈300 token)——相对被裁掉的数千~数万 token 仍是零头, +/// 不侵蚀裁剪收益;PROTECT_COUNT 保持 6 不动(无实证症状,避免无据行为变更)。 +pub const EVICTION_SUMMARY_MAX_CHARS: usize = 1000; /// Anthropic 流式 tool_use 缺 id 时的占位 id 前缀(见 anthropic_compat.rs 170-176)。 /// 此类 id 必然无匹配 tool_result,是历史中毒的标志,sanitize 时据此剔除畸形三元组。 @@ -1068,18 +1070,18 @@ mod tests { #[test] fn eviction_summary_hard_capped_at_max_chars() { - // 12 条纯标识符消息(每个标识符 39 字符,freq 均等且无中文干扰)→ top-10 全为长词, - // 总长超 300 → 硬截断到 EVICTION_SUMMARY_MAX_CHARS。 - let msgs: Vec = (0..12) + // 40 条纯标识符消息(每个标识符 39 字符,freq 均等且无中文干扰)→ top-10 全为长词, + // 总长超上限(上限放大到 1000 后需更多数据才触发截断) → 硬截断。 + let msgs: Vec = (0..40) .map(|i| { ChatMessage::user(&format!("long_identifier_{:02}_for_truncation_probe", i)) }) .collect(); let text = build_eviction_summary_text(&msgs).expect("应产出摘要"); - assert_eq!( - text.chars().count(), - EVICTION_SUMMARY_MAX_CHARS, - "超长时应精确截断到硬上限, 实际 {}", + // 上限是防御性天花板(结构性产出 top-10 词表通常远低于它),断言「不超」而非「精确等于」 + assert!( + text.chars().count() <= EVICTION_SUMMARY_MAX_CHARS, + "摘要不得超硬上限, 实际 {}", text.chars().count() ); } diff --git a/docs/todo.md b/docs/todo.md index 65144c5..16eba21 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -706,6 +706,7 @@ - **HIGH ②** 每轮 O(n) 全量深克隆 + sanitize + namespace 展开 + 摘要 + 估算(`context/mod.rs:198-205` / `agentic/mod.rs:1904-1921`,`1945-1957`,`1981-1984`)→ 会话内 O(n²) 累积(全量重发本体);治本=设计档 P2 分层组装。 - **★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 可回读。 + - **④ 设计要点(2026-10-11 登记,待实施)**:①落盘时机=compress_old_messages 标记时(原文在手,compress_old_messages 返回值即 newly_compressed 克隆);②句柄复用 tool_offload(ctx- 前缀同目录,或独立 archive- 前缀区分语义);③召回入口=压缩摘要尾部附一行「早期历史全文已存档: (read_file 可召回)」;④开关 df-ai-compress-recall 默认开(行为变更可回退);⑤磁盘治理:存档随会话删除级联(复用 959 后的清理挂点)或按「只增不减」纪律暂不清理;⑥估算零影响(存档不在上下文);⑦与 907 邻接防线的交互:摘要+句柄行是 system 消息内容,不楔三元组(插入点已在 system 锚点避让保护内)。工作量:中(一个落盘调用+摘要尾部拼行+开关),可与小批实施。 - 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 两处修复(锚点避让/单锁复位)现状有效,未见旧窗口复现。