尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Agent项目落地指南:调试、错误处理与成本优化实战

Agent项目落地指南:调试、错误处理与成本优化实战 先说结论Agent 项目能不能稳定落地一半靠模型能力一半靠调试、错误处理和成本这三件“脏活”做得够不够细。很多团队把一个带工具调用和多轮规划的 Agent 跑通 Demo 之后就急着上线结果线上第一周就被执行中断、工具返回异常、Token 费用超预算这几个问题按在地上摩擦。这篇文章我结合自己做 Agent 项目的经验把调试、错误处理、成本优化这三块的实操方法一次说清楚。1. Agent 调试为什么传统调试思路在 Agent 上不好使1.1 Agent 调试的特殊性不确定性的放大镜传统程序调试你面对的是一个确定性系统。你传入同样的输入函数走同样的分支变量有固定的值用 gdb 打断点、看调用栈、查变量问题基本能复现、能定位。但 Agent 不一样它依赖大模型做推理决策同样的用户问题模型今天回答“调用 A 工具”明天可能就回答“直接给出答案”后天甚至可能在一个工具调用上反复横跳三次然后报错。我把这个称作“不确定性放大镜”Agent 的每一轮 LLM 调用都会把前一轮的微小偏差放大。比如模型在某一轮把工具参数里的日期格式写错了如果这个错误没有被捕获下一轮规划里这个错误参数会被当作事实继续参与推理最终导致整个任务链崩塌。这种级联错误在传统程序里几乎不会出现但在 Agent 项目里是家常便饭。所以Agent 调试的第一原则是不要试图用确定性系统的思维去调试一个非确定系统。你需要一套新的调试基础设施核心是三层结构可观测性、可回放性、可控制性。可观测性解决“发生了什么”的问题。传统程序你打日志看变量Agent 项目你要记录的是完整的决策轨迹——每一步的输入消息、模型输出、工具调用请求、工具返回结果、Agent 内部状态变化。这比普通日志要重得多但它是排查一切问题的基础。可回放性解决“怎么复现”的问题。由于模型输出有随机性你没法保证同样的输入复现同样的问题。这时候你要做的不是复现错误而是把之前记录下来的错误现场完整“重放”一遍把当时的消息序列原封不动地重新喂给 Agent观察它在哪一步开始偏离预期。这个思路很像你在浏览器里打开 chrome 的调试模式把网络请求记录下来然后复制请求负载参数去单独验证某个接口——只是 Agent 的“接口”更复杂。可控制性解决“怎么干预”的问题。调试时你要能随时中断 Agent 的执行、修改某一轮的模型输出、手动指定某一轮要调用哪个工具。这要求你在设计 Agent 框架时把执行循环和决策逻辑解耦而不是把一个 while 循环加 LLM 调用写成铁板一块。1.2 日志怎么做才不白做我见过太多 Agent 项目的日志就是把 prompt 和 response 打成两行 JSON 丢进文件里然后排查问题时对着满屏的 JSON 发愁。真正能用的 Agent 日志至少要满足两个条件按会话session组织、按事件event切分。按 session 组织意思是同一轮用户任务的所有日志包括子任务、工具调用、重试记录、Token 消耗都要能通过一个 request_id 或 session_id 串起来。这样你在查问题时输入一个任务 ID 就能拉出整条链路。这与互联网后端常说的 trace 系统是同一个思路只是 Agent 项目里 trace 的粒度更细、信息密度更大。按 event 切分意思是每条日志要有明确的事件类型和状态。我在项目里定义过一套 Agent 事件模型基本覆盖了所有调试场景planner_start开始规划planner_result规划结果记录了子任务列表tool_call_start开始调用工具tool_call_result工具调用成功返回tool_call_error工具调用失败记录了原始异常和重试次数llm_request_start发送给模型的消息体快照llm_response_raw模型返回的完整响应session_endAgent 收尾这套事件模型跑下来所有环节都能回溯。有一次线上 Agent 反复执行失败我拉日志一看发现 tool_call_error 里显示工具返回了一个 500 错误但 Agent 内部重试了 4 次每次都把同一个错误当作事实继续推理最后生成了一段看似合理但完全错误的结论——因为那个工具返回的错误信息被模型解读成了“查询成功结果为空”。所以事件切分不是简单的结构化日志而是要在关键节点让运维人员和调试者能在日志基础上还原完整心智。1.3 VSCode 调试、网络调试与串口调试思维的迁移Agent 调试工具链很多人纠结要不要上重量级方案我的建议是分层来。第一层用 VSCode 断点调试 Agent 的流程代码。这是最基础的作用于 Agent 框架本身的逻辑比如循环结构、工具注册表、错误处理分支。你在某个工具函数里打一个断点或者在某轮 planner 调用前打断点能快速确认是不是代码层的 bug。VSCode 突然调试报错、端口冲突、断点不生效这类问题基本都是环境配置或者路径问题这里不展开。第二层用网络调试思路看 Agent 与外部系统的交互。Agent 每调一个工具本质就是一次外部请求。这一层的调试方式和调试 Web 接口没有本质区别你需要像 chrome 调试模式下的网络面板那样看到每次请求的 URL、请求头、请求体、响应体。我在项目里专门写了一个中间件把 Agent 框架发出的所有工具请求和响应包在一个可拦截的层里面方便在调试时直接修改请求参数再发送。这类“右键不弹出复制值的弹框”之类的小痛点基本就是工具链层面的便利性问题不值得花大精力真正重要的是把请求-响应链路完整暴露出来。第三层用串口调试助手思维的“原始字节”视角看模型输出。做硬件开发时用 sscom、串口调试助手看单片机吐出来的原始指令很多时候能发现上位机解析逻辑掩盖掉的问题。Agent 也类似——框架、SDK、测试 harness 会把模型输出“整理”得很规整但你要排查问题得学会看模型返回的原始内容包括那些被框架过滤掉的 reasoning 字段、置信度字段、分块传输的中间片段。有时候模型其实已经在 response 里给出了“我不确定”的信号只是框架只把最终回答拿出来展示你根本看不到。这三层链路打通之后Agent 调试才算是真正有了一套可依赖的工具箱。还有一点要提醒harness 和 agent 本身的区别要分清。Agent 是执行任务的核心循环harness 是承载 Agent 的那套外围系统——prompt 注入、工具注册、沙箱环境、错误处理、日志采集。调试时如果问题出在 harness 层你分析 agent 本身的决策逻辑没有任何意义反之亦然。我每次排查问题第一件事就是先确认 bug 属于哪一层这能省掉一半的无效时间。2. Agent 错误处理把“崩溃”变成“可恢复”2.1 错误分类Agent 项目的错误比普通程序多一维普通程序的错误处理核心是捕获异常、记录日志、返回错误码。Agent 项目的错误处理多了一个非常特殊的维度模型“觉得一切正常”但实际上已经错了。我把 Agent 项目里常见错误分成四大类第一类基础设施错误。网络超时、API 返回 429限流、5xx、请求体超长被网关拒绝。这类错误很原始和普通程序遇到的一样处理方式也类似但要注意叠加的重试策略。第二类工具层错误。工具函数抛出的异常、工具返回的数据格式不符合 Agent 预期、第三方 API 返回业务错误码。这类错误最难受的地方是工具返回的“错误信息”会被模型当作正常的上下文进行推理如果不加处理模型会基于错误信息编造出荒谬的回答。第三类推理/规划错误。模型这一步理解错了用户意图、生成了不存在的工具名、给工具传了错误的参数、规划的子任务逻辑混乱。这类错误没有异常堆栈它就是模型输出了“错误的正确答案”你要做的不是捕获它而是识别它。第四类状态错误。Agent 执行到一半上下文窗口被撑爆、内存溢出、会话超时、状态丢失。这类错误在长会话 Agent 里最常见。很多初学 Agent 开发的人只处理第一类和第二类结果线上大量故障都出在第三类和第四类。这就是我常说的“agent execution terminated due to error” 并不是一个真正的错误信息——它只是表象你真正要追的是模型在哪一步决策导致了执行链终止。2.2 重试策略设计指数退避、上限、边界Agent 的重试策略和普通后端服务有很大不同。普通服务重试通常针对单次请求Agent 的重试至少有三个层级单次模型调用重试、单次工具调用重试、整个任务级重试。这三个层级要分别设定策略不能混为一谈。单次模型调用重试通常针对的是 429、5xx、网络抖动。我一般设置最多 3 次重试采用指数退避加抖动backoff with jitter。比如初始等待 1 秒每次翻倍加上 0~500ms 的随机抖动避免多个请求同时重试造成流量尖峰。单次工具调用重试要区分幂等和非幂等操作。查询操作可以重试写入操作要非常谨慎。有一次我们一个工具是“创建工单”的写操作因为第三方 API 超时Agent 框架自动重试了两次结果同一个工单被创建了三份。所以写操作的工具重试要设计成“查询 确认”两步先查目标资源是否存在再决定是否重新提交。整个任务级重试是 Agent 特有的。当一轮完整规划执行失败时是重新让 Agent 从头规划还是让它在失败点继续我的经验是如果失败发生在执行阶段工具调用出错应该让 Agent 带着错误信息继续修复而不是重新规划如果失败发生在规划阶段模型结构混乱、JSON 解析失败则直接重新生成规划。这个判断逻辑本身也可以用模型来做——你把失败信息和当前状态发给模型让模型决定是继续还是重开往往效果比硬编码规则更稳定。2.3 让 Agent“带着错误走下去”错误信息的构造艺术Agent 错误处理的核心不是拦截错误而是把错误变成下一步决策的输入。这里有个关键技巧——错误信息的构造方式直接决定了模型下一步的表现。我见过太多 Agent 项目工具调用失败后只是简单地把异常信息抛出去比如“工具执行失败FileNotFoundError: [Errno 2] No such file or directory: ‘xxx.csv’”。模型拿到这个信息后能知道文件不存在但它不知道该怎么办。你要做的是给你抛给模型的错误信息填充三个要素发生了什么原始错误信息但要转化为模型能理解的自然语言表述为什么发生经过初步分析的可能原因可以怎么处理给模型 2~3 条可行的修复建议或者允许采取的替代方案比如上面那个文件失败的例子抛给模型的错误信息应该改造成“文件 xxx.csv 未找到。可能在工具执行前的路径拼接时出现问题也可能文件尚未生成。你可以先检查该路径是否存在其他文件或用 工具 find_file 在 workdir 中检索该文件的真实位置。”这样一改造模型在下一轮决策时就清晰得多。实测这种带“修复建议”的错误回报能把 Agent 自动修复成功率提高 40% 以上。这是我从一次项目复盘里总结出来的经验——单纯把原始异常塞给模型它经常走弯路。另外错误信息不要只堆叠在消息历史的最后一条。如果中间某个工具失败后模型已经基于错误信息做了错误的中间推理后面再改正的难度就会越来越大。所以一旦检测到工具调用失败要主动清理掉已经进入上下文的错误推理片段或者用一条构造好的“错误分析 修复建议”消息去覆盖当时模型产生的错误结论。这条在长链路 Agent 里尤其重要。3. 成本优化让 Agent 不“烧钱”的工程手段3.1 成本结构先算清楚钱花在哪里Agent 项目的成本核算和普通 API 调用完全不是一个数量级。普通 API 是“一次请求一次计费”Agent 是“一个任务 N 次请求计费”而且这 N 次之间还有复杂的相互影响。我在项目里把 Agent 成本拆成四个公式任务成本 调用轮次 × 单轮平均 Token 消耗 × 单价这个公式看起来简单每个变量都值得深挖。调用轮次是最主要的放大因子。一个 5 轮 Tool Call 的任务天然比一个 2 轮的任务贵 2 倍以上。所以成本优化的首要目标是压缩轮次而不是让模型说得更少。Token 消耗又拆分两部分输入 Token 和输出 Token。大多数价格体系下输出 Token 比输入 Token 贵 3~5 倍。所以优化输出 Token 是性价比最高的手段——让模型少说废话、少重复、少输出冗余推理。上下文膨胀是 Agent 成本的最大隐形杀手。每轮对话都会把之前的所有消息重新发送给模型消息越长每一轮的成本越高。一个原本只要 10 轮的任务如果消息历史膨胀到 10 万 token那第 10 轮的请求就是 10 万 input token 起步直接把单任务成本推到几块钱人民币。最后是失败成本。错误处理章节里提到的重试策略每次重试都会产生额外的 Token 消耗而且重试时模型还要处理更多错误信息导致输入 Token 进一步膨胀。任务失败后从头重试的成本往往是成功任务成本的 2~3 倍。把这四块成本拆清楚之后优化方向就非常明确了压缩轮次、压缩 Token、减少重试浪费。3.2 上下文管理剪裁、摘要、结构化记忆上下文管理是 Agent 成本优化里收益最大、见效最快的一个环节。我总结了一套“三层上下文”策略第一层核心上下文。包括系统提示、工具定义、当前用户目标、最近 1~2 轮对话。这部分必须完整保留属于 Agent 推理的“工作记忆”。第二层工作摘要。把更早的历史对话用摘要方式保留。每执行完一个子任务就生成一段摘要放在上下文中作为“长期记忆”。摘要的生成也要消耗 Token但比每次重复发送原文便宜得多。第三层外部记忆。将对话历史、中间结果、工具返回的大块数据存入外部存储数据库、向量库、文件需要的时候通过检索把相关内容拉回来。上下文里只保留检索到的结果而不是全部历史。这里要格外注意工具返回的大数据。很多工具一次会返回几百 KB 的 JSON如果不加处理直接塞进上下文几轮下来上下文直接爆炸。我的方案是设置工具返回截断策略超出阈值自动截断、结构化压缩成要点列表、必要时把完整结果写入临时文件并且在上下文里只放文件路径。最近模型推理成本里有一个新趋势就是从模型供应商的角度看前缀缓存对成本的稀释也很关键同一段系统提示和工具定义如果每次都一模一样很多服务商会对重复前缀打折。所以系统提示和工具定义最好不要动态拼入变量而是保持完全静态让前缀缓存命中的概率最大化。这一点在选型时和做体量增长后都要关注。3.3 模型路由快慢结合贵贱分明Agent 开发里一个常见的成本浪费是让一个“能力过剩”的大模型去处理所有请求。以我实际的项目为例一个简单的“查询天气然后回答用户”的任务根本不需要顶级模型中级模型就够了只有涉及复杂推理、多工具协同、长文本规划时才需要顶级模型。所以我在 Agent 框架里加了一个“模型路由层”根据任务难度动态选择模型。路由层的判断方式有两种规则路由和模型路由。规则路由就是根据任务类型、用户输入长度、需要的工具数量来硬编码一个模型选择逻辑。比如工具数量少于 3 个、任务类型是“查询类”直接走快模型工具数量大于 5 个、任务有明确的分步依赖关系走强模型。模型路由则是用小模型先判断一下任务复杂度再决定调用哪个模型。这也要消耗少量 token但相比省下来的部分通常很划算。模型路由的核心目标是让 70% 的简单任务跑在便宜模型上剩下 30% 的高难任务才动用强势模型。这样综合成本能省下 50% 以上同时最终效果几乎不受影响。这里顺便说一句模型路由不是把“问题简单”和“问题便宜”划等号而是在效果、延迟、成本三者之间做动态平衡。3.4 缓存与批处理省的是重复劳动的钱Agent 项目里有几类请求非常值得做缓存。一是完全相同的历史 query。如果用户反复问同一个问题比如“帮我查一下今天的库存”在库存数据没变的情况下整个任务结果都可以缓存。这类缓存要设计好失效机制数据源变更时主动失效。二是工具调用结果的缓存。同一个工具、同样的入参在短时间内返回相同结果的概率很高。比如查天气、查汇率、查系统状态这些结果自然有 TTL在 TTL 内直接读缓存避免每次查询都触发一次工具调用并消耗 Token。三是中间过程的缓存。Agent 的规划结果是可以复用的。同一个任务类型的子任务拆分方案基本是稳定的把“用户意图 拆人结果”缓存下来下一次同类任务直接沿用之前的规划只被跳过规划步骤能省一大块 Token。这种缓存要留意如果任务里涉及的动态信息占比高缓存收益会明显下降。批处理适合另一类场景非实时任务。比如 Agent 每 5 分钟要汇总一份报告或者每天要定时处理一批批量工单。这类任务完全可以在低峰时段用批量接口处理价格会比实时流式接口低一截。而且批处理天然允许更长的响应时间可以用更强的模型而不增加大量成本因为批量计费通常按处理量而不是按调用次数来算。如果你做了一个周期性任务型的 Agent这个方向值得深入研究。4. 常见问题与排查技巧实录4.1 Agent 执行中断的排查方法标题热词里的“agent execution terminated due to error”我太熟了——这几乎成了 Agent 项目的“Eternal 报错”。收到这个错误时千万不要直接搜这个字符串你得到的只会是一堆同病相怜的帖子。正确排查路径是第一步拉完整的 trace 。去日志系统里把的 session 下所有事件拖出来定位是哪一个事件触发了 termination。第二步区分终止类型。是模型侧终止比如模型输出了 stop sequence、上下文长度跑满还是框架侧终止比如重试次数耗尽、工具抛未捕获异常还是编排层终止比如子任务规划恶性循环被保险丝熔断。这三类终止的处置方式完全不同。第三步把“模型侧终止”再细分。如果是上下文长度终止优先优化上下文压缩策略如果是模型主动终止输出 stop需要检查工具注册表是不是模型想调用一个不存在的工具被迫停止如果终止发生在工具调用中而且错误信息被模型当作正常输出处理了那就回到错误处理章节检查错误信息构造是否优雅。我遇到过一例Agent 在读取一个 PDF 文件时报告“成功”实际上文件被加密打不开但工具的返回 JSON 里 status 字段被模型误读成了成功。结果是 Agent 后面所有决策都建立在错误前提上最终以确定性错误终止。这类问题的根源不是模型而是工具的返回结构设计不合理——错误状态没有和正常状态区分开。4.2 工具返回异常的调试技巧Agent 工具调试和普通函数调试最大的区别是“工具返回的结果必须能被模型正确解读”。我做过一次统计Agent 工具调用失败的原因里有 30% 是工具真的出错了70% 是工具结果格式对模型不友好导致模型错误解读。所以调试工具时除了确认工具拿到的参数对不对、逻辑对不对还要额外做一步把工具输出只给一个独立的模型看让模型描述“这个工具返回了什么”。如果模型描述出来的信息和你想要的返回信息不一致这个工具的输出就要改。格式层面我推荐所有工具统一返回结构化 JSON 并且带上明确的 status 字段和 message 字段。模型解析 JSON 的能力明显优于解析纯文本这是一个特稳的经验。4.3 成本暴涨的排查清单线上 Agent 成本突然涨了按下面的顺序排查基本能定位是不是某类任务调用轮次增加了很多比如之前 3 轮、现在 8 轮如果增加去分析模型在哪个环节开始反复循环最常见的是工具失败后Agent 一直在选同一个失败工具不停重试。是不是上下文膨胀失控看一眼任务的平均输入 Token 分布如果呈持续上涨趋势检查是否做了上下文压缩这个最常见的原因是工具返回大段数据后没有截断。是不是大量任务触发了重试或者从头再来。如果是说明错误处理策略有问题大量的重试浪费了 token。 这个方向值得重点复盘减少一次失败重试比在 prompt 里让模型“少说废话”节省的成本多得多。是不是有线上缓存失效。缓存命中率下降通常会导致成本直接翻倍比如 TTL 设置太短。4.4 一些用过就回不来的技巧和配置最后分享几个实战中验证过多次的细节给每个工具调用分配一个独立的 trace_id并把它放进工具返回结构里。这样无论模型怎么折腾排查问题时都能把一次具体的工具调用从整个决策链里精确对出来。在 Agent 的 planner 接收到模型输出后加一层 JSON 解析校验失败时自动让模型重新输出一次规范 JSON。这个比在后端硬解析报错后打断任务要稳得多。成本监控要按任务级别、按天、按配置维度去设预算超过阈值自动切换到快速模型或直接限制最大执行轮数。不要把成本优化做成事后复盘它应该是线上运行时的实时熔断手段。VSCode 调试 Agent 代码时可以配置 launch.json 里面的“调试信息保存到日志文档同时打印显示”功能。这个技巧在调试耗时长、日志量大的 Agent 流程时非常好用能一边看实时的输出流一边保留完整的日志记录去回溯。这个领域迭代太快上面这些都是规矩的层面真正到项目里还会冒出各种新问题。但只要你调试的链路是完整的、错误处理是分层的、成本是可观测的Agent 项目的基本盘就稳了。
返回列表