Agent 与工具调用
Function Calling 机制、Agent 架构和实现模式。
什么是工具调用?
工具调用(Function Calling / Tool Use)让大模型能够”使用工具”。模型本身只能生成文本,但通过工具调用,它可以:
- 查询实时数据(天气、股价、新闻)
- 执行计算和代码
- 操作外部系统(发邮件、创建任务)
- 检索知识库
这是构建 AI Agent 的核心能力。
工具调用的本质:
大模型本身是”无状态”的文本生成器,它不能访问互联网、不能执行代码、不能操作数据库。工具调用是一种”桥接”机制,让模型可以通过你的代码与外部世界交互。
工具调用 vs 传统 API:
| 方面 | 传统 API | 工具调用 |
|---|---|---|
| 调用决策 | 程序员硬编码 | 模型自主决定 |
| 参数提取 | 手动解析 | 模型从自然语言提取 |
| 灵活性 | 固定流程 | 动态适应 |
| 适用场景 | 结构化输入 | 自然语言输入 |
工具调用流程
工具调用是一个多轮交互过程:
- 用户提问 + 告诉模型有哪些工具可用
- 模型决策:判断是否需要调用工具,生成调用参数
- 执行工具:你的代码执行实际操作
- 返回结果:将工具执行结果告诉模型
- 生成回答:模型基于工具结果生成最终回答
模型不会真正执行工具,它只是”决定”调用什么工具、传什么参数。实际执行由你的代码完成。
sequenceDiagram
participant User
participant LLM
participant Tool
User->>LLM: 问题 + 工具定义
LLM->>LLM: 决定调用工具
LLM-->>User: tool_call
User->>Tool: 执行工具
Tool-->>User: 结果
User->>LLM: 工具结果
LLM-->>User: 最终回答关键理解:
- 模型只是”建议”调用工具,不会真正执行
- 你可以选择执行或拒绝工具调用
- 工具结果需要再次发送给模型
- 模型可能需要多次工具调用才能完成任务
工具定义
工具定义告诉模型有哪些工具可用、每个工具的功能和参数。好的工具定义是工具调用成功的关键。
工具定义对比
| 字段 | OpenAI | Claude | Gemini |
|---|---|---|---|
| 容器 | tools[].function | tools[] | tools[].functionDeclarations[] |
| 名称 | name | name | name |
| 描述 | description | description | description |
| 参数 | parameters | input_schema | parameters |
统一结构
所有厂商都使用 JSON Schema 定义参数:
{
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
工具定义最佳实践:
| 要素 | 建议 |
|---|---|
| name | 简洁明了,使用 snake_case |
| description | 详细描述功能和使用场景 |
| parameters | 每个参数都要有 description |
| required | 明确标注必需参数 |
| enum | 对于有限选项,使用 enum 约束 |
好的 description 示例:
{
"name": "search_products",
"description": "在商品数据库中搜索产品。当用户询问商品信息、价格、库存时使用此工具。支持按名称、类别、价格范围搜索。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,如产品名称或描述"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food"],
"description": "产品类别,可选"
},
"max_price": {
"type": "number",
"description": "最高价格限制,单位:元"
}
},
"required": ["query"]
}
}
工具调用响应
flowchart LR
A[LLM 响应] --> B{包含工具调用?}
B -->|是| C[提取 tool_call]
B -->|否| D[直接返回文本]
C --> E[执行工具]
E --> F[返回结果给 LLM]
F --> A响应格式对比
| 厂商 | 工具调用字段 | 结束原因 | 参数格式 |
|---|---|---|---|
| OpenAI | tool_calls | tool_calls | JSON 字符串 |
| Claude | content[].tool_use | tool_use | 对象 |
| Gemini | parts[].functionCall | STOP | 对象 |
注意: OpenAI 的 arguments 是 JSON 字符串,需要 json.loads() 解析;Claude 和 Gemini 的参数已经是对象。
Agent 架构
Agent 是能够自主决策、多步执行的 AI 系统。它不只是”问一答一”,而是能够规划任务、调用工具、根据结果调整策略。
Agent vs 简单对话:
| 方面 | 简单对话 | Agent |
|---|---|---|
| 交互模式 | 一问一答 | 多步骤执行 |
| 决策能力 | 无 | 自主决策 |
| 工具使用 | 可选 | 核心能力 |
| 状态管理 | 无 | 维护执行状态 |
| 错误处理 | 简单 | 可重试、可调整 |
ReAct 模式
ReAct(Reasoning + Acting)是最经典的 Agent 模式。它交替进行”思考”和”行动”:
- Thought:分析当前状态,决定下一步
- Action:执行具体操作(调用工具)
- Observation:观察执行结果
- 循环直到任务完成
flowchart TD
A[输入] --> B[思考 Thought]
B --> C[行动 Action]
C --> D[观察 Observation]
D --> E{完成?}
E -->|否| B
E -->|是| F[输出]ReAct 的优势:
- 可解释性:每一步都有明确的思考过程
- 可调试:可以看到 Agent 的决策逻辑
- 可控制:可以在任何步骤介入
实现框架
def agent_loop(query, tools, max_steps=10):
messages = [{"role": "user", "content": query}]
for step in range(max_steps):
resp = llm.chat(messages, tools=tools)
if not resp.tool_calls:
return resp.content # 完成
# 记录助手消息
messages.append({"role": "assistant", "tool_calls": resp.tool_calls})
# 执行工具
for call in resp.tool_calls:
result = execute_tool(call.name, call.args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result)
})
return "达到最大步数"
Agent 循环的关键点:
- 设置最大步数,防止无限循环
- 正确维护消息历史
- 处理工具执行错误
- 判断任务是否完成
常见 Agent 模式
根据复杂度和场景,Agent 有不同的架构模式。
1. 单工具 Agent
最简单的形式,模型只能调用一个工具。适合单一功能场景,如天气查询、翻译等。
flowchart LR
A[用户] --> B[LLM]
B --> C[工具]
C --> B
B --> A2. 多工具 Agent
模型可以从多个工具中选择,根据任务需要调用不同工具。这是最常见的 Agent 形式。
flowchart TD
A[用户] --> B[LLM]
B --> C{选择工具}
C --> D[搜索]
C --> E[计算]
C --> F[代码执行]
D & E & F --> B
B --> A3. 多 Agent 协作
复杂任务可以拆分给多个专门的 Agent,由一个协调者统筹。例如:研究员负责收集信息,写作者负责生成内容,审核者负责检查质量。
flowchart LR
A[协调者] --> B[研究员]
A --> C[写作者]
A --> D[审核者]
B --> A
C --> A
D --> Atool_choice 控制
tool_choice 参数控制模型如何选择工具。不同场景需要不同的策略。
| 值 | 说明 | 场景 |
|---|---|---|
auto | LLM 自动决定 | 默认,让模型判断是否需要工具 |
none | 禁止调用工具 | 纯对话,不需要工具 |
required | 必须调用工具 | 强制执行某个操作 |
{name: "xxx"} | 指定工具 | 明确知道要用哪个工具 |
并行工具调用
现代模型支持一次返回多个工具调用,可以并行执行以提升效率。
例如用户问”北京和上海的天气”,模型可以同时返回两个 get_weather 调用,你可以并行执行它们。
flowchart TD
A[LLM] --> B[tool_call 1]
A --> C[tool_call 2]
A --> D[tool_call 3]
B & C & D --> E[并行执行]
E --> F[合并结果]
F --> G[返回 LLM]# 并行执行
import asyncio
async def execute_parallel(tool_calls):
tasks = [execute_tool(c.name, c.args) for c in tool_calls]
return await asyncio.gather(*tasks)
内置工具
| 厂商 | 工具 | 说明 |
|---|---|---|
| OpenAI | web_search | 网页搜索 |
| OpenAI | code_interpreter | 代码执行 |
| OpenAI | file_search | 文件检索 |
| Gemini | codeExecution | 代码执行 |
| Gemini | googleSearch | Google 搜索 |
Model Context Protocol (MCP)
除了各厂商私有的 Function Calling 格式,Anthropic 推出了开放标准 MCP。
- 标准化:统一了连接本地资源(数据库、文件、Git)的方式
- 互操作:写一次 Server,任何支持 MCP 的 Client(如 Claude Desktop, Cursor)都能用
- 详解:请参考 27-大模型-MCP协议详解
最佳实践
| 实践 | 说明 |
|---|---|
| 工具描述清晰 | LLM 依赖描述决策 |
| 参数验证 | 执行前校验参数 |
| 错误处理 | 工具失败要告知 LLM |
| 限制步数 | 防止无限循环 |
| 日志记录 | 便于调试追踪 |
工具设计原则
好的工具设计是 Agent 成功的关键。
命名规范
动词_名词 格式,清晰表达功能
✅ get_weather 获取天气
✅ search_documents 搜索文档
✅ send_email 发送邮件
❌ weather 不清楚是获取还是设置
❌ do_stuff 太模糊
描述要点
工具描述是 LLM 决策的依据,需要包含:
- 功能说明:这个工具做什么
- 使用场景:什么时候应该用
- 限制条件:什么情况不能用
{
"name": "search_web",
"description": "搜索互联网获取实时信息。当用户询问最新新闻、实时数据、或你不确定的事实时使用。不要用于已知的常识性问题。"
}
参数设计
{
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,应该简洁明确,2-5 个词最佳"
},
"limit": {
"type": "integer",
"description": "返回结果数量,默认 5",
"default": 5
}
},
"required": ["query"]
}
}
记忆管理
长对话中,Agent 需要管理记忆以保持上下文连贯。
记忆类型
flowchart LR
A[记忆] --> B[短期记忆]
A --> C[长期记忆]
A --> D[工作记忆]
B --> B1[当前对话]
C --> C1[向量数据库]
D --> D1[当前任务状态]| 类型 | 存储 | 用途 |
|---|---|---|
| 短期记忆 | 消息列表 | 当前对话上下文 |
| 长期记忆 | 向量数据库 | 历史知识检索 |
| 工作记忆 | 变量 | 当前任务中间状态 |
上下文压缩
当对话过长时,需要压缩历史消息:
def compress_history(messages, max_tokens=4000):
"""压缩对话历史"""
# 保留系统消息
system = [m for m in messages if m["role"] == "system"]
# 保留最近 N 轮
recent = messages[-10:]
# 中间部分生成摘要
middle = messages[len(system):-10]
if middle:
summary = summarize(middle)
return system + [{"role": "system", "content": f"历史摘要:{summary}"}] + recent
return system + recent
调试技巧
可视化执行过程
def agent_loop_debug(query, tools):
print(f"🎯 任务: {query}\n")
for step in range(10):
print(f"--- 步骤 {step + 1} ---")
resp = llm.chat(messages, tools=tools)
if resp.tool_calls:
for call in resp.tool_calls:
print(f"🔧 调用工具: {call.name}")
print(f" 参数: {call.args}")
result = execute_tool(call.name, call.args)
print(f" 结果: {result[:100]}...")
else:
print(f"💬 回答: {resp.content}")
return resp.content
常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 不调用工具 | 描述不清晰 | 优化工具描述 |
| 调用错误工具 | 工具太相似 | 区分工具职责 |
| 参数错误 | 参数描述不明 | 添加示例和约束 |
| 无限循环 | 缺少终止条件 | 限制最大步数 |