为什么你的智能体应该编写代码,而不是 JSON
大多数 Agent 框架都默认采用同一种动作模型:LLM 输出一个 JSON 块,宿主系统对其进行解析,调用工具,然后返回结果。如此循环。这种方式整洁、可审计,且几乎被普遍使用——而这恰恰是问题所在。对于超出单一工具调用的任何场景,这种架构都会迫使你编写脚手架代码来解决 Agent 本可以自行解决的问题——前提是如果允许它编写代码。
还有另一种方法:给 Agent 一个 Python 解释器,让它输出可执行代码作为其动作。一项已发布的基准测试显示,与 JSON 工具调用相比,其 任务成功率高出 20%。内部基准测试显示,平均 LLM 往返次数减少了 30%。一个围绕这一理念构建的框架在发布后不久便登顶 GAIA 排行榜榜首(验证集准确率为 44.2%)。权衡在于执行环境更加复杂——但所需的工程量是可控的,而且带来的行为增益是实实在在的。
JSON 工具调用的结构性限制
考虑一个任务:Agent 需要同时搜索餐饮方案和派对主题,然后合并结果。在 JSON 工具调用的 Agent 中,这是两次独立的 LLM 调用,两次往返:
// 步骤 1
{"name": "web_search", "arguments": "Best catering services in Gotham City"}
// 步骤 2(在观察步骤 1 之后)
{"name": "web_search", "arguments": "Party theme ideas for superheroes"}
代码动作 Agent 将此压缩为一个步骤:
results = {}
for query in ["Best catering services in Gotham City", "Party theme ideas for superheroes"]:
results[query] = web_search(query)
print(results)
即使操作是相互独立的,JSON 方法也会强制采用顺序的、逐次调用的结构。这是一个根本性的约束,而非实现细节。JSON 无法表达循环、条件判断、变量赋值,或者将输出作为一等公民在工具调用之间传递。代码则可以。
这在实践中非常重要。如果工具返回一个图像张量、一个 pandas DataFrame 或任何不透明对象,JSON 无法在下一个动作中引用该输出——你需要框架对其进行序列化和反序列化,这对于复杂类型来说会失效。而代码则将其存储在变量中。如果你需要使用不同的输入运行同一个工具十几次并汇总结果,JSON 需要十次 LLM 往返。代码一次就能搞定。
基准测试说明了什么
代码动作的实证案例主要不在于直觉——它是可衡量的。
最严谨的证据来自 2024 年发表在 ICML 上的一篇论文,该论文引入了 CodeAct 框架,并在多个基准测试中将其与 JSON 和基于文本的工具调用进行了对比。CodeAct 实现了 高达 20% 的任务成功率提升,这并非通过使用更好的 LLM,而是通过将动作表示从 JSON 更改为 Python 实现的。
另一个基于相同原理构建的框架对数百个基准测试任务进行了自己的分析。结论是:在相同的模型上,CodeAgent 使用的 步骤始终比同等的 ToolCallingAgent 少约 30%。更少的步骤意味着更少的 LLM API 调用、更低的延迟和更低的成本——这并不是因为任务变简单了,而是因为代码可以批量处理 JSON 强制序列化的内容。
第三个数据点:一个基于代码动作原则构建的系统在 GAIA 验证集排行榜上排名第一 (44.2%),在测试集上排名第二。GAIA 的问题旨在要求多步工具使用,紧密追踪现实世界的助手任务。代码动作方法跨过了这一门槛,包括大多数系统完全失败的 Level 3(最难)问题。
性能优势的存在是因为 LLM 在 Python 上经过了极其出色的训练。代码是它们在预训练中看到最多的内容。要求 LLM 编写 JSON 动作模式是要求它使用一种为机器解析优化的格式,而不是为了表达 LLM 自身的推理逻辑。相比之下,代码是思维过程的自然延伸——更接近模型内部表示计算的方式。
