
每天都会碰到做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_activefalse。这里有一个很关键的细节记忆写入和任务执行必须异步解耦。如果同步写每次对话的时延会多出一个检索和写入的往返异步写则完全不影响主链路还能做批量合并。5. 实测对比、常见问题与避坑经验5.1 我实测下来的效果这套方案上线后在内部工具类Agent上跑过两周我挑了三个指标看效果。第一是上下文体积。之前我们的Agent跑一个10步的工具调用链上下文大概要膨胀到28000 token左右。改成压缩方案之后同样一个任务链上下文稳定在9000 token上下降幅超过65%。第二是成本。API账单上单任务平均token消耗从原来的12000左右降到4500上下费用省了大约六成。最明显的是多轮会话场景之前每个会话结尾上下文已经塞满了历史现在按快照压缩之后每轮请求的输入token变得非常平稳。第三是工具调用成功率。这一点特别讽刺——原来以为把历史消息全都留下模型会更聪明实际上压缩之后工具调用的成功率反而提升了。原因也很简单上下文里的噪声少了注意力能集中在真正关键的参数上模型出错频率自然下降。5.2 最容易踩的坑和排查思路这个方案我在推行过程中踩过的坑比想象中多挑几个最典型的说。坑一向量检索的结果不总是有用的。向量相似度高的记忆条目未必是当前任务需要的。比如用户以前提过我喜欢简洁回复当前任务却是给出详细技术方案这俩在语义上距离可能不近但恰恰是当前应该放入的记忆。解决思路是不要只做embedding相似度检索要叠加一个领域标签过滤然后再排序。坑二摘要的生成时机不对。如果每个会话都实时生成摘要往往白做——因为用户聊两句就不聊了。我现在的做法是延迟摘要会话结束5分钟之后如果用户没有继续发言才把这次会话的摘要写入快照表。热门持续会话可以不断延期保持摘要始终反映最新状态。坑三工具结果的结构化摘要写不好。压缩本身不是问题问题是压缩得不到位。一开始我让人工写了字段白名单效果时好时坏。后来换成了小模型做裁剪摘要虽然多了一次模型调用成本很低的小模型但压缩质量稳定了很多。这属于实践后的经验修正宁可用便宜模型多跑一步也别为了省一次调用把上下文搞臃肿。坑四缓存失效处理不当。用户偏好这类长期记忆可以缓存很久但用户状态类数据比如购物车内容、当前审批环节必须实时取不能缓存。我建议在记忆表里增加一个memory_type来控制这一行为或者更简单一点缓存的时候写明确的TTL。最后分享一个我最近正在验证的方向让记忆本身具备时效性权重。不光是记住用户说了什么还要记住这句话是在什么场景下说的、已经多久了。同一个偏好如果用户在去年秋天提过一次今年没有再提它在长期记忆里的权重应当逐步下降最后自动淡出。这种做法能让记忆库一直保持自清洁不会随着时间越积越乱——这也是我认为长期记忆系统下一步最值得做的优化。如果你也在折腾类似的问题可以从这个方向试试看。