本节目标:理解如何让大模型调用外部工具(函数),从"只能说"变成"能做事"。这是 Agent 和 MCP 的基础。
一、为什么大模型需要工具?
1.1 大模型的天然局限
ini
大模型擅长的: 大模型不擅长的:
✓ 理解语言 ✗ 精确计算(1234 × 5678 = ?)
✓ 生成文本 ✗ 获取实时信息(今天的天气)
✓ 逻辑推理 ✗ 操作外部系统(发邮件、查数据库)
✓ 代码理解 ✗ 访问最新数据(最新股价)
解决方案:给大模型一些"工具",让它在需要时自己调用!
1.2 一个生活化的比喻
arduino
大模型 = 一个很聪明的助手
没有工具的助手:
你:"帮我查一下明天北京的天气"
助手:"我记得北京这个季节一般是...(可能说错)"
有工具的助手:
你:"帮我查一下明天北京的天气"
助手:(打开天气App查了一下)"明天北京晴,15-25度"
工具让助手从"猜测"变成"查证"!
二、Function Calling 工作原理
2.1 整体流程
graph TD
A["第1步:你告诉模型有哪些工具可用
┌──────────────────────────┐
工具清单:
1. get_weather(城市)
2. search_web(关键词)
3. send_email(收件人,内容)
└──────────────────────────┘"] B["第2步:用户提问
'明天北京天气怎么样?'"] C["第3步:模型决定调用哪个工具(模型不执行,只返回调用意图)
→ {'function': 'get_weather', 'args': {'city': '北京'}}"] D["第4步:你的程序执行这个函数
→ get_weather('北京') → '晴,15-25度'"] E["第5步:把执行结果告诉模型
→ 模型收到 '晴,15-25度'"] F["第6步:模型组织语言回答用户
→ '明天北京天气晴朗,气温15到25度,适合出行~'"] A --> B B --> C C --> D D --> E E --> F classDef actor fill:#e3f2fd,stroke:#1565c0,stroke-width:2px; classDef step fill
┌──────────────────────────┐
工具清单:
1. get_weather(城市)
2. search_web(关键词)
3. send_email(收件人,内容)
└──────────────────────────┘"] B["第2步:用户提问
'明天北京天气怎么样?'"] C["第3步:模型决定调用哪个工具(模型不执行,只返回调用意图)
→ {'function': 'get_weather', 'args': {'city': '北京'}}"] D["第4步:你的程序执行这个函数
→ get_weather('北京') → '晴,15-25度'"] E["第5步:把执行结果告诉模型
→ 模型收到 '晴,15-25度'"] F["第6步:模型组织语言回答用户
→ '明天北京天气晴朗,气温15到25度,适合出行~'"] A --> B B --> C C --> D D --> E E --> F classDef actor fill:#e3f2fd,stroke:#1565c0,stroke-width:2px; classDef step fill
关键理解:模型只负责"决定调用什么",不负责"真正执行"。执行是你的程序做的。
sequenceDiagram
participant P as 你的程序
participant M as 大模型
P->>M: "①定义工具"
P->>M: "②用户提问"
M-->>P: "③返回调用意图"
Note over P: ④执行函数(本地执行)"
P->>M: "⑤返回执行结果"
M-->>P: "⑥自然语言回答"
三、实战:OpenAI Function Calling
3.1 定义工具
python
# 第1步:定义可用的工具(用 JSON 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 + 3 * 4'"
}
},
"required": ["expression"]
}
}
}
]
3.2 完整调用流程
python
from openai import OpenAI
import json
client = OpenAI()
# 实际的工具函数
def get_weather(city: str, unit: str = "celsius") -> str:
"""模拟天气API(实际项目中会调用真实API)"""
weather_data = {
"北京": {"temp": 22, "condition": "晴"},
"上海": {"temp": 25, "condition": "多云"},
}
data = weather_data.get(city, {"temp": 20, "condition": "未知"})
return json.dumps({"city": city, "temperature": data["temp"],
"condition": data["condition"]}, ensure_ascii=False)
def calculate(expression: str) -> str:
"""安全的数学计算"""
try:
result = eval(expression) # 注意:生产环境应使用更安全的方式
return json.dumps({"expression": expression, "result": result})
except Exception as e:
return json.dumps({"error": str(e)})
# 工具函数映射
tool_functions = {
"get_weather": get_weather,
"calculate": calculate,
}
# 完整的对话循环
def chat_with_tools(user_message: str):
messages = [{"role": "user", "content": user_message}]
# 第1次调用:模型决定是否使用工具
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
message = response.choices[0].message
# 检查模型是否要调用工具
if message.tool_calls:
messages.append(message) # 把模型的回复加入对话
# 执行每个工具调用
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f" 调用工具:{func_name}({func_args})")
# 执行函数
result = tool_functions[func_name](**func_args)
# 把结果告诉模型
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
# 第2次调用:模型根据工具结果生成最终回答
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return final_response.choices[0].message.content
else:
# 不需要工具,直接返回
return message.content
# 测试
print(chat_with_tools("北京今天天气怎么样?"))
# 调用工具:get_weather({'city': '北京'})
# 输出:北京今天天气晴朗,气温22度,适合外出活动!
print(chat_with_tools("帮我算一下 1234 × 5678"))
# 调用工具:calculate({'expression': '1234 * 5678'})
# 输出:1234 × 5678 = 7,006,652
print(chat_with_tools("你好啊"))
# (不调用工具,直接回答)
# 输出:你好!有什么我可以帮你的吗?
四、Anthropic Tool Use(Claude)
Claude 的工具使用方式略有不同,但核心思想一样:
python
import anthropic
import json
client = anthropic.Anthropic()
# 定义工具
tools = [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
]
# 第1次调用
response = client.messages.create(
model="claude-sonnet-4-6-20250415",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "北京天气怎么样?"}]
)
# 检查是否需要调用工具
if response.stop_reason == "tool_use":
# 找到工具调用块
tool_use = next(b for b in response.content if b.type == "tool_use")
print(f"调用工具:{tool_use.name}({tool_use.input})")
# 执行工具并返回结果
tool_result = get_weather(**tool_use.input)
# 第2次调用:带上工具结果
final_response = client.messages.create(
model="claude-sonnet-4-6-20250415",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "北京天气怎么样?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": tool_result
}]
}
]
)
print(final_response.content[0].text)
五、多工具并行调用
模型可以一次调用多个工具,你的程序并行执行后一起返回:
ini
用户:"帮我查一下北京和上海的天气,顺便算一下 100 * 200"
模型返回(一次性3个调用):
1. get_weather(city="北京")
2. get_weather(city="上海")
3. calculate(expression="100 * 200")
你的程序并行执行这3个函数 → 把3个结果一起告诉模型 → 模型综合回答
python
import asyncio
async def execute_tools_parallel(tool_calls):
"""并行执行多个工具调用"""
tasks = []
for call in tool_calls:
func = tool_functions[call.function.name]
args = json.loads(call.function.arguments)
# 创建异步任务
tasks.append(asyncio.to_thread(func, **args))
# 并行执行所有任务
results = await asyncio.gather(*tasks)
return results
六、工具设计最佳实践
6.1 好的工具描述
python
# ❌ 差的工具定义
{
"name": "query",
"description": "查询数据",
"parameters": {
"properties": {
"q": {"type": "string"}
}
}
}
# 问题:名称模糊、描述太简单、参数不清楚
# ✅ 好的工具定义
{
"name": "search_products",
"description": "在商品数据库中搜索商品。"
"支持按商品名称、类别、价格范围搜索。"
"返回匹配的商品列表,包含名称、价格、库存信息。",
"parameters": {
"properties": {
"keyword": {
"type": "string",
"description": "搜索关键词,如'笔记本电脑'、'耳机'"
},
"category": {
"type": "string",
"enum": ["电子产品", "图书", "服装", "食品"],
"description": "商品类别筛选"
},
"max_price": {
"type": "number",
"description": "最高价格(元),如 1000"
},
"limit": {
"type": "integer",
"description": "返回结果数量上限,默认10",
"default": 10
}
},
"required": ["keyword"]
}
}
6.2 工具设计原则
arduino
┌────────────────────────────────────────────────────────────┐
│ 工具设计七原则 │
│ │
│ 1. 命名清晰:用动词+名词,如 search_products, send_email │
│ 2. 描述详细:说清楚做什么、需要什么参数、返回什么 │
│ 3. 参数精确:用 enum 限制可选值,用 description 解释 │
│ 4. 职责单一:一个工具只做一件事 │
│ 5. 错误友好:返回有意义的错误信息 │
│ 6. 安全第一:敏感操作需确认,限制危险参数 │
│ 7. 结果简洁:只返回必要的信息,不要返回大段无关数据 │
└────────────────────────────────────────────────────────────┘
七、错误处理与安全
7.1 工具调用可能出错
python
def safe_tool_execution(func_name: str, func_args: dict) -> str:
"""安全地执行工具函数"""
try:
# 检查函数是否存在
if func_name not in tool_functions:
return json.dumps({"error": f"未知工具:{func_name}"})
# 执行函数(设置超时)
import signal
def timeout_handler(signum, frame):
raise TimeoutError("工具执行超时")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30) # 30秒超时
result = tool_functions[func_name](**func_args)
signal.alarm(0) # 取消超时
return result
except TimeoutError:
return json.dumps({"error": "工具执行超时,请稍后重试"})
except Exception as e:
return json.dumps({"error": f"工具执行失败:{str(e)}"})
7.2 安全考量
┌────────────────────────────────────────────────────────────┐
│ 安全检查清单 │
│ │
│ ✓ 永远不要让模型直接执行任意代码 │
│ ✓ 对危险操作(删除、支付)添加人工确认步骤 │
│ ✓ 限制工具的访问范围(只读 vs 读写) │
│ ✓ 对输入参数进行验证和清洗 │
│ ✓ 设置执行超时和重试限制 │
│ ✓ 记录所有工具调用日志 │
│ ✓ 防止 Prompt 注入导致的恶意工具调用 │
└────────────────────────────────────────────────────────────┘
八、从 Function Calling 到 MCP
css
Function Calling 的局限:
每个 AI 应用都要自己实现工具 → 重复造轮子
┌─────────┐ ┌─────────┐ ┌─────────┐
│ App A │ │ App B │ │ App C │
│ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │
│ │天气 │ │ │ │天气 │ │ │ │天气 │ │ ← 每个App都要写一遍
│ │数据库│ │ │ │数据库 │ │ │ │邮件 │ │
│ │邮件 │ │ │ │搜索 │ │ │ │搜索 │ │
│ └─────┘ │ │ └─────┘ │ │ └─────┘ │
└─────────┘ └─────────┘ └─────────┘
MCP 的解决方案:标准化!
┌─────────┐ ┌─────────┐ ┌─────────┐
│ App A │ │ App B │ │ App C │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘
│
MCP 标准协议
│
┌────────────┼────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 天气 │ │ 数据库 │ │ 邮件 │ ← 写一次,到处用
│ Server │ │ Server │ │ Server │
└─────────┘ └─────────┘ └─────────┘
九、本篇小结
javascript
┌─────────────────────────────────────────────────────┐
│ 本篇知识地图 │
│ │
│ Function Calling = 让大模型能调用外部工具 │
│ │
│ 核心流程: │
│ 定义工具 → 用户提问 → 模型选择工具 → │
│ 程序执行 → 返回结果 → 模型回答 │
│ │
│ 关键点: │
│ ├── 模型只负责"决定调用什么" │
│ ├── 你的程序负责"真正执行" │
│ ├── 支持多工具并行调用 │
│ └── 需要注意安全性 │
│ │
│ 工具设计原则: │
│ 命名清晰 + 描述详细 + 职责单一 + 安全第一 │
│ │
│ 演进方向: │
│ Function Calling → MCP(标准化协议) │
└─────────────────────────────────────────────────────┘
十、扩展学习资源
必读
- OpenAI Function Calling 文档 ------ 官方指南
- Anthropic Tool Use 文档 ------ Claude 工具使用
- Gemini Function Calling ------ Google 的实现
推荐
- Gorilla LLM ------ 专门优化工具调用的研究项目
- ToolBench ------ 工具使用评测基准
动手实践
- 实现一个能查天气 + 算数学的对话机器人
- 给现有项目的 API 包装成 Function Calling 工具
- 尝试让模型在一次对话中连续调用多个工具完成复杂任务
下一篇章预告 :将讲解 MCP(Model Context Protocol)------一个标准化的协议,让工具像 USB 一样"即插即用"。
觉得有用的话,点个关注吧!大模型方面你想看什么?留言区说,我来写。
声明:本博客内容素材来源于网络,文章由AI技术辅助生成。如有侵权或不当引用,请联系作者进行下架或删除处理。