codeAgent 系列第 3 篇,每天一个真实技术点,全部附源码。
仓库:https://github.com/Harvil1/codeAgent
为什么分级?因为压缩本身也花钱
一动手就上"LLM 全量摘要"是新手最常见的错:摘要要调一次模型(花钱)、有信息损失(丢细节)、还慢。正确思路跟收拾房间一样——能揉废纸的别动吸尘器:轻度污染格式裁剪就够,堆成山才请钟点工。
七层流水线(源码)
总调度在 agent/context_pipeline.py,docstring 把流水线写得明明白白:
async def compress_if_needed(
messages: list,
*,
llm_client,
model: Optional[str],
config: dict,
session_state: CompressionSessionState,
...
) -> Tuple[list, bool, bool]:
"""分层压缩总调度(编排器)。返回 (新消息, 是否有改动, 是否发生了 LLM 摘要级压缩)。
流水线顺序(从便宜到贵):时间清理 → L1 裁中间 → L2 单条折叠 →
L2.5 按段聚合 → L2.6 总量预算 → L3.5 整段折叠 → L4 LLM 摘要。
每层各自判断要不要出手,最后统一过一遍工具调用配对修复。
「有改动」vs「LLM 级压缩」的区分:changed 表示任何
一层动过消息(包括无损层)——调用方只需把消息同步回对话历史;compacted
特指 L4 的有损摘要——要做重建 prompt / [COMPACT_BOUNDARY] /
<post_compress_brief> 那一整套后续动作。
"""
两个设计点值得说:
返回值把「动了消息」和「有损压缩」分开。前六层基本无损(裁格式、折叠长文本),只有 L4 是真摘要——后者要触发一整套重活(边界标记、提示重建),前者只需要把消息同步回去。调用方拿两个布尔值就知道该走哪条路。
每层自己判断要不要出手。没有 if-else 大楼梯,每层是个独立插件,加一层(比如以后加向量去重)不动别的层。
L4 深度摘要的四套保命机制
最贵的 L4 也最容易出事,agent/context_compressor.py 给它配了四套保险:
L4 压缩的核心动作,带四套保命机制:
- **9 段式结构化 prompt**:强制逐字保留文件路径/错误消息/用户原话
- **PTL 重试**:摘要请求自己报 prompt_too_long 时,丢掉一部分旧消息再试
(最多 MAX_PTL_RETRIES 次)
- **熔断器**:连续 MAX_CONSECUTIVE_FAILURES 次失败后不再调 LLM,直接走规则总结
- **session_memory 替代**:有预提取的会话记忆就直接用,一次 LLM 都不调
翻译一下:摘要 prompt 分九个段(目标/决策/文件路径/报错/用户原话……),强制逐字保留关键信息;摘要请求自己超长就丢旧消息重试;连续失败就熔断降级成"不花钱的规则总结";已经有预提取记忆就一次 LLM 都不调。
彩蛋:摘要请求也蹭前缀缓存
最妙的一处(接上篇前缀缓存的话题):
fork_prefix_messages:完整对话(含 system)。有值且没配 summary_model 时,
摘要请求 = 完整对话前缀 + 追加一句摘要指令(tools 与主调用相同)——
请求开头和主对话一模一样,能命中服务商的前缀缓存,省一次全量缓存写入;
摘要请求的开头和主对话逐字节一致——等于摘要这次调用蹭了主对话的复读折扣,缓存写入费都省了。
小结
- 压缩分级 = 按污染程度选最便宜的手段,七层流水线各管一档
- 「动了消息」和「有损压缩」是两个返回值,后续动作完全不同
- L4 四套保命:9 段式 prompt、PTL 重试、熔断、预提取替代——最贵的一层反而要最抗造
下一篇:每次请求都在为工具说明书付费——工具套餐化怎么省(附 toolsets.py 源码)。
仓库在这,注释全中文,欢迎 Star ⭐:https://github.com/Harvil1/codeAgent