ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agentic ReAct 3.0:多模态记忆与成本控制的工程实践

Agentic ReAct 3.0:多模态记忆与成本控制的工程实践

老规矩,先交代一下背景。我手头维护着一个内部的智能Agent框架,前后拖了大半年,最近终于把两个最头疼的短板补上了:一个是多模态记忆,一个是成本控制。新的版本我内部编号叫Agentic ReAct 3.0,底子还是2022年那套ReAct范式的思路,但加入Multimodal Memory和Cost Control之后,整个系统的行为方式和原来完全不一样了。这篇文章不是概念介绍,是我自己从零改造、踩坑、调优的真实记录。如果你也在做Agent类应用,或者正打算把一个只会"对话完就忘"的ReAct循环升级成能记住、能省钱的生产级系统,那这篇应该对你有点用。

1. 从ReAct到Agentic ReAct 3.0:这次升级到底动了哪根筋

1.1 ReAct范式为什么还值得留在2026年

先说清楚ReAct是什么。这个名字是Reasoning和Acting的合成词,核心思路就是让模型在一个循环里交替做三件事:先思考当前情况(Thought),再决定调用什么工具或执行什么动作(Action),然后观察工具返回的结果(Observation),接着再思考、再行动,直到任务完成。

我见过很多刚接触这个概念的开发者,第一反应是"这不就是个while循环吗"。确实,从代码层面看它就是循环,但这个循环的价值在于:它把"推理过程"显式地暴露出来,每一步的思考都成为下一步行动的输入。用修水管来类比的话,传统一次性问答是你直接凭经验报一个维修方案,ReAct则是先拧开水龙头看看水流情况,再决定是换垫圈还是换整根管子,换完之后再开一次水确认是否还漏。这个"观察-行动-再观察"的闭环,让模型在处理多步骤、需要工具协作的任务时,可靠性和可控性都上了一个台阶。

所以尽管这几年Agent的编排方式层出不穷,我个人还是把ReAct当作默认骨架。它不花哨,但足够稳定,出了问题也容易定位。框架的选择上,"稳定"比"新颖"重要得多。

1.2 传统ReAct在实际项目里的三个老毛病

我并不是直接就把老版本推倒重来的。促使我升级的,是它在实际生产环境中暴露出来的三个问题。

第一个问题是"失忆"。老版本的ReAct每次任务开始都是一张白纸,上一个任务里总结出来的经验、查到的资料、用户的偏好,全部随着会话结束被丢进回收站。哪怕用户反复问同一个领域的知识,模型每一次都要重新理解、重新搜索、重新推理。这也是"ReAct循环"最常见的抱怨——它是个很能干的临时工,但永远记不住自己昨天干过什么。

第二个问题是"眼睛和耳朵是摆设"。原始的ReAct是基于纯文本的。可现实业务里,用户发来的往往是一张截图、一段录音、一个表格文件。你让模型去处理这些,它要么说"我看不了图片",要么得靠人工把内容转成文本再喂进去。这个体验放在2026年已经说不过去了——主流多模态模型的能力早就具备,只是你的Agent框架根本没有设计"把多模态信息存下来、用起来"的通道。

第三个问题,也是让运维同学最头疼的,就是"烧钱"。一个复杂任务往往要循环十几轮,每一轮都要调用大模型。而且模型经常会在同一个问题上反复思考,生成一堆意义不大的中间草稿。更麻烦的是,因为没记忆,同样的信息换个问法又得重新调用一次。月底看账单的时候你才会意识到:绝大部分成本都花在了重复获取早已获取过的信息上。

1.3 3.0版本的设计目标:补系统,不是推翻重来

所以在设计Agentic ReAct 3.0的时候,我给自己定了两条原则:第一,不推翻ReAct的循环骨架,因为它没坏;第二,把"记忆"和"成本"从外围功能提升为框架的核心组件。

Multimodal Memory的定位是:让Agent在处理文本、图像、音频之后,能把这些信息按照统一的方式存下来,并在后续任务中主动检索使用。Cost Control的定位是:从单次token消耗到整个任务预算,建立三层防线,让每一分钱都花得明明白白。这两个系统不是互不相关的——记忆存得好,重复调用就少,成本自然下降;成本控制得好,又能给记忆系统留出更充裕的调用额度。接下来我分别展开讲。

2. Multimodal Memory不是"多模态+记忆"的拼接

2.1 统一向量空间:让文本、图像、音频进同一个索引

多模态记忆的第一步是解决表示问题。文本好办,用文本编码器转成向量就行;但图像、音频怎么和文本放同一个索引里比较相似度?我一开始想过维护三套独立的向量库,图像一套、文本一套、音频一套,检索的时候分别查再合并。实测下来这个方案的问题很明显:跨模态检索几乎不可用,你想知道"之前用户提过的那张产品截图"和当前这段文字描述是否相关,两套向量库之间根本没有可比性。

后来我改成了统一向量空间的做法:给多模态模型加一个向量输出头,让文本、图像、音频经过各自的编码器之后,映射到同一维度(我用的是1536维)的向量空间里。这样做的本质,是让模型在训练阶段就学会把不同模态的信息"对齐"到语义相近的位置。比如用户上传一张"红色跑鞋"的照片,和一句"用户想买红色跑鞋"的文字,它们在向量空间里的距离就会比较接近。检索的时候只查一个索引,不管是文本还是图片都能用同一套相似度计算逻辑。

这里要提醒一句:并不是所有的多模态模型都适合直接拿来生成检索向量。我试过一些模型,文本向量和图像向量虽然能输出,但分布差异很大,强行塞进同一个索引之后检索精度很惨。最后我用的方案是拿一个专门的对比学习模型来做向量化,再把它嵌入到Agent的工具层里。如果你们也想走这条路,先拿一批"文本-图像对"验证一下跨模态检索的召回率,再决定要不要上。

2.2 记忆写入:不是所有信息都值得记

确定了表示层之后,第二个问题是:什么样的信息要被写入记忆?我见过很极端的做法——把Agent每一步的思考过程、每一个工具的返回结果,全量塞进记忆库。这样做的后果是记忆库飞快膨胀,检索时返回一堆无关信息,反而把真正关键的记忆淹没了。

我采用的策略是"设卡"。只有满足以下条件之一的信息才会被写入记忆库:

  • 工具返回中包含用户明确的偏好或事实性结论(比如用户说"我只用安卓手机");
  • 模型在思考中明确标注了"这条信息对后续任务可能有价值";
  • 某个操作踩了坑、报了错,并且最终通过修正解决了问题(这类经验型记忆最值钱)。

每条记忆记录都会附带元信息,包括:时间戳、来源(哪一次会话、哪个工具)、模态类型、重要性评分(1到5),以及一个可选的过期时间。这些元信息在检索阶段会发挥非常大的作用,千万别省。

这里我犯过一个很典型的错误:一开始把重要性评分交给模型自己打,结果模型倾向于把一切东西都打高分。后来我干脆把评分规则硬编码进框架——凡涉及用户身份、支付、公司政策的关键词自动加权,一般性的闲聊内容固定给低分。宁可保守一点,也比记忆库变成垃圾场好。

2.3 记忆检索:让Agent自己决定何时翻记忆

记忆系统接入ReAct循环的方式,决定了整个框架的可用性。我见过两种实现路线。

一种叫"自动注入",也就是每一轮都把记忆库中和当前输入相关的Top-K条记忆直接塞进上下文。这个方案实现简单,但问题很明显:上下文很快被历史记忆填满,留给当前推理的空间越来越小,而且那些"看起来相关但已经过时"的记忆会干扰当前判断。这在后面踩坑环节里我还会详细说。

另一种是"主动检索",也就是把"记忆检索"本身做成一个工具。Agent在Thought阶段如果发现自己缺少某方面的背景信息,就调用一个search_memory工具,传入查询词,返回相关记忆。这个方案的好处是:记忆只在需要的时候进入上下文,成本可控,也不容易干扰当前推理。我最终选择的就是这个方案。

主动检索的核心是实现一个混合检索策略。纯靠向量相似度不够,因为多模态数据里经常有专有名词、编号、型号这类词,向量检索对精确匹配不友好。所以我在向量检索之外,叠加了一个基于关键词的BM25检索,然后把两组结果做融合排序,取最终的Top-K。融合规则其实不复杂:两边都命中的记录直接加权排前面,单边命中的按得分各取一半,再按时间戳做轻微降权,避免老记忆一直压着新记忆。

还有一个细节值得注意:检索结果必须带来源和可信度标识。一个记忆如果来自官方文档截图,可信度就比来自用户随口一句话高得多。我在返回结果里给每条记忆加了一个source_type字段,Agent在后续推理时可以参考这个字段判断该信几分。

2.4 遗忘与压缩:记忆库不能无限膨胀

记忆系统的最后一个大问题是容量管理。你不可能让记忆库无限膨胀下去——向量索引的检索延迟会越来越高,存储成本也会变成一笔糊涂账。

我给记忆库设计了两个清理机制。第一个是"主动遗忘":每条记忆都有一个重要度分数,定期把低于阈值的记忆归档到冷存储;对于设置了过期时间的临时候信息,到期直接清除。第二个是"摘要压缩":当同一个主题下的记忆条数超过5条时,触发一次压缩任务,把这几条记忆交给大模型生成一段摘要,替换掉原始多条记录。压缩会损失细节,所以我现在只在记忆条数达到阈值时做,并且压缩前的原始记录会保留在冷存储里,可以被追溯。

到这里你会发现,Multimodal Memory其实是一个独立的子系统,它有自己的写入策略、检索接口和生命周期管理。这个子系统一旦稳定运行,Agent的行为会有一个肉眼可见的变化:它不再是每次都从零开始理解问题,而是像一个有经验的老员工那样,带着之前的积累来处理新任务。

3. 成本控制的三层防线:从每一次调用到整个任务

3.1 Token级控制:把每一轮对话都精打细算

多模态记忆解决的是"重复获取信息"的问题,但即使有记忆,每一轮ReAct循环里的token消耗仍然有大量可压缩空间。Token级的成本控制,是我在整个3.0改造中最先做的、也是见效最快的一层。

第一个手段是精简系统提示词和工具描述。我们经常习惯性地把工具描述写得非常详细,生怕模型不理解。但实际上,主流模型对工具描述的理解能力比我们想象中强得多。我把每个工具的description从平均120个词压缩到40个词左右,只保留"工具名称、输入参数、何时使用、返回数据类型"这四个要素。实测下来,工具调用的准确率几乎没有下降,但每轮循环的输入token少了将近四分之一。

第二个手段是结果缓存。ReAct循环里经常出现连续几轮调用同一个工具、查询同一个资源的情况。我在工具调用层加了一个缓存封装:对幂等的、返回结果可预期的工具(比如查询型工具)做LRU缓存,缓存key是参数序列化后的哈希值。遇到同样的查询,直接返回缓存结果,完全不需要再调用一次大模型去理解工具输出。这一项在我们实际项目里省下的token非常可观,因为我们的Agent经常要反复查同一个知识库条目。

第三个手段是动态上下文截断。ReAct循环跑久了,历史observation会越来越长。我原来用的策略是"全部保留",导致上下文窗口经常报警。后来我改成动态滑动窗口:只保留最近3轮完整的Thought/Action/Observation,更早的轮次压缩成一段摘要(摘要由模型生成,存为短文本)。需要追溯完整过程时再查记忆库。这个策略要把握好度——截断太狠会让模型丢失关键推理链条,太轻则省不了钱。

3.2 任务级控制:模型路由与降级策略

Token级控制解决的是"每一轮怎么省",但真正让成本结构发生质变的,是任务级的模型路由。说白了:不是所有问题都需要动用旗舰级的最大参数模型。

我把任务按照难度分成三档。第一档是简单任务,比如格式化输出、查询返回、单轮知识问答,交给一个响应快、价格低的小参数模型就行。第二档是中等任务,需要多步骤推理或工具调用,用中等规模的模型。第三档才是复杂推理任务,比如涉及多模态记忆融合、跨领域分析、需要长程规划的,才动用旗舰模型。

路由的判断逻辑,我一开始用的是硬规则:关键词匹配、用户意图分类、上下文长度阈值。但硬规则很容易误判。后来改成了"两段式路由":先让小模型做一个极简的意图分类(只输出一个分类标签),根据分类结果再决定是否升级到高一级模型。小模型做分类的token成本极低,但准确率足够——毕竟让模型把"这事难不难"分个类,比让它直接解决这个问题简单得多。

和路由配套的是降级策略。我给每个任务设了一个预算上限,当某个任务的实际消耗快逼近上限时,Agent会自动进入"降级模式":停止调用推理开销大的工具,改用更轻量的检索方式,或者干脆生成一条"当前任务复杂度超出预算,建议简化问题"的提示。这听起来有点粗暴,但在生产环境里,这种强制性的"刹车"能避免账单爆炸。

3.3 架构级控制:预算追踪器与记忆替换上下文

架构级成本控制的核心,是给整个Agent运行时加一个全局的预算追踪器。它的作用类似于信用卡账单:每一轮循环结束,把输入token、输出token、工具调用次数、缓存命中率全部记录到一个结构化日志里,同时更新当前任务的剩余预算。

追踪器不只是记账,它还会主动干预。当发现某类工具调用频繁超预算时,追踪器会把这类工具的优先级调低,优先返回更便宜的替代工具。当整个任务的成本消耗超过预设的80%时,追踪器会触发一次"成本告警",要求Agent在当前预算内输出一个尽量完整的结论,而不是继续无休止地探索。

架构级的另一个省钱点,是用记忆替换重复上下文。这一点和我的多模态记忆系统是联动的:老版本ReAct的上下文里往往重复出现同一大段背景信息,新版本里这些背景信息只需要第一次从记忆库检索出来,之后每次需要时再检索一次,而不是一直塞在上下文窗口里。本质上,是把"上下文窗口"从Agent自身切换到外部记忆系统。这样做不仅省钱,还能让上下文窗口释放出更多空间给当下的关键信息。

最后一项是异步批处理。有些场景下,Agent要处理一批相对独立的请求(比如批量审核一批图片)。我把这些请求的推理调用合并成批次,走批量API而不是逐个实时调用。批处理虽然会牺牲一点响应速度,但单价能便宜不少,特别适合非交互式的后台任务。

4. 一个知识助手项目的完整改造记录与踩坑复盘

4.1 项目背景:从"纯文本问答"到"多模态知识助手"

为了让上面的设计有一个具体的落点,我拿团队内部的一个知识助手项目做了次完整改造。这个项目我们内部叫它"某跨平台知识助手",原来就是最原始的ReAct循环:用户问一个问题,Agent在几个内部知识库里检索、总结、回答。纯文本,无记忆,成本没有监控。用户一直在抱怨两件事:第一,我给它发一张软件报错的截图,它说看不了;第二,同样的问题,今天问完明天问,它还是一副第一次见的样子。

改造目标很明确:第一,支持用户上传截图、录音、文档摘要,并把这些信息存入多模态记忆;第二,跨会话记住用户偏好和历史疑难问题;第三,把单次问答的平均成本压到原来的50%以下。

4.2 踩坑一:跨模态检索的排序漂移

第一个踩的坑出现在记忆检索环节。我的知识助手上线多模态记忆之后,出现了一个怪现象:用户明明上传过一张截图,截图内容在向量空间里也和当前问题很接近,但检索结果总是排在很后面,Agent压根看不到。

排查下来发现,问题不在向量相似度本身,而在融合排序。我的BM25关键词检索主要针对文本,面对图像时几乎没有有效命中;而向量检索虽然命中了,却在融合时被BM25的低分拖了后腿。也就是说,一个"只有向量命中、没有关键词命中"的结果,和一个"两边都有命中"的结果相比,被严重不公平地压低了。

修复方案是在融合算法里加入模态感知权重:当查询本身包含图片或查询文本中出现"截图""照片""这张图"这类线索时,向量检索的权重由0.5提升到0.75,关键词权重相应调低。同时,图片类记忆在展示给Agent时,附上由多模态模型生成的文字描述,这样即使后续不直接传图片,Agent也能从描述里获取足够信息。

4.3 踩坑二:记忆污染——旧信息带偏了新判断

第二个坑比较隐蔽,而且我差点没发现。有一天测试同事反馈:用户之前问过"某软件新版本支持安卓吗",当时的答案是"不支持";过了一周,新版本上线了,用户又来问同样的问题。按理说知识库已经更新,Agent应该答"支持"。但我们的Agent在记忆库里检索到了上一次的旧回答,把它作为高权重证据,得出了"不支持"的错误结论。

这是典型的记忆污染:记忆系统本身没错,错在它缺少"时效性"这个维度。我在元信息里其实记录了时间戳,但检索策略没有把它用起来。

修复分两步。第一步,在检索排序中加入时间衰减因子:对于"事实型"记忆,如果发布时间距今超过7天,相关性得分乘以0.6;超过30天,乘以0.4。第二步,在记忆返回结果中标记obsolete字段——凡是被知识库新版本覆盖过的旧事实,置为不可用。这一步需要定期做一次"记忆对账":拿知识库里最新的权威信息,去批量扫描记忆库中同一主题的历史记录,发现冲突就把旧记录标为失效。这也是我目前认为最有效的防污染手段。

4.4 踩坑三:模型路由本身也在烧钱,而且烧得毫无知觉

成本控制上线后的第一版,我预期单任务成本能降一半。实际跑了一周,成本确实降了,但远没有达到预期。一查日志才发现,模型路由模块自己捅了个大篓子——我用小模型做意图分类的那一步,每天被调用了上万次,单独看单次很便宜,但架不住量大,而且很多任务是同一类意图,重复分类毫无价值。

这里的教训是:路由分类也要做缓存和去重。我在路由层加了一个意图结果缓存,同一个任务的前缀文本(比如前100个字符)哈希后作为key,如果最近30分钟内已经有同类路由结果,直接复用,不再调用分类模型。这个改动之后,路由模块自身的成本几乎可以忽略不计。

另外还有一个细节:路由判断的时间点也很关键。我原来在每一轮循环都做一次路由判断,这本身就会增加额外的token消耗。后来改成"首轮路由、后续保持"的策略——第一轮决定用哪个档次的模型,后续轮次除非任务难度显著变化,否则不再改变模型档次。这样既避免了频繁切换带来的成本,也保证了推理风格的连贯性。

4.5 最终配置参考与实测数据

改造完成后的配置,我整理了一份简化版本。你们可以根据自己的情况调整参数。

agent: name: knowledge-assistant framework: react version: 3.0 memory: enabled: true embedding_dim: 1536 vector_store: local-hnsw hybrid_search: vector_weight: 0.75 keyword_weight: 0.25 top_k: 5 write_policy: min_importance_score: 3 expire_after_days: 90 compress_after_count: 5 retrieval: tool_name: search_memory time_decay_days: 7 decay_factor: 0.6 cost_control: level: token: cache_enabled: true cache_size: 512 description_compress: true task: router_small_model: fast-classifier-v2 router_medium_model: general-v4 router_flagship_model: flagship-v1 intent_cache_ttl: 30m budget: max_cost_per_task: 0.5 warn_at_percent: 80 fallback_mode: soft-stop

最终实测一组数据,供参考。改造之前,我们处理一个典型的"用户截图+文字描述"的知识问答任务,平均需要12轮ReAct循环,消耗约18万token,成本大约0.8美元。改造完成后,同样场景平均只需6轮,token消耗降到约7万,成本降到0.2美元。成本降了75%,而回答准确率还提升了几个百分点——因为Agent不再重复踩同样的坑,记忆让它越用越聪明。

代价也很明确:框架复杂度上来了,记忆库需要定期维护,遗忘和压缩任务本身会吃掉一些后台资源。但权衡下来,这点代价换来的是跨会话的经验积累和可控的预算,值。

最后再分享一个小技巧:如果你也想给现有Agent加记忆系统,别一上来就搞多模态,先跑通纯文本的记忆写入、检索、遗忘闭环,让团队看到"同样的老问题不用再问第二遍"的效果,再逐步把图像和音频接进来。循序渐进的节奏,无论是从技术风险还是从团队接受度来看,都顺畅得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进