1. 从"能跑"到"跑得稳":Agent 工程化的分水岭在哪
很多人第一次接触 AI Agent,都是从一段几十行的 demo 开始的:给大模型挂上几个工具函数,写一个 while 循环,判断模型返回里有没有工具调用请求,有就执行、把结果塞回对话历史,没有就输出最终答案。这段代码跑起来那一刻确实很爽,但只要你把它放到真实业务里跑上几天,问题就会像潮水一样涌出来——循环停不下来、工具参数传错、上下文越滚越长最后爆掉、同一个动作反复执行、模型开始"幻觉"出一个根本不存在的工具名。
这些问题的根源不在于模型不够聪明,而在于Agent 本质上是一个分布式系统问题,而不是一个提示词问题。你写的那个 while 循环,其实是一个状态机;你挂的那些工具,其实是远程过程调用;你维护的对话历史,其实是一份需要持久化和裁剪的状态快照。一旦你用"系统"的眼光去看 Agent,就会发现它和任何一个后端服务一样,需要考虑容错、幂等、超时、可观测性、成本控制。
这篇内容我想做的事情,是把 Agent 从"玄学调参"拉回到"工程实现"的层面。我会先拆解构成一个可用 Agent 的七个核心要素,再顺着一次完整的执行链路,讲清楚工程上必须做出的七个关键决策点。每个决策点我都会给出为什么这么选、常见做法是什么、以及我自己踩过的坑。如果你正在从 demo 走向生产,或者正准备搭一套自己的 Agent 框架,这篇应该能帮你少走不少弯路。
需要提前说明的是,Agent 这个领域变化极快,框架层出不穷,但底层的工程约束是相对稳定的。我下面讲的东西不绑定任何特定框架,你可以把它当成一套"设计检查清单",无论你用的是哪家的 SDK,都能对照着看自己的实现有没有漏掉关键环节。
2. 拆开一个 Agent:七个要素缺一不可
在讲决策点之前,得先把 Agent 的构成讲清楚。我见过太多人把 Agent 等同于"大模型 + 工具调用",结果做出来的东西只能叫"带工具的聊天机器人"。真正的 Agent 至少包含七个要素,它们各司其职,缺了任何一个,系统都会在某个场景下崩掉。
2.1 模型:不只是选个最强的
模型是 Agent 的"大脑",但选模型不是越强越好。这里有个很现实的权衡:推理能力、延迟、成本、上下文窗口这四个维度往往是互相拉扯的。一个需要多步规划、复杂工具编排的任务,可能需要强推理模型;但一个只是做意图分类、参数抽取的子步骤,用一个小模型就够了,又快又便宜。
我的经验是采用分层模型策略:主循环用强模型负责规划和决策,一些确定性的子任务(比如从用户输入里抽取结构化参数、判断工具返回结果是否成功)交给小模型甚至规则代码处理。这样整体成本能降下来一大截,而效果几乎不受影响。很多团队一上来所有环节都用最贵的模型,账单跑起来才发现根本扛不住。
还有一个容易被忽略的点是模型的工具调用格式支持。不同模型对 function calling 的支持程度不一样,有的原生支持结构化输出,有的需要你在提示词里约定 JSON 格式再自己解析。后者在工程上要脆弱得多,因为模型偶尔会输出带 markdown 代码块的 JSON、或者多输出一段解释文字,你的解析器就挂了。选模型时一定要确认它的结构化输出能力是否可靠。
2.2 工具:Agent 的手脚,也是最容易出事的地方
工具是 Agent 与外部世界交互的接口。一个工具的定义通常包含名称、描述、参数 schema 三部分。看起来简单,但这里埋的坑最多。
首先是工具描述的质量直接决定调用准确率。模型是靠描述来判断该不该用这个工具的,如果你的描述写得含糊,比如"查询数据",模型根本不知道查的是什么数据、什么时候该用。好的工具描述应该像一份给新人的 API 文档:说清楚这个工具做什么、什么场景下用、参数分别是什么含义、返回什么。
其次是参数 schema 的严格性。我强烈建议用 JSON Schema 严格约束参数类型、必填项、枚举值。模型在宽松的 schema 下很容易传错类型,比如该传整数传了字符串,该传数组传了单个值。严格 schema 能在模型生成阶段就减少大量错误。
最后是工具的数量控制。我实测过一个现象:当工具数量超过 15 到 20 个时,模型的调用准确率会明显下降,因为它要在太多选项里做选择。解决办法是做工具分组,或者用一层路由先判断该用哪类工具,再在小组内做精细选择。
2.3 记忆:短期上下文与长期知识的分离
记忆分两层。短期记忆就是当前任务的对话历史和中间状态,它决定了 Agent 能不能记住前面几步做了什么。长期记忆是跨会话的知识,比如用户偏好、历史交互摘要、领域知识库。
短期记忆的核心矛盾是上下文窗口有限 vs 任务链路可能很长。一个复杂任务跑二三十步很正常,每步的工具返回可能都很长,很快就把窗口塞满了。这时候就需要裁剪策略:保留最近的若干轮、保留关键的工具调用结果、把中间过程压缩成摘要。粗暴地截断最早的消息是最省事但也最危险的做法,因为最早的消息里往往包含用户的原始意图。
长期记忆则要考虑写入时机和检索质量。不是什么信息都值得存,存太多会污染检索结果。我一般只在任务结束时写入一条结构化摘要,而不是把整个对话历史都塞进向量库。
2.4 规划:让 Agent 知道下一步该干嘛
规划能力决定了 Agent 是"走一步看一步"还是"胸有成竹"。简单的 Agent 用 ReAct 模式,边想边做;复杂任务可能需要先分解成子任务列表,再逐个执行。
这里有个反直觉的结论:不是所有任务都需要显式规划。对于步骤少、路径明确的任务,显式规划反而增加了一次模型调用和出错机会。只有当任务步骤多、存在依赖关系、或者需要回溯时,显式规划才划算。判断标准很简单:如果人类做这件事也需要先列个清单,那 Agent 大概率也需要。
2.5 执行循环:Agent 的心跳
执行循环是驱动整个 Agent 运转的引擎。它的基本逻辑是:把当前状态喂给模型,模型返回要么是工具调用请求,要么是最终答案;如果是工具调用,就执行工具、把结果追加到状态里,然后进入下一轮。
这个循环看起来简单,但终止条件的设计是重中之重。你必须同时设置多个刹车:最大轮数限制、总 token 预算、总耗时上限、以及检测到重复动作时的强制中断。少任何一个,都可能出现 Agent 无限循环烧钱的情况。我见过最惨的案例是一个 Agent 因为工具一直返回错误,模型一直重试,一晚上烧掉了几百美元。
2.6 状态管理:Agent 的"记忆体"
状态管理是把 Agent 从"无状态函数"变成"有状态服务"的关键。你需要决定状态存在哪、怎么序列化、怎么在中断后恢复。
一个成熟的做法是把 Agent 的每一步执行都持久化成一条事件记录,这样整个执行过程就是可回放、可审计的。出问题时你能精确看到第几步、模型输入输出是什么、工具返回了什么。没有这层记录,线上排查基本靠猜。
2.7 可观测性:看不见的 Agent 等于失控的 Agent
可观测性包括日志、指标、追踪三件套。对 Agent 来说,你需要记录每次模型调用的输入输出和 token 消耗、每次工具调用的参数和结果、整个任务的耗时和轮数、以及失败率。
这些数据不只是用来排错的,更是用来优化的。比如你发现某个工具经常被调用但返回结果很少被用到,那可能是工具描述有问题;你发现某类任务平均要跑 15 轮,那可能是规划环节需要优化。没有数据,这些优化无从谈起。
3. 决策点一:循环终止条件怎么设才不失控
讲完要素,进入决策点。第一个也是最要命的一个:循环什么时候停。
3.1 单一终止条件的致命缺陷
新手最常见的写法是只判断"模型有没有返回最终答案"。这个条件在理想情况下没问题,但现实是模型可能永远不返回最终答案——它可能陷入"调用工具→结果不满意→再调用→还是不满意"的死循环,也可能因为工具持续报错而不断重试。
我踩过的第一个大坑就是:一个查询类 Agent,因为后端接口偶发超时返回了错误信息,模型看到错误就想"再试一次",结果连续重试了几十次,直到把 token 预算烧光。当时没有任何轮数限制,全靠模型自己"想通",而它想不通。
3.2 多重刹车机制的设计
正确的做法是设置一组互相独立的终止条件,任何一个触发就停止:
| 终止条件 | 建议阈值 | 触发后的处理 |
|---|---|---|
| 最大执行轮数 | 10-25 轮(视任务复杂度) | 返回当前最佳结果或明确报错 |
| 总 token 预算 | 按成本倒推 | 中断并记录,提示预算耗尽 |
| 总耗时上限 | 30-120 秒 | 中断,返回部分结果 |
| 重复动作检测 | 连续 2-3 次相同调用 | 强制中断,判定为卡死 |
| 连续错误次数 | 3-5 次 | 中断,上报错误 |
这里的关键是阈值要按任务类型分别设定。一个简单的信息查询任务,5 轮就该出结果了,设 25 轮纯属浪费;而一个需要多步操作的复杂任务,10 轮可能不够。我的做法是给每类任务配置独立的参数,而不是全局一个值。
3.3 重复动作检测的实现细节
重复动作检测值得单独说,因为它最容易被忽略但收益很高。实现思路是给每次工具调用生成一个指纹(工具名 + 规范化后的参数),然后检查最近几次调用里有没有相同指纹。
要注意的是参数规范化。比如{"query": "北京天气"}和{"query": "北京天气 "}(末尾多个空格)应该被视为同一个调用。如果不做规范化,检测会失效。另外,有些工具的参数里包含时间戳之类的每次都不同的字段,这类字段在生成指纹时要排除掉。
提示:重复检测的窗口不要设太大,一般看最近 3 次就够了。窗口太大容易误伤那些确实需要多次调用同一工具但参数不同的正常场景。
3.4 优雅降级而不是硬崩
终止不等于失败。当触发终止条件时,如果 Agent 已经产出了部分有用结果,应该尽量返回它,而不是直接抛一个错误给用户。比如一个研究类 Agent 跑了 20 轮还没完全结束,但它已经收集了不少信息,这时候返回"基于目前收集到的信息,初步结论是……"比返回"任务失败"体验好得多。
实现上,你可以在状态里维护一个"当前最佳答案",每轮模型输出最终答案时就更新它。终止时如果存在这个最佳答案,就返回它并附上说明。
4. 决策点二:工具调用的参数校验与容错
第二个决策点关乎 Agent 的"手脚"是否可靠。工具调用出错是 Agent 最常见的失败原因之一,而大部分错误其实可以在工程层面提前拦截。
4.1 模型生成的参数为什么经常不对
模型生成工具参数时出错,通常有几个原因。一是schema 描述不够明确,比如一个字段叫date,没说是YYYY-MM-DD格式还是时间戳,模型就可能猜错。二是参数之间存在约束关系,比如选了 A 类型就必须填 B 字段,这种条件约束模型很难自己推出来。三是模型对枚举值不敏感,你定义了status只能是active或inactive,它可能给你返回enabled。
理解这些原因,才能对症下药。schema 描述要尽可能详细,把格式、示例、约束都写进去;条件约束要么在描述里显式说明,要么干脆拆成两个工具;枚举值要在描述里列全。
4.2 三层校验的落地方式
我的做法是三层校验,层层过滤:
第一层是 schema 校验,用标准的 JSON Schema 验证器检查类型、必填、枚举、格式。这一层能拦掉大部分低级错误。
第二层是业务校验,检查参数的语义合理性。比如查询日期不能是未来、ID 格式要符合业务规则、数值要在合理范围内。这一层需要你针对每个工具写校验逻辑。
第三层是执行前预检,比如调用外部 API 前先检查目标资源是否存在、权限是否足够。这一层能避免一些"注定失败"的调用。
校验失败时不要直接把错误抛给用户,而是把错误信息作为工具结果返回给模型,让它有机会修正参数重试。这是 Agent 相比传统程序的优势——它能从错误中学习。但要注意,同一个工具连续校验失败超过 2 次,就该中断了,否则会陷入"改参数→还错→再改"的循环。
4.3 工具执行超时与重试策略
工具执行本身也可能出问题:网络超时、下游服务不可用、返回格式异常。这些都需要在工具层做封装。
超时设置要区分工具类型。查询类工具通常几秒内就该返回,设 10 秒超时合理;而一些耗时的生成类工具可能需要几十秒。统一设一个超时值是不合理的。
重试要区分错误类型。网络抖动、临时不可用这类瞬时错误值得重试;而参数错误、权限不足这类确定性错误重试多少次都没用,只会浪费时间。重试还要加退避策略,不要立即重试,否则可能加剧下游压力。
注意:重试次数不要太多,一般 2-3 次足够。而且重试要在工具层内部完成,不要把重试逻辑暴露给模型,否则模型会以为工具"时好时坏",行为变得不可预测。
4.4 工具返回结果的裁剪
工具返回的结果可能很长,比如一个查询返回了几十条记录。如果原样塞回上下文,很快就会把窗口撑爆。所以工具层应该做结果裁剪:只返回最相关的部分,或者返回一个摘要加少量样本。
裁剪策略要按工具定制。列表类结果可以只返回前 N 条加总数;文本类结果可以截断到一定长度;结构化数据可以只保留关键字段。裁剪时要在结果里明确标注"已截断",让模型知道信息不完整,避免它基于不完整信息做出错误判断。
5. 决策点三:上下文膨胀的治理方案
第三个决策点是我认为最考验工程功力的地方:上下文管理。Agent 跑得越久,上下文越长,成本和延迟都随之上升,最后可能直接超出窗口限制。
5.1 上下文为什么会失控
上下文膨胀有三个来源。一是对话历史累积,每轮的用户输入、模型输出、工具调用和结果都堆在里面。二是工具返回冗长,一个搜索结果可能几千 token。三是中间推理过程,如果模型输出了详细的思考过程,这些也会占空间。
一个跑 20 轮的 Agent,如果每轮平均消耗 2000 token,那就是 4 万 token 的上下文。这还没算系统提示词和工具定义。很多模型的窗口也就 8k 到 128k,稍不注意就爆了。
5.2 分层裁剪策略
我的做法是把上下文分成几个区域,分别处理:
固定区是系统提示词和工具定义,这部分必须完整保留,不能裁剪。
近期区是最近几轮的完整交互,保留原文,因为模型最依赖这部分做决策。
远期区是较早的交互,做压缩处理。压缩方式可以是摘要(用小模型把几轮交互总结成一段话),也可以是结构化提取(只保留关键结论和状态,丢掉过程细节)。
关键信息区是那些必须始终保留的内容,比如用户的原始需求、已经确认的关键参数。这部分要显式标记,裁剪时优先保留。
5.3 摘要压缩的实操要点
摘要压缩听起来简单,做起来有讲究。首先,摘要要用小模型做,用主模型做摘要太浪费。其次,摘要要保留决策相关的信息,比如"用户要求查询 A 和 B 的对比"这种意图,而不是"用户说了你好,助手回复了你好"这种寒暄。
我一般会在摘要提示词里明确要求:保留用户的原始目标、已经完成的关键步骤、得到的重要结论、以及尚未解决的问题。丢掉那些中间的试错过程。
还有一个技巧是增量摘要。不要每次都重新摘要整个历史,而是把新完成的几轮追加到已有摘要上。这样既省 token,又保持了摘要的连贯性。
5.4 什么时候该开新会话
有些任务天然就该分阶段。如果一个任务已经完成了一个明确的子目标,与其在同一个上下文里继续,不如把关键结论提取出来,开一个新的会话继续下一阶段。这样上下文更干净,模型也更聚焦。
判断标准是:如果接下来的工作不再需要之前的详细过程,只需要结论,那就该开新会话。比如一个"先调研再写报告"的任务,调研阶段结束后,写报告阶段只需要调研结论,不需要调研过程中的每一次搜索。
6. 决策点四:多步任务的规划粒度
第四个决策点关于规划。规划做得好,Agent 事半功倍;做得不好,要么过度设计,要么走一步看一步最后迷路。
6.1 隐式规划与显式规划的取舍
隐式规划就是 ReAct 模式,模型每轮自己决定下一步做什么,没有预先的任务列表。显式规划是先让模型输出一个任务分解,然后按列表执行。
隐式规划的优点是灵活,能根据中间结果动态调整;缺点是容易迷失,尤其是在长任务里,模型可能忘了最初的目标。显式规划的优点是目标清晰、进度可控;缺点是缺乏灵活性,如果中间发现计划不可行,调整起来麻烦。
我的经验是混合使用:先做一次轻量的显式规划,把任务分解成几个大阶段,但每个阶段内部用隐式规划。这样既有全局视角,又保留了局部灵活性。规划不要太细,分解到 3-7 个大步骤就够了,太细反而束缚手脚。
6.2 任务分解的常见错误
任务分解最容易犯的错误是粒度过细。有人把"查询天气"分解成"确定城市→调用天气 API→解析结果→格式化输出"四步,这完全是过度设计,一个工具调用就能搞定的事被拆成了四轮模型调用,成本和延迟都翻倍。
另一个错误是步骤之间依赖不清。如果两个步骤没有依赖关系,就应该并行执行,而不是串行。但模型在做规划时往往默认串行,需要你在提示词里明确鼓励它识别可并行的步骤。
还有一个隐蔽的错误是没有定义完成标准。每个步骤应该有一个明确的"做完的标志",否则模型不知道什么时候该进入下一步。比如"收集信息"这个步骤,收集多少算够?没有标准的话,模型可能收集个没完。
6.3 动态重规划的触发时机
计划赶不上变化,执行过程中发现原计划不可行是常事。这时候需要触发重规划。触发条件可以设几个:某个步骤连续失败、发现新的关键信息改变了任务性质、或者执行轮数超出预期。
重规划不是推倒重来,而是基于当前进展调整剩余计划。已经完成的部分不要重复,只调整未完成的部分。实现上,可以把"已完成步骤 + 当前状态 + 剩余目标"喂给模型,让它输出新的剩余计划。
提示:重规划要设次数上限,一般 2-3 次。如果频繁重规划,说明初始规划质量太差,或者任务本身就不适合用 Agent 做,该考虑换方案了。
7. 决策点五:状态持久化与断点恢复
第五个决策点关乎可靠性。Agent 执行可能因为各种原因中断——服务重启、超时、人工干预。如果没有状态持久化,中断就意味着从头再来。
7.1 事件溯源式的状态记录
我推荐用事件溯源的思路来管理状态:不存最终状态,而是存导致状态变化的所有事件。每个事件记录"发生了什么",比如"模型返回了工具调用"、"工具执行成功返回了结果"、"用户补充了信息"。
这样做的好处是可回放、可审计、可恢复。出问题时你能精确重放整个执行过程;中断后可以从最后一个事件继续;还能基于事件做各种分析,比如统计每类工具的平均耗时。
事件记录要包含足够的信息:时间戳、事件类型、输入、输出、耗时、token 消耗。这些字段在排查问题时都是金矿。
7.2 检查点的设置位置
不是每个事件都需要持久化,那样开销太大。合理的做法是设置检查点,在关键节点保存完整状态。检查点一般设在:每轮循环结束时、每个工具调用完成后、每个规划阶段切换时。
检查点的粒度是个权衡。太粗,恢复时丢失的进度多;太细,持久化开销大。我的经验是每轮循环存一次是个不错的平衡点,因为一轮循环通常对应一个有意义的进展。
7.3 恢复时的状态重建
恢复时,从最近的检查点加载状态,然后继续执行。这里有个细节要注意:恢复后要重新校验外部状态。比如检查点记录"已经调用了支付接口",但恢复时你不确定支付到底成功没有,这时候需要查询支付状态来确认,而不是盲目继续。
对于有副作用的操作(写数据、发消息、扣款),恢复时要特别小心,避免重复执行。解决办法是给每个副作用操作分配一个幂等键,执行前先检查这个键是否已经处理过。
8. 决策点六:成本与延迟的平衡术
第六个决策点很现实:钱和时间。Agent 的每次模型调用都要花钱花时间,一个设计不当的 Agent 可能又慢又贵。
8.1 token 消耗的主要来源
token 消耗主要来自三块:系统提示词和工具定义(固定开销)、对话历史(随轮数增长)、工具返回结果(随数据量增长)。
固定开销容易被低估。如果你定义了 20 个工具,每个工具的描述加 schema 有 200 token,那就是 4000 token 的固定开销,每次调用都要带上。所以工具定义要精简,描述够用就行,不要写小作文。
对话历史的开销随轮数线性增长,这就是前面讲上下文治理的原因。工具返回结果的开销则取决于你的裁剪策略。
8.2 缓存能省下多少
提示词缓存是省钱利器。很多模型服务商支持缓存系统提示词和工具定义这部分不变的内容,命中缓存的部分按更低价格计费。如果你的系统提示词很长,缓存能省下可观的成本。
要用好缓存,关键是保持前缀稳定。把不变的内容(系统提示词、工具定义)放在前面,变化的内容(对话历史)放在后面。这样前缀部分才能被缓存命中。如果你把动态内容混在前面,缓存就失效了。
8.3 延迟优化的几个抓手
延迟优化有几个方向。一是用流式输出,让用户尽早看到内容,感知延迟降低。二是并行化,独立的工具调用并行执行,而不是串行等待。三是模型分层,简单任务用小模型,减少单次调用时间。四是减少轮数,通过更好的规划让 Agent 用更少的轮数完成任务。
这里有个反直觉的点:有时候多花 token 反而更快。比如让模型一次性输出完整的规划,比让它边想边做要快,因为减少了往返次数。所以成本和延迟不总是同向的,要具体分析。
8.4 成本监控与告警
上线前一定要有成本监控。按任务、按用户、按时间段统计 token 消耗和费用。设置告警阈值,比如单任务成本超过 X 元、单日总成本超过 Y 元就告警。
我见过因为没有成本监控,某个 bug 导致 Agent 疯狂循环,一晚上烧掉大笔费用的案例。有了监控和告警,这种问题能在几分钟内被发现并止损。
9. 决策点七:可观测性体系的搭建
最后一个决策点,也是我认为最被低估的:可观测性。一个看不见内部状态的 Agent,出了问题只能靠猜。
9.1 需要记录哪些数据
至少要记录这几类数据:
模型调用:输入提示词、输出内容、token 消耗、耗时、模型名称、是否命中缓存。
工具调用:工具名、参数、返回结果、耗时、成功与否、错误信息。
循环控制:当前轮数、终止原因、是否触发重规划。
任务级别:任务 ID、用户 ID、总轮数、总耗时、总 token、最终状态。
这些数据要能关联起来,通过一个任务 ID 串起整个执行链路。
9.2 追踪链路的组织方式
推荐用分布式追踪的思路,给每个任务分配一个 trace ID,每个步骤是一个 span。这样你能看到整个任务的调用树,一眼看出时间花在哪、哪一步出错。
对于 Agent 这种多轮循环的结构,追踪特别有价值。你能直观看到第几轮开始变慢、哪个工具是瓶颈、哪次模型调用消耗了异常多的 token。
9.3 从日志到洞察
记录数据只是第一步,关键是从数据里提取洞察。我一般会关注几个指标:
任务成功率:整体和分类的成功率,找出失败率高的任务类型。
平均轮数:轮数异常高的任务,往往是规划或工具设计有问题。
工具调用分布:哪些工具被频繁调用,哪些几乎不用。不用的工具考虑下线,频繁出错的工具重点优化。
成本分布:哪些任务类型最烧钱,有没有优化空间。
这些指标要定期看,而不是出了问题才看。很多问题在指标上早有征兆。
9.4 线上问题的排查路径
有了可观测性,排查问题就有了章法。我的排查路径一般是:
先看任务级别的状态,确认是成功还是失败、终止原因是什么。然后顺着 trace 看每一轮的输入输出,定位到出问题的那一轮。再看那一轮里模型为什么这么决策、工具返回了什么。最后判断是提示词问题、工具问题还是模型能力问题。
没有这套体系,排查就是大海捞针;有了它,大部分问题能在几分钟内定位。
10. 我在实际项目里踩过的几个坑
前面讲的都是方法论,最后分享几个具体的坑,都是真金白银换来的教训。
第一个坑是工具描述里的"想当然"。我写过一个查询订单的工具,描述是"根据订单号查询订单信息"。结果模型经常在用户只提供了用户名的情况下就调用它,然后传一个空订单号或者把用户名当订单号传进去。后来我把描述改成"根据订单号查询订单信息,注意:必须提供订单号,不能使用用户名或其他信息",调用准确率立刻上去了。工具描述要明确说清楚"什么时候不该用"。
第二个坑是忽略了工具返回的错误信息格式。一开始工具出错时我直接返回异常堆栈,结果模型看到一堆技术细节完全懵了,不知道该怎么办。后来改成返回结构化的错误信息,包含错误类型、原因、建议的下一步,模型就能根据这些信息做出合理反应,比如换个参数重试或者换个工具。
第三个坑是上下文裁剪裁掉了关键信息。有一次为了控制上下文长度,我设置了只保留最近 5 轮。结果一个需要参考早期信息的任务失败了,因为用户最初的需求在第 1 轮,被裁掉了。后来改成"最近 5 轮 + 用户原始需求始终保留",问题解决。裁剪策略一定要保证核心信息不被误伤。
第四个坑是没有区分"可重试错误"和"不可重试错误"。早期我对所有工具错误都让模型重试,结果遇到权限不足这种确定性错误时,模型反复重试直到耗尽预算。后来在工具层区分了错误类型,确定性错误直接返回给模型并明确标注"此错误重试无效",模型就不会白费力气了。
第五个坑是低估了并发场景下的状态隔离。单用户测试时一切正常,上线后多用户并发,发现状态串了。原因是状态存储用了全局变量。改成按会话 ID 隔离后解决。Agent 服务一定要保证每个会话的状态是独立的。
这些坑的共同点是:它们都不是模型能力问题,而是工程实现问题。把工程做扎实,Agent 的可靠性会有质的提升。
11. 从七要素到七决策:一张自查清单
把前面的内容浓缩成一张清单,你可以对照着检查自己的 Agent 实现:
| 维度 | 检查项 | 是否达标 |
|---|---|---|
| 循环控制 | 是否有轮数、token、耗时、重复检测四重终止条件 | |
| 工具调用 | 是否有 schema、业务、预检三层校验 | |
| 上下文 | 是否有分层裁剪和摘要压缩策略 | |
| 规划 | 是否有轻量显式规划 + 动态重规划 | |
| 状态 | 是否事件溯源 + 检查点持久化 | |
| 成本 | 是否有缓存、分层模型、成本监控 | |
| 可观测 | 是否有完整追踪链路和关键指标 |
这张表里任何一项打不了勾,你的 Agent 在生产环境都会遇到对应的问题。我建议在开发阶段就把这些补齐,而不是等线上出事了再补——后者的代价往往高得多。
Agent 的工程实现没有银弹,它是一堆细节的堆叠。但好消息是,这些细节都是可学习、可复制的。把七个要素理解透,把七个决策点做扎实,你就能做出一个真正能扛住生产流量的 Agent,而不只是一个演示用的玩具。