☰
AI Agent上下文优化:长期记忆与长程工具调用的共存架构
2026/10/4 7:35:49 网站建设 项目流程

每天都会碰到做AI Agent的团队问我同样的问题:模型老是忘记早期对话,工具调用一长就乱,上下文越塞越多,账单也跟着飞涨。这三个问题表面上是独立的,实际上是一根藤上的三个瓜——长期记忆、长程工具调用、成本控制,本质都是在跟"上下文窗口"这个概念博弈。这篇文章把我自己在这条路上踩过的坑、验证过的方案、以及最终沉淀下来的一套可复用的架构,完整地摊开来讲。

先说结论:长期记忆和长程工具调用完全可以共存,但你不能指望把对话历史一股脑丢给模型。你要做的是让记忆"分层",让工具调用"瘦身",然后通过缓存和结构化存储把成本压下来。下面我按问题拆解、架构设计、成本模型、落地实操、踩坑实录的顺序,把这套东西讲透。

1. 问题全景:长期记忆和长程工具调用放在一起,到底哪里不对

1.1 两种能力的本质差异

长期记忆,指的是Agent在跨会话、跨任务的情况下,还能记得用户偏好、项目背景、历史决策。它的载体通常是向量数据库、键值存储或者结构化文件。

长程工具调用,指的是Agent在一个任务内连续调用多个工具、多步推理,比如查数据库、调API、改代码、再验证结果。它要求模型在短时间内保持高度一致的上下文,不能"走两步就忘了自己要干嘛"。

这两者的矛盾,集中体现在两个层面。

第一,上下文窗口是有限的。你把长期记忆塞进去,工具调用的中间结果就被挤出去了;你把工具调用过程完整保留,长期记忆里的关键信息就会被截断。

第二,记忆是"高密度但低时效"的信息,工具调用是"高时效但零散"的信息。它们对上下文的需求模式完全不同。长期记忆适合放在外面、按需检索;工具调用必须放在模型眼前、随时可见。

问题在于,很多团队把这两者简单粗暴地合并成一个"system prompt + 历史消息"的巨型上下文。结果就是:模型的能力被浪费在了无关紧要的历史细节上,真正的工具调用反而因为上下文被稀释而频繁出错。

1.2 长上下文不是免费的:内存与token的账

这就要说到成本问题。无论是调用各家大模型API,还是本地部署模型,长上下文都意味着真实的、可量化的开销。

API场景下,账单直接按token计费。输入侧的上下文越长,每一次请求的输入token就越高。一个对话进行到第50轮,历史消息可能膨胀到几万token,而其中真正有用的可能不到20%。这20%被你花了几倍甚至几十倍的钱。

本地部署场景下,长上下文的代价更隐蔽——它体现在显存占用上。Transformer模型的自注意力机制,其计算量和显存占用和序列长度的平方成正比。同样一个模型,塞进4k上下文和32k上下文,显存需求差好几倍,推理延迟也跟着涨。这就是为什么很多本地部署方案"能跑但跑不快"。

所以,记忆和工具调用共享同一份长上下文的方案,天然是违背经济性的。必须从根本上改变上下文的组织方式。

2. 让记忆与工具调用共存的架构思路

2.1 记忆分层:短期工作记忆、长期记忆与工具上下文分离

我在实践中最认可的方案,是把AI的记忆拆成三层,各自独立存储、按需注入。

第一层是短期工作记忆,对应当前任务中模型需要"随时可见"的信息。这包括用户本轮输入、最近一两轮对话、当前正在执行的工具返回结果。它的特点是体积小、更新快、必须完整放入上下文。

第二层是长期记忆,对应跨会话的核心信息。比如用户身份、项目背景、领域术语偏好、历史决策结论。它的特点是体积大、变化慢、不能全量塞进上下文。要让它起作用,必须在"需要的时候"通过检索把它拉回来。

第三层是工具上下文,这是最容易被人忽略的一层。它指工具本身的schema定义、调用约定、中间结果。工具返回的数据经常很大,但真正影响下一步决策的往往只有几个关键字段。

分层之后,每一层在上下文里的占比就变得可控:短期工作记忆始终保持最新,长期记忆只注入检索命中片段,工具上下文经过裁剪后再注入。三者的总量恒定,成本也就随之稳定。

2.2 工具调用的上下文压缩与重建

工具调用之所以特别吃上下文,是因为它天然具有"累积性"——第1个工具的输出,可能成为第2个工具的输入,然后一直链式传递下去。如果每次都把上一次的完整输出原样拼进去,上下文必然在几十步之内爆掉。

我的做法是,对工具调用的中间结果做"结构化摘要",而不是保留原始输出。

举个例子,假设第一步调了个用户列表接口,返回了100条用户记录。模型接下来要做的,是筛选出VIP用户并发送优惠通知。这时候,你不需要把那100条记录原样留着,你需要的是:100条记录中VIP用户的子集、它们的ID、以及下一步要用到的关键字段。把这部分压缩成一行JSON存起来,其余全部丢掉。

这种"重建式压缩"的关键在于,你要为每次工具调用定义明确的"下一步索引",也就是告诉模型:这次调用的结果里,有哪些字段是后续步骤要用的。这样模型才能基于索引做裁剪,而不是搞一刀切。

3. 成本控制:把每一分token花在刀刃上

3.1 token成本拆解:钱到底花在哪里了

做成本控制的第一步,是在自己的系统里把token消耗拆开看。我一般把它分成四类:

一次性注入成本:大段system prompt、角色设定、功能说明。这部分每个会话要付几次,但可以通过优化prompt长度来压缩。

记忆注入成本:长期记忆的检索结果。这部分是可变的,取决于检索命中的数量和质量。常见浪费是检索结果冗余,一次注入几千token,真正相关的就一两句。

工具调用成本:工具的schema定义加中间结果。schema不可避免,但中间结果的可压缩空间往往最大。

历史消息成本:多轮对话的完整回放。这部分是全流程里最容易被忽略、也最容易被无脑拉满的。

大部分团队的账单爆炸,都是历史消息成本和工具中间结果这两块在作祟。前者靠分层截断解决,后者靠结构化摘要压缩。

3.2 缓存、压缩与复用:三个省钱利器

先讲缓存。如果一个用户的多个会话共用同一份长期记忆摘要,那这份摘要的生成成本应该只付一次。把这个摘要缓存起来,按用户ID+缓存版本管理,能省掉大量重复生成token。

再讲压缩。除了前面提到的工具结果压缩,还有一种更激进的方案:把多轮对话中的"低信息密度"轮次合并为摘要。比如用户在第2轮讲了个需求背景,第5轮打了个招呼,第7轮确认了一个细节。你可以把5和7合并成一行"用户确认希望周五交付",把第2轮保留全文。这样历史消息的体积就能控制在极小的范围。

最后是复用。工具调用的输入输出,如果具备通用性(比如一个固定的查询语句),可以直接缓存下来,下次同类任务直接取用结果,而非重新调用工具。这不仅是省token,也是省了一次工具的真实执行成本。

4. 一套可落地的实操方案

4.1 记忆表设计与索引

下面是我正在生产环境中使用的一套简化方案,适配中小规模项目。数据库用的是PostgreSQL加pgvector,你也可以换成Redis加向量插件。

第一张表,用户记忆表,存长期记忆的条目:

CREATE TABLE user_memory ( id BIGSERIAL PRIMARY KEY, user_id VARCHAR(64) NOT NULL, memory_key VARCHAR(128) NOT NULL, content TEXT NOT NULL, embedding VECTOR(1536), -- 向量字段,按需要调整维度 memory_type VARCHAR(16) DEFAULT 'fact', -- fact | preference | decision importance FLOAT DEFAULT 0.5, -- 重要性,用于后续淘汰 updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, memory_key) );

这张表的关键在设计上:多个记忆条目之间互相独立,按user_id维度独立检索,避免把多个事实堆进一行,导致检索时上下文携带了大量无用信息。

第二张表,会话上下文快照表,存每个会话的压缩摘要:

CREATE TABLE session_snapshot ( id BIGSERIAL PRIMARY KEY, session_id VARCHAR(64) NOT NULL, user_id VARCHAR(64) NOT NULL, summary TEXT NOT NULL, -- 摘要文本,可注入上下文 token_estimate INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), is_active BOOLEAN DEFAULT TRUE );

索引策略上,向量检索走pgvector的IVFFlat索引;同时为user_id加普通B-tree索引,保证按用户维度的查询快速命中。检索时,把用户近期的重要记忆前K条取出来,再按embedding相似度排序。

4.2 工作流编排与关键步骤

我的完整工作流大概分六步:

第一步,触发阶段。用户发出请求后,系统先把本轮输入放进短期工作记忆。

第二步,记忆检索。用用户本轮输入做向量检索,从user_memory表捞出top5的记忆条目;再取出最近的session_snapshot摘要。两条路径的结果一起构成"记忆注入包"。这里要注意,对检索结果要做一个去重和优先级排序,把当前任务真正需要的放前面。

第三步,工具上下文裁剪。根据Agent当前操作的需要(是继续查询还是做验证,是写文件还是改配置),从历史工具结果里提取必要字段。这一步通常我在代码里用一个纯函数完成,把工具结果与一个"字段白名单"做比对。

第四步,组装上下文。把三类信息按"短期工作记忆 → 记忆注入包 → 裁剪后的工具上下文"的顺序组装。原则上,这个顺序保持稳定,模型不容易迷失。

第五步,模型推理。调用大模型,让它生成下一步动作。如果有工具调用需求,模型会再生成对应的函数调用指令。

第六步,异步记忆更新。每完成一轮关键交互,把值得记住的信息异步写入user_memory表;每完成一个任务,把会话过程压缩成一则summary,写入session_snapshot表,并把上一轮摘要标记为is_active=false。

这里有一个很关键的细节:记忆写入和任务执行必须异步解耦。如果同步写,每次对话的时延会多出一个检索和写入的往返;异步写则完全不影响主链路,还能做批量合并。

5. 实测对比、常见问题与避坑经验

5.1 我实测下来的效果

这套方案上线后在内部工具类Agent上跑过两周,我挑了三个指标看效果。

第一是上下文体积。之前我们的Agent跑一个10步的工具调用链,上下文大概要膨胀到28000 token左右。改成压缩方案之后,同样一个任务链,上下文稳定在9000 token上下,降幅超过65%。

第二是成本。API账单上,单任务平均token消耗从原来的12000左右降到4500上下,费用省了大约六成。最明显的是多轮会话场景,之前每个会话结尾上下文已经塞满了历史,现在按快照压缩之后,每轮请求的输入token变得非常平稳。

第三是工具调用成功率。这一点特别讽刺——原来以为把历史消息全都留下模型会更聪明,实际上压缩之后工具调用的成功率反而提升了。原因也很简单:上下文里的噪声少了,注意力能集中在真正关键的参数上,模型出错频率自然下降。

5.2 最容易踩的坑和排查思路

这个方案我在推行过程中,踩过的坑比想象中多,挑几个最典型的说。

坑一:向量检索的结果不总是有用的。向量相似度高的记忆条目,未必是当前任务需要的。比如用户以前提过"我喜欢简洁回复",当前任务却是"给出详细技术方案",这俩在语义上距离可能不近,但恰恰是当前应该放入的记忆。解决思路是:不要只做embedding相似度检索,要叠加一个"领域标签"过滤,然后再排序。

坑二:摘要的生成时机不对。如果每个会话都实时生成摘要,往往白做——因为用户聊两句就不聊了。我现在的做法是延迟摘要:会话结束5分钟之后,如果用户没有继续发言,才把这次会话的摘要写入快照表。热门持续会话可以不断延期,保持摘要始终反映最新状态。

坑三:工具结果的结构化摘要写不好。压缩本身不是问题,问题是压缩得不到位。一开始我让人工写了字段白名单,效果时好时坏。后来换成了小模型做裁剪摘要,虽然多了一次模型调用(成本很低的小模型),但压缩质量稳定了很多。这属于实践后的经验修正:宁可用便宜模型多跑一步,也别为了省一次调用把上下文搞臃肿。

坑四:缓存失效处理不当。用户偏好这类长期记忆可以缓存很久,但用户状态类数据(比如购物车内容、当前审批环节)必须实时取,不能缓存。我建议在记忆表里增加一个memory_type来控制这一行为,或者更简单一点,缓存的时候写明确的TTL。


最后分享一个我最近正在验证的方向:让记忆本身具备"时效性权重"。不光是记住"用户说了什么",还要记住"这句话是在什么场景下说的、已经多久了"。同一个偏好,如果用户在去年秋天提过一次,今年没有再提,它在长期记忆里的权重应当逐步下降,最后自动淡出。这种做法能让记忆库一直保持自清洁,不会随着时间越积越乱——这也是我认为长期记忆系统下一步最值得做的优化。如果你也在折腾类似的问题,可以从这个方向试试看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询