Planning 决定「做什么」,Action 决定「怎么做」。这一跃看似简单,却是 Agent 从「能说」到「能做」的关键一公里——也是 Agent 风险最高的模块。规划错了顶多绕远路,行动错了可能转错账、发错邮件、删错数据。
Action 模块接收 Planning 输出的「下一步动作」,把它翻译成对真实世界的操作,再把结果(观察)回传给 Memory 与下一轮 Planning。
输入: Planning 输出的动作指令 (如 "调用搜索工具,query=财报") ↓ [Action 行动模块] ├── 1. 解析动作 (工具名 + 参数) ├── 2. 校验权限 (是否允许调?参数合法?) ├── 3. 执行动作 (调用工具/运行代码/直接回答) └── 4. 提取观察 (结果格式化 + 成功判定) ↓ 输出: 观察结果 (写回 Memory,喂给下一轮 Planning)
注意 Action 模块内部的 4 个子步骤——这不是「调用一下就完事」,而是包含解析、校验、执行、提取的完整流水线。其中校验这一步常被新手忽略,却是 Agent 安全的关键闸门(见 2.4.4)。
💡 Action 与工具的关系:Action 是 Agent 的「模块」,工具(Tool)是 Action 调用的「资源」。一个 Action 模块可以管理成百上千个工具;每个工具是一个可被 LLM 调用的函数/API/能力。Action 模块的工作,是把 LLM 的「调用意图」安全地落到具体工具上。
Agent 能执行的动作可分三类,风险与复杂度递增:
| 动作类型 | 实现方式 | 副作用 | 风险 | 详见 |
|---|---|---|---|---|
| 直接回答 | LLM 生成文本,不调工具 | 无 | 极低 | — |
| 工具/函数调用 | 调用预注册的 API/函数 | 视工具而定 | 中 | 第 6.1-6.2 节 |
| 代码执行 | 在沙箱里运行 LLM 生成的代码 | 高 | 高 | 第 6.3 节 |
<svg viewBox="0 0 720 360" xmlns="http://www.w3.org/2000/svg"> <!-- 三层风险金字塔 --> <polygon points="360,40 540,160 540,320 180,320 180,160" fill="#dcfce7" stroke="#16a34a" stroke-width="2"/> <polygon points="360,40 540,160 180,160" fill="#fef9c3" stroke="#ca8a04" stroke-width="2"/> <polygon points="360,40 450,100 270,100" fill="#fce7f3" stroke="#db2777" stroke-width="2"/> <text x="360" y="70" font-size="14" fill="#831843" text-anchor="middle" font-weight="bold">代码执行</text> <text x="360" y="135" font-size="14" fill="#713f12" text-anchor="middle" font-weight="bold">工具调用</text> <text x="360" y="240" font-size="14" fill="#14532d" text-anchor="middle" font-weight="bold">直接回答</text> <text x="100" y="100" font-size="12" fill="#db2777">↑ 风险/能力</text> <text x="100" y="300" font-size="12" fill="#16a34a">↑ 频率</text> <text x="360" y="345" font-size="11" fill="#475569" text-anchor="middle">能力越强,风险越高,使用频率越低</text> </svg>
最简单的动作——LLM 信息已足够,直接生成最终答案文本,不调用任何工具。
调用预注册的工具(API、函数、数据库查询),获取信息或改变状态。
让 LLM 生成代码(通常 Python),在沙箱里运行,处理复杂数据分析或计算。
⚠️ 代码执行是 Agent 能力最大的双刃剑。它让 Agent 几乎能做任何事——但「能做任何事」也意味着「能搞砸任何事」。一个没有沙箱的代码执行模块,等于把整台服务器交给 LLM。代码执行必须配套沙箱、权限、人类在环三重保障,这是第 6.4 节的核心主题。
Action 模块需要一个执行接口(Execution Interface) 把 LLM 的动作指令翻译成实际调用。这个接口的设计是 Agent 工程的核心。
每个工具都需要向 LLM 描述三件事:
| 要素 | 含义 | 例子 |
|---|---|---|
| 名称与描述 | 工具是干什么的 | search_stock_price(ticker): 查询股票实时价格 |
| 参数 Schema | 接受什么参数、什么类型 | ticker: string (股票代码,如 AAPL) |
| 返回 Schema | 返回什么结构 | {price: float, currency: string, timestamp: int} |
LLM 根据这三要素判断「该不该调这个工具、怎么调」。这就是 OpenAI Function Calling、Anthropic Tool Use、MCP 协议等标准化的核心——让 LLM 用结构化的方式理解工具。第 6.1 节会展开。
一个 Agent 通常有多个工具,统一由工具注册表(Tool Registry) 管理:
工具注册表 (Tool Registry) ├── 信息查询类 │ ├── search(query) # 网页搜索 │ ├── get_stock(ticker) # 股价 │ └── get_weather(city) # 天气 ├── 数据处理类 │ ├── run_python(code) # 代码执行 │ └── sql_query(sql) # 数据库查询 ├── 通信类 │ ├── send_email(to,subj) # 发邮件 │ └── post_message(chan) # 发消息 └── 系统类 ├── read_file(path) └── write_file(path,data)
工具一多,新的问题就来了:LLM 怎么知道该用哪一个? 这是「工具检索(Tool Retrieval)」问题——当工具数量超过几十个,不能把它们全部塞进 Prompt(会冲淡注意力),需要按当前任务动态检索最相关的几个工具。第 6.2 节会详细讨论。
💡 工具数量与能力的悖论:工具越多,Agent 理论上能力越强;但工具太多,LLM 反而会选错、混淆。生产实践通常是「按场景分组 + 动态检索」——总库几百个工具,但每次只暴露与当前任务相关的 5-10 个给 LLM。
Action 是 Agent 风险最高的模块,必须有明确的安全闸门。本书反复强调两条铁律:
给 Agent 的权限刚好够完成任务,多余的一点不给。
| 原则 | 做法 | 例子 |
|---|---|---|
| 按任务授权 | 只给当前任务需要的工具 | 财务分析 Agent 不给发邮件工具 |
| 只读优先 | 能用只读工具就不用写工具 | 查股价用 get_stock,不用 trade_stock |
| 范围限定 | 工具内嵌范围约束 | read_file 只允许 /data/ 目录 |
| 时效授权 | 权限随会话结束而失效 | 临时授权不持久化 |
任何不可逆操作必须人类确认后才能执行。
| 操作类型 | 可逆性 | 处理 |
|---|---|---|
| 读数据、查询 | 可逆 | 直接执行 |
| 写数据、改配置 | 部分可逆 | 视情况确认 |
| 发邮件、发消息 | 不可逆 | 必须确认 |
| 转账、删除、提交 | 不可逆且高影响 | 双重确认 |
⚠️ 「能做」不等于「该做」。Agent 工程最常见的灾难,不是 Agent 不会做,而是它做了不该做的事。一道「不可逆 → 必须确认」的闸门,能挡掉绝大多数生产事故。这条闸门的成本只是一次人类点击,收益是避免一次无法挽回的损失——性价比极高,生产 Agent 必须标配。
Action 执行完后,结果不能直接丢给 LLM——需要经过观察提取(Observation Extraction):把工具返回的原始数据,转成 LLM 能高效消化的结构化文本。
| 问题 | 例子 |
|---|---|
| 数据过大 | API 返回 500 个字段,全塞进 Prompt 会爆窗口 |
| 格式不友好 | 嵌套 JSON、二进制、HTML,LLM 难直接读 |
| 噪声过多 | 返回含大量无关元数据 |
| 缺少判定 | 工具返回成功/失败,但没说「离目标多近」 |
原始返回 → [字段筛选] → [格式化] → [判定注入] → 观察文本
| 步骤 | 做法 |
|---|---|
| 字段筛选 | 只留决策相关的关键字段 |
| 格式化 | 转成 LLM 易读的键值对或表格 |
| 判定注入 | 附加「执行成功?离目标多近?」 |
工具返回 (原始): { "request_id": "abc-123", "meta": {"api_version": "v3", "server": "us-east-1", ...100 个字段...}, "data": {"price": 187.5, "currency": "USD", "timestamp": 1721400000}, "warnings": [] } 观察提取后: [工具: get_stock_price] 执行成功 价格: 187.5 USD 时间: 2026-07-20 09:30 UTC 判定: 已获取所需数据,可继续分析
💡 观察提取的质量直接决定 Agent 的收敛速度。一个糟糕的提取层会让 LLM 在「差不多成功了」和「其实还差一步」之间反复横跳。这是 Agent 工程最容易被低估、却最影响实际效果的细节之一。
工具调用不可能 100% 成功——API 会超时、参数会出错、权限会拒绝。Action 模块必须有明确的错误处理策略:
| 错误类型 | 处理 |
|---|---|
| 网络超时 | 重试 N 次(指数退避) |
| 参数错误 | 把错误信息回喂 LLM,让其修正参数 |
| 权限拒绝 | 不重试,直接报告 Planning 换路径 |
| 结果异常 | 触发 VLM/规则验证,判断是否真成功 |
| 服务不可用 | 切换备用工具或降级 |
⚠️ 错误处理的核心原则:错误信息要回喂给 LLM。很多新手在工具失败时直接吞掉错误或返回空——这会让 Agent 困惑地继续走,最终任务失败却不知道为什么。正确的做法是把错误信息格式化后送回 Planning,让 LLM 自己决定「重试、换参数、换工具、还是求助」。这正是 ReAct 范式(第 4 章)的核心循环。
Action 处于 Agent 与外部世界的交界,耦合关系明确:
| 耦合对象 | 耦合点 |
|---|---|
| Planning | 接收 Planning 的动作指令,返回观察供其再决策 |
| Memory | 观察写回 Memory;Memory 提供「上次类似工具的返回」 |
| Profile | Profile 决定「可用工具范围」,Action 强制执行该范围 |
最关键的是 Action ↔ Planning 的循环:Action 不是终点,而是「为下一轮 Planning 提供观察」的中间环节。没有观察回传的 Action 是断头路——Agent 无法迭代纠错。这个紧密循环正是第 4 章 ReAct 的精髓。
| 反模式 | 表现 | 后果 | 正确做法 |
|---|---|---|---|
| 给所有工具不给沙箱 | 代码执行无隔离 | 灾难 | 强制沙箱 |
| 无人类在环 | 不可逆操作直接执行 | 事故 | 高危确认 |
| 吞掉错误 | 工具失败返回空 | Agent 困惑 | 错误回喂 LLM |
| 观察不提取 | 原始 JSON 直塞 Prompt | 窗口爆+注意力稀释 | 三步提取 |
| 工具过宽授权 | 给一堆用不上的工具 | 误调用 | 能力最小化 |
| 只读与写操作混用 | 一个工具既能读又能删 | 误操作 | 读写分离 |
把本章四节串起来,一张完整的单体 Agent 工程架构图就浮现出来:
这张图是后续所有章节的「总参考」。读者可以从任意一节回看这张图,找到该模块在整体中的位置: