1. 从一份日报的选题逻辑说起:为什么Agent与LLM的交叉地带最值得盯
做技术日报这件事,我坚持了快两年,最大的体会是:选题的眼光比写稿的能力更稀缺。每天铺天盖地的论文、开源仓库、工程博客,如果只是按热度排序搬运,读者看三天就腻了。真正有价值的日报,是能从一堆看似不相关的内容里,嗅出某条技术脉络正在成形。2026年这个时间点,Agent与LLM的结合恰好处于这样一个"脉络成形"的阶段——单点技术已经足够多,但把它们串成可落地的系统,才是大多数人卡住的地方。
这份日报的定位很明确:面向已经上手过大模型API、但还没跑通完整Agent链路的开发者。如果你还在纠结怎么调通第一个对话接口,那这份内容可能偏深;但如果你已经能让模型回答问题,却不知道怎么让它自主规划、调用工具、记住上下文、在失败时重试,那这里面的每一条都值得细看。我选内容的标准有三条:一是有可复现的代码或明确的架构图,二是解决了某个具体的工程痛点而非纯理论推演,三是作者自己踩过坑并写了出来。纯营销向的"某某模型又刷新榜单"这类,我基本不选。
为什么把Agent和LLM放在一起做日报,而不是分开?因为这两者已经分不开了。2024年之前,大家讨论LLM更多是"怎么让回答更准",讨论Agent更多是"怎么让流程自动化",两条线各走各的。但到了2026年,LLM本身就是Agent的推理内核,Agent则是LLM从"聊天"走向"干活"的必经之路。你没法只谈模型不谈调度,也没法只谈调度不谈模型的输出稳定性。所以这份日报的每一期,我都会刻意把"模型侧的新能力"和"Agent侧的工程实践"配对呈现,让读者看到它们是怎么咬合的。
这一期的内容我筛了整整两天,从几十个候选里留下六条。它们覆盖了记忆管理、工具调用可靠性、多Agent协作、成本控制、评测方法、以及一个容易被忽视的"失败恢复"话题。下面逐条拆开讲,每条我都会补上自己的实操注释和避坑提醒,而不只是转述原文。
2. 记忆管理:从"塞进上下文"到"分层检索"的范式转移
2.1 为什么"长上下文"没有解决记忆问题
过去一年最容易被误解的一件事,就是"上下文窗口够大,记忆问题就解决了"。我实测下来的结论恰恰相反:窗口越大,模型对中间段信息的利用率反而越低。有个很典型的实验,把一份关键信息放在十万token上下文的中间位置,模型召回率比放在开头或结尾低了将近四成。这不是模型不行,而是注意力机制在长序列上的固有衰减。
所以真正做Agent记忆的团队,早就不玩"全塞进去"这一套了。他们做的是分层:把记忆拆成工作记忆、情景记忆、语义记忆三层,每层用不同的存储和检索策略。工作记忆就是当前对话的最近几轮,直接放上下文;情景记忆是过去发生过的具体事件,存进向量库按相似度召回;语义记忆是提炼后的稳定知识,比如"用户偏好用Python而非Java",这类用结构化存储更合适。
这个分层思路的价值在于,它把"记忆"从一个容量问题变成了一个检索质量问题。你不再需要无限大的窗口,而是需要一个足够准的召回器。我见过太多项目卡在"上下文塞满了但模型还是答非所问",根因就是没有分层,所有信息平铺在一起,模型根本分不清哪条是当前任务相关的。
2.2 一个可落地的三层记忆实现骨架
下面这个骨架是我自己在几个项目里反复用过的,简化后贴出来。核心是用三个独立的存储后端,各管各的,检索时按权重融合。
class LayeredMemory: def __init__(self, vector_store, kv_store, max_working_turns=6): self.working = [] # 最近N轮对话,直接进上下文 self.episodic = vector_store # 情景记忆,向量检索 self.semantic = kv_store # 语义记忆,键值查询 self.max_working_turns = max_working_turns def add_turn(self, role, content): self.working.append({"role": role, "content": content}) if len(self.working) > self.max_working_turns: old = self.working.pop(0) # 旧对话压缩后存入情景记忆 summary = self._summarize(old) self.episodic.add(summary) def retrieve(self, query, top_k=3): # 工作记忆全量返回 context = list(self.working) # 情景记忆按相似度召回 context += self.episodic.search(query, top_k) # 语义记忆按实体匹配 context += self.semantic.match_entities(query) return context这里有个关键细节:旧对话不是原样存进向量库,而是先压缩成摘要再存。原因很实际——原始对话里大量是寒暄和重复,直接存进去会污染检索结果。压缩这一步用一个小模型跑就行,成本很低,但召回准确率能提升一大截。
2.3 记忆检索里最容易踩的三个坑
第一个坑是时间衰减没做。情景记忆如果不加时间权重,三个月前的一次闲聊可能被召回,而昨天的关键决策反而排在后面。我的做法是给每条记忆打一个时间戳,检索时用相似度 × 时间衰减因子排序,衰减因子用指数函数,半衰期设成一周左右比较合适。
第二个坑是语义记忆的更新冲突。用户上周说"我习惯用MySQL",这周说"我们迁到PostgreSQL了",如果两条都存着,检索时可能同时召回,模型就懵了。解决办法是给语义记忆加版本号或时间戳,同一实体的新值覆盖旧值,而不是追加。
第三个坑最隐蔽:检索结果没有去重。工作记忆和情景记忆可能包含同一件事的不同表述,一起塞进上下文会让模型以为这件事发生了两次。我一般会在拼接前做一次语义去重,相似度超过阈值的只保留信息量更大的那条。
提示:记忆系统的评测不要只看"召回率",更要看"上下文利用率"。我的做法是构造一批带标准答案的多轮任务,看模型在有无记忆系统下的完成率差异,这个指标比单纯的检索指标更贴近真实效果。
3. 工具调用的可靠性:让Agent"少犯错"比"更聪明"更重要
3.1 工具调用失败的真实分布
很多人以为Agent调工具失败是因为模型"不会用",其实我统计过自己项目里的失败日志,分布是这样的:参数格式错误占四成,工具选择错误占三成,超时或异常未处理占两成,剩下才是模型理解偏差。也就是说,八成的问题根本不需要换更强的模型,只需要在工程层面加约束。
参数格式错误最常见的是类型不匹配,比如工具要求整数,模型传了字符串"5";或者必填字段漏了。这类问题用结构化输出约束就能解决大半。现在主流框架都支持JSON Schema约束,把工具的入参定义成严格的schema,模型输出时强制校验,不合格就重试。我实测下来,光这一招就能把参数错误率压到个位数。
工具选择错误则更微妙。当你有二十个工具时,模型经常选一个"看起来相关但实际不对"的。我的经验是工具描述要写得像给新人看的文档,而不是像API文档。API文档写"query_user_info(user_id)",模型可能不知道什么时候该用;但写成"当需要查询某个用户的账户状态、注册时间等基础信息时使用,输入用户ID",选择准确率明显提升。
3.2 重试策略:不是简单重跑,而是带反馈的修正
工具调用失败后直接重试,是最浪费钱的做法。因为如果失败原因是参数错了,重试十次还是错。正确的做法是把错误信息回传给模型,让它修正后再试。
def call_tool_with_retry(tool, params, max_retries=3): for attempt in range(max_retries): try: result = tool.execute(params) return result except ValidationError as e: # 把校验错误回传,让模型修正参数 params = llm_fix_params( tool.schema, params, str(e) ) except TimeoutError: # 超时类错误,退避后重试 time.sleep(2 ** attempt) raise ToolCallFailed(tool.name)这里的关键是区分错误类型:参数类错误走"回传修正",超时类错误走"退避重试",业务逻辑错误(比如余额不足)则直接返回给上层决策,不要重试。我见过有项目对所有错误一视同仁地重试三次,结果一个余额不足的错误被重试三次,白白多花了两倍的钱。
3.3 工具结果的"可信度标注"
还有一个容易被忽视的点:工具返回的结果不一定可信。比如搜索工具返回的网页内容可能过时,数据库查询可能返回空结果。如果Agent不加区分地把所有工具结果当成事实,就容易产生幻觉式的连锁错误。
我的做法是给每个工具结果附一个可信度标签,来源可靠的(如内部数据库)标高,来源开放的(如网页搜索)标低,并在提示词里告诉模型"低可信度结果需要交叉验证"。这个机制在多步推理里特别有用,能显著减少"基于错误前提继续推理"的情况。
4. 多Agent协作:什么时候该拆,什么时候纯属自找麻烦
4.1 单Agent的天花板在哪里
先说结论:大部分场景不需要多Agent。我见过太多项目一上来就设计五六个Agent互相通信,结果调试成本爆炸,效果还不如一个精心设计的单Agent。单Agent真正的天花板出现在两个地方:一是任务需要截然不同的专业视角(比如一个负责代码安全审查,一个负责性能优化,两者的关注点几乎不重叠);二是任务天然可并行且子任务之间依赖很弱。
如果只是任务步骤多,那用单Agent加工作流编排就够了,没必要拆成多个Agent。拆分的代价是通信开销、状态同步、以及最麻烦的——错误在Agent之间传播。一个Agent的幻觉输出被另一个Agent当成事实,最后错误被放大,这种案例我踩过不止一次。
4.2 一个"主从式"协作的最小实现
当确实需要多Agent时,我推荐主从式而非对等式。主从式有一个协调者Agent负责拆解任务、分发给执行者、汇总结果,执行者之间不直接通信。这样错误传播路径是可控的,调试时也能定位到具体是哪个环节出的问题。
class Orchestrator: def __init__(self, workers): self.workers = workers # {name: agent} def run(self, task): plan = self.decompose(task) # 主Agent拆解 results = {} for step in plan.steps: worker = self.workers[step.worker_name] # 只传必要上下文,不传全部历史 results[step.id] = worker.execute( step.instruction, context=step.depends_on_results(results) ) return self.synthesize(results) # 主Agent汇总注意这里有个刻意的设计:执行者只拿到自己需要的上下文,而不是全局历史。这既省token,又减少了无关信息对执行者的干扰。我试过传全局历史,结果执行者经常被其他子任务的细节带偏。
4.3 协作中的"死锁"与"空转"
多Agent系统有两个特有的故障模式。一个是死锁:A等B的输出,B等A的输出,谁都不动。这通常是因为任务拆解时依赖关系没理清。我的预防办法是在拆解阶段就做一次依赖检查,确保依赖图是有向无环的。
另一个是空转:Agent之间互相确认"我完成了""收到",但实际什么都没推进。这在对话式协作里特别常见。解决办法是给每个Agent设定明确的产出物要求,比如"必须返回一个JSON格式的结果",而不是"确认一下"。有产出物约束,空转就无处遁形。
5. 成本控制:Agent跑起来之后,钱是怎么悄悄烧掉的
5.1 成本结构拆解:不只是token
大多数人算Agent成本只算token,但实际账单里还有几块容易被忽略的:工具调用费用(比如搜索API按次收费)、向量检索费用(如果用了托管服务)、重试带来的重复消耗、以及多Agent通信的额外轮次。我做过一次完整的成本审计,发现token只占总成本的六成左右,剩下四成全是这些"隐性开销"。
更麻烦的是,这些隐性开销往往在开发阶段看不出来,因为测试量小。一上生产,量级上来,账单就失控了。所以我的建议是在开发阶段就埋好成本埋点,每次工具调用、每次检索、每次重试都记一笔,定期汇总看趋势。
5.2 三个立竿见影的降本手段
第一个是缓存。Agent的很多调用是重复的,比如同样的查询、同样的工具调用。加一层语义缓存,相似度超过阈值的直接返回缓存结果,能省下相当可观的调用量。注意要用语义缓存而非精确匹配缓存,因为用户换个说法问同一个问题太常见了。
第二个是模型分级。不是所有步骤都需要最强的模型。任务拆解、结果汇总这类需要推理的用强模型,格式转换、简单抽取这类用弱模型就够了。我一般会把Agent的步骤按"推理复杂度"分个级,然后映射到不同档位的模型上,成本能降三到五成,效果几乎无损。
第三个是上下文裁剪。前面讲记忆时提过,但这里再强调一次:每次调用只传必要上下文。我见过有项目把整个对话历史每次都全量传,token消耗随轮次线性增长,跑到几十轮时单次调用成本高得离谱。正确做法是只传最近几轮加检索到的相关记忆。
5.3 成本监控的一个实用指标
我习惯盯一个指标:单任务平均成本。不是单次调用成本,而是完成一个完整任务的总成本。这个指标能反映系统的整体效率,而不只是单点优化。如果单任务成本在涨,但任务复杂度没变,那一定是哪里在漏钱——可能是重试变多了,可能是检索召回了太多无关内容,可能是某个Agent陷入了循环。顺着这个指标往下钻,通常很快能找到问题。
6. 评测:怎么知道你的Agent是真的变好了
6.1 为什么传统评测指标不够用
用BLEU、ROUGE这类指标评Agent,基本等于没评。因为Agent的输出不是一段文本,而是一系列动作加最终结果。你真正关心的是"任务完成了吗""花了多少步""有没有走弯路",而不是"输出和参考答案的相似度"。
我现在的评测体系分三层:结果层看任务是否完成、完成质量如何;过程层看步数、工具调用次数、重试次数;成本层看token和费用。三层一起看,才能判断一次改动到底是真优化还是拆东墙补西墙。比如你优化了提示词让结果变好了,但步数翻倍,那可能得不偿失。
6.2 构造评测集的实操方法
评测集不要自己凭空编,最好的来源是真实失败案例。我有个习惯,每次线上出问题就记一笔,攒够一批就整理成评测用例。这样构造出来的评测集,覆盖的都是真实痛点,而不是想象中的痛点。
构造时要注意标注要细。不能只标"成功/失败",要标清楚失败在哪一步、失败原因是什么。这样跑评测时才能定位问题。我一般会标这几个字段:任务描述、期望结果、实际结果、失败步骤、失败类型(参数错/选择错/超时/幻觉)。有了这些,评测报告才有指导意义。
6.3 回归测试:别让优化变成倒退
Agent系统最怕的是"修好一个坏掉三个"。因为各模块耦合紧,改一处提示词可能影响另一处的行为。所以每次改动后必须跑全量回归,而不是只测改动的那个场景。我吃过这个亏:优化了工具选择逻辑,结果发现记忆检索的准确率掉了,因为两者共享了同一段提示词。
回归测试的频率,我建议是每次合并前必跑,而不是每天跑一次。因为Agent的改动往往牵一发动全身,等到第二天才发现问题,中间可能已经合并了好几个改动,定位成本翻倍。
7. 失败恢复:Agent跑挂了之后怎么办
7.1 失败恢复为什么是刚需
Agent和传统程序最大的区别是:它会在运行时遇到没预料到的情况。传统程序遇到异常抛错就行,Agent不行,因为它正在执行一个多步任务,中途挂掉意味着前面的工作全白费。所以失败恢复不是锦上添花,是刚需。
我见过最惨的案例是一个跑了四十分钟的数据处理任务,在最后一步因为一个格式问题挂掉,前面三十九分钟全废。如果做了检查点,从最近的成功步骤恢复,损失能控制在几分钟内。
7.2 检查点设计的关键决策
检查点要存什么?我的经验是存状态快照而非操作日志。操作日志回放起来慢,而且如果外部状态变了(比如数据库被改了),回放结果可能不对。状态快照直接存当前进度,恢复时从快照继续,简单可靠。
存多频繁?太频繁开销大,太稀疏恢复代价高。我的做法是在每个"不可逆操作"之前存一次。比如调用一个会写数据库的工具之前,先存检查点。这样即使写失败了,也能从检查点恢复,不会留下半写状态。
class CheckpointedAgent: def execute_step(self, step): self.save_checkpoint(step.id) # 执行前存 try: result = step.run() self.mark_done(step.id) return result except Exception as e: self.rollback_to(step.id) # 失败回滚 raise def resume(self): last_done = self.get_last_completed() return self.execute_from(last_done + 1)7.3 恢复时的状态一致性
恢复最麻烦的不是代码,是外部状态的一致性。比如Agent调了一个工具发了封邮件,然后挂了,恢复后可能又发一遍。这类问题没法靠检查点解决,得靠幂等设计——让每个操作重复执行也不产生副作用。发邮件前先查是否已发,写数据库用upsert而非insert,这些都是幂等的手段。
还有一个容易忽略的点:恢复后要重新校验前提条件。检查点存的时候某个前提成立,恢复时可能已经不成立了。比如检查点存的时候库存充足,恢复时库存没了,这时候直接继续执行就会出错。所以恢复流程里要加一步前提校验,不满足就重新规划。
8. 这一期日报给我的几点个人体会
做这份日报两年,最大的收获不是读了多少论文,而是看清了哪些是真进展,哪些是噪音。Agent和LLM这个领域,每天都有新东西,但真正能沉淀下来、半年后还在被用的,其实不多。我的判断标准很简单:看它有没有解决一个具体的、可复现的工程问题。纯概念炒作的,热闹一阵就没了;解决实际问题的,会慢慢变成基础设施。
另一个体会是,工程能力在这个领域的权重被严重低估了。大家都在追新模型、新框架,但真正决定一个Agent系统好不好用的,往往是那些不起眼的工程细节——重试策略、上下文裁剪、检查点设计。这些东西不性感,但缺了就是不行。我自己踩过的坑,八成都是工程坑而非算法坑。
最后分享一个我一直在用的小方法:每做一个Agent项目,都留一份"失败日志"。记录每次失败的现象、根因、修复方式。这份日志比任何教程都值钱,因为它是针对你自己的场景的。攒够半年,你会发现大部分问题都是重复的,有了这份日志,下次遇到类似情况,几分钟就能定位。这比到处找"最佳实践"有用得多。