loading

Loading

探索与发现

首页 Agent 工程

工具返回值类型推断校验:别让好数据混进坏数据

分类:Agent 工程
字数: (2117)
阅读: (3)
0
摘要:Agent 调用工具后,返回值不是结构合规就能直接用。本文从来源追溯、字段级异常、枚举边界、缓存代价几个角度,讲工具返回值类型推断校验的工程做法与代价。

Agent 跑长任务时,工具返回值的校验很容易被简化成“结构正确就等于数据可靠”。我们在一次客服工单场景里吃过亏:工具返回的 JSON 结构完全合规,客户 ID 也在,但那个 ID 是从历史上下文里“回忆”出来的,不是本次工具返回值里的。AWS 那篇报销工具复盘写过几乎一样的场景:create_expense_report 返回了 reportId,后续 add_expense 时 Agent 没从返回值里读,而是拿了一个前面查询过的旧 ID,所有写操作静默失败,直到对账才露馅。

所以返回值校验不止是字段在不在、类型对不对。类型推断本身是在给每个字段建立来源和可信度标签,决定它能不能参与后续推理,还是只能当“待确认的文本片段”。

类型校验的首要目标是拦住“看起来对的幻觉”

如果只拿 JSON Schema 兜底,那模型生成的任何一条符合 schema 的字符串都会被当成本次调用的真实结果。但工具层实际上会返回四种东西:本次真实返回、上次返回的缓存副本、模型从上下文里捏造的字符串、错误信息被包装成正常响应的产物。传统 JSON Schema 只能拦住第四种里明显畸形的那些(腾讯云那篇文章引用 Gartner 数据说,38% 的 Agent 故障来自参数幻觉或接口误用,这个比例和我们线上观察一个量级)。

真正有效的做法是把“本轮工具返回”和“历史上下文”分开存储,校验时明确要求 ID、链接、金额这类关键字段必须在本轮返回值里出现过一次。听起来很笨,但它拦掉了我们大约三成“张冠李戴”类错误。OpenAI Agents SDK 的做法也类似:用 Zod 或 Standard Schema 在本地解析输出,让 TS 推断出输出类型;如果配的是原始 JSON Schema,解析后的结果是 unknown,得自己填类型断言。这个设计在提醒,默认的可信边界比大家以为的窄很多,需要业务层显式确认消费这一段返回值的是谁。

用运行时校验加边界与枚举把模型填写开关缩小

类型定义往严了写这件事已经说了很多遍,真正落地时出错在“字段里数据形态混杂”的那一刻。比如中台的工单系统返回的 priority,说是枚举 high/medium/low,但老接口里返回过 P1 和数值 1。Pydantic 加了 Literal 定义就挂了,没加又被模型当作 low 解析,整批工单的紧急度全被过滤。

我们会写一条“转换层 + 原始保存”的约定:边界校验用联合类型,不放宽 schema;解析失败不抛错,而是用该字段原始字符串构造 RawUnknown 结构,同时在返回结构里塞上 validation_status。模型下一步如果需要动到该字段,prompt 中该字段节点必须带括注。这一条是从可观测角度保证,不会有人拿它继续往下推理。

异常也要按类型元分别处理

超时、空结果、权限不足被混成同一个 error,再被 Text 包裹塞回 messages 里,这类坑在框架层常常暴露。OpenHands 这一点处理得较细,工具执行结果通过 Observation 组件结构化返回,错在哪里、是不是带着 error 和 recovery_suggestion 不同形态传回去。我们更偏向 Anthropic 在工具设计最佳实践里的说法:不是拦停而是给出修复建议。字段里不设置只有“type=error”一种错误,而要把失败当结构化事件,配 parse OK、验证通过是字段。

举个例子,翻页游标失效时会返回 cursor_invalid,不会只返回字段里空字符串;时间超范围的查询会被打上 window_too_large,重试前收窄到区间。让模型拿到的不是一句话,而是能自己纠错的东西。

置信度排序不能盖过字段级判断

检索类工具经常会返回 relevance 评分或置信度,实际写代码时容易误用——排序是模型组织答案的依据,但真正阻住写污染靠的是每条返回的来源、时间和字段完整性。前面提到的那个核对思路也是如此:可信度是模型内部感觉的可信,不是在业务里就可以确认的口径。我们会把相似度排序做二次规则审查,金额 amount>0、链接出现在 source_url、时间戳在今天附近,再做一轮才能标记 verified。高风险写操作仍然会在人工接管队列里,这一层不替人吃掉。

校验成本与性能

代价要写清楚:完整逐字段校验和结构化回注会占到 Agent 端到端延迟的大约 5%~8%(在 1~3 次工具调用的任务里测的),Pydantic 严格模式下更明显。如果把它们一股脑放在 tools 循环里,长任务会因为额外 token 被撑爆,我们写了个缓存和一个降级策略——常见字段改用 validate_assignment=True,重转升到 Pydantic V2 的 Rust 侧,这个比例降下来一些;对幂等读接口,把验证结果视 key 缓存一分钟,代价基本不再体现在 P99 上。

结论说回来:工具返回的字段应该像人做交接那样追溯出处。什么样的返回是证据、什么样的返回只是草稿草草落在上下文里,最稳妥的做法是显式标注,不要依赖 schema 看起来没问题就放过去。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/agent-engineering/673.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图