CodeAgents + 结构:执行操作的更好方式

发布于 2025 年 5 月 28 日
在 GitHub 上更新

今天我们分享的研究弥合了人工智能代理设计中的两个强大范式:基于代码的表达能力和结构化生成的可靠性。我们的研究结果表明,强制 CodeAgents 以结构化 JSON 格式生成思想和代码,在多个基准测试中可以显著优于传统方法。

accuracy.png 图 1:三种方法(结构化 CodeAgent (蓝色)、CodeAgent (橙色) 和 ToolCallingAgent (灰色))在 SmolBench (GAIA, MATH, SimpleQA 和 Frames) 上的准确性比较。误差线表示 95% 置信区间。

🤔 代理行动的演变

人工智能代理需要在世界中采取行动——无论是调用 API、处理数据还是推理复杂问题。代理表达这些行动的方式已经发展了几个范式。

传统 JSON 代理:代理生成结构化 JSON 以调用工具。

{"tool": "get_weather", "arguments": {"city": "Paris"}}

这些代理通过从预定义工具列表中选择并生成 JSON 格式的调用来操作。这种调用工具的方法已通过 OpenAI 的 函数调用 API 普及,此后成为最广泛使用的工具调用方法。

它可靠,但受以下限制:

  • 有限的行动集:代理可以采取的行动只能通过预定义的工具表达,这限制了其功能。
  • 缺乏可组合性:如果任务需要组合来自多个来源的信息,JSON 代理会遇到困难,因为它们缺乏对跨工具调用维护中间状态的支持。尽管某些模型支持并行工具调用,但它们无法轻松处理一个工具的输出决定下一个行动或需要比较和一起处理结果的场景。
  • 僵硬的结构:在处理工具与需要完成的事情不完全匹配的情况下,非常有限。

代码代理:代理利用其固有的编码能力,直接编写可执行的 Python 代码。

# We can get the average temperature in 3 cities in 1 model call.
temperature_sum = 0
for city in ["Paris", "Tokyo", "New York"]:
    temp = get_weather(city)
    temperature_sum += temp
    
print(f"Average temperature: {temperature_sum / 3:.1f}°C")

这种转变,首次在论文“可执行代码行动引发更好的 LLM 代理”中以 CodeAct 形式提出,使 AI 代理能够灵活地编写任意可执行 Python 代码,以及进行工具调用。

这里的关键在于 工具直接从代码内部调用,这使得变量和状态管理更加可靠。代理可以在循环、函数和条件语句中调用工具——本质上在每个行动中生成一个动态的工具执行图!

使用 CodeAgent 的优点

  • 智能工具使用:代理根据当下发生的事情决定使用哪些工具。
  • 无限灵活性:可以使用任何 Python 功能来实现目标。
  • 测试思想的能力:代理可以提出假设并进行测试,从而使其行动更具灵活性。

然而,从 Markdown 中解析代码可能容易出错,这引出了一个命题:为什么不使用结构化生成来生成代码操作呢?

➡️ 为代码代理添加结构化输出

通过结构化输出,您可以强制 LLM 生成明确的思想和代码,作为 JSON blob。

// The "code" block gets parsed into executable Python
{
  "thoughts": "I want to find the average temperature across 3 cities.",
  "code": "temperature_sum = 0\nfor city in [\"Paris\", \"Tokyo\", \"New York\"]:\n    temp = get_weather(city)\n    temperature_sum += temp\n\nprint(f\"Average temperature: {temperature_sum / 3:.1f}°C\")"
}

关键区别在于生成是强制执行的:基本上,现在不再只是提示输出思想,然后是代码,而是使用结构化输出强制其遵守结构。

这种方法将结构化生成的可靠性与代码执行的灵活性相结合,从而实现两全其美。

  • 显式推理thoughts 字段强制代理在采取行动之前进行推理。
  • 可靠解析:JSON 结构消除了 Markdown 解析错误
  • 完整的代码表达能力code 字段保持了代码代理的所有灵活性
  • 更好的分离:规划和执行之间清晰分离

🧪 基准测试结果

我们比较了这三种范式在多个基准测试中的表现,包括 GAIA、MATH、SimpleQA 和 Frames。结果显示了一个清晰的模式:代码操作 + 结构化生成持续地提高了高性能模型的效果

在大多数有能力的模型中,结构化方法平均比常规 CodeAgent 方法高出 2-7 个百分点。

  • OpenAI 模型:通过结构化显示出最大的改进,特别是在推理密集型任务上。
  • Claude 模型:受益于结构化,其中 Claude 3.7 Sonnet 显示出特别强劲的结果。
  • Qwen 模型:通常通过结构化得到改进,尽管对于较小的模型,“结构税”(参见下一节)会悄然出现。

💡 为什么结构(通常)有帮助

解析问题真实存在

我们在 smolagents 中 CodeAgent 的实现 从 LLM 输出中提取 Python 代码,当出现以下情况时可能会失败:

  • Markdown 中的代码块格式不完整或不正确
  • 单个响应中出现多个代码块

结构化生成通过可靠的 JSON 解析消除了这些问题。

为了理解为什么结构化生成很重要,我们分析了基准测试中的 15,724 个代理轨迹。结果令人震惊:

  • 2.4% 的轨迹在首次调用时出现解析错误。
  • 首次调用解析错误的轨迹:成功率为 42.3%
  • 首次调用解析错误的轨迹:成功率为 51.3%

没有解析错误的代理轨迹的成功率比有解析错误的轨迹高出 21.3%。

这不仅仅是为了方便——解析错误会引发一连串的失败,严重影响代理的整体性能。当代理由于格式错误的代码而无法执行其第一个操作时,它通常难以恢复,从而导致次优的问题解决路径。

parsing error.png 图 2:第一步中的解析错误将代理的成功率降低了 21.3%,并将平均步骤从 3.18 增加到 4.63。

此外:强制推理过程

结构化生成和明确的 thoughts 的使用不仅提示代理,而且强制代理在行动前阐明其推理。这导致:

  • 更好的规划:代理更系统地思考问题。
  • 增强的可靠性:明确的推理能及早发现逻辑错误。

结构开销

我们的结果还揭示了一个清晰的能力阈值:模型需要足够的指令遵循能力和其预训练数据中的 JSON 覆盖率才能从结构化生成中受益。这表明结构化方法最适用于:

  • 大型、训练良好的模型
  • 具有强大指令遵循能力的模型
  • 经过结构化生成微调的模型。

结构何时失效:真实示例

当一个较小的模型(例如 mistralai/Mistral-7B-Instruct-v0.3)尝试生成结构化代码时,认知负荷变得过重,就会发生以下情况:

{
  "thought": "I need to find the height...",
  "code": "web_search(query=\"Eiffel Tower height\")\", "
}

该模型生成了语法损坏的 Python 代码:web_search(query="Eiffel Tower height")", ——注意带有额外引号和逗号的畸形字符串。这会导致立即的语法错误和执行失败。

这说明了“结构开销”:较小的模型难以同时处理 JSON 格式、Python 语法和实际的问题解决逻辑。结构化生成的认知开销可能会压垮那些在更简单的基于 Markdown 的代码生成中表现相当好的模型。

🚀 何时使用结构化 CodeAgents

✅ 在以下情况使用结构化 CodeAgent:

  • 使用功能强大的模型(32B+ 参数或前沿模型)时。
  • 任务需要复杂的推理和代码执行时。
  • 需要可靠解析代理输出时。

⚠️ 在以下情况考虑替代方案:

  • 使用在结构化生成方面表现不佳的小型模型时。
  • 简单的预定义工作流就足够时。

如何与 smolagents 一起使用:

非常简单!只需使用 use_structured_outputs_internally: 启用它。

from smolagents import CodeAgent, InferenceClientModel, GoogleSearchTool

# Configure agent for structured generation
agent = CodeAgent(
    tools=[GoogleSearchTool(provider="serper")],
    model=InferenceClientModel("Qwen/Qwen3-235B-A22B", provider='nebius'),
    use_structured_outputs_internally=True # Enable structured output
)

result = agent.run("Calculate the time for a cheetah to run across the Golden Gate Bridge")

LLM 将生成如下内容:

{
  "thoughts": "I need to find the length of the Golden Gate Bridge and the top speed of a cheetah, then calculate the time.",
  "code": "bridge_info = web_search('Golden Gate Bridge length meters')\ncheetah_speed = web_search('Cheetah top speed') ..."
}

然后“代码”部分像往常一样由代理执行:这是标准的 CodeAgent,但现在它具有 100% 的解析可靠性!

实施技巧

  1. 清晰的提示:确保您的提示明确指定了预期的 JSON 结构。
  2. 模型选择:选择具有强大结构化生成能力的模型。
  3. 选择正确的提供商: 某些 API 提供商(如 OpenAI 或 Anthropic)原生支持结构化生成。如果您通过 Hugging Face 使用推理提供商,结构化生成的支持因提供商而异。以下是支持结构化生成的提供商列表:smolagents 中模型对结构化生成的支持‣

更大的图景 - 接下来是什么?

这项研究表明我们正在走向对代理架构更细致的理解。这不仅仅是关于“代理能做什么?”,而是“代理应该如何思考它正在做什么?”

也许使推理过程更明确有助于模型保持正轨。或者,也许只是更容易解析。无论哪种方式,都是一个胜利。

但这仅仅是个开始。还有很多问题需要探索:

  • 还有哪些结构改进会有帮助?
  • 我们如何让它在不同的模型架构中更好地工作,特别是小型模型?
  • 这告诉我们关于人工智能推理的本质是什么?

目前,如果您正在使用 smolagents(或构建您自己的 CodeAgent 系统),请考虑尝试结构化输出。您的解析错误会减少,并且您可能会看到性能显著提升!

社区

您尝试过 YAML 块标量或 Python 三引号字符串吗?这些需要更少的转义,因此它们可能会减轻“结构开销”。

除了工具调用之外,AI 代理还能灵活地编写任意可执行的 Python 代码。这里的关键在于工具直接从代码内部调用,这使得变量和状态管理更加可靠。代理可以在循环、函数和条件语句中调用工具——本质上在每个动作中生成一个动态的工具执行图。
https://spacebar-clicker.org/

注册登录 发表评论