Agent 工具返回值怎么设计:成功回什么、失败回什么、报错给谁看

2026-07-29

数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。

agent 拿到工具返回之后动作开始变形——已经成功的写操作又做一遍、对着一句报错重试到轮次耗尽、或者干脆编一个结果继续往下走——多数人第一反应是模型不听话,于是去加约束、换更贵的模型。真正的成因通常在返回值这一层:你回给模型的那串字符串,没有把它做下一步判断所需要的信息带上。模型不是不听话,是没得听。

这里先说清楚本篇和站内两篇相近文章的分工:工具的名字、描述、参数 schema 该怎么写,看给 Agent 设计工具;agent 挑错工具、对着同一个工具空转该按什么顺序排查,看Agent 选错工具、反复调同一个工具。那两篇管的是「调用之前」。本篇只管「调用之后」——工具跑完往回吐的那段内容该长什么样,成功回什么、失败回什么、错误信息到底写给人还是写给模型。

一、先分因:现象都出在返回之后,成因只有四类

排查的第一步不是改代码,是把最近几轮的工具返回原文完整打出来看一眼。很多人调 agent 的时候只看模型说了什么,从来没看过工具真正回了什么,于是一直在错误的一层上折腾。

把返回原文摊开之后,成因基本落在四类里:

  • 信息缺失:返回里没有模型做下一步决策必需的字段,比如新建资源之后没回 id。
  • 状态不明:返回里分不清「这次真的改了」还是「本来就是这样」,模型只能靠猜。
  • 错误语义丢失:所有失败都长一个样,模型没法区分该重试、该改参数、还是该停下叫人。
  • 体积失控:一次返回塞回来几千上万字,把上下文挤爆,模型开始忘记早前的指令。

下面这张表按现象倒推成因,用它把手上的问题先归位:

现象大概率成因怎么验证处置动作
同一个写操作被重复执行成功返回里没有可识别的资源标识,模型分不清刚才那次是不是自己做的打印最近几轮返回,看成功分支里有没有唯一 id、版本号或明确的动作词成功返回补资源标识与动作类型,见第二节
对着同一个报错重试到轮次上限失败返回没有区分可重试与不可重试看返回文本里有没有明确的「能不能重试」结论;把 HTTP 状态码单独打出来按第三节的四分类改造错误结构,401/403 明确标注不要重试
模型宣称做完了,实际什么都没发生工具实现里异常被吞掉,返回了空串或者一句 ok搜工具实现中的裸 except / catch,看后面是不是直接 return 成功异常一律不许吞,见避坑清单第 1 条
返回一长串之后模型开始跑偏、前面的约束不认了返回体积失控统计单次工具返回的字符数,以及一轮任务里的累计量改成摘要加句柄再加分页,见第二节
模型把报错原文当文案抄进最终答复错误信息写成了给人读的一整段自然语言看 error 字段是不是长句散文,有没有栈信息拆成结构化字段加动作建议,详细内容进日志,见第四节
同一个参数反复填错校验失败的返回只说了「参数不合法」看校验失败分支回了什么,有没有字段名和期望格式报错里带字段名、期望格式和一个合法示例

第三行那种「谎报成功」值得单独警惕,因为它不报错、不中断,很容易一路走到线上才被发现,相关的识别办法在Agent 谎报任务成功里讲得更细。

二、成功返回:回「做了什么、结果在哪、下一步能干什么」

成功分支最常见的写法是把上游响应原样透传,或者干脆 return "ok"。前者体积失控,后者信息为零,两头都不对。

一个可用的成功返回,回三样东西就够了:

  1. 动作类型。用固定词区分 created / updated / unchanged / deleted。这一项是防重复执行的关键——模型看到 unchanged,就知道目标状态已经达成,不必再来一次。只回 ok: true,它没法判断刚才那次调用到底改没改东西。
  2. 资源标识。id、版本号、路径,凡是后续能拿来引用的都要回。没有标识,模型下一步想操作同一个对象时只能靠名字模糊匹配,很容易匹配到别的。
  3. 一句人话摘要加下一步可用动作。摘要压缩事实,动作列表把选择空间收窄。

写成结构大概是这样:

{
  "ok": true,
  "action": "created",
  "resource_id": "doc_8f21",
  "summary": "已创建文档,3 段正文,当前为未发布状态",
  "next": ["publish_doc(resource_id)", "get_doc(resource_id)"]
}

大结果一律不要整坨回。正确姿势是回句柄:总条数、前几条摘要、一个能继续取的偏移量或游标,让模型显式再要一次。截断这件事必须显式告知,否则模型会以为自己看到的就是全部,然后基于残缺数据下结论:

MAX_CHARS = 2000  # 按你自己的上下文预算定,不是什么通用常数

def clip(text: str, offset: int = 0) -> dict:
    chunk = text[offset:offset + MAX_CHARS]
    truncated = offset + len(chunk) < len(text)
    return {
        "content": chunk,
        "truncated": truncated,
        "total_chars": len(text),
        "next_offset": offset + len(chunk) if truncated else None,
        "hint": "内容已截断,用 next_offset 继续读取" if truncated else "",
    }

还有一点容易被忽略:返回的结构要稳定。同一个工具在不同分支回不同形状的 JSON,模型解析起来就得每次重新猜。字段可以为空,但键要在。结构不稳带来的连锁反应,结构化输出不稳定那篇有更多例子。

三、失败返回:先把错误分四类,再决定回什么

「失败」在工具这一层是个太粗的概念。模型面对失败只需要回答一个问题:下一步我该干什么。所以错误必须先分类,分类的依据就是「下一步动作不同」。

A 类,瞬时故障,等一下再来。 典型是 429、500/502/503,以及 ETIMEDOUT、ECONNRESET 这类网络层错误。返回里标 retryable: true,并说明应当退避后重试。具体退避多久、允许重试几次,各家服务的规则不同而且会调整,以官方最新说明为准——工具返回里别硬写一个数字当真理,把节奏交给调用侧的重试策略去管。重试策略本身怎么设计,见Agent 任务失败后的重试

B 类,调用方自己能修。 参数校验失败、格式不对、引用了不存在的资源。这类要 retryable: true,但重试的前提是改参数,所以必须把「改哪里、改成什么样」写清楚:

{
  "ok": false,
  "code": "INVALID_ARGUMENT",
  "field": "start_date",
  "expected": "YYYY-MM-DD",
  "example": "2026-07-01",
  "retryable": true,
  "agent_action": "修正 start_date 的格式后重新调用本工具"
}

只回一句「参数不合法」,模型只能穷举着试,试几轮就把轮次耗光了。

C 类,必须人来处理。 401、403、凭据过期、权限不足、提示额度已用尽、内网自签证书导致的证书链校验失败——这些无论重试多少次结果都一样。返回里 retryable: false,并且把动作写成「停止重试,向使用者报告需要人工处理什么」。这一类如果标错,代价最直接:agent 会在一个注定失败的调用上把整个轮次预算烧完。

D 类,其实不是错误。 查询执行成功但命中 0 条、目标状态本来就符合预期、幂等操作发现已经做过。这些一律走成功分支,用 action: "unchanged" 或者 count: 0 表达。把空结果当错误抛出去,模型会误以为系统坏了,转而去做各种「修复」动作,那才是真正的灾难开端。

四类之外还有一个判断:这个错误该不该让模型看见。有些底层重试、连接池抖动,工具内部消化掉就好,回给模型只会增加噪声。原则是——凡是不改变模型下一步动作的信息,就不要往返回里放。

四、错误信息给人看还是给模型看:答案是两份

这是本篇标题里那个问题的正面回答:不要试图用一段文字同时服务两个读者。人和模型需要的东西几乎不重叠。

给模型的那份,特征是短、结构化、只含决策依据:错误分类、是否可重试、下一步动作、必要的字段名。不含调用栈,不含内部主机名、连接串、SQL 原文、任何凭据片段。理由有两条:一是这些内容对模型的决策毫无帮助,纯占地方;二是模型可能把返回原文当素材复述进最终答复,敏感信息就这么漏出去了。

给人的那份,特征是全:完整调用栈、上游原始响应、请求耗时、时间戳。这份写进日志,返回体里只带一个可关联的 trace_id,人拿着这个 id 去日志里捞。这样做还有个附带好处——排查时你能确认模型看到的到底是什么,不用靠猜。

措辞上也有讲究。给模型的错误信息要写成对执行者的指令,不要写成对用户的道歉。「请求处理失败,请稍后再试」这种话对模型是零信息量;「凭据无效,停止重试,需要使用者更新访问令牌」才是可执行的。

人工复现的时候,最省事的办法是把这次调用还原成一条 curl,注意用 -i 把状态码和响应头一起看到,凭据走环境变量不要写死在命令里:

curl -i -X GET "https://api.example.com/v1/docs/doc_8f21" \
  -H "Authorization: Bearer $API_TOKEN"

看到 401 还是 403、有没有限流相关的响应头,往往一眼就能定位到底是 C 类还是 A 类。这一步是人做的,不该让 agent 代劳——它看不到你的终端,也不该拿到你的令牌。

五、什么情况下别再折腾

返回值这一层是可以雕到没完的,得给自己划止损线。

改了两轮返回结构还是老样子,就换层排查。 如果字段该补的补了、错误该分类的分类了,模型的行为没有肉眼可见的变化,那问题大概率不在返回值,而在工具选择或者上下文管理。回到Agent 选错工具、反复调同一个工具那条线去查,别在返回值里继续加字段——字段越加越长,反而把上下文占满了。

根因是环境问题时,返回值只能让它更快停下,修不了它。 401/403、证书链校验失败、DNS 解析不到,这些改再漂亮的错误结构也不会变成成功。此时正确的收益预期是「让 agent 三秒内停下并说清需要人做什么」,而不是「让 agent 自己搞定」。把这条想明白,能省下很多无谓的调试时间。

回滚点要先留好。 工具返回结构本质是接口契约,改它会同时影响 agent 行为和下游解析。开工前起一个独立分支,改完对比一下影响面:

# 开工前:从基线分支拉一条独立分支(main 换成你自己的基线分支名)
git switch -c fix/tool-return-shape

# 改完并提交之后,再看影响面;三点写法只统计本分支新增的提交
git diff --stat main...HEAD

第二条要在提交之后跑才有输出——三点比较的是「基线与本分支的共同祖先」到 HEAD,工作区里没提交的改动不计入。想在提交前就看一眼,用 git diff --stat main 这种两点写法,它会把未提交的改动一起算上。

真要退回去,共享分支上用 git revert <sha> 生成一个反向提交,而不是 git reset --hard 硬拽——后者会把别人已经拉走的历史搞乱。

有一条路是「把决策收回来」。 如果某个流程无论返回值怎么设计,模型都稳不住,考虑把多步判断合并进工具内部,一次调用把事做完,只回最终结果。少一次模型决策,就少一次不确定性。这不是认输,是把不适合交给模型的部分放回代码里。

六、避坑清单

except 吞掉异常然后返回成功。 会踩是因为写工具的时候图省事,想着「别让 agent 崩」,于是加了个兜底捕获,结果把真实故障也一起吃了。避法:捕获必须落到具体异常类型,兜底分支也要返回 ok: false 并带上错误类型;宁可让 agent 看到失败,也不要让它基于假成功往下走。

把上游响应原样透传回去。 会踩是因为透传最省代码,而且本地测试时数据量小看不出问题。真实数据一上来,几千字的 JSON 直接把上下文挤爆。避法:工具层做投影,只留模型用得上的字段,大结果走摘要加句柄。

用自然语言表达状态。 「好像没找到相关内容」这种话,模型有时理解成失败、有时理解成空结果,行为就随机了。避法:状态用固定枚举字段承载,自然语言只做补充说明,不承担语义。

把空结果当错误抛。 会踩是因为很多内部接口习惯了「查不到就 404」。避法:在工具层把「查询成功但 0 条」翻译成成功返回加 count: 0,别让模型误判系统故障。

错误信息里夹带敏感内容。 数据库连接串、完整 SQL、内网地址、令牌片段,都可能跟着上游报错一起被拼进返回。模型不知道哪些不能说,复述出去就是事故。避法:错误信息走白名单拼装,只允许列出的字段进返回体,其余进日志。

成功返回不带资源标识。 会踩是因为创建接口经常只回一个 200,写工具的人没觉得需要补。后果是重复创建。避法:任何写操作的成功返回,必须带上能唯一定位结果的标识和动作类型。

上游改字段,工具层静默跟着变。 会踩是因为工具直接引用上游字段名,上游一改,返回结构就变了,而且不报错。避法:工具层做一次显式映射,并为返回结构写一个最小的契约测试,字段缺失就让测试红,而不是让 agent 在线上迷惑。

收个尾

工具返回值不是函数的返回值,它是喂给模型的输入。判断一段返回设计得好不好,只用一个标准:模型读完这段内容,能不能唯一确定下一步动作。不能,就是缺信息;能但读得很累,就是噪声太多。

上线前照着过一遍:

  • 成功返回里有没有动作类型和资源标识?
  • 大结果是不是摘要加句柄,截断有没有显式标注?
  • 错误分没分类,retryable 标得对不对,401/403 是不是明确写了不要重试?
  • 给模型的错误信息里有没有栈、密钥、连接串这类不该出现的东西?
  • 空结果走的是成功分支还是错误分支?
  • 返回结构在所有分支上形状一致吗?有没有契约测试兜着?

六个问题都过了,agent 那些「重复做、无脑重试、谎报成功」的毛病,大概能消掉一多半。剩下的那部分,才轮到去怀疑模型。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。