工具调用概述
工具调用(Tool Calling)是 Agent 系统的核心能力,使大语言模型能够与外部系统交互、执行实际操作。理解工具调用的发展历程和核心机制,是构建实用 Agent 的第一步。
一、核心原理
1.1 什么是工具调用?
工具调用是指 LLM 在生成文本时,能够识别出需要调用外部工具的场景,并生成结构化的工具调用请求:
┌─────────────────────────────────────────────────────────────┐
│ 工具调用本质 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户输入: │
│ "北京现在的天气怎么样?" │
│ │
│ LLM 内部推理: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 分析意图:用户需要实时天气信息 │ │
│ │ 2. 判断能力:我的训练数据没有实时信息 │ │
│ │ 3. 选择工具:有天气查询工具可用 │ │
│ │ 4. 构建调用:weather_search(city="北京") │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 工具调用输出(结构化): │
│ { │
│ "tool": "weather_search", │
│ "arguments": { │
│ "city": "北京" │
│ } │
│ } │
│ │
│ 执行结果: │
│ { │
│ "temperature": "18°C", │
│ "condition": "晴", │
│ "humidity": "45%" │
│ } │
│ │
│ 最终回答: │
│ "北京现在天气晴朗,气温18°C,湿度45%。" │
│ │
└─────────────────────────────────────────────────────────────┘1.2 工具调用的核心要素
| 要素 | 说明 | 示例 |
|---|---|---|
| 工具定义 | 描述工具的功能、参数、返回值 | JSON Schema |
| 意图识别 | 判断是否需要调用工具 | "搜索" → 需要搜索工具 |
| 参数提取 | 从自然语言中提取工具参数 | "北京" → city 参数 |
| 调用执行 | 实际执行工具并获取结果 | API 调用、代码执行 |
| 结果整合 | 将工具结果整合到回答中 | 自然语言描述 |
1.3 工具调用 vs 普通 LLM 调用
┌─────────────────────────────────────────────────────────────┐
│ 对比:普通调用 vs 工具调用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 普通 LLM 调用: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 输入 │ ──→ │ LLM │ ──→ │ 输出 │ │
│ │ "问题" │ │ 处理 │ │ "回答" │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 单次调用,无外部交互 │
│ │
│ 工具调用: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 输入 │ ──→ │ LLM │ ──→ │工具调用 │ │
│ │ "问题" │ │ 分析 │ │ 请求 │ │
│ └─────────┘ └─────────┘ └────┬────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 最终 │ ←── │ LLM │ ←── │ 工具 │ │
│ │ 回答 │ │ 整合 │ │ 结果 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 多次调用,有外部交互 │
│ │
└─────────────────────────────────────────────────────────────┘二、发展历程
2.1 三个阶段演进
┌─────────────────────────────────────────────────────────────┐
│ 工具调用发展历程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一阶段:Prompt Hacking(2022-2023初) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点:通过 Prompt 工程模拟工具调用 │ │
│ │ │ │
│ │ 方法: │ │
│ │ • 在 Prompt 中描述工具用法 │ │
│ │ • 让 LLM 输出特定格式(如 [SEARCH:xxx]) │ │
│ │ • 用正则表达式解析输出 │ │
│ │ │ │
│ │ 问题: │ │
│ │ • 格式不稳定,容易出错 │ │
│ │ • 无法保证参数正确性 │ │
│ │ • 上下文消耗大 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第二阶段:Native Function Calling(2023中-2024) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点:LLM 原生支持结构化工具调用 │ │
│ │ │ │
│ │ 代表: │ │
│ │ • OpenAI Function Calling (2023.06) │ │
│ │ • Anthropic Tool Use (2024) │ │
│ │ • Google Gemini Function Calling │ │
│ │ │ │
│ │ 优势: │ │
│ │ • 结构化输出,格式可靠 │ │
│ │ • 参数类型验证 │ │
│ │ • 多工具选择支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第三阶段:Protocol Standardization(2024至今) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点:标准化协议实现跨平台工具互联 │ │
│ │ │ │
│ │ 代表: │ │
│ │ • MCP (Model Context Protocol) - Anthropic │ │
│ │ • OpenAPI / Swagger 标准 │ │
│ │ • LangChain Tool Standard │ │
│ │ │ │
│ │ 优势: │ │
│ │ • 跨平台兼容 │ │
│ │ • 工具生态共享 │ │
│ │ • 标准化开发体验 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 里程碑事件
| 时间 | 事件 | 意义 |
|---|---|---|
| 2022.10 | ReAct 论文发表 | 提出 Thought-Action-Observation 框架 |
| 2023.03 | LangChain 发布 | 标准化 Tool 抽象 |
| 2023.06 | OpenAI Function Calling | 首个原生工具调用支持 |
| 2023.10 | OpenAI Assistants API | 内置工具(代码解释器、检索) |
| 2024.04 | Claude Tool Use | 多模态工具调用 |
| 2024.11 | MCP 协议发布 | 工具调用标准化 |
2.3 各厂商实现对比
| 特性 | OpenAI | Anthropic | 开源方案 | |
|---|---|---|---|---|
| 调用方式 | function_call | tool_use | function_calling | 自定义 |
| 并行调用 | ✅ 支持 | ✅ 支持 | ✅ 支持 | 需实现 |
| 流式输出 | ✅ 支持 | ✅ 支持 | ✅ 支持 | 需实现 |
| 多模态 | ✅ Vision | ✅ 全模态 | ✅ 多模态 | 视实现 |
| 工具数量限制 | 128个 | 无明确限制 | 无明确限制 | 无限制 |
三、工具调用的本质
3.1 从语义空间到执行空间
┌─────────────────────────────────────────────────────────────┐
│ 工具调用的本质映射 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 语义空间(LLM 理解的世界) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ "查询北京天气" ──→ 意图:获取天气信息 │ │
│ │ │ │ │ │
│ │ ↓ ↓ │ │
│ │ 实体识别:北京 参数映射:city="北京" │ │
│ │ │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ↓ 工具调用桥梁 │
│ │
│ 执行空间(真实世界) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ weather_api.query("北京") ──→ API 请求 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ { temp: 18, condition: "晴" } ← API 响应 │ │
│ │ │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ↓ 结果映射 │
│ │
│ 回到语义空间 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ "北京现在天气晴朗,气温 18°C" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.2 为什么需要结构化输出?
┌─────────────────────────────────────────────────────────────┐
│ 结构化 vs 非结构化输出 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 非结构化输出(Prompt Hacking): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 输出: │ │
│ │ "我需要搜索一下。[SEARCH: 北京天气]" │ │
│ │ │ │
│ │ 问题: │ │
│ │ • 格式不一致:可能是 [SEARCH:xxx] 或 SEARCH(xxx) │ │
│ │ • 参数解析困难:引号、空格、特殊字符 │ │
│ │ • 错误恢复:解析失败后怎么办? │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结构化输出(Function Calling): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 输出(JSON): │ │
│ │ { │ │
│ │ "function": "weather_search", │ │
│ │ "arguments": { │ │
│ │ "city": "北京" │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 优势: │ │
│ │ • 格式统一:标准 JSON 结构 │ │
│ │ • 类型安全:参数类型由 Schema 约束 │ │
│ │ • 易于解析:直接 JSON 解析 │ │
│ │ • 错误处理:Schema 验证提供清晰错误信息 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘四、代码示例
4.1 Prompt Hacking 方式(早期)
"""
早期工具调用方式:Prompt Hacking
通过提示词工程模拟工具调用
"""
def create_tool_prompt(tools: list) -> str:
"""创建工具提示词"""
tool_descriptions = []
for tool in tools:
tool_descriptions.append(f"- {tool['name']}: {tool['description']}")
prompt = f"""
你是一个智能助手,可以使用以下工具:
{chr(10).join(tool_descriptions)}
当你需要使用工具时,请按以下格式输出:
[ACTION: 工具名]
[INPUT: 输入参数]
示例:
用户:北京天气怎么样?
助手:[ACTION: weather_search]
[INPUT: 北京]
现在请回答用户的问题。
"""
return prompt
def parse_tool_output(output: str) -> tuple:
"""解析工具调用输出"""
import re
# 尝试解析 ACTION
action_match = re.search(r'\[ACTION:\s*(.+?)\]', output)
input_match = re.search(r'\[INPUT:\s*(.+?)\]', output, re.DOTALL)
if action_match:
action = action_match.group(1).strip()
input_val = input_match.group(1).strip() if input_match else ""
return action, input_val
return None, None
# 使用示例
tools = [
{"name": "weather_search", "description": "搜索城市天气"},
{"name": "calculator", "description": "执行数学计算"}
]
prompt = create_tool_prompt(tools)
# 将 prompt 发送给 LLM,然后解析输出4.2 OpenAI Function Calling 方式
"""
OpenAI Function Calling 示例
使用原生函数调用能力
"""
from openai import OpenAI
client = OpenAI()
# 定义工具(Function Schema)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如:北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如:2+2, 10*5"
}
},
"required": ["expression"]
}
}
}
]
def get_weather(city: str, unit: str = "celsius") -> dict:
"""模拟天气 API"""
# 实际应用中这里会调用真实的天气 API
weather_data = {
"北京": {"temp": 18, "condition": "晴"},
"上海": {"temp": 22, "condition": "多云"}
}
return weather_data.get(city, {"temp": 20, "condition": "未知"})
def calculate(expression: str) -> float:
"""执行数学计算"""
try:
return eval(expression)
except Exception as e:
return f"计算错误:{str(e)}"
def run_agent(user_message: str):
"""运行 Agent"""
messages = [{"role": "user", "content": user_message}]
# 第一次调用:LLM 决定是否调用工具
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto" # 让模型自动决定
)
message = response.choices[0].message
# 检查是否需要调用工具
if message.tool_calls:
# 将助手消息添加到历史
messages.append(message)
# 执行每个工具调用
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# 执行工具
if function_name == "get_weather":
result = get_weather(**arguments)
elif function_name == "calculate":
result = calculate(**arguments)
else:
result = "未知工具"
# 添加工具结果到消息历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 第二次调用:LLM 根据工具结果生成最终回答
final_response = client.chat.completions.create(
model="gpt-4",
messages=messages
)
return final_response.choices[0].message.content
return message.content
# 使用示例
import json
result = run_agent("北京和上海现在的天气怎么样?")
print(result)
# 输出:北京现在天气晴朗,气温18°C;上海多云,气温22°C。4.3 LangChain 工具调用方式
"""
LangChain 工具调用示例
标准化工具抽象
"""
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
# 使用装饰器定义工具
@tool
def get_weather(city: str) -> str:
"""
获取指定城市的当前天气
Args:
city: 城市名称,如:北京、上海
Returns:
天气信息描述
"""
weather_data = {
"北京": "晴天,18°C",
"上海": "多云,22°C"
}
return weather_data.get(city, f"未找到{city}的天气信息")
@tool
def calculate(expression: str) -> str:
"""
执行数学计算
Args:
expression: 数学表达式,如:2+2, 10*5
Returns:
计算结果
"""
try:
result = eval(expression)
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{str(e)}"
def create_agent():
"""创建工具调用 Agent"""
# 初始化 LLM
llm = ChatOpenAI(model="gpt-4", temperature=0)
# 工具列表
tools = [get_weather, calculate]
# 创建提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个智能助手,可以使用工具帮助用户。"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
# 创建 Agent
agent = create_tool_calling_agent(llm, tools, prompt)
# 创建执行器
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=5
)
return agent_executor
# 使用示例
if __name__ == "__main__":
executor = create_agent()
result = executor.invoke({
"input": "北京天气怎么样?如果气温是18度,换算成华氏度是多少?"
})
print(result["output"])五、适用场景
5.1 最佳适用场景
| 场景 | 具体应用 | 工具需求 |
|---|---|---|
| 实时信息查询 | 天气、股价、新闻 | 搜索工具、API 调用 |
| 数据处理 | 计算、分析、转换 | 计算器、代码执行 |
| 系统操作 | 文件管理、流程自动化 | 系统工具、CLI |
| 知识检索 | 文档搜索、数据库查询 | RAG、数据库工具 |
| 多模态处理 | 图片分析、音频处理 | 多模态工具 |
5.2 不适用场景
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 纯文本对话 | 无需外部工具 | 直接 LLM 调用 |
| 确定性流程 | 不需要动态决策 | 硬编码流程 |
| 超低延迟需求 | 工具调用增加延迟 | 预计算、缓存 |
六、面试高频问题
Q1: 工具调用和普通 LLM 调用有什么区别?
答案要点:
- 交互方式:工具调用涉及多轮交互(LLM → 工具 → LLM),普通调用是单次请求
- 输出格式:工具调用需要结构化输出(JSON),普通调用是自由文本
- 能力边界:工具调用可突破 LLM 的知识截止和能力限制
- 成本和延迟:工具调用通常需要多次 LLM 调用,成本和延迟更高
Q2: 为什么 OpenAI 的 Function Calling 比早期 Prompt Hacking 更可靠?
答案要点:
- 结构化保证:原生输出 JSON 格式,避免解析错误
- 类型安全:通过 JSON Schema 进行参数类型验证
- 训练优化:模型专门针对工具调用场景进行了训练
- 错误处理:提供清晰的错误信息和重试机制
Q3: 如何设计一个好的工具定义?
答案要点:
- 清晰的描述:描述工具的功能、适用场景、限制条件
- 完整的 Schema:定义所有参数的类型、必填项、枚举值
- 合理的粒度:工具功能单一但完整,避免过于复杂或过于简单
- 幂等性设计:相同输入产生相同输出,便于缓存和重试
Q4: 工具调用可能遇到哪些问题?如何解决?
答案要点:
| 问题 | 解决方案 |
|---|---|
| 幻觉调用 | 提高工具描述清晰度,添加工具使用示例 |
| 参数错误 | Schema 验证 + 自动修复 + 用户确认 |
| 执行超时 | 设置超时时间 + 异步执行 + 进度反馈 |
| 结果过大 | 结果截断 + 摘要生成 + 分页返回 |
| 安全风险 | 沙箱执行 + 权限控制 + 审计日志 |
Q5: 多工具场景下如何提高工具选择准确率?
答案要点:
- 语义检索:使用 Tool RAG 根据意图检索相关工具
- 分层路由:先分类再选择,减少候选工具数量
- 工具描述优化:添加更多使用示例和适用场景描述
- Fine-tuning:针对特定工具集进行模型微调
七、小结
| 概念 | 一句话总结 |
|---|---|
| 工具调用本质 | LLM 从语义空间到执行空间的映射 |
| 发展历程 | Prompt Hacking → Native Function Calling → Protocol Standardization |
| 核心价值 | 突破 LLM 的知识截止和能力限制 |
| 关键挑战 | 意图识别、参数提取、执行安全、结果处理 |
一句话总结:工具调用是 Agent 系统的核心能力,理解其本质和发展历程有助于更好地设计和优化 Agent 应用。
最后更新:2026年3月18日