上周有个朋友跑过来跟我吐槽,说他搭的ClaudeAgent聊到第40轮的时候,把用户早上刚说过的一句话忘得干干净净。用户问“我之前是不是明确说过不用邮件通知”,Agent特别诚恳地回答“没有哦”。这个场景我太熟了——所有做LLM Agent的人,迟早都会撞上同一堵墙:上下文窗口是有限的,但对话是无限的。这篇就聊我在从0到1构建ClaudeAgent时,怎么搞定“内存管理”里最核心的一环:上下文压缩。
当时我刚开始做Agent内存管理的时候,搜了一圈资料,发现大家都在说“要压缩上下文”,但真正讲清楚“什么时候压、压掉什么、压完怎么接回去”的文章少之又少。这篇我尽量把整个决策过程和实现细节摊开讲,适合正在用Claude API接Agent、被上下文窗口和token成本折磨的开发者,也适合想从原理上理解LLM“记忆”机制的人。
1. 构建Agent之前,先想清楚“记忆”到底是怎么运作的
1.1 大模型本身没有记忆,上下文窗口就是唯一的内存
很多第一次做Agent的人都会有个错觉:既然Claude能记住我之前说的话,那它是不是天生就有“记忆功能”?不是的。大模型本质上是个无状态函数,你每次调用API,它看到的只有你这次请求里塞进去的那一串文本。它之所以看起来“记得”之前的事,完全是因为你每次都在请求里附带了全部历史消息。
所以Agent的“内存”,说白了就是请求里的上下文窗口。窗口有多大,它就能记住多少;窗口塞满了,最早的对话就会被挤掉。这和传统编程里的内存管理完全是两码事。写C的时候你关心malloc和free,写Java你关心GC和引用泄漏,但搞LLM Agent,你真正要管的是另一件事:哪些话值得继续留在上下文里,哪些话该被“换一种更省地方的方式”存着。
我一开始也犯过傻,想着“上下文窗口不是有200K吗,随便聊呗”。结果做了两轮真实场景测试就被打脸了。
1.2 不压缩的代价,比你想的严重得多
先说最直接的成本账。假设一个Agent平均每轮要带3000 token的上下文去做工具调用和思考,聊天聊到50轮,单次请求就接近15万token。按照官方API价格粗算,一次请求的成本就已经高得让人肉疼,如果Agent是多用户并发跑,后台账单基本就是在燃烧。注意,这个费用是“每次请求都要付”的,不是一次性。
再说质量。上下文越长,模型对早期信息的注意力就越容易被稀释。我做过实验:给一个Agent塞了2万token的历史记录之后,它在第一轮就收到的系统指令,到后面偶尔会执行走样,因为它“看不过来”了。这就像你开一个超长的会议纪要,重点被淹没了,真正关键的决策反而没人记得。
还有个经常被忽略的问题是延迟。token越多,API响应越慢,用户等的时间越长。如果你做的是交互型Agent,多等两三秒的感觉完全是两回事。
最后就是最要命的:一旦历史超出窗口上限,API直接报错,对话当场断掉。所以上下文压缩不是一个“优化项”,而是一个Agent能长期稳定跑下去的必要前提。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 上下文压缩的方案选型:别一上来就写“总结代码”
2.1 三种主流方案:截断、摘要、结构化提取
真的要动手做压缩的时候,你会发现市面上能走的路大概有三条。
第一条是滑动窗口截断,超过窗口就删最早的对话,简单粗暴,实现成本最低,但信息丢失完全不可控。用户最早说过的关键偏好可能就这么没了。
第二条是用LLM做摘要压缩,把旧对话丢给模型,让它总结成一段浓缩文本拼回去。这个方案信息密度高,能保留大致脉络,缺点是细节容易丢,而且模型还可能在摘要里“脑补”出原文没有的内容。
第三条是结构化提取,也就是从对话里把用户偏好、任务状态、约束条件这类高价值信息抽出来,存成JSON字段。这东西机器可读、定位精准,但需要你提前设计好数据格式,而且只靠它的话,对话的连贯性会差一些。
说实话,我一开始在一个内部项目里只用截断方案,毕竟五分钟就能实现。跑了一周,用户反馈最多的问题就是“Agent怎么连这个都忘了”。后来换成纯摘要方案,好了一些,但模型偶尔会在摘要里梦游,把A客户的需求总结成B客户的。真正让我稳定下来的是混合方案。
| 方案 | 实现成本 | 信息保留能力 | 风险点 | 适用场景 |
|---|---|---|---|---|
| 滑动窗口截断 | 极低 | 差,不可控 | 关键信息丢失 | 低要求短会话 |
| LLM摘要压缩 | 中等 | 中高,有幻觉风险 | 细节丢失、脑补 | 长对话、通用场景 |
| 结构化提取 | 较高 | 高,按需保留 | 需要设计schema | 强约束、多偏好场景 |
