☰
Agent 报错别急着换模型:从错误码到失败链的逐层排查指南
2026/11/4 9:04:20 网站建设 项目流程

1. 别急着换模型,先看看错误码在说什么

Agent 项目跑崩了,很多人的第一反应是“模型不行”,然后换模型、调温度、加提示词,折腾一圈发现该崩还是崩。我踩过太多次这种坑了,后来才慢慢意识到:大部分 Agent 失败根本不是模型能力问题,而是失败链路上游某个环节先出了问题,模型只是最后一个背锅的。

举个很典型的场景。你写了一个 Agent,让它调用工具查数据、做分析、生成报告。跑着跑着突然报错,日志里只有一行agent execution terminated due to error,或者更粗暴的model request failed。这时候你去看模型服务商的错误信息,可能显示的是 422、超时、连接中断。但如果你只盯着模型这一层排查,永远找不到根因。

我后来总结了一套方法:把 Agent 的失败当成一条链来看,从最外层的粗错误码开始,一层一层往里剥,直到定位到真正断掉的那个环节。这条链大致是这样的:

用户输入 → Agent 编排层 → 工具调用层 → 外部服务/API 层 → 模型请求层 → 结果解析层 → 输出层

任何一层出问题,最终表现出来的都可能是“Agent 失败了”。但错误码的粒度和位置,能告诉你该往哪个方向查。

这篇文章适合两类人:一是刚开始做 Agent 开发、遇到报错就懵的新手;二是已经跑过几个 Agent 项目、但排查效率一直不高的开发者。我会用实际案例拆解,从错误码怎么读、失败链怎么建、每一层怎么排查,到常见坑和速查表,尽量把这件事讲透。

1.1 为什么“换模型”是最差的排查策略

先说说为什么不能一上来就换模型。原因很简单:换模型会掩盖问题,而不是解决问题。

你换了一个模型,可能因为它的超时阈值更宽、对畸形输入的容忍度更高、或者恰好那次请求没触发某个边界条件,于是“看起来好了”。但失败链上游的问题还在,过几天换个场景又崩了。更糟糕的是,你换模型之后,原来的错误码和日志对不上了,排查难度反而更大。

我自己的经验是:除非确认是模型本身的能力边界问题(比如上下文长度不够、不支持某种工具调用格式),否则不要动模型。先把错误码读明白,把失败链建起来,找到真正断掉的那一环。

1.2 粗错误码到底粗在哪里

大部分 Agent 框架和平台返回的错误码,粒度都很粗。常见的几类:

错误码/信息实际含义可能根因
agent execution terminated due to errorAgent 执行被终止工具调用失败、超时、解析异常、模型返回异常
model request failed模型请求失败网络问题、鉴权失败、请求格式错误、限流
422请求实体无法处理参数格式错误、必填字段缺失、类型不匹配
timeout超时外部服务慢、模型响应慢、死锁
connection refused连接被拒目标服务没启动、端口不对、防火墙
out of memory内存不足容器内存限制、大对象未释放、并发过高

你看,这些错误码没有一个能直接告诉你“是哪一行代码、哪个工具、哪个参数出了问题”。所以我们需要把粗错误码展开成失败链,逐层缩小范围。

2. 失败链的搭建:从错误码到根因的映射方法

失败链的核心思想是:每一个错误码都不是孤立的,它一定对应链路上某个节点的异常状态。我们要做的,就是把这个对应关系建起来。

2.1 先给 Agent 执行过程打上“层标签”

我在自己的项目里,会给 Agent 的每个执行阶段打上标签,日志里必须带这些标签。这样一旦出错,我能立刻知道是哪一层的问题。

# 示例:给 Agent 执行阶段打标签 import logging logger = logging.getLogger("agent") def execute_agent(user_input): logger.info("[LAYER=INPUT] user_input=%s", user_input) try: plan = planner.plan(user_input) logger.info("[LAYER=PLAN] plan=%s", plan) except Exception as e: logger.error("[LAYER=PLAN] error=%s", e) raise for step in plan.steps: try: logger.info("[LAYER=TOOL] tool=%s args=%s", step.tool, step.args) result = tool_registry.call(step.tool, step.args) logger.info("[LAYER=TOOL] result=%s", result) except Exception as e: logger.error("[LAYER=TOOL] tool=%s error=%s", step.tool, e) raise try: logger.info("[LAYER=MODEL] prompt=%s", prompt) response = model.call(prompt) logger.info("[LAYER=MODEL] response=%s", response) except Exception as e: logger.error("[LAYER=MODEL] error=%s", e) raise return response

这样做的目的是:当粗错误码出现时,你能通过日志里的 LAYER 标签,快速定位到是哪一层先出的问题。没有这个标签,你只能看到“Agent 失败了”,然后从头开始猜。

2.2 错误码到失败链的映射表

基于我自己的项目经验,整理了一张映射表。这张表不是绝对的,但能覆盖大部分常见情况。

粗错误码优先排查层次要排查层常见根因
agent execution terminated due to errorTOOL 层PLAN 层工具参数错误、工具内部异常、工具超时
model request failedMODEL 层网络层鉴权、限流、请求格式、网络不通
422TOOL 层 / MODEL 层输入层参数类型错误、必填缺失、JSON 格式错误
timeoutTOOL 层MODEL 层外部 API 慢、模型响应慢、死锁
connection refused外部服务层网络层服务未启动、端口错误、防火墙
out of memory运行时层TOOL 层容器内存限制、大对象、并发过高
permission denied鉴权层TOOL 层API Key 错误、权限不足、角色配置错误

这张表的使用方法是:先看错误码,确定优先排查层,然后去日志里找对应 LAYER 标签的最后一条记录。最后一条记录的位置,就是断点所在。

2.3 一个真实案例:422 错误背后的三层问题

我之前遇到过一个 422 错误,表面上看是模型请求失败,但实际排查下来,根因在工具层。

现象:Agent 执行到某一步时,报422 Unprocessable Entity,模型服务商返回的错误信息是“请求体格式错误”。

第一层排查(MODEL 层):检查发给模型的请求体,发现 JSON 格式没问题,字段也齐全。但注意到一个细节:请求体里有一个字段tool_result,它的值是一个很长的字符串,里面包含了换行符和特殊字符。

第二层排查(TOOL 层):去看工具返回的结果,发现工具返回的是一个未经过滤的原始文本,里面包含了\n、\t、引号等字符。这些字符在拼接进 JSON 时,没有被正确转义,导致模型服务端解析失败。

第三层排查(PLAN 层):为什么工具会返回这种未过滤的文本?因为规划层在调用工具时,没有指定输出格式,工具就按默认方式返回了原始数据。

根因:规划层缺少输出格式约束 → 工具层返回未转义文本 → 模型层 JSON 解析失败 → 422。

修复:在规划层给工具调用加上output_format="json"约束,工具层统一做转义处理,模型层增加请求体校验。

这个案例说明:422 不一定真的是模型的问题,它可能是上游工具层和规划层的问题,只是最终在模型层暴露出来。

3. 逐层排查实操:从 terminalBlocker 到根因定位

这一章我按失败链的顺序,逐层讲怎么排查。每一层我都会给出具体的操作步骤、命令、代码片段和注意事项。

3.1 输入层:最容易被忽略的起点

输入层的问题往往最隐蔽,因为用户输入看起来“很正常”,但可能包含一些边界情况。

排查要点:

  • 输入长度是否超过限制
  • 是否包含特殊字符(换行、引号、反斜杠、Unicode 控制字符)
  • 是否包含注入类内容(比如让 Agent 忽略之前指令的文本)
  • 编码是否统一(UTF-8 vs GBK)

实操命令:

# 检查输入文件的编码 file -i input.txt # 查看输入中的特殊字符 cat -A input.txt | head -50 # 统计输入长度 wc -c input.txt

注意事项:

我遇到过好几次因为输入里有一个不可见的 Unicode 字符(比如零宽空格),导致工具调用参数解析失败。这种问题用肉眼根本看不出来,必须用cat -A或者十六进制查看器才能发现。

3.2 规划层:Agent 的“大脑”怎么排查

规划层负责把用户输入拆解成可执行的步骤。这一层出问题,通常表现为:步骤缺失、步骤顺序错误、工具选择错误、参数生成错误。

排查方法:

  1. 把规划层的输出完整打印出来,包括每一步的工具名、参数、预期输出。
  2. 检查每一步的参数是否满足工具的输入要求。
  3. 检查步骤之间是否有依赖关系被遗漏。
# 打印规划结果 plan = planner.plan(user_input) for i, step in enumerate(plan.steps): print(f"Step {i}: tool={step.tool}, args={step.args}, expected={step.expected}")

常见问题:

问题表现修复
步骤缺失最终结果不完整优化规划提示词,增加步骤校验
工具选择错误调用了不存在的工具限制工具列表,增加工具描述
参数类型错误工具调用报 422在规划层做参数类型校验
循环依赖Agent 卡死增加最大步骤数限制

3.3 工具层:terminalBlocker 和外部服务的排查

工具层是失败链上最容易出问题的一层,因为它涉及外部服务、网络、鉴权、数据格式等多个变量。

terminalBlocker 是什么:在我的项目里,terminalBlocker 指的是工具调用过程中,导致整个 Agent 执行被阻塞或终止的异常状态。它可能是一个超时、一个连接拒绝、一个未捕获的异常,或者一个死循环。

排查步骤:

  1. 确认工具是否被正确注册:检查工具注册表里有没有这个工具,名称是否匹配。
  2. 确认工具参数是否正确:把参数打印出来,和工具文档对比。
  3. 确认外部服务是否可达:用 curl 或 telnet 测试。
  4. 确认鉴权是否有效:检查 API Key、Token 是否过期。
  5. 确认超时设置是否合理:工具调用超时、外部服务超时、模型超时。
# 测试外部服务可达性 curl -v -X POST https://api.example.com/endpoint \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"key": "value"}' \ --max-time 10 # 检查端口是否开放 telnet api.example.com 443 # 查看 DNS 解析 nslookup api.example.com

注意事项:

工具层的超时设置一定要比外部服务的超时设置长。比如外部 API 超时是 10 秒,工具层超时至少设 15 秒。否则外部服务还没返回,工具层就先超时了,你会误以为是外部服务的问题。

3.4 模型层:请求失败的真实原因

模型层的错误码通常比较明确,但“明确”不等于“好排查”。因为模型层的错误可能是上游传过来的,也可能是模型服务本身的问题。

排查清单:

  • 请求体是否符合模型 API 的格式要求
  • 请求体大小是否超过限制
  • 是否触发了限流(429)
  • 鉴权是否有效(401/403)
  • 模型名称是否正确
  • 是否支持请求中的参数(比如某些模型不支持tools参数)
# 模型请求排查示例 import requests payload = { "model": "gpt-4", "messages": [{"role": "user", "content": prompt}], "temperature": 0.7, "max_tokens": 1000 } try: resp = requests.post( "https://api.example.com/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json=payload, timeout=30 ) print("Status:", resp.status_code) print("Body:", resp.text) except requests.exceptions.Timeout: print("请求超时") except requests.exceptions.ConnectionError: print("连接失败")

常见问题速查:

错误码含义排查方向
401未授权API Key 是否正确、是否过期
403禁止访问权限是否足够、IP 是否在白名单
429限流降低请求频率、申请提额
500服务端错误模型服务商问题,重试或联系支持
502/503网关错误服务暂时不可用,重试
504网关超时请求处理时间过长,优化请求

3.5 结果解析层:模型返回了,但解析失败

这一层经常被忽略。模型返回了 200,你以为成功了,结果解析的时候报错。

常见问题:

  • 模型返回的不是合法 JSON
  • 模型返回的 JSON 结构和你预期的不一样
  • 模型返回了多余的解释文字,导致 JSON 解析失败
  • 模型返回了空内容

排查方法:

import json response_text = model.call(prompt) print("Raw response:", repr(response_text)) try: data = json.loads(response_text) except json.JSONDecodeError as e: print("JSON 解析失败:", e) # 尝试提取 JSON 部分 import re match = re.search(r'\{.*\}', response_text, re.DOTALL) if match: data = json.loads(match.group()) else: raise

注意事项:

不要假设模型一定会返回合法 JSON。即使你在提示词里明确要求了,模型也可能返回带 markdown 代码块标记的 JSON,或者前后加了解释文字。解析层一定要做容错处理。

4. 常见问题与排查技巧实录

这一章我把实际项目中遇到的高频问题整理成速查表,并补充一些独家避坑技巧。

4.1 高频问题速查表

现象可能根因排查动作修复方案
Agent 突然终止工具层未捕获异常查看 TOOL 层最后一条日志增加 try-catch,返回结构化错误
模型请求 422请求体格式错误打印请求体,检查特殊字符转义特殊字符,校验 JSON
工具调用超时外部服务慢curl 测试外部服务增加超时,增加重试
内存持续增长大对象未释放查看容器内存监控限制并发,及时释放对象
Agent 卡死循环依赖或死锁查看步骤日志增加最大步骤数,增加超时
结果不完整规划层步骤缺失打印规划结果优化提示词,增加校验
鉴权失败Token 过期检查 Token 有效期自动刷新 Token
连接被拒服务未启动telnet 测试端口启动服务,检查端口

4.2 独家避坑技巧

技巧一:给每个工具调用加唯一 ID

这样在日志里可以追踪一次工具调用的完整生命周期:开始、参数、结果、耗时、异常。

import uuid call_id = str(uuid.uuid4())[:8] logger.info("[TOOL_CALL=%s] start tool=%s args=%s", call_id, tool_name, args) try: result = tool.call(args) logger.info("[TOOL_CALL=%s] success result=%s", call_id, result) except Exception as e: logger.error("[TOOL_CALL=%s] failed error=%s", call_id, e)

技巧二:失败链日志要包含上下文

不要只记录错误信息,还要记录当时的输入、规划结果、工具参数、模型请求体。这样你才能复现问题。

技巧三:给 Agent 执行加“黑匣子”

把每次 Agent 执行的完整日志(输入、规划、工具调用、模型请求、模型响应、解析结果)写入一个单独的文件。出问题时,直接看这个文件,不用在大量日志里翻找。

import json from datetime import datetime def save_blackbox(execution_id, data): filename = f"blackbox/{execution_id}_{datetime.now().isoformat()}.json" with open(filename, "w") as f: json.dump(data, f, ensure_ascii=False, indent=2)

技巧四:区分“可重试”和“不可重试”错误

不是所有错误都值得重试。网络超时、限流可以重试;参数错误、鉴权失败重试也没用。

RETRYABLE_ERRORS = ["timeout", "429", "502", "503", "504"] def should_retry(error_code): return any(code in str(error_code) for code in RETRYABLE_ERRORS)

技巧五:用最小复现法定位问题

当失败链很长时,不要从头到尾查。把问题拆成最小可复现单元:单独调用工具、单独调用模型、单独解析结果。哪个单元失败,问题就在哪里。

4.3 一个完整的排查实录

最后分享一个我最近排查的案例,把上面的方法串起来。

现象:Agent 在执行到第 3 步时,报agent execution terminated due to error,日志里只有这一行。

第一步:看错误码。粗错误码,没有更多信息。需要看 LAYER 标签。

第二步:看日志。发现最后一条日志是[LAYER=TOOL] tool=search_api args={...},没有对应的 result 或 error。说明工具调用开始了,但没有返回。

第三步:查工具层。单独调用 search_api,发现它依赖一个外部服务。用 curl 测试外部服务,返回 504 网关超时。

第四步:查外部服务。联系外部服务方,确认是他们那边的一个数据库查询慢,导致接口超时。

第五步:修复。在工具层增加超时设置(15 秒),增加重试机制(最多 2 次),增加降级方案(超时后返回缓存数据)。

结果:Agent 不再因为外部服务慢而整体失败,而是能在超时后走降级路径,继续执行。

这个案例的关键点是:粗错误码只是入口,真正的根因在失败链的最末端。如果没有 LAYER 标签和工具调用日志,我可能还在怀疑模型。

4.4 工具选型:排查辅助工具推荐

工具用途备注
curl -v测试 HTTP 接口看请求头、响应头、状态码
telnet测试端口连通性快速判断服务是否可达
jq格式化 JSON方便查看请求体和响应体
htop查看进程资源排查内存、CPU 问题
docker stats查看容器资源排查容器内存、CPU 限制
tcpdump抓包排查网络层问题
strace跟踪系统调用排查文件、网络、信号问题

提示:tcpdump和strace需要一定权限,生产环境慎用。优先用日志和监控排查。

5. 把失败链变成排查习惯

排查 Agent 失败,最怕的就是“猜”。猜模型不行、猜网络不好、猜工具挂了。猜来猜去,时间浪费了,问题还在。

我现在的习惯是:每次 Agent 失败,先建失败链,再逐层排查。失败链不是画给别人看的,是画给自己看的。把链路写下来,把每一层的状态标出来,断点自然就出现了。

具体来说,我会做三件事:

第一,日志必须带层标签。没有层标签的日志,在排查时等于没有日志。

第二,每个工具调用必须有唯一 ID 和完整上下文。这样我才能追踪一次调用的完整生命周期。

第三,每次排查完,把根因和修复方案记下来。下次遇到类似错误码,直接查记录,不用重新排查。

这套方法我用了大半年,Agent 项目的平均排查时间从原来的几个小时降到了十几分钟。不是因为我变聪明了,而是因为我不再瞎猜了。

最后再分享一个小技巧:如果你的 Agent 框架支持,给每个执行阶段加一个“健康检查”钩子。比如工具调用前检查外部服务是否可达,模型请求前检查 Token 是否有效,结果解析前检查 JSON 是否合法。这样很多问题在发生之前就能被拦截,而不是等到 Agent 整体失败才发现。

这个内容后续还可以这样扩展:把失败链和监控系统对接,每个层的异常自动上报,形成告警。这样你甚至不用等用户反馈,系统自己就能告诉你哪里快断了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询