Agent框架与工具调用(Function Calling)深度实践:从协议解析到多工具编排
📋 目录
一、Function Calling协议解析
1.1 Function Calling的本质:结构化输出交互协议
Function Calling(或称为Tool Calling)是一种允许LLM与外部系统交互的协议。其核心思想不是LLM"调用"函数,而是LLM输出一个结构化的函数调用请求,由应用程序解析并执行。这种设计将"决策层"(LLM决定调用什么函数、传入什么参数)和"执行层"(应用实际执行函数调用)解耦,使得Agent系统可以独立优化两个层面。
从架构角度看,Function Calling实现了"语义接口"向"程序化接口"的桥梁转换:用户用自然语言表达意图 → LLM理解意图并映射到结构化API → 应用程序执行API并返回结果 → LLM将结果转化为自然语言回复。这个闭环使LLM能够"接触"现实世界——查询数据库、调用API、执行计算、控制设备等。
1.2 OpenAI Function Calling协议详解
OpenAI在2023年6月发布的GPT-4-0613中首次引入了Function Calling能力,此后成为业界标准。其核心协议通过Chat Completion API中的tools参数传递函数定义,LLM返回tool_calls字段指示要调用的函数。
OpenAI Function Calling 协议请求格式:
POST /v1/chat/completions
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "你是一个天气助手,可以使用工具查询天气"},
{"role": "user", "content": "北京今天多少度?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海"
},
"date": {
"type": "string",
"description": "日期,格式YYYY-MM-DD,默认为今天"
}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto" // "auto"自动选择,"none"禁止,"required"必须调用
}
响应格式(当LLM决定调用函数时):
{
"choices": [{
"message": {
"role": "assistant",
"content": null, // 可能为null,因为回复是函数调用而不是文本
"tool_calls": [{
"id": "call_xxx",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\",\"date\":\"2026-06-01\"}"
}
}]
}
}]
}
协议的关键设计点包括:1)函数通过JSON Schema格式定义参数,天然与TypeScript/JSDoc兼容;2)tool_choice参数控制函数调用的行为策略;3)支持并行调用(在同一响应中请求多个函数);4)调用结果通过tool角色返回,形成完整的调用闭环。tool_choice的三种模式在工程上非常重要:"auto"让模型自主决策,"none"强制文本回复(适用于不需要工具的对话),"required"强制调用工具(适用于必须使用工具的指令式场景)。
1.3 Anthropic Tool Use协议
Anthropic的Claude系列模型提供了Tool Use接口,与OpenAI类似但有一些关键区别。Claude的工具定义直接在请求体中通过tools数组传递,每个工具同样使用JSON Schema描述输入参数。但Claude不会在content中包含content: null,而是通过content中的tool_use块来指示工具调用(可与文本回复并存),这代表Claude可以在单次回复中既输出文本又调用工具。
Anthropic Claude Tool Use 协议请求格式:
POST /v1/messages
{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"system": "你是一个帮助用户的AI助手。",
"messages": [
{"role": "user", "content": "北京今天多少度?"}
],
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
}
]
}
响应格式:
{
"content": [
{
"type": "text",
"text": "我来查询北京的天气"
},
{
"type": "tool_use",
"id": "toolu_xxx",
"name": "get_weather",
"input": {"city": "北京"}
}
]
}
Claude与OpenAI的三个关键区别:1)Claude可以在同一条消息中同时输出文本和工具调用,而OpenAI需要通过content: null标记纯工具调用状态;2)Claude的input参数使用蛇形命名(snake_case),而OpenAI使用嵌套的arguments字符串(JSON序列化);3)Claude的工具调用ID前缀为"toolu_"(Tool Use),而OpenAI为"call_"(Call)。这些区别在构建跨模型Agent框架时需要特别注意。
1.4 Google Gemini Function Calling协议
Google的Gemini模型通过Generative AI API提供Function Calling能力。其协议设计更加贴近Google生态,使用FunctionDeclaration结构来定义函数,并通过FunctionCall/FunctionResponse来构建调用回合。
| 特性 | OpenAI | Anthropic Claude | Google Gemini |
|---|---|---|---|
| 工具定义字段 | tools[].function.{name, description, parameters} | tools[].{name, description, input_schema} | tools[].function_declarations[] |
| 参数Schema | JSON Schema嵌套 | JSON Schema(input_schema) | OpenAPI 3.0 Schema |
| 返回值格式 | tool_calls数组+tool角色 | content中tool_use块 | FunctionCall对象 |
| 并行调用 | ✅ 原生支持 | ✅ 支持(同一条回复多个tool_use) | ✅ 支持 |
| 特殊控制 | tool_choice参数 | tool_choice参数 | function_calling_config |
三大厂商的协议尽管细节不同,但核心模式一致:函数定义(JSON Schema)、函数调用请求(结构化输出)、函数调用结果反馈。理解这些差异对于构建模型无关的Agent框架至关重要。
二、Tool Schema设计与参数校验
2.1 JSON Schema驱动:从描述到验证
Tool Schema的核心是JSON Schema——一个广泛使用的JSON数据结构描述标准。每个工具的参数schema定义了参数的类型、必填性、默认值、枚举值等约束。LLM根据这些schema生成参数JSON,应用程序收到后需要进行校验以确保参数合法。Schema的设计质量直接影响函数调用的成功率和可靠性。
精心设计的schema应当遵循以下原则:1)描述要清晰——LLM会"阅读"description字段来理解参数含义,好的描述比类型约束更重要;2)枚举要明确——对于有限取值范围的参数(如排序方式asc/desc),应当使用enum来约束;3)示例要准确——部分模型支持examples字段,提供好的示例能显著提高调用成功率。
工具Schema设计示例(TypeScript类型定义):
interface ToolSchema {
name: string; // 工具名称(唯一标识符)
description: string; // 工具功能的自然语言描述
parameters: { // JSON Schema 格式的参数定义
type: "object";
properties: Record;
required: string[];
// 可选的高级校验
additionalProperties?: boolean;
$ref?: string; // 复杂类型引用
};
}
interface PropertySchema {
type: "string" | "number" | "integer" | "boolean" | "array" | "object";
description: string; // 参数含义的自然语言描述
// 字符串类型
enum?: string[]; // 枚举约束
minLength?: number;
maxLength?: number;
pattern?: string; // 正则约束
// 数字类型
minimum?: number;
maximum?: number;
exclusiveMinimum?: boolean;
// 数组类型
items?: PropertySchema; // 元素类型
minItems?: number;
uniqueItems?: boolean;
// 对象类型
properties?: Record;
required?: string[];
// 通用
default?: any; // 默认值
examples?: any[]; // 示例值(部分模型支持)
}
// 实战示例:电商商品搜索工具
const searchProducts = {
name: "search_products",
description: "搜索商品列表,支持多种过滤条件和排序",
parameters: {
type: "object",
properties: {
keyword: {
type: "string",
description: "搜索关键词,如'连衣裙'或'Nike运动鞋'",
minLength: 1,
maxLength: 100,
examples: ["2026春季新款连衣裙", "iPhone 16手机壳"]
},
category: {
type: "string",
description: "商品分类",
enum: ["服装", "电子产品", "家居", "食品", "美妆"],
default: "服装"
},
price_range: {
type: "object",
description: "价格区间过滤",
properties: {
min: { type: "number", minimum: 0, description: "最低价格" },
max: { type: "number", minimum: 0, description: "最高价格" }
}
},
sort_by: {
type: "string",
enum: ["price_asc", "price_desc", "sales_asc", "sales_desc", "rating"],
description: "排序方式",
default: "sales_desc"
},
page: {
type: "integer",
minimum: 1,
default: 1,
description: "分页页码"
},
page_size: {
type: "integer",
minimum: 1,
maximum: 50,
default: 20,
description: "每页商品数量"
}
},
required: ["keyword"]
}
};
2.2 参数校验的工程实践
即使工具schema定义再精确,LLM仍然可能生成非法参数。参数校验是Agent系统的硬性要求,需要在以下层面进行防御:Schema层校验(JSON Schema校验器如ajv)、业务层校验(语义合法性)、类型转换(字符串转数字、默认值填充)。一个可靠的参数校验流程应当处理常见的LLM生成错误:缺少必填字段、字段类型错误(LLM可能把数字写成字符串"123")、枚举值拼写错误、数值超出范围。
参数校验实现(Node.js + ajv):
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({
coerceTypes: true, // 自动类型转换(如 "123" → 123)
useDefaults: true, // 填充默认值
removeAdditional: false,
allErrors: true
});
addFormats(ajv);
function validateToolCall(schema: object, args: unknown): ValidationResult {
const validate = ajv.compile(schema);
const valid = validate(args);
if (valid) {
return { valid: true, data: args };
}
// 错误消息分类处理
const errors = validate.errors!.map(err => ({
path: err.instancePath,
message: err.message!,
// 常见错误类型:
// - "must have required property 'city'" → 缺少必填字段
// - "must be number" → 类型错误
// - "must be equal to one of the allowed values" → 枚举非法
// - "must NOT have more than 100 characters" → 长度超限
type: getErrorType(err)
}));
return {
valid: false,
errors,
// 尝试自动修复(可选)
fixedArgs: tryAutoFix(args, errors)
};
}
// 常见自动修复策略
function tryAutoFix(args: any, errors: ValidationError[]): any | null {
const fixed = { ...args };
for (const err of errors) {
if (err.message.startsWith("must be number") && typeof args[err.path] === "string") {
// 尝试将字符串转换为数字
const num = Number(args[err.path]);
if (!isNaN(num)) fixed[err.path] = num;
}
// ... 更多修复策略
}
return fixed;
}
实践中还需要处理一个常见陷阱:当LLM返回的参数包含schema中未定义的字段时(additionalProperties)。OpenAI模型对此控制较好,但某些开源模型可能会"创造"额外字段。最佳实践是在schema中将additionalProperties设为false,并在校验时严格拒绝未定义的参数。
2.3 工具注册与发现机制
在大型Agent系统中,工具数量可能达数百个。将所有这些工具的描述全部塞进每次LLM调用的上下文是不现实的(token成本高且信息稀释)。因此需要工具注册与发现机制来动态选择工具子集。常见策略包括:基于意图分类(先分类用户意图,再选择对应工具集)、基于语义检索(对工具描述做embedding,根据用户输入检索最相关工具)、以及层级目录(将工具组织成层级分类,Agent先选择类别再选择具体工具)。
架构原则:工具Schema的设计质量直接影响Agent的可靠性。一个好的Schema应当让LLM"一看就懂"——description字段用自然语言说清楚何时调用、参数含义和约束。不要吝啬description的文字,这是LLM理解工具意图的最重要信息来源。每个工具的description应该在30-200字之间,包含:工具功能概述、适用场景、使用注意事项。
三、Agent循环:Thought → Action → Observation
3.1 Agent循环的基本框架
Agent循环(Agent Loop)是Agent系统的核心模式,描述了Agent在与环境交互时不断迭代的过程。最基本的Agent循环由三个步骤组成:Thought(思考)→ Action(行动)→ Observation(观察)。Agent在每个循环中分析当前状态、决定应用哪个工具、执行工具调用、分析工具返回结果,然后进入下一个循环,直到任务完成或达到上限。
Agent循环(伪代码):
async function agentLoop(
systemPrompt: string,
userQuery: string,
tools: Tool[],
model: LLMModel,
config: AgentConfig
): Promise {
// 初始化消息历史
const messages = [
{ role: "system", content: systemPrompt },
{ role: "user", content: userQuery }
];
let iteration = 0;
const maxIterations = config.maxIterations || 10;
while (iteration < maxIterations) {
iteration++;
// Step 1: Thought - LLM思考并决定下一步行动
const response = await model.chat({
messages,
tools: tools.map(t => t.schema),
tool_choice: "auto"
});
const choice = response.choices[0];
messages.push(choice.message);
// Step 2: Action - 检查LLM是否请求工具调用
if (choice.message.tool_calls) {
for (const toolCall of choice.message.tool_calls) {
const tool = tools.find(t => t.name === toolCall.function.name);
if (!tool) {
// 工具未找到 - 返回错误
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: `错误:未找到工具 "${toolCall.function.name}"`
});
continue;
}
// 解析并校验参数
const { valid, data, errors } = validateParams(tool, toolCall);
if (!valid) {
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: `参数校验失败:${errors.map(e => e.message).join("; ")}`
});
continue;
}
// 执行工具调用
try {
const result = await tool.handler(data);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
} catch (error) {
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: `工具执行错误:${error.message}`
});
}
}
// 继续循环(需要更多轮思考行动)
continue;
}
// Step 3: Observation - LLM返回文本回复,任务完成
return {
status: "completed",
message: choice.message.content,
iterations: iteration,
messages: messages
};
}
// 达到最大迭代次数
return {
status: "max_iterations_reached",
message: "任务未能在最大迭代次数内完成",
iterations: iteration,
messages: messages
};
}
3.2 迭代控制:防止无限循环
Agent循环的重要挑战之一是防止无限循环或死循环。未加控制的Agent可能会反复调用同一个工具而不推进任务,或在两个工具之间来回切换(如先搜索再计算再搜索再计算...)。需要设置合理的迭代上限和提前终止条件。
常见策略包括:1)最大迭代次数(经验值:大多数简单任务2-5轮,复杂任务8-15轮);2)重复检测(检测到相同工具+相同参数在短时间内被连续调用时终止);3)时间超时(从系统层面设置Agent循环总时长上限,如60秒);4)语义终止(当LLM输出明确的"任务完成"信号时提前终止)。
Agent循环安全控制实现:
interface AgentGuardrails {
// 迭代控制
maxIterations: number; // 最大迭代次数,默认10
totalTimeoutMs: number; // 总超时毫秒,默认60000
// 重复检测
dedupWindow: number; // 检测窗口(最近N次调用)
dedupThreshold: number; // 相同调用允许次数
// 工具调用频率控制
maxPerTool: number; // 每个工具最多调用次数
coolDownMs: number; // 工具冷却时间(毫秒)
}
async function executeWithGuardrails(
handler: () => Promise,
guardrails: AgentGuardrails
): Promise {
const toolCallHistory: ToolCall[] = [];
const startTime = Date.now();
while (true) {
// 超时检查
if (Date.now() - startTime > guardrails.totalTimeoutMs) {
throw new AgentError("Agent循环超时");
}
// 迭代次数检查
if (toolCallHistory.length >= guardrails.maxIterations) {
throw new AgentError("超出最大迭代次数");
}
const response = await handler();
// 重复调用检测
if (isRepeating(toolCallHistory, guardrails)) {
// 尝试注入纠正提示
await injectCorrectionPrompt(response);
continue;
}
// 工具频率控制
if (exceedsToolRate(response, toolCallHistory, guardrails)) {
await injectRateLimitPrompt(response);
continue;
}
return response;
}
}
function isRepeating(history: ToolCall[], guardrails: AgentGuardrails): boolean {
const recent = history.slice(-guardrails.dedupWindow);
if (recent.length < guardrails.dedupWindow) return false;
// 检查是否有连续的相同工具+参数调用
return recent.every(call =>
call.name === recent[0].name &&
JSON.stringify(call.args) === JSON.stringify(recent[0].args)
);
}
3.3 状态管理与上下文窗口
Agent每多迭代一轮,消息历史就会多添加几条消息(LLM的思考、工具调用请求、工具返回结果)。随着迭代深入,消息历史会迅速膨胀——几轮工具调用下来,上下文窗口可能被工具返回的原始数据塞满(如一次数据库查询可能返回数千行结果)。因此,上下文管理是Agent工程中容易被低估但至关重要的环节。
实用的上下文管理策略包括:1)结果摘要——工具返回大数据量时,用LLM摘要后只保留摘要;2)丢弃中间轮——保留第一轮和最新几轮的完整历史,中间的中间轮次可以摘要化;3)滑动窗口——固定最大消息数,超过后丢弃最早的消息;4)结构化压缩——将历史信息压缩为结构化的"Agent状态"。
四、ReAct与Plan-and-Execute模式对比
4.1 ReAct:推理与行动的深度融合
ReAct(Reasoning + Acting)模式由Yao等人于2022年提出,是Agent系统中最基础的框架之一。其核心理念是将推理(Reasoning)与行动(Acting)紧密结合:LLM在每一步既进行"思考"(通过thought描述推理过程),又进行"行动"(调用工具或生成最终回复)。这种方法借鉴了人类解决问题的自然模式——我们在行动之前会先思考,在观察结果后又会调整思考。
ReAct模式的典型流程是:1)Thought:分析当前状态,思考下一步需要做什么;2)Action:调用工具获取额外信息;3)Observation:解析工具返回的结果;4)循环或Final Answer。这种"边想边做"的模式非常适合需要多步推理的任务,如复杂问题求解、研究调查、代码调试等。
ReAct Agent 完整对话示例(内部消息记录):
系统提示词:
你是一个可以调用工具的AI助手。
当你需要信息时,使用提供的工具。
在每次工具调用前,先解释你的思考过程。
用户:最近苹果公司股票的表现怎么样?
=== 第1轮 ===
Assistant (Thought):
用户想了解苹果公司(AAPL)的股票表现。我需要查询股价信息和公司动态。
我先搜索苹果最近的股市表现和相关新闻。
Assistant (Action):
{
"function": "get_stock_price",
"arguments": {"symbol": "AAPL", "timeframe": "1m"}
}
Tool Response (Observation):
{"price": 189.72, "change": +2.3%, "high_52w": 198.23, "low_52w": 164.08}
=== 第2轮 ===
Assistant (Thought):
苹果当前股价189.72美元,月度涨幅2.3%,接近52周高点198.23。我需要进一步了解近期的新闻动态,看看有什么事件影响股价。
Assistant (Action):
{
"function": "search_news",
"arguments": {"query": "Apple stock 2026 trends", "limit": 3}
}
Tool Response (Observation):
[{"title": "Apple AI战略推动股价创新高"}, {"title": "iPhone 18系列销量超预期"}, ...]
=== 第3轮 ===
Assistant (Thought):
苹果股价表现良好,受到AI战略和新产品销售推动。
我觉得现在有足够的信息来回答用户了。
Assistant (Final Answer):
苹果公司股票近期表现不错。目前股价为189.72美元,近一个月上涨约2.3%,距离52周高点(198.23美元)不远。推动股价上涨的主要因素包括:
1. AI战略推动 - 苹果在生成式AI方面的投入获得市场认可
2. iPhone 18系列销量超预期,带动营收增长
...
(3轮迭代完成,任务完成,总共调用2次工具)
4.2 Plan-and-Execute:先规划再执行
与ReAct的"边想边做"不同,Plan-and-Execute(计划-执行)模式采用"先想好再做"的策略。Agent首先生成一个完整的多步计划(Plan),然后逐步执行该计划,在执行过程中根据中间结果调整计划。
这种模式源于人类处理复杂项目时的经验:在开始动手之前,先制定详细的项目计划。Plan-and-Execute的典型流程包括:1)Plan Step:LLM将用户请求分解为多个子任务,形成一个DAG(有向无环图)或线性计划;2)Execute Step:逐个执行子任务,可并行执行无依赖的子任务;3)Review Step:检查执行结果,必要时修改计划。
Plan-and-Execute 对比 ReAct:
=== Plan-and-Execute 流程 ===
Step 1: 规划阶段
Assistant (Plan):
我将按照以下计划回答用户的问题:
1. 查询AAPL当前股价
2. 搜索苹果公司最近的新闻
3. 搜索分析师对苹果股票的评级
4. 汇总所有信息形成投资分析报告
(以上步骤中,1/2/3可以部分并行执行)
Step 2: 执行阶段
→ 执行步骤1:get_stock_price("AAPL") → 结果A
→ 执行步骤2(与步骤1并行):search_news("Apple") → 结果B
→ 执行步骤3:search_analyst("AAPL") → 结果C
Step 3: 汇总阶段
Assistant:
根据收集到的信息,以下是苹果股票的分析报告:
[最终回复]
=== ReAct 流程(对比) ===
→ 用户提问
→ 查询股价 → 观察 → 查询新闻 → 观察 → 查询分析师 → 观察 → 回复
(每一步依赖上一步,串行执行)
=== 关键区别 ===
Plan-and-Execute:
- 先规划再执行
- 支持并行执行
- 适合多步骤、可预见的任务
- 计划开销固定(1次额外LLM调用)
ReAct:
- 边推理边执行
- 串行执行
- 适合探索性、路径不确定的任务
- 灵活性高,可动态调整路线
4.3 模式选择:何时用ReAct,何时用Plan-and-Execute
两种模式各有适用场景,不存在绝对的优劣之分。ReAct更适合探索性任务——当Agent不知道完成任务的确切路径时,ReAct的动态决策能力更强;Plan-and-Execute更适合结构化任务——当任务的步骤可以预先规划时,Plan-and-Execute的效率和可控性更高。
| 对比维度 | ReAct | Plan-and-Execute |
|---|---|---|
| 决策方式 | 动态、在线 | 先规划、再执行 |
| 并行能力 | 弱(天然串行) | 强(可并行执行子任务) |
| 适用范围 | 路径不确定、探索性任务 | 路径确定、结构化任务 |
| LLM调用次数 | 工具调用次数 +1(略多) | 1次规划 + 工具调用次数 |
| 纠错能力 | 强(每步都可以调整) | 弱(需要重新规划) |
| 典型场景 | 研究调查、调试、问答 | 数据分析、报告生成、工作流 |
实践中,许多高性能Agent系统采用混合模式:先用Plan-and-Execute生成顶层计划,在每个执行步骤内部使用ReAct灵活应对变化。这种"宏观规划+微观ReAct"的层次化架构,兼顾了效率和灵活性。
五、多工具编排与并行调用
5.1 单次调用的并行执行
OpenAI和Anthropic等模型支持在单次LLM响应中发起多个工具调用(parallel tool calls)。这意味着Agent可以在一个Agent循环步骤中同时请求多个独立工具,然后等待所有结果返回后再做下一步决策。这种并行能力可以显著减少Agent的交互轮数。
然而,并行调用需要注意依赖关系:如果工具B的输入依赖于工具A的输出,则不能并行执行。因此,Agent框架需要支持两种调用模式:平行执行(Parallel)和串行执行(Sequential)。平行执行适用于工具调用之间无数据依赖的场景;串行执行适用于工具之间输入输出相互依赖的场景。
并行工具调用处理框架:
// 工具调用图(DAG)
interface ToolCallGraph {
nodes: ToolCallNode[];
edges: DependencyEdge[]; // A → B 表示B依赖A的输出
}
interface ToolCallNode {
id: string;
toolName: string;
args: Record;
status: "pending" | "running" | "completed" | "failed";
result?: any;
dependsOn: string[]; // 依赖的节点ID列表
}
// 拓扑排序执行器
async function executeToolDAG(graph: ToolCallGraph): Promise {
const queue: ToolCallNode[] = graph.nodes.filter(n => n.dependsOn.length === 0);
const inProgress = new Set();
while (queue.length > 0 || inProgress.size > 0) {
// 并行执行所有无依赖的节点
const batch = queue.splice(0, queue.length);
const promises = batch.map(async (node) => {
node.status = "running";
try {
node.result = await executeTool(node.toolName, node.args);
node.status = "completed";
} catch (e) {
node.status = "failed";
node.result = { error: e.message };
}
inProgress.delete(node.id);
// 检查是否有新节点可以加入队列
for (const n of graph.nodes) {
if (n.status === "pending" && n.dependsOn.every(d =>
graph.nodes.find(nd => nd.id === d)?.status === "completed"
)) {
queue.push(n);
}
}
});
inProgress.add(...batch.map(n => n.id));
await Promise.all(promises);
}
}
5.2 工具编排模式:Chain、Router与Orchestrator
在复杂的Agent系统中,单一的工具调用远不能满足需求,需要多种编排模式来灵活组织工具调用。三种基本编排模式——Chain(链式)、Router(路由)和Orchestrator(编排器)——可以组合使用以应对各种复杂的编排场景。
Chain模式是最简单的编排:工具A的输出直接作为工具B的输入,形成线性链条。Router模式根据条件选择不同的工具路径:如果条件A成立走路线A,如果条件B成立走路线B。Orchestrator模式则包含一个协调节点,负责动态分配子任务并合并结果。
三种编排模式的实现对比:
// 1. Chain模式 - 线性执行
// 场景:JIRA工单处理
// 步骤:提取工单内容 → 分类 → 指派 → 生成回复
async function jiraChain(issueKey: string): Promise {
// Step 1: 获取工单
const issue = await getJiraIssue(issueKey);
// Step 2: 根据内容分类
const category = await classifyIssue(issue.summary + issue.description);
// Step 3: 指派负责人
const assignee = await findBestAssignee(category, issue.project);
// Step 4: 生成回复
return await generateResponse(issue, assignee, category);
}
// 2. Router模式 - 条件分支
// 场景:客服系统路由
async function customerServiceRouter(query: string): Promise {
const intent = await classifyIntent(query);
// 路由到不同的处理函数
const routes = {
"order_query": () => queryOrder(query),
"refund_request": () => processRefund(query),
"technical_support": () => transferToTechSupport(query),
"complaint": () => escalateToManager(query)
};
const handler = routes[intent] || routes["fallback"];
return await handler();
}
// 3. Orchestrator模式 - 协调子任务
// 场景:生成行业研究报告
async function orchestratorOrchestrate(topic: string): Promise {
// Orchestrator规划子任务
const subTasks = await planner.splitTask(topic, [
"market_size", "competitors", "trends", "risks"
]);
// 并行执行子任务
const results = await Promise.all(
subTasks.map(task => executeSubTask(task))
);
// 汇总结果生成报告
return await generateReport(topic, results);
}
5.3 条件执行与动态路由
在真实系统中,工具调用路径往往不是静态的,而是依赖于中间结果动态决定。例如,一个数据库查询工具根据前一步的查询结果决定下一步是继续查询还是进入数据分析工序。这种动态路由要求Agent框架支持条件分支和状态感知。
实现动态路由的常见方法包括:1)条件判断——在工具的AI分析中加入条件逻辑;2)动态注册——Agent运行时动态注册新的子任务;3)子Agent递归——创建子Agent处理复杂子任务,主Agent等待子任务完成。这些方法可以组合使用。例如,主Agent使用Orchestrator模式分配子任务,每个子任务内部使用ReAct循环。
六、实战:构建一个Tool-augmented Agent
6.1 系统架构设计
下面我们构建一个完整的工具增强Agent(Tool-Augmented Agent)系统。该系统支持:动态工具注册、ReAct循环、多工具编排、上下文管理、错误恢复。系统采用分层架构:Gateway层(API和认证)、Agent层(LLM交互和工具调度)、Tool层(工具注册和执行)、Data层(持久化状态和缓存)。
系统架构:
┌─────────────────────────────────────────────────┐
│ API Gateway │
│ REST API / WebSocket / 消息队列 │
├─────────────────────────────────────────────────┤
│ Agent Core │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Parser │ │ Router │ │ Context Mgr │ │
│ │ (意图理解)│ │ (工具选择)│ │ (上下文管理) │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Agent Loop (ReAct Engine) │ │
│ │ ┌──────────┐ ┌──────┐ ┌────────────┐ │ │
│ │ │ Thought │→│Action│→│Observation │ │ │
│ │ └──────────┘ └──────┘ └────────────┘ │ │
│ │ ↑ ↓ ↑ │ │
│ │ └────── Iterate ───────┘ │ │
│ └─────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Tool Layer │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Registry│ │ Executor│ │ Schema Valid.│ │
│ └──────────┘ └──────────┘ └───────────────┘ │
├─────────────────────────────────────────────────┤
│ Data Layer │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Session │ │ Cache │ │ State Store │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
└─────────────────────────────────────────────────┘
6.2 Agent核心实现
以下是Agent Core的实现,包括工具注册管理、ReAct循环核心、以及上下文管理。这是一个生产级别的Agent实现,涵盖了参数校验、错误处理、超时控制、上下文压缩等关键功能。
// agent-core.ts - Agent核心实现
import { OpenAI } from "openai";
import Ajv from "ajv";
interface Tool {
name: string;
description: string;
parameters: object;
handler: (args: any) => Promise;
// 工具执行配置
timeoutMs?: number;
retryCount?: number;
cacheTTL?: number;
}
interface AgentConfig {
model: string; // 模型名称
maxIterations: number; // 最大迭代次数
maxToolCalls: number; // 每轮最大工具调用数
timeoutMs: number; // 总超时毫秒
contextWindow: number; // 上下文窗口大小
}
class ToolRegistry {
private tools: Map = new Map();
private ajv = new Ajv({ coerceTypes: true, useDefaults: true });
register(tool: Tool): void {
if (this.tools.has(tool.name)) {
throw new Error(`工具 ${tool.name} 已注册`);
}
this.tools.set(tool.name, tool);
}
getSchema(): object[] {
return Array.from(this.tools.values()).map(t => ({
type: "function",
function: {
name: t.name,
description: t.description,
parameters: t.parameters
}
}));
}
async execute(name: string, args: any): Promise {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`未注册的工具: ${name}`);
}
// 参数校验
const validate = this.ajv.compile(tool.parameters);
if (!validate(args)) {
throw new Error(
`参数校验失败: ${validate.errors!.map(e => e.message).join(", ")}`
);
}
// 带超时的工具执行
const timeout = tool.timeoutMs || 30000;
return await Promise.race([
tool.handler(args),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("工具执行超时")), timeout)
)
]);
}
}
class AgentContext {
private messages: any[] = [];
private maxTokens: number;
constructor(systemPrompt: string, maxTokens = 64000) {
this.messages = [{ role: "system", content: systemPrompt }];
this.maxTokens = maxTokens;
}
add(role: string, content: any, extra?: any): void {
this.messages.push({ role, content, ...extra });
this.compress();
}
getMessages(): any[] {
return this.messages;
}
private compress(): void {
// 估算token数(简化实现)
const estimatedTokens = JSON.stringify(this.messages).length / 4;
if (estimatedTokens > this.maxTokens * 0.8) {
// 保留system消息和最近的N条消息
const systemMsgs = this.messages.filter(m => m.role === "system");
const recentMsgs = this.messages.slice(-20);
this.messages = [...systemMsgs, ...recentMsgs];
}
}
}
class Agent {
private registry: ToolRegistry;
private client: OpenAI;
private config: AgentConfig;
constructor(config: AgentConfig) {
this.registry = new ToolRegistry();
this.client = new OpenAI();
this.config = config;
}
registerTool(tool: Tool): void {
this.registry.register(tool);
}
async run(userQuery: string): Promise {
const context = new AgentContext(
`你是一个可以使用工具的AI助手。` +
`你有以下工具可用:${this.registry.getSchema().map(t => t.function.name).join(", ")}。` +
`在每一步,分析当前情况并调用合适的工具。` +
`当你获得足够的信息后,给出最终答案。`,
this.config.contextWindow
);
context.add("user", userQuery);
let iterations = 0;
const startTime = Date.now();
while (iterations < this.config.maxIterations) {
iterations++;
// 超时检查
if (Date.now() - startTime > this.config.timeoutMs) {
return "抱歉,处理您的请求超时了,请简化您的要求后重试。";
}
const response = await this.client.chat.completions.create({
model: this.config.model,
messages: context.getMessages(),
tools: this.registry.getSchema(),
tool_choice: "auto"
});
const choice = response.choices[0];
if (choice.message.tool_calls) {
context.add("assistant", choice.message.content, {
tool_calls: choice.message.tool_calls
});
// 限制每轮工具调用数
const toolCalls = choice.message.tool_calls
.slice(0, this.config.maxToolCalls);
for (const call of toolCalls) {
const args = JSON.parse(call.function.arguments);
try {
const result = await this.registry.execute(
call.function.name, args
);
context.add("tool", JSON.stringify(result), {
tool_call_id: call.id
});
} catch (error) {
context.add("tool",
`错误: ${error.message}`,
{ tool_call_id: call.id }
);
}
}
continue;
}
// LLM决定不调用工具,直接生成回复
return choice.message.content || "好的,我会根据已有信息回答。";
}
return "已达到最大推理步数,请重新描述您的问题。";
}
}
6.3 注册真实工具
现在我们为Agent注册一组实用的工具,涵盖搜索、数据查询和代码执行等常见能力。这些工具的Schema设计遵循前文讨论的最佳实践。
// 工具注册示例
async function registerTools(agent: Agent): Promise {
// 1. 搜索引擎工具
agent.registerTool({
name: "web_search",
description: "在互联网上搜索信息。当需要查询实时信息、最新新闻或不确定的事实时使用此工具。",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "搜索关键词,应简洁准确",
examples: ["2026年苹果股价", "最新AI论文进展"]
},
max_results: {
type: "integer",
description: "返回结果数量(1-10)",
default: 5
}
},
required: ["query"]
},
handler: async (args) => {
// 实际调用搜索API
return await searchWeb(args.query, args.max_results);
},
timeoutMs: 10000,
cacheTTL: 300
});
// 2. 知识库查询工具
agent.registerTool({
name: "knowledge_base_query",
description: "查询公司内部知识库。用于获取产品文档、技术规范、FAQ等信息。",
parameters: {
type: "object",
properties: {
question: {
type: "string",
description: "需要查询的问题",
examples: ["支付接口对接流程"]
},
top_k: {
type: "integer",
description: "返回文档数",
default: 3
}
},
required: ["question"]
},
handler: async (args) => {
// 向量检索知识库
return await queryKB(args.question, args.top_k);
},
timeoutMs: 5000,
cacheTTL: 600
});
// 3. 计算器工具
agent.registerTool({
name: "calculator",
description: "执行数学计算。可以使用标准的数学操作符和函数。",
parameters: {
type: "object",
properties: {
expression: {
type: "string",
description: "数学表达式,支持 + - * / () 等操作符和数学函数"
}
},
required: ["expression"]
},
handler: async (args) => {
return { result: mathEval(args.expression) };
},
timeoutMs: 2000
});
}
// 启动Agent
async function main() {
const agent = new Agent({
model: "gpt-4o",
maxIterations: 10,
maxToolCalls: 3,
timeoutMs: 60000,
contextWindow: 64000
});
await registerTools(agent);
// 运行对话
const response = await agent.run(
"请帮我分析2026年第一季度全球AI芯片市场的情况," +
"包括主要厂商的市场份额和增长趋势。"
);
console.log(response);
}
工程建议:在实际部署Agent时,建议为每个工具添加:1)缓存机制——频繁调用相同参数的场景(如查询天气)可以缓存结果减少API消耗;2)限流机制——防止Agent在短时间内过度调用同一工具(如搜索引擎耗时高且有配额限制);3)审计日志——记录每次工具调用的输入输出,用于调试和问题排查。
七、踩坑:工具冲突、参数幻觉与超时处理
7.1 工具冲突的隐蔽陷阱
当Agent系统中注册了多个功能相似的工具时,LLM可能会混淆它们,导致调用了错误的工具。典型的场景包括:1)名称过于相似(如search_products和search_orders);2)功能描述含糊不清,多个工具都能处理同一类请求;3)参数结构冲突,两个工具共用相同参数名但含义不同。
解决工具冲突的核心策略是命名的区分度和参数设计的正交性。每个工具应该有一个明确、独特的职责边界,且命名要有辨识度。例如,search_products应该只负责商品搜索,search_orders应该只负责订单查询。同时,参数命名应该避免歧义——如果order_id在A工具中指订单号,在B工具中指排序方式,应该分别命名为order_id和sort_order。
7.2 参数幻觉:LLM的"创造性错误"
参数幻觉(Parameter Hallucination)是Agent系统中最常见的问题之一。LLM可能会生成:1)不存在的参数名(自由发挥出schema中没有的字段);2)不存在的枚举值(猜错了参数的可选值);3)不合理的参数组合(如同时指定"最新排序"和"最旧排序");4)参数值超过业务限制(如page_size=1000但系统允许最大100)。
对抗参数幻觉的最有效方法是:1)极强约束——enum字段尽可能列举所有合法值,不给LLM自由发挥的空间;2)校验兜底——任何情况下都不要假设LLM的参数是合法的,校验是第一道防线;3)错误反馈——当参数校验失败时,将详细的错误信息返回给LLM,让它自愈。实践表明,80%的参数幻觉可以通过给LLM返回具体的校验错误来修正。
参数幻觉处理策略示例:
function handleParameterError(
toolName: string,
rawArgs: string,
validationError: string
): string {
// 生成结构化的错误反馈(返回给LLM用于修正)
const errorFeedback = {
status: "parameter_error",
tool: toolName,
received_args: rawArgs,
validation_errors: validationError,
hint: `请在下次调用时修正参数。确保参数名正确,值在合法范围内。`
};
// 深度错误分类
if (validationError.includes("enum")) {
errorFeedback.hint += ` 枚举值必须从预设列表中选择,不要自行创造。`;
}
if (validationError.includes("pattern")) {
errorFeedback.hint += ` 参数格式不符合要求,请检查格式。`;
}
if (validationError.includes("type")) {
errorFeedback.hint += ` 参数类型错误,如数字不要写为字符串。`;
}
return JSON.stringify(errorFeedback);
}
7.3 超时与降级策略
Agent系统中的超时是多层次的:LLM请求超时、单工具执行超时、Agent循环总超时。每个层次的超时需要不同的处理策略。LLM请求超时应当重试(最多2-3次,指数退避),工具执行超时应当记录并继续(返回"工具超时"消息让LLM决定下一步),Agent循环超时应当优雅降级(返回当前的最佳结果而非空回复)。
| 超时层次 | 常见原因 | 推荐策略 |
|---|---|---|
| LLM API超时 | 网络波动、模型负载高 | 重试2-3次,指数退避,切换备用模型 |
| 工具执行超时 | 数据库慢查询、外部API不可用 | 记录日志,返回错误信息,让LLM决定 |
| Agent循环超时 | 推理路径过长、死循环 | 返回当前中间结果,提示未完全完成 |
| 上下文溢出超时 | 工具返回大量数据 | 自动压缩上下文,截断或摘要化 |
7.4 真实场景雷区清单
基于大量生产环境的踩坑经验,以下是最常见的Agent故障模式及其解决方案:1)token耗尽——工具返回大数据时不截断,解决:自动截断或摘要工具返回;2)循环回溯——Agent反复回到上一个状态,解决:Monte Carlo树搜索或beam search多样化解空间;3)身份混淆——Agent把工具返回的"工具输出"当作"系统指令"来执行,解决:在system prompt中明确提示角色边界;4)过度推理——Agent在5步内能解决的问题用了15步,解决:在system prompt中加入"直接调用工具,不要过度思考"的约束。
实战总结:Agent系统经历了数年的进化后,一个清晰的共识是:没有银弹。ReAct模式最简单但可能效率低,Plan-and-Execute效率高但适应性差。实用方案是组合使用+动态切换。选择原则:简单任务ReAct直接够用,复杂任务先用Plan-and-Execute生成计划再ReAct执行。此外,无论采用何种架构,参数校验、超时控制、上下文管理、错误恢复都是必须实现的底层设施——它们是Agent系统稳定运行的基石。
八、主流框架:LangChain Agent/AutoGen/MetaGPT
8.1 LangChain Agent:灵活的框架
LangChain是最成熟的Agent框架之一,提供了从简单Agent到复杂AgentExecutor的完整层级。LangChain的Agent系统围绕"LLM + Tools"的核心理念构建,通过AgentExecutor实现ReAct循环。LangChain支持多种Agent类型(包括Plan-and-Execute)、多种工具集成(内置超过100种工具)以及丰富的Callback系统(用于监控和调试)。
LangChain Agent 使用示例:
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import Tool, tool
from langchain_openai import ChatOpenAI
# 定义工具
@tool
def search_web(query: str) -> str:
"""搜索互联网获取实时信息"""
return call_search_api(query)
@tool
def calculate(expression: str) -> str:
"""执行数学计算"""
return str(eval(expression))
# 创建Agent
tools = [search_web, calculate]
prompt = create_react_agent_prompt(
tools=tools,
system_message="你是一个帮助用户的AI助手。使用工具时逐步推理。"
)
agent = AgentExecutor(
agent=create_react_agent(
llm=ChatOpenAI(model="gpt-4o"),
tools=tools,
prompt=prompt
),
tools=tools,
verbose=True,
max_iterations=10,
early_stopping_method="generate" # 或 "force"
)
# 运行
result = agent.invoke({
"input": "查询2026年全球AI芯片市场规模,并计算如果增长20%会是多少"
})
print(result["output"])
8.2 AutoGen:多Agent对话协作
AutoGen(Microsoft, 2023)采用多Agent对话模式(Multi-Agent Conversation,MAC),将复杂任务分配给多个专门化的Agent,通过Agent之间的对话协作完成任务。AutoGen的核心设计是"Agent作为人"(Agent as Persona)——每个Agent扮演一个角色(如程序员、审查员、项目经理),通过对话协商解决问题。
AutoGen的核心组件是ConversableAgent和UserProxyAgent。ConversableAgent是一个能够参与对话、调用工具的Agent;UserProxyAgent是一个特殊的Agent,它在需要人类输入时会暂停对话等待用户输入,在其他情况下可以自动执行代码和工具。Agent之间通过send/receive消息机制进行通信,可以实现复杂的多Agent协作模式。
AutoGen 多Agent协作示例:
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
# 配置LLM
config = [{"model": "gpt-4o", "api_key": "..."}]
# 创建专门的Agent角色
planner = AssistantAgent(
name="Planner",
system_message="你是项目经理,负责将任务分解为可执行的子任务,并协调执行。",
llm_config={"config_list": config}
)
coder = AssistantAgent(
name="Coder",
system_message="你是高级Python工程师,负责编写和调试代码。",
llm_config={"config_list": config},
code_execution_config={"work_dir": "/tmp/workspace"}
)
critic = AssistantAgent(
name="Critic",
system_message="你是代码审查员,负责检查代码质量。",
llm_config={"config_list": config}
)
# 创建GroupChat实现多Agent协作
group_chat = GroupChat(
agents=[planner, coder, critic],
messages=[],
max_round=12,
speaker_selection_method="round_robin" # 或 "auto"
)
manager = GroupChatManager(
groupchat=group_chat,
llm_config={"config_list": config}
)
# 启动多Agent协作
user_proxy = UserProxyAgent(
name="User",
code_execution_config=False
)
user_proxy.initiate_chat(
manager,
message="""请帮我分析这个csv文件中的数据:
1. 首先读取sales_2026_q1.csv
2. 计算每月销售额汇总
3. 找出销售额最高的产品类别
4. 生成一个可视化报告"""
)
AutoGen的独特优势在于Agent之间的角色分离和任务解耦。每个Agent可以有自己的LLM配置、工具集、甚至知识库。这种设计使得Agent系统可以灵活组合:你可以用GPT-4o做Planner、用Claude做Coder、用本地模型做Critic,各取其长。
8.3 MetaGPT:软件开发仿真
MetaGPT(2023)是一个将软件开发流程结构化的多Agent框架。与一般的多Agent框架不同,MetaGPT引入了一个关键设计:SOP(Standard Operating Procedures,标准操作流程)。它将软件产品的开发流程固化为SOP,每个角色(产品经理、架构师、工程师、测试员)遵循自己的SOP执行任务。
MetaGPT的独特之处在于:1)结构化输出——每个角色的输出有标准格式(如PRD、设计文档、测试用例),使得Agent之间的信息传递更加可控;2)角色经验——每个Agent角色有专属的系统提示和上下文(如架构师需要了解系统设计模式);3)可执行的SOP——SOP不仅仅是文本描述,而是可编程的执行流程。
MetaGPT 软件开发流程:
用户需求:"开发一个在线咖啡订购APP"
↓
[产品经理 (Product Manager)]
→ 输出:PRD(产品需求文档)
→ 包含:用户故事、功能列表、验收标准
↓
[架构师 (Architect)]
→ 输入:PRD
→ 输出:架构设计文档
→ 包含:系统架构图、技术选型、API设计
↓
[项目经理 (Project Manager)]
→ 输入:PRD + 架构设计
→ 输出:任务列表、里程碑、依赖关系
↓
[工程师 (Engineer)] × N
→ 输入:子任务描述
→ 输出:代码实现 + 单元测试
↓
[测试员 (QA Engineer)]
→ 输入:全部输出
→ 输出:测试报告 + 问题列表
关键设计:
- SOP驱动的角色执行
- 结构化文档流转(非自由对话)
- 可追踪的决策记录
8.4 架构师的框架选择指南
选择Agent框架时,需要根据实际场景权衡。以下是不同框架的应用场景和建议:
| 框架 | 核心模式 | 优势 | 适用场景 |
|---|---|---|---|
| LangChain Agent | 单Agent + 工具 | 工具丰富、社区大、上手快 | 通用Agent,快速原型 |
| AutoGen | 多Agent对话协作 | 角色分离、灵活组合 | 复杂团队协作任务 |
| MetaGPT | SOP驱动仿真 | 结构化输出、可预测 | 软件开发、文档生成 |
| CrewAI | 角色化多Agent | 轻量、Pythonic、易用 | 中规模多Agent任务 |
| 自研框架 | 按需定制 | 完全控制、无依赖 | 需要深度定制的生产系统 |
需要强调的是,框架只是起点而非终点。任何框架最终都需要根据业务需求进行定制。一个常见的演进路径是:先用LangChain快速验证概念 → 发现通用工具的不足 → 用AutoGen引入多Agent协作 → 最终自研满足生产需求的定制框架。对于大型企业项目,自研框架往往是最终选择,因为只有自己最了解业务场景的独特需求。
架构师总结:Agent系统和Function Calling正在重塑AI应用的架构图景。从底层的工具Schema设计,到Agent循环的核心模式,再到多Agent协作的编排流程,每个环节都有其独特的工程挑战和最佳实践。2024-2026年间,Agent系统已经从"能不能调用工具"的基础能力,演进到"如何高效、可靠、安全地调用工具"的系统工程问题。
对于准备构建Agent系统的团队,建议从最小可行Agent开始,先实现单Agent+几个核心工具的ReAct循环,验证人机交互体验和系统可靠性。然后逐步引入多Agent协作、并行执行、DAG编排等高级特性。记住:一个能用1个工具解决80%需求的Agent,比一个能用10个工具但频繁出错的Agent更有价值。