当 Agent 调用 API 时,到底发生了什么?
从 LLM 输出 token 到真实系统调用,拆解 Agent 工具调用的完整链路。每一步都可能出错,而大部分框架只帮你做了前两步。
每次看到 Agent 调工具的时候,我脑子里都会浮现一个画面:LLM 吐出一段 JSON,然后奇迹般地,一个 HTTP 请求就发出去了。
但奇迹的背面是工程。那段 JSON 从 LLM 的输出端走到系统的网卡,中间经过了整整五层转换,每一层都在做不同的事。而大部分 Agent 框架只帮你封装好了前两层,后面三层——如果你不自己处理——就会变成线上问题。
第一层:结构生成(LLM 内部)
LLM 本身并不知道什么是"API"。它只知道概率分布。
当你给 LLM 的 system prompt 里描述了一个工具的 JSON Schema,并且告诉它"如果用户需要查天气,就输出以下格式",LLM 内部发生的事是:
- 你的 tool schema 被序列化成了一堆 token,塞进了 prompt 的上下文
- LLM 在生成时,通过 instruct fine-tuning 学习到的模式,会倾向于在合适的时机输出符合 schema 的 JSON
- 但这个 JSON 的正确性完全取决于 LLM 的"推理能力"(或者说,训练数据中类似 pattern 的覆盖程度)
这一层的问题: LLM 输出的 JSON 可能格式不对、字段缺失、参数值超出枚举范围。这不是 bug,这是 LLM 的本质决定的。
所以所有靠谱的 Agent 框架都会做一件事:对 LLM 输出做结构校验。校验不过就重试,或者给 LLM 回传一个 "你输出的 JSON 有问题,原因是 XXX,请重新生成" 的错误消息。
第二层:JSON 解析与参数绑定
LLM 吐出一段文本后,框架要做的事:
- 从文本中提取出 JSON(可能是夹在 ```json 标记里的)
- 用 JSON Schema 验证它的结构
- 把 JSON 字段映射到实际函数的参数上
这层看起来简单,但有个容易被忽略的细节:类型转换。
比如 LLM 输出 "temperature": "28.5"(字符串),但实际的 API 参数期望的是一个 float。如果框架不做类型 coercion,这个请求就会在函数入口处报 TypeError。
另一个容易被忽视的问题:LLM 可能会输出 JSON Schema 之外的额外字段。这些字段不该被忽视。好的做法是严格校验,只提取 Schema 中定义的字段,拒绝多余字段。拿到的字段少可以警告,但是拿到多余的字段说明 LLM 可能"过度思考"了。
第三层:函数调度与错误边界
参数绑定完成后,框架需要真正去调用那个函数。
这一层的设计决定了你的 Agent 系统有多"稳"。好的函数调度层应该做到三件事:
超时控制:每个工具调用都应该有独立的超时时间。一个卡住的 API 请求不该阻塞整个 Agent 循环。
错误分类:网络错误和业务错误需要分开处理。前者应该重试,后者应该返回给 LLM 让它决定下一步。
幂等性保障:如果 LLM 因为网络超时而重试了同一个工具调用,系统不应该产生重复数据。这要求你的工具设计本身是幂等的,或者框架层面做去重。
在实践里,这层通常被设计成一个可插拔的执行器接口——你可以针对不同的工具类型配置不同的超时策略和重试逻辑。关键 API 调用可以设 3 次重试加指数退避,而本地计算函数可能一次失败就直接报错。
第四层:网络传输与协议适配
如果被调用的工具是一个远程 API(大部分情况下都是),那函数调度层最终要发出一个 HTTP 请求。
这一层的问题往往来自网络环境的不可靠性,而不是代码逻辑。
实践中最常见的三个坑:
SSL 握手失败:特别是在某些网络环境下,海外 API 服务的 SSL 连接经常因为中间网络设备的 TLS 协商问题而失败。一个务实的解法是用标准库的 urllib.request 配合自定义 SSL 上下文,或者用 HTTP/1.1 而非 HTTP/2。
连接池耗尽:如果你的 Agent 要并发调用多个工具,每个调用都新建一个连接,很容易把系统的文件描述符耗尽。解决方案是使用连接池复用。
DNS 解析超时:容易被忽略的一点。一次 DNS 解析失败可能导致整个 Agent 流程卡住几十秒。设置合理的 DNS 超时和备用 DNS 是必要的。
第五层:结果解析与回传
API 返回了响应数据,现在要把这个结果"喂回"给 LLM。
这一层的核心问题是:怎么把结构化的 API 响应转换成 LLM 能理解的自然语言描述?
简单的做法是把整个 JSON 原样塞回去。但这样有两个问题:
-
token 浪费:一个包含 200 个字段的 API 响应,可能只有 5 个字段对 LLM 的下一步决策有用。把全部 200 个字段都塞进上下文,既浪费 token 又稀释了注意力。
-
信息过载:LLM 在处理大量无关字段时,决策质量会下降。它可能被一个不重要的字段分散注意力,或者因为信息太多而忽略了关键信号。
好的做法是对 API 响应做摘要——就像人在看日志时不会逐行读,而是扫一眼关键指标一样。你可以为每个工具定义响应处理器,把原始响应精简为 LLM 关心的那几条信息。
这层可以叫做 Response Shaper——一个可配置的响应转换层。它存在的意义是:API 返回的是数据,而 LLM 需要的是信号。两者之间的差距,就是这层要填的。
这条链路的启示
如果你从头到尾看完这五层,会发现一个规律:
前三层是通用逻辑——任何 Agent 框架都会做。后两层是工程细节——大部分框架不做,或者做得不好。
但后两层恰恰是线上问题和用户体验的分水岭。
网络传输层的稳定性决定了你的 Agent 会不会崩;结果解析层的质量决定了你的 Agent 聪不聪明。
所以如果你在搭建自己的 Agent 系统,不要把精力只花在 prompt 优化上。看看你的工具调用链——从 LLM 输出到真实系统调用之间的那条路,每一层都检查过了吗?
评论
发表评论