Agent 行动模块:从"决策"到"动手"的翻译层

副标题:API 调用、代码执行与自然语言响应背后的工程原理
参考范围:本文仅梳理国外前沿文献与工业实践(ReAct、Toolformer、CodeAct、MCP 等)
阅读时间:约 25 分钟
难度:中等(需要基础 LLM 概念,我们会用你熟悉的 RNN、全连接层做类比)


0. 先画一张大图:Agent 的完整循环里,行动模块坐在哪?

别急,我们先不碰代码。想象一下你面前有一个正在工作的 Agent——比如一个帮你订机票的智能助手。它并不是"唰"地一下就把票订好了,而是经历了一个不断重复的循环。

如果把这个循环画成一张流程图,它会长什么样?

🧠 感知层
Perception
接收用户输入 / 环境反馈

🤔 推理层
Reasoning
LLM 生成 Thought

⚡ 决策层
Decision
选择工具 / 策略

🔧 行动模块
Action Module
决策 → 具体输出

🌍 执行环境
Execution Env
API / 代码 / 沙箱

📊 观察层
Observation
收集执行结果

看到那个标红的方框了吗?那就是我们今天要聊的行动模块(Action Module)。它位于"决策"和"真实世界"之间,就像一个翻译官:左边说的是高层意图(“我想查天气”),右边需要的是可执行的具体指令(GET /weather?city=Tokyo)。

听起来抽象对吧?没关系,我们一步一步来。


第一节:为什么需要"翻译层"?LLM 不是已经会输出了吗?

好,先解决第一个认知疑点:LLM 本身不就在生成文本吗?为什么还要一个专门的"行动模块"来翻译?

这是一个特别好的问题。我们先从一个你熟悉的概念锚定:回想一下 RNN 或者早期的 seq2seq 模型。它们接收输入,经过隐藏状态变换,最后输出一个概率分布——在词表上做一个 softmax,挑出一个词。这个过程是端到端的,输入是文本,输出也是文本。

但 Agent 场景下,输出不再只是给人看的自然语言了。Agent 的输出需要被机器解析和执行。这就引入了一个根本性的断裂:

LLM 的 native 输出空间 = 自然语言 token 的序列
环境的可接受输入空间 = JSON 格式的 API 参数 / Python 代码 / 数据库 SQL / 机器人控制信号

这两个空间并不重合。如果直接把 LLM 生成的自由文本丢给 API,大概率会报 400 Bad Request

所以,行动模块本质上是一个"格式对齐层"(Format Alignment Layer)。它的任务是把 LLM 的"想法"翻译成环境能听懂的"方言"。

我们用一张对比图来看清这个翻译过程:

环境可执行空间

{
tool: get_weather,
params: {
city: 'Tokyo'
}
}

🔧 行动模块
Translation Layer

解析意图
Intent Parsing

参数抽取
Slot Filling

格式封装
Format Wrapping

LLM 原生输出空间

Token: '查'

Token: '天气'

Token: '东京'

Token: '吧'

如果画成图,左边是一串漂浮的 token(像 RNN 的隐藏状态流),中间是一个红色的翻译工厂,右边是一个规整的 JSON 方块。没有中间这层,LLM 就像一位只会说母语的诗人,而环境是一位只接受 JSON 的德国工程师——他们需要一个翻译。


第二节:行动模块的三种"方言"——API、代码、自然语言

现在我们已经了解了"翻译层"的必要性。接下来看看,这个翻译层到底能输出哪几种"方言"?

国外文献中,目前主流的行动输出格式有三种,它们构成了一个光谱:从高度结构化到完全自由。

行动输出格式光谱

🔷 结构化 API 调用
JSON / Function Call
Toolformer, Gorilla
确定性最高

🟩 可执行代码
Python / Bash
CodeAct, OpenHands
灵活性中等

🟨 自然语言响应
Text / Markdown
ReAct, ChatGPT
自由度最高

2.1 第一种方言:JSON 格式的 API 调用(Function Calling)

这是目前工业界最成熟的形态。OpenAI 在 2023 年 6 月首次将 Function Calling 作为一等 API 能力发布citeweb_search:3#5,随后 Anthropic、Google 等厂商迅速跟进。

它的核心思想是:把工具定义成 JSON Schema,让 LLM 输出符合该 Schema 的结构化 JSON。这就像是给 LLM 发了一本**“填表说明书”**——你只需要按空格填参数,不要自由发挥。

我们来看一个 toy example。假设我们的词表超级小,只有两个工具:

  • get_weather(city: str)
  • calculate(expression: str)

如果用户问:“东京现在多少度?” LLM 在推理层已经决定调用 get_weather。那么行动模块要做什么?

第一步(向量级视角):LLM 的输出头(output head)实际上是在做一个多标签分类 + 序列生成的混合任务。它先决定"调用哪个工具"(可以类比全连接层的分类头),再决定"每个参数填什么值"(类比 seq2seq 的生成头)。

第二步(矩阵实现视角):在真实系统中,这通常通过约束解码(Constrained Decoding)后处理解析完成。模型要么在 logits 层面被强制输出合法 JSON(如 Outlines、Guidance 库),要么先自由生成再由行动模块解析并校验。

2.2 第二种方言:可执行代码(Code as Action)

听起来抽象对吧?代码怎么能是一种"行动输出"?

这里我们要引入一篇关键论文:Wang et al. (2024) 在 ICML 上发表的 CodeActciteweb_search:4#9。他们的核心发现是:让 LLM 直接生成可执行的 Python 代码,比让它生成 JSON 工具调用成功率提高多达 20%,而且需要的交互轮数减少 30%。

为什么?因为代码天然具备两种能力:

  1. 控制流(Control Flow)ifforwhile——一个 action 就能处理分支和循环
  2. 数据流(Data Flow):变量赋值、中间结果复用——前一个工具的输出可以直接作为后一个工具的输入

如果画成图,JSON 调用像是一排独立的积木,每块之间没有连接;而代码像是一张管道网,数据可以在里面自由流动:

渲染错误: Mermaid 渲染失败: Lexical error on line 13. Unrecognized text. ...ion TB C1["```python
cities ----------------------^

上图左边是 JSON 模式:三个孤立的 API 调用,每次都要重新把城市名写进参数。右边是 CodeAct 模式:一个 for 循环搞定,而且结果存在 results 字典里,后续代码可以直接用。

这就是 Wang et al. 说的:**“Code inherently supports control and data flow.”**citeweb_search:4#9

2.3 第三种方言:自然语言作为行动(ReAct 模式)

最后一种看起来最"弱",但恰恰是最通用的——自然语言本身也可以是一种行动。

在 Yao et al. (2023) 的经典论文 ReAct(Reasoning + Acting)中,Action 不一定是 API 调用,它可以是:

  • Search[query] —— 搜索某个关键词
  • Lookup[keyword] —— 在文档中查找
  • Finish[answer] —— 结束任务并给出答案

这些 action 的语法是自然语言,但语义是预定义的操作。它处于"完全自由文本"和"严格结构化 JSON"之间的 sweet spot。

如果画成一张三层架构图,我们可以这样理解三种方言的位置:

Layer 3: 执行层
环境

Layer 2: 行动模块
翻译层

Layer 1: 语义层
LLM 的意图

我想知道东京、伦敦、纽约的天气对比

JSON 翻译器
Function Calling

代码翻译器
CodeAct

文本翻译器
ReAct

REST API

Python 解释器

搜索引擎 / 知识库

现在我们已经了解了三种输出形态,接下来看看它们在循环中是如何运转的。


第三节:ReAct 循环——行动模块的心跳节律

在继续之前,我们先停一下。你可能在想:这些行动输出是怎么被触发的?是一次性生成,还是反复迭代?

答案是:循环。Agent 的核心不是单次前向传播,而是一个带有反馈的闭环。这正是 ReAct(Reasoning + Acting)范式揭示的真理citeweb_search:3#2。

如果把这个循环画成一张时序图,它就像心电图一样有规律的节律:

执行环境 🔧 行动模块 LLM 推理层 用户 执行环境 🔧 行动模块 LLM 推理层 用户 loop [ReAct 循环(最多 N 轮)] 任务:查三城市天气并对比 Thought: "需要查 Tokyo, London, NYC" Action: get_weather('Tokyo') 翻译:JSON 格式化 执行 API 调用 Observation: 22°C, cloudy 将观察结果注入上下文 Thought: "已得 Tokyo,继续 London" Action: get_weather('London') 执行 API 调用 Observation: 15°C, rainy 将观察结果注入上下文 Thought: "已有足够信息,生成最终答案" Final Answer: 东京 22°C 多云...(对比分析)

看到那个循环了吗?每一轮都遵循 Thought → Action → Observation 的三拍子。行动模块位于 Action 拍,它接收 LLM 的原始输出,翻译成可执行格式,驱动环境,再把环境的反馈(Observation)重新编码成 LLM 能理解的上下文。

现在,让我们用结构化伪代码把这个循环精确地描述出来。注意,这里的风格是类 Pascal/Algol 的学术标准伪代码:

Algorithm: ReAct_Agent_Loop
Input:  user_query ∈ String
        tool_set ∈ {Tool<sub>1</sub>, Tool<sub>2</sub>, ..., Tool<sub>n</sub>}
        max_iter ∈ ℕ  (默认 10)
Output: final_answer ∈ String  或  failure_signal

1.  context ← Initialize_Context(user_query, system_prompt, tool_set)
    // context 是一个消息队列,类似 RNN 的隐藏状态历史 h<sub>1:t</sub>

2.  for iter ← 1 to max_iter do
3.      response ← LLM.Generate(context)
        // LLM 生成 Thought + Action 的联合输出

4.      thought ← Parse_Thought(response)
        // 提取推理链,类似从隐藏状态解码语义

5.      action ← Parse_Action(response)
        // 提取行动指令,这是行动模块的入口

6.      if action.type = "Finish" then
7.          return action.content
            // 终止条件:LLM 决定直接回答用户

8.      if action.type = "Tool_Call" then
9.          tool_name ← action.tool_name
10.         args ← action.arguments

11.         // ═══════════════════════════════════════
            // 行动模块核心:决策 → 执行的翻译
            // ═══════════════════════════════════════
12.         validated_args ← Validate_Parameters(tool_name, args, tool_set)
            // 参数校验:类型检查、范围约束、必填项验证

13.         if validated_args = ∅ then
14.             observation ← "ERROR: Parameter validation failed"
15.         else
16.             observation ← Execute_Tool(tool_name, validated_args)
                // 沙箱执行,带超时和资源限制

17.         // 将观察结果编码为 LLM 可理解的格式
18.         context ← context ⊕ Format_Observation(observation, tool_call_id)
            // ⊕ 表示追加操作,类似 RNN 的时间步更新 h<sub>t+1</sub> = f(h<sub>t</sub>, x<sub>t</sub>)

19.  end for

20. return "Agent exceeded maximum iterations"
    // 循环终止但未完成,触发降级策略

这段伪代码有几个关键细节值得注意:

  • 第 12 行Validate_Parameters 是行动模块的安全闸口。它确保 LLM 产生的参数符合 JSON Schema,就像全连接层的输出要经过激活函数约束一样。
  • 第 16 行Execute_Tool 运行在沙箱中。这是工业界的铁律——你永远不要信任 LLM 生成的代码或 API 调用可以直接在主机上运行。Anthropic 的 Computer Use 和 OpenAI 的 Code Interpreter 都遵循这一原则citeweb_search:3#0。
  • 第 18 行Format_Observation 是反向翻译。环境返回的原始数据(可能是二进制、JSON、错误堆栈)需要被重新包装成文本,才能被注入 LLM 的上下文窗口。

第四节:从 Toy Example 到真实规模——参数填充的微观机制

别急,概念已经够多了。我们现在钻进行动模块的最微观层面:当 LLM 决定调用一个工具时,那些参数是怎么"填"进去的?

4.1 Toy Example:只有两个工具的微型世界

假设我们的 Agent 生活在一个极简世界里,只有两个工具:

Tool<sub>1</sub>: get_weather(city: string, units: "celsius" | "fahrenheit")
Tool<sub>2</sub>: calculate(expr: string)

用户的 query 是:“东京比伦敦热多少度?”

LLM 在推理层已经分解了任务:

  1. 查东京天气 → get_weather
  2. 查伦敦天气 → get_weather
  3. 计算温差 → calculate

现在到了行动模块。它需要为第一次 get_weather 调用填充参数。在向量级视角下,这类似于一个序列标注 + 生成的混合问题:

// 微观视角:单步参数生成
Input:  context = [用户 query, Thought, Action 意图]
Output: parameter_json = {city: "Tokyo", units: "celsius"}

过程:
1. 工具选择 ← argmax<sub>i</sub> P(Tool<sub>i</sub> | context)
   // 类似分类头的 softmax,在工具集上做离散选择

2. 对于每个必填参数 p<sub>j</sub> ∈ Tool<sub>i</sub>.required_params:
   value<sub>j</sub> ← Decode(context, p<sub>j</sub>.name, p<sub>j</sub>.type)
   // 类似 seq2seq 解码,但受限于 JSON Schema 的语法约束

3. 组装 ← {p<sub>1</sub>: value<sub>1</sub>, p<sub>2</sub>: value<sub>2</sub>, ...}

如果画成一张信息流动图,参数填充就像水流过管道:

结构化输出

{
tool: 'get_weather',
params: {
city: 'Tokyo',
units: 'celsius'
}
}

参数填充
生成头

city: 'Tokyo'

units: 'celsius'

工具选择
分类头

P(get_weather) = 0.92

P(calculate) = 0.08

输入上下文

用户: '东京比伦敦热多少'

Thought: '先查东京'

4.2 真实规模:从 2 个工具到 16,000 个 API

现在我们已经了解了 toy example,接下来看看真实世界有多疯狂。

Qin et al. (2024) 的 ToolLLM 项目构建了一个包含 16,000+ 真实 RESTful API 的基准测试集 ToolBenchciteweb_search:4#1。在这个规模下,工具选择不再是简单的 softmax 分类——因为类别空间太大了。

这时候,行动模块需要引入检索增强机制:

用户 Query + Thought

嵌入编码
Embedding

向量检索
Top-K 工具召回

候选工具集
~50 个相关 API

LLM 精排
In-Context 选择

最终工具 + 参数填充

这张图展示了一个两阶段漏斗:先用向量检索(类似 RAG)从 16K 个 API 中粗筛出 50 个候选,再用 LLM 的上下文学习能力精排并选择。ToolLLM 的实验表明,这种分层策略比 brute-force 枚举所有 API 的准确率高出一大截citeweb_search:4#13。


第五节:CodeAct 的深层优势——为什么代码是更好的"行动语言"

在继续之前,让我们回顾一下第二节提到的 CodeAct。Wang et al. (2024) 的论文不只是提出了一个新格式,它揭示了一个更深层的原理:代码是 LLM 的"母语"之一citeweb_search:4#9。

为什么这么说?因为现代 LLM(如 GPT-4、Claude、Llama、Mistral)的预训练数据中,代码占比极高(通常 10%-30%)。这意味着:

  1. 格式熟悉度:LLM 写 Python 函数调用的准确率,往往比写自定义 JSON Schema 更高
  2. 自调试能力:当代码执行报错时,LLM 能读懂 Traceback 并自我修正——这是 JSON 模式不具备的
  3. 组合能力:代码可以把多个工具调用、控制流、数据处理打包成一个 action

我们用一张能力雷达图(用文字描述其形状)来对比三种格式:

行动格式能力对比
(数值为相对评分 1-5)

Natural Language

结构化: ★★☆☆☆

可解析: ★★☆☆☆

组合性: ★★★☆☆

自调试: ★☆☆☆☆

LLM 熟悉度: ★★★★★

Python CodeAct

结构化: ★★★★☆

可解析: ★★★★☆

组合性: ★★★★★

自调试: ★★★★★

LLM 熟悉度: ★★★★★

JSON Function Call

结构化: ★★★★★

可解析: ★★★★★

组合性: ★★☆☆☆

自调试: ★☆☆☆☆

LLM 熟悉度: ★★★☆☆

从这张图可以看出,JSON 在"结构化"和"可解析性"上满分,但组合性和自调试能力很弱。自然语言正好相反。而 CodeAct 位于 sweet spot——它在所有维度上都保持较高水平,没有明显短板。

这也是 HuggingFace 的 Smolagents 框架选择"代码即动作"作为默认模式的原因citeweb_search:3#0。


第六节:MCP——当工具数量爆炸时,我们需要一个"通用插座"

现在我们已经了解了行动模块的内部机制。但还有一个中观层面的问题没解决:如果每个 API 都有自己的接入方式,Agent 怎么管理成百上千的工具?

想象你家里的电器——手机、电脑、台灯、吹风机——它们不需要各自发明一种插头,而是统一使用国标插座。Anthropic 在 2024 年提出的 Model Context Protocol (MCP) 就是 Agent 世界的"国标插座"citeweb_search:4#3。

MCP 的核心思想是解耦。它把 Agent 和工具之间的 N×M 集成问题,降维成 N+M 问题:

传统模式:3 个 LLM 提供商 × 5 个工具 = 15 种定制集成
MCP 模式:3 个提供商各实现 1 次 MCP + 5 个工具各实现 1 次 MCP = 8 种实现

如果画成架构图,MCP 就像一个三层网络:

🖥️ MCP Server
工具提供者

🔌 MCP Client
协议中间件
每个 Server 对应一个

🏠 Host
AI 应用环境
Claude Desktop / Cursor

Agent Runtime

Client A

Client B

Client C

Server 1
文件系统
Resources + Tools

Server 2
数据库
Resources + Tools

Server 3
Web API
Tools + Prompts

在这个架构中,行动模块不再直接对接杂乱的 API,而是对接统一的 MCP Client。Client 负责:

  1. 发现(Discovery):运行时枚举 Server 提供的工具列表
  2. 协商(Negotiation):确认参数 Schema 和能力边界
  3. 路由(Routing):把行动模块的输出翻译成 Server 能理解的 JSON-RPC 2.0 请求
  4. 回传(Callback):把执行结果标准化后返回给 Agentciteweb_search:4#5

这就像是行动模块和外部世界之间加了一个适配器层(Adapter Layer)。无论后端是 PostgreSQL、Slack API 还是本地文件系统,行动模块看到的都是统一的接口。


第七节:安全层——行动模块的"刹车系统"

在结束技术细节之前,我们必须聊一个不能回避的话题:安全

行动模块是 Agent 唯一能与外部世界产生副作用的通道。如果 LLM 被 prompt injection 攻击,恶意指令可能让 Agent 执行危险操作——比如删除数据库、转账、泄露敏感文件citeweb_search:4#4。

所以,一个生产级的行动模块必须内置多层"刹车系统":

LLM 原始输出

🛡️ 第一层:语法校验
Schema Validation
JSON 格式 / 类型检查

🛡️ 第二层:语义校验
Semantic Guardrails
参数范围 / 黑名单过滤

🛡️ 第三层:权限校验
Capability Check
该 Agent 有权调用此工具吗?

🛡️ 第四层:人工确认
Human-in-the-Loop
高风险操作需审批

🛡️ 第五层:沙箱执行
Sandbox Execution
超时 / 资源限制 / 网络隔离

环境

这五层防御就像漏斗一样,越往下通过率越低,但安全性越高:

  1. 语法校验:确保输出是合法的 JSON/Python,参数类型匹配 Schema
  2. 语义校验:检查参数值是否在合理范围(比如 transfer_amount 不能是负数或超大值)
  3. 权限校验:基于 RBAC(角色权限控制)判断当前 Agent 是否有权调用该工具
  4. 人工确认:对于删除、转账、权限提升等高风险操作,强制暂停并等待人类审批
  5. 沙箱执行:即使前面都通过了,实际执行也在隔离环境中进行,带超时和资源配额citeweb_search:3#2

Anthropic 的 MCP 规范甚至把**访问控制(Access Control)**作为协议原语,在连接建立时就进行能力协商citeweb_search:4#3。


第八节:结构化伪代码——行动模块的完整算法

现在我们已经从宏观架构聊到了微观安全机制。让我们用一段完整的结构化伪代码,把行动模块的核心逻辑封装起来。这段代码融合了前面所有的概念:ReAct 循环、工具选择、参数填充、MCP 路由、安全检查。

Algorithm: Action_Module_Translation
Input:  raw_llm_output ∈ String
        available_tools ∈ {Tool<sub>1</sub>, ..., Tool<sub>n</sub>}  // 通过 MCP 发现
        session_context ∈ Context
        safety_policy ∈ Policy
Output: execution_result ∈ Result  或  rejection_reason ∈ String

// ═══════════════════════════════════════════════════════
// Phase 1: 意图解析(Parsing)
// ═══════════════════════════════════════════════════════
1.  intent ← Parse_LLM_Output(raw_llm_output)
    // 识别输出类型:Direct_Response / Tool_Call / Code_Block / Finish

2.  if intent.type = Direct_Response then
3.      return Result{type: "NL", content: intent.text}
        // 自然语言响应,无需翻译,直接返回给用户

// ═══════════════════════════════════════════════════════
// Phase 2: 工具解析与选择(Tool Resolution)
// ═══════════════════════════════════════════════════════
4.  if intent.type = Tool_Call then
5.      tool_name ← intent.extracted_tool_name
6.      candidate ← MCP_Discover(tool_name, available_tools)
        // 通过 MCP Client 在已连接 Server 中查找匹配工具

7.      if candidate = ∅ then
8.          return Rejection{reason: "Tool not found in registry"}

9.      schema ← candidate.input_schema  // JSON Schema

// ═══════════════════════════════════════════════════════
// Phase 3: 参数填充与校验(Parameter Binding)
// ═══════════════════════════════════════════════════════
10.     raw_params ← intent.extracted_arguments

11.     // 渐进式校验(类似编译器的多趟扫描)
12.     validated ← ∅
13.     for each (key, value) ∈ raw_params do
14.         if key ∉ schema.required ∧ key ∉ schema.optional then
15.             return Rejection{reason: "Unknown parameter: " + key}
16.         if Type_Match(value, schema.properties[key].type) = False then
17.             return Rejection{reason: "Type mismatch for: " + key}
18.         if Range_Check(value, schema.properties[key].constraints) = False then
19.             return Rejection{reason: "Value out of range: " + key}
20.         validated ← validated ∪ {(key, value)}
21.     end for

22.     if schema.required ⊄ Keys(validated) then
23.         return Rejection{reason: "Missing required parameters"}

// ═══════════════════════════════════════════════════════
// Phase 4: 安全策略检查(Safety Gate)
// ═══════════════════════════════════════════════════════
24.     risk_score ← Evaluate_Risk(candidate, validated, session_context)
        // 基于工具敏感度、参数值、用户权限计算风险分

25.     if risk_score > safety_policy.auto_approve_threshold then
26.         if risk_score > safety_policy.hard_block_threshold then
27.             return Rejection{reason: "High-risk action blocked by policy"}
28.         else
29.             status ← Request_Human_Approval(candidate, validated, risk_score)
30.             if status = Denied then
31.                 return Rejection{reason: "Rejected by human operator"}
32.             end if
33.     end if

// ═══════════════════════════════════════════════════════
// Phase 5: 执行与观察(Execution & Observation)
// ═══════════════════════════════════════════════════════
34.     if candidate.execution_mode = "remote_api" then
35.         observation ← MCP_Invoke(candidate.endpoint, validated)
36.     else if candidate.execution_mode = "local_code" then
37.         observation ← Sandbox_Execute(candidate.code_template, validated)
            // 在隔离容器中运行,限制 CPU / 内存 / 网络
38.     else if candidate.execution_mode = "natural_language" then
39.         observation ← Route_To_NL_Engine(candidate.handler, validated)
40.     end if

41.     // 后处理:将原始观察转化为 LLM 友好的文本
42.     formatted_obs ← Truncate(observation, max_tokens=2000)
43.     formatted_obs ← Strip_Sensitive_Patterns(formatted_obs, safety_policy.pii_filter)

44.     return Result{type: "Observation", content: formatted_obs, tool_call_id: UUID()}

45. end if  // Tool_Call branch

// ═══════════════════════════════════════════════════════
// Phase 6: 代码块执行(CodeAct Branch)
// ═══════════════════════════════════════════════════════
46. if intent.type = Code_Block then
47.     // CodeAct 模式:直接执行 Python 代码
48.     static_analysis ← Lint_Check(intent.code)
49.     if static_analysis.severity = "error" then
50.         return Rejection{reason: "Static analysis failed"}
51.     end if
52.     
53.     observation ← Sandbox_Execute_Python(intent.code, timeout=30s)
54.     return Result{type: "Code_Observation", stdout: observation.stdout, 
                     stderr: observation.stderr, exit_code: observation.code}
55. end if

这段伪代码虽然长,但它展示了行动模块的完整生命周期:从一团 LLM 生成的原始文本,经过解析、校验、安全审查,最终变成环境可执行的指令,再把环境的反馈翻译回 LLM 能消化的文本。

这就像一条双向翻译流水线

  • 正向:LLM 语言 → 环境语言
  • 反向:环境语言 → LLM 语言

闭环结尾:这在训练和实际使用中意味着什么?

好了,我们已经从宏观的 Agent 循环,一路剥到了微观的参数校验和安全策略。现在让我们把盖子盖回去,回答一个实际问题:了解这些原理后,在训练和部署 Agent 时,我们应该怎么做?

训练阶段的启示

  1. 数据格式选择:如果你正在微调一个开源模型来做 Agent,优先选择 CodeAct 格式(Python 代码作为 action)而非自定义 JSON。因为 LLM 对代码的"熟悉度"天然更高,预训练数据中已经包含了大量代码-执行-反馈的隐含模式citeweb_search:4#9。Wang et al. 的 CodeActInstruct 数据集(7K 高质量多轮轨迹)就是一个很好的起点。

  2. 课程学习(Curriculum Learning):在训练数据中,先从单工具、单参数的 toy example 开始,逐步增加到多工具组合、嵌套参数、多轮对话的复杂案例。这与我们本文的讲解顺序一致——渐进披露不仅适合教学,也适合模型学习。

  3. 负样本工程:行动模块的校验层需要见过足够多的"错误样例"才能学会拒绝。在训练集中加入故意写错的参数、越界的数值、格式错误的 JSON,让模型学会"说不"。

部署阶段的启示

  1. 永远不要信任 LLM 的输出:即使 GPT-4 或 Claude 3.5 的准确率已经很高,行动模块的五层安全闸(语法→语义→权限→人工→沙箱)一个都不能少。Invant Labs 在 2025 年 4 月披露的 MCP Tool Poisoning Attack 证明,攻击者可以在工具描述中嵌入恶意指令,诱导 Agent 执行未授权操作citeweb_search:4#4。

  2. 采用 MCP 标准化工具接入:如果你的 Agent 需要对接超过 5 个外部工具,强烈建议实现 MCP Server/Client 架构。这不仅能降低集成成本,还能获得动态发现、统一鉴权、版本协商等能力citeweb_search:4#3。

  3. 监控行动模块的"翻译失败率":在生产环境中,跟踪以下指标:

    • Schema Violation Rate:LLM 输出不符合 JSON Schema 的比例
    • Execution Error Rate:参数合法但执行失败的比例(如 API 超时、权限不足)
    • Safety Trigger Rate:安全层拦截 action 的比例
      这些指标比单纯的"任务成功率"更能反映行动模块的健康状况。
  4. 为代码执行配置强沙箱:如果启用 CodeAct,确保 Python 解释器运行在隔离容器(如 Docker、gVisor)中,禁用网络访问(除非必要),限制文件系统只能访问特定目录,并设置严格的 CPU/内存/时间配额。SWE-Agent、OpenHands 等代码 Agent 框架都遵循这一原则citeweb_search:4#12。

延伸阅读推荐

如果你想深入某个方向,以下是国外文献中各子领域的代表作:

方向 推荐文献 核心贡献
ReAct 推理-行动循环 Yao et al., 2023, ReAct: Synergizing Reasoning and Acting in Language Models 提出 Thought-Action-Observation 三拍子循环
工具学习奠基 Schick et al., 2023, Toolformer: Language Models Can Teach Themselves to Use Tools 通过自监督学习让模型学会插入 API 调用
大规模 API 调用 Qin et al., 2024, ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs ToolBench 基准,16K API 的泛化测试
代码作为行动 Wang et al., 2024, CodeAct: Executable Code Actions Elicit Better LLM Agents (ICML) CodeAct 框架,代码比 JSON 更适合做 action
API 专用模型 Patil et al., 2023, Gorilla: Large Language Model Connected with Massive APIs 检索增强微调,让模型学会调用海量 API
工具协议标准 Anthropic, 2024, Model Context Protocol (MCP) Specification 统一 Agent-工具通信协议,N×M → N+M
Agent 安全 Hou et al., 2025, Analyzing MCP Lifecycle Vulnerabilities MCP 安全分析,Tool Poisoning 攻击面
数字环境 Agent Zhou et al., 2024, WebArena: A Realistic Web Environment for Building Autonomous Agents 浏览器环境下的长程任务基准
操作系统 Agent Xie et al., 2024, OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks in Real Computer Environments 真实 OS 环境下的多模态 Agent 测试

结语

我们从一张 Agent 的宏观循环图出发,逐步剥开了行动模块这个"翻译层"的内部结构:

  1. 为什么需要翻译层——因为 LLM 的 native 输出空间(自然语言 token)与环境的输入空间(JSON/API/代码)不重合
  2. 三种输出方言——JSON 工具调用(确定性最高)、Python 代码(灵活性最强)、自然语言(通用性最广)
  3. ReAct 循环——Thought → Action → Observation 的三拍子心跳,行动模块位于 Action 拍
  4. 参数填充的微观机制——从 toy example 的向量级分类+生成,到真实规模的检索增强+精排
  5. MCP 协议——当工具数量爆炸时,用统一协议把 N×M 集成降维成 N+M
  6. 安全五层盾——语法、语义、权限、人工、沙箱,缺一不可

行动模块可能是 Agent 架构中最容易被低估的组件。它不像 LLM 那样"聪明",也不像感知层那样"敏锐",但它是唯一能让 Agent 从"动嘴"变成"动手"的桥梁。没有它,再强大的推理能力也只是纸上谈兵。

希望这篇文章帮你建立了从宏观到微观的完整认知地图。下次当你调用 OpenAI 的 function_call、配置 Anthropic 的 MCP Server、或者调试 HuggingFace Smolagents 的 CodeAgent 时,你会知道在那几行简洁的 API 调用背后,藏着怎样一套精密的翻译、校验、执行与反馈机制。

我们下篇见。


本文所有架构图使用 Mermaid 绘制,伪代码遵循结构化 Pascal/Algol 风格。参考文献均为国外前沿论文与工业标准,未引用中文二手资料。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐