Arize AI Blog

Code mode: Why your agent should code

8.5内容质量

TL;DR · AI 摘要

代码模式使AI代理能通过编程解决复杂任务,非工程领域应用激增,Claude和Codex模型已推出非工程变体。

核心要点

  • Claude Design和Codex Work已支持非工程领域应用
  • Anthropic年收入增长达10倍,验证代码模式市场价值
  • 代码模式能处理工具调用无法解决的复杂逻辑问题

结构提纲

按章节快速跳转。

  1. 代码模式正在改变AI代理的工作方式,从工程领域扩展到全行业。

  2. 代码是当前解决代理任务的最佳工具,可处理复杂逻辑和动态工作流。

  3. LLM通过JSON参数调用工具,但面临工具定义过多导致的上下文窗口溢出问题。

  4. Claude和Codex推出非工程变体,Anthropic年收入增长10倍印证市场需求。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 代码模式的重要性
    • 优势
      • 处理复杂逻辑
      • 跨领域适用
    • 挑战
      • 工具爆炸问题
      • 上下文限制
    • 解决方案
      • 沙箱执行环境
      • 动态工作流

金句 / Highlights

值得收藏与分享的关键句。

#AI代理#代码模式#LLM#工具调用
打开原文

代码模式:为什么你的代理应该编写代码而不是调用工具

关于代理的大多数讨论都集中在编程上。无论你是用代理构建软件还是仅仅清理电子表格,代理编写代码的能力已经解锁了大量功能。编程代理不再只是工程师的专属工具;他们也适用于营销人员、设计师和分析师。由于它们帮助人们完成实际工作,编程代理正在白领工作中迅速普及,这种普及正在推动增长速度。市场证明了这一点:Claude Code 和 Codex 现在都有非工程领域的变体,即 Claude Design 和 Codex Work,Anthropic 的收入每年增长 10 倍。

那么,为什么我们会用编程代理来处理非编程任务?代码解锁了什么秘密能力?

简单的答案是,目前代码是大多数代理任务的正确工具。大型语言模型 (LLM) 可以通过代码而不是通过其他模态进行训练来解决问题。这显然是前沿实验室的关注重点:自从编程代理兴起以来,最新的前沿模型主要在软件工程基准上进行训练和评估。

你可能听说过诸如代码模式、动态工作流和沙箱等术语,并想知道为什么它们现在如此流行。毕竟,这只是一个 LLM 调用工具。这与调用函数有什么不同?

在本文中,我将解释为什么代码模式很重要,简单工具调用在哪些地方会失效,以及在为自己的代理框架添加沙盒执行环境时需要注意什么。

尝试 Arize AX

使用 Arize 构建更好的代理

追踪、评估和学习。使用 Arize AX 构建代理,今天就开始追踪你的运行。

开始使用

查看 AX 文档

更喜欢开源?尝试 Arize Phoenix,用于自托管的开源代理可观测性。

工具调用基础:它是如何工作的

在深入探讨代码模式之前,我们需要先理解什么是工具调用。工具可以扩展 LLM 的能力。例如,许多 LLM 在数学方面表现不佳。给 LLM 一个计算器,它很可能会得到正确的答案。

在最基本的层面上,循环的工作方式如下:LLM 被提供一组工具,用户提出一个问题。模型要么直接回答,要么决定信息不足并选择一个工具。如果 LLM 选择了一个工具,它会生成满足工具参数的 JSON 并停止生成。框架会为 LLM 执行该工具并将响应反馈给模型,以便模型推理下一步该做什么。有了结果在上下文窗口中,模型要么向用户生成响应,要么决定仍然缺乏足够信息并选择另一个工具。代理框架的任务是让这个循环持续进行,直到请求的任务完成。

这个循环是每个代理的核心,它在许多情况下都能正常工作。但是当代理被提供太多工具时会发生什么?

工具过多的问题

随着代理连接到越来越多的工具(通常通过多个 MCP 服务器),一些成本会迅速累积。

  • 工具定义会淹没上下文窗口。大多数 LLM 会在一开始就加载工具定义,以便模型知道它拥有什么。但随着工具数量的增加,这些定义会挤占用于推理的标记,模型的性能会随着上下文窗口的填满而下降。
  • 每个中间结果都会流经模型。让代理执行“下载会议记录并附加到Salesforce线索”操作时,完整的记录可能会经过上下文窗口两次:一次作为工具结果,另一次作为模型将其复制到下一个工具调用中。对于长时间的会议,这会增加数以千计的额外token,而且模型在转录过程中确实有可能导致数据混乱。
  • 延迟和成本会在每一轮中累积。每个工具调用都意味着一次模型轮次,而每次轮次都需要重新发送整个不断增长的上下文。包含30个小型步骤的工作流意味着需要进行30轮token生成、工具执行和上下文重建。这既慢又昂贵:你需要为30次LLM调用付费,每次提示词都比上一次更长。提示缓存可以降低费用,但无法完全消除,因为缓存的token仍然会产生成本,而每次轮次都会新增新的token。

工具调用对于构建代理至关重要。但随着工具数量、记录和中间产物的增加,"模型参与每个步骤"的模式变得昂贵且限制了代理处理长期任务的能力。上下文窗口根本无法承受这种负载。

什么是代码模式?

代码模式是解决"工具过多"问题的一种方法。让我们逐步深入探讨。

假设你有100个工具,但不想一开始就将它们全部加载到上下文窗口中。一个诱人的解决方案是只暴露两个工具:搜索和执行。搜索工具让LLM通过自然语言找到需要的工具,执行工具让它运行目录中的任意工具。就这样,100个工具变成了2个。我们解决了问题!

但等一下。这仅解决了加载100个定义的前期成本。要真正完成工作,模型仍然需要反复进行搜索和执行,现在每个步骤都多了一个之前不需要的搜索调用。我们用更简洁的前期提示词换取了更多的往返次数,因此延迟和成本问题依然存在。

这就是代码沙箱发挥作用的地方。与其直接将每个工具暴露给模型,不如给代理一个沙箱,并将你的工具作为其中的函数暴露出来。代理不再逐个调用工具,而是编写一个小型程序,通过循环和条件语句组合这些工具完成任务,只返回最终有用的输出。

现在我们只有两个工具,更少的轮次,以及显著提升的token效率。这就是代码模式的核心所在。

实践中的代码模式

代码模式不仅提高了工具使用的效率。它是一种强大的模式,因为代理现在可以编写代码来完成那些从未被构建过工具的任务。

让我们通过一个具体例子来说明。假设你不慎让信用卡号进入了某个数据集,然后要求代理进行清理。使用普通的工具调用方式,代理需要手动循环使用三个工具:

python
list_datasets()
  → get_dataset_examples("dataset-1")
  → [模型逐条阅读每个示例,将信用卡号替换为掩码]
  → update_examples("dataset-1", redacted)
  → get_dataset_examples("dataset-2")
  → ...

每个示例都需要经过上下文窗口两次,一次进入,一次返回。模型需要逐token地对可能成千上万条记录进行脱敏处理。这既慢又昂贵,每次重写都可能导致数据混乱、遗漏信用卡号或将其泄露到转录内容中。

在代码模式下,代理编写了一个程序。请注意,redactPII并不是任何人提供的工具,而是模型现场生成的:

code
const CARD_NUMBER = /\b(?:\d[ -]?){13,16}\b/g

function redactPII(example) {
  return {
    ...example,
    input: example.input.replace(CARD_NUMBER, "[REDACTED]"),
    output: example.output.replace(CARD_NUMBER, "[REDACTED]"),
  }
}

const datasets = await list_datasets()
let redacted = 0

for (const dataset of datasets) {
  const examples = await get_dataset_examples(dataset.id)
  const cleaned = examples.map(redactPII)
  redacted += cleaned.filter((e, i) => e !== examples[i]).length
  await update_examples(dataset.id, cleaned)
}

console.log(`redacted ${redacted} examples across ${datasets.length} datasets`)

代理仍然使用相同的底层工具,变化的是接口形式。与其面对N个工具模式和N次往返交互,模型现在看到的是代码API,并通过一次工具调用执行程序。

作为额外优势,信用卡号码从未进入其上下文,模型看到的只是事件的一行摘要。

代码模式并非小众技巧,而是前沿实验室在每一层架构中都在构建的通用模式。像Claude Code这样的代码工具本质上都是带有外壳、文件系统和沙箱环境的模型。正是这种代码能力使工具套件在与LLM的循环交互中表现更优。

新一代模型明确接受过在该场景下训练,许多MCP服务器也在向相同方向演进,越来越多地将工具暴露为代码API。企业选择这一方向是因为代码是模型能力提升最快的领域。

代码模式的权衡

代码模式并非全然利好。它用一组新问题替代了原有已知问题,其中一些问题十分严重。

现在你正在运行LLM生成的代码

运行LLM生成的代码需要真正的沙箱环境,代码运行的环境至关重要。生成的代码需要在隔离环境中执行(容器、微虚拟机或解释器级沙箱),并对CPU、内存、执行时间和文件系统访问设置硬性限制。网络访问需要谨慎处理:拥有开放连接的提示注入代理仅需编写代码即可窃取数据。沙箱应仅暴露用户被授权访问的函数和数据,你需要记录代理实际执行的内容,而不仅仅是其声明的内容。这些都不是容易后期添加的功能,因此必须从一开始就进行设计。好消息是隔离问题可以通过购买现成方案解决,Vercel Sandboxes和Daytona等服务商的存在正是为了解决众多团队同时遇到的这一难题。

调试难度增加

失败的工具调用是一个具有明确输入输出的离散事件。失败的程序则更复杂:错误可能出现在代理的逻辑中,出现在工具组合方式中,或出现在因始终未离开沙箱而从未被看到的中间结果中。追踪记录会变得更难阅读和评估。

可能重复模型或工具套件已具备的功能

代码模式需要的不仅是沙箱。代理还需要一种找到合适函数并学习其API的方法,这意味着需要某种形式的工具搜索。

但基础模型和编码框架正越来越多地内置工具搜索和代码执行功能。如果你在MCP服务器内部自行构建这些功能,可能会在不应该拥有这些功能的层级上重复框架已提供的能力。最佳实践仍在演变中,因此应将其视为警示而非硬性规则。

你可能根本不需要代码模式

推动代码模式出现的问题确实存在,但其严重性已不如一年前。模型现在能更好地处理更长的工具列表,框架加载工具的策略也更加智能。

对于工具数量较少的产品,普通工具调用可能是更简单安全的选择。只有当工具数量、数据量或任务时间范围超出工具调用的处理能力时,代码模式所带来的复杂性才是必要的。

代码赋予代理的能力

赋予代理编写代码的能力,使其能够实现纯自然语言推理无法企及的突破。代理现在可以即时生成工作流、大规模转换数据,并在一个程序中协调数十个工具。借助动态工作流等新范式,甚至可以通过代码而非僵化的编排层来启动和协调子代理。

在Arize,我们大量依赖代码模式。我们的代理使用代码模式管理自己的上下文窗口,将实验结果等大体积数据保留在沙箱中,并在任何数据到达模型上下文前进行过滤处理。

当追踪中出现异常时,我们的代理会直接在沙箱中编写并运行SQL,通过聚合查询精准定位问题,而非逐个工具调用地翻查结果。

工具调用让代理变得有用,但代码模式无疑帮助它们实现了扩展。模型正在为此进行优化,基础设施也已成熟,这种模式正在整个生态系统中形成共识。

我们为LLM提供的每个新接口都在扩展它的能力范围,而代码是迄今为止我们发现的最通用的接口。能够编写自身软件的代理不再局限于被赋予的工具,它可以构建问题本身所需要的一切。这种模式是否是通向AGI的路径仍有待观察,但很难不将其视为自主性方面的重要进步。