12-大模型智能体开发工程师:Function Calling原理与实战
·
系列文章导航:AI系列文章导航目录-持续更新中
第12课:Function Calling原理与实战
📝 本文摘要:本文详解Function Calling机制——没有它Agent只能聊天,有了它Agent能调API、查数据库、发邮件。内容包括:无Function Calling时的变通方案(Prompt解析JSON/ReAct)、Function Calling的完整工作流(工具定义→模型决策→输出结构化→调用执行→结果返回)、OpenAI vs Anthropic工具调用格式对比、多函数并行调用、实战示例(天气查询+数据库+邮件发送),以及Function Calling局限性讨论。
Function Calling是Agent"动手"的基础。没有它,Agent只能聊天;有了它,Agent能调API、查数据库、发邮件——真正地"做事"。
一、Function Calling的本质
一句话理解:Function Calling让模型能够"动手做事"。没有它,模型只能聊天;有了它,模型能调API、查数据库、发邮件——真正地"做事"。
核心原理:模型不是自己执行函数,而是告诉你"我想调哪个函数、传什么参数",然后你的代码去执行。
整个流程类比:
你(开发者) → 给模型一份"工具清单"(告诉它有哪些工具可用)
用户 → 提问题
模型 → 判断: 这个问题需要用工具吗?
├─ 不需要 → 直接回答
└─ 需要 → 输出: "我要调get_weather,参数是{city:北京}"
你(开发者) → 执行get_weather("北京"),把结果返回给模型
模型 → 基于工具结果生成最终回答: "北京今天晴,28°C"
1.1 没有Function Calling时怎么做
方式1: Prompt Hack
"请输出以下格式来调用工具:
TOOL_CALL: {\"name\": \"get_weather\", \"args\": {\"city\": \"北京\"}}"
问题:
- 模型不一定按格式输出
- 格式可能被模型"创造性"修改
- 参数可能不符合预期类型
- 每次都要写很长的Prompt
方式2: 正则匹配
让模型输出自然语言,用正则提取意图
"我想查北京天气" → 正则匹配 → 调get_weather("北京")
问题:
- 复杂意图很难用正则覆盖
- 参数提取不可靠
1.2 Function Calling做了什么
核心思想: 把"工具调用"变成模型的原生能力
传统方式: 模型只输出文本 → 你从文本中提取意图 → 你调API
问题: 提取意图不可靠,格式不稳定
FC方式: 模型输出结构化的工具调用 → 你直接调API
优势: 100%结构化,参数类型正确,可靠性极高
本质: 模型被训练成"知道什么时候该调工具、怎么调"
这不是Prompt技巧,而是模型能力(通过专门训练获得)
类比:
传统方式 = 你跟一个不会用电脑的人说"帮我查下天气",他口头告诉你怎么查,你自己操作
FC方式 = 你跟一个会用电脑的人说"帮我查下天气",他直接打开天气网站查给你
1.3 Function Calling的完整流程
这是Agent最核心的循环,必须彻底理解!
┌─ 你的代码 ──────────────────────────────────────────┐
│ │
│ 1. 定义工具 (tools参数) │
│ → 告诉模型: 你有哪些工具可用,每个工具做什么 │
│ 2. 发送: messages + tools → LLM API │
│ │
├─ LLM处理 ─────────────────────────────────────────┤
│ │
│ 3. LLM判断: 需要调工具吗? │
│ ├── 不需要 → 直接生成文本回复 │
│ └── 需要 → 生成tool_calls (结构化的工具调用) │
│ { │
│ "name": "get_weather", │
│ "arguments": "{\"city\": \"北京\"}" │
│ } │
│ │
├─ 你的代码 ──────────────────────────────────────────┤
│ │
│ 4. 解析tool_calls,执行对应函数 │
│ → 你的代码调用真实的get_weather()函数 │
│ 5. 把结果加入messages,再次调用LLM │
│ → 告诉模型: 工具返回了什么结果 │
│ │
├─ LLM处理 ─────────────────────────────────────────┤
│ │
│ 6. LLM基于工具结果生成最终回复 │
│ → "北京今天天气晴,温度28°C,湿度45%" │
│ │
└──────────────────────────────────────────────────┘
注意: 整个过程中,LLM从未直接执行任何函数!
它只是"说"它想调什么,你的代码负责实际执行。
这是安全性的关键——你可以在执行前做权限检查、参数验证等。
二、Function Calling实战
2.1 定义工具
from openai import OpenAI
import json
client = OpenAI()
# 工具定义(告诉模型有哪些工具可用)
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"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "query_order",
"description": "查询订单信息",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号"
}
},
"required": ["order_id"],
"additionalProperties": False
}
}
}
]
2.2 完整的Function Calling循环
# 实际的工具实现
def get_weather(city: str, unit: str = "celsius") -> dict:
"""模拟天气API"""
weather_data = {
"北京": {"temp": 28, "condition": "晴", "humidity": 45},
"上海": {"temp": 32, "condition": "多云", "humidity": 78},
"深圳": {"temp": 35, "condition": "雷阵雨", "humidity": 85},
}
data = weather_data.get(city, {"temp": 25, "condition": "未知", "humidity": 50})
if unit == "fahrenheit":
data["temp"] = data["temp"] * 9/5 + 32
return data
def query_order(order_id: str) -> dict:
"""模拟订单查询API"""
orders = {
"ORD001": {"status": "已发货", "items": ["手机壳", "充电器"], "total": 128.5},
"ORD002": {"status": "待发货", "items": ["耳机"], "total": 299.0},
}
return orders.get(order_id, {"status": "未找到", "items": [], "total": 0})
# 工具映射
tool_map = {
"get_weather": get_weather,
"query_order": query_order,
}
# 完整的Agent循环
def agent_chat(user_message: str) -> str:
messages = [
{"role": "system", "content": "你是一个智能助手,可以查询天气和订单信息。"},
{"role": "user", "content": user_message}
]
max_rounds = 5 # 防止死循环
for _ in range(max_rounds):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto" # auto: 模型自己决定是否调工具
)
msg = response.choices[0].message
# 情况1: 模型直接回复(不需要调工具)
if msg.content and not msg.tool_calls:
return msg.content
# 情况2: 模型要调工具
if msg.tool_calls:
messages.append(msg) # 把模型的tool_call加入历史
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f" → 调用工具: {func_name}({func_args})")
# 执行工具
result = tool_map[func_name](**func_args)
# 把工具结果加入messages
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
return "抱歉,处理过程中遇到了问题。"
# 测试
print(agent_chat("北京今天天气怎么样?"))
# → 调用工具: get_weather({'city': '北京'})
# → "北京今天天气晴,温度28°C,湿度45%。"
print(agent_chat("我的订单ORD001到哪了?"))
# → 调用工具: query_order({'order_id': 'ORD001'})
# → "您的订单ORD001已发货,包含手机壳和充电器,总金额128.5元。"
print(agent_chat("你好"))
# → 不调工具,直接回复问候
2.3 tool_choice参数(控制模型是否/如何调用工具)
# "auto"(自动模式): 模型自己决定是否调工具(默认)
tool_choice="auto"
# "none"(禁用模式): 禁止调工具,强制纯文本回复
tool_choice="none"
# "required"(强制模式): 强制调工具,模型必须选择一个工具调用
tool_choice="required"
# 指定工具(指定模式): 强制调用某个特定工具
tool_choice={"type": "function", "function": {"name": "get_weather"}}
三、Function Calling的底层原理
3.1 模型是怎么学会调工具的
训练阶段:
1. 收集大量"用户意图→工具调用"的配对数据
2. 用SFT(Supervised Fine-Tuning,监督微调)教模型: 看到什么意图时应该输出什么工具调用
3. 用RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)优化: 工具调用正确的给奖励
推理阶段:
1. tools参数被转换为特殊Token序列,拼接到输入中
2. 模型看到这些特殊Token,"知道"有工具可用
3. 当判断需要调工具时,输出tool_calls格式的Token序列
4. 这些Token序列被API层解析为结构化的JSON
3.2 并行工具调用
# 一个回复中可以包含多个tool_calls
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "北京和上海今天天气怎么样?"}],
tools=tools
)
# 模型会并行输出两个tool_calls:
# tool_calls[0]: get_weather({"city": "北京"})
# tool_calls[1]: get_weather({"city": "上海"})
# 你需要都执行,然后把两个结果都返回
四、Function Calling的工程化要点
4.1 工具描述是关键
模型选择工具完全依赖description!
❌ 差的描述:
"获取信息" → 模型不知道获取什么信息
✅ 好的描述:
"查询指定城市的当前天气信息,包括温度、天气状况和湿度"
→ 模型知道什么时候该调这个工具
参数描述同样重要:
❌ "城市" → 模型可能传"北京朝阳区"
✅ "城市名称,如北京、上海、广州" → 模型传正确的值
4.2 错误处理
def agent_chat_robust(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(5):
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools
)
except Exception as e:
return f"API调用失败: {e}"
msg = response.choices[0].message
if not msg.tool_calls:
return msg.content or ""
messages.append(msg)
for tool_call in msg.tool_calls:
try:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
if func_name not in tool_map:
result = {"error": f"未知工具: {func_name}"}
else:
result = tool_map[func_name](**func_args)
except json.JSONDecodeError:
result = {"error": "参数解析失败"}
except TypeError as e:
result = {"error": f"参数类型错误: {e}"}
except Exception as e:
result = {"error": f"执行失败: {e}"}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
return "处理超时,请稍后重试。"
4.3 工具设计原则
1. 单一职责: 每个工具只做一件事
✅ query_order() + create_refund()
❌ order_operation(type="query_or_refund")
2. 清晰的输入输出: 参数类型明确,返回值结构化
3. 有边界: 工具应该有明确的成功/失败状态
4. 安全性: 危险操作需要确认(如删除、支付)
5. 幂等性: 同样的输入应该得到同样的结果
五、不同模型的Function Calling对比
| 模型 | 并行调用 | 强制调用 | Structured Output | 可靠性 |
|---|---|---|---|---|
| GPT-4o | ✅ | ✅ | ✅ | ★★★★★ |
| GPT-4.1 | ✅ | ✅ | ✅ | ★★★★★ |
| Claude 3.5+ | ✅ | ✅ | ✅ | ★★★★★ |
| DeepSeek-V3 | ✅ | ❌ | 部分 | ★★★★☆ |
| Qwen2.5 | ✅ | 部分 | 部分 | ★★★★☆ |
| 本地模型 | 有限 | ❌ | ❌ | ★★★☆☆ |
📝 作业
作业1:实现一个带有3个工具的Agent
实现一个"个人助手Agent",支持以下工具:
search_web(query)- 搜索网络(模拟实现)calculate(expression)- 计算数学表达式translate(text, target_lang)- 翻译文本(模拟实现)
参考答案:
from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# 工具实现
def search_web(query: str) -> str:
mock_results = {
"Python": "Python是由Guido van Rossum创建的高级编程语言,最新版本3.12",
"AI": "2026年AI领域最热门的方向是Agent和推理模型",
}
for key, val in mock_results.items():
if key.lower() in query.lower():
return val
return f"搜索'{query}'的结果:未找到相关信息"
def calculate(expression: str) -> str:
try:
# 安全起见,只允许基本数学运算
allowed = set("0123456789+-*/().% ")
if all(c in allowed for c in expression):
result = eval(expression)
return str(result)
return "不支持的表达式"
except:
return "计算错误"
def translate(text: str, target_lang: str) -> str:
mock = {"hello": "你好", "world": "世界", "你好": "Hello", "世界": "World"}
words = text.lower().split()
result = " ".join(mock.get(w, w) for w in words)
return f"[{target_lang}] {result}"
tool_map = {"search_web": search_web, "calculate": calculate, "translate": translate}
tools = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "搜索互联网获取信息",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string", "description": "搜索关键词"}},
"required": ["query"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学表达式,如'2+3*4'",
"parameters": {
"type": "object",
"properties": {"expression": {"type": "string", "description": "数学表达式"}},
"required": ["expression"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "translate",
"description": "翻译文本到目标语言",
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "要翻译的文本"},
"target_lang": {"type": "string", "description": "目标语言,如'en'或'zh'"}
},
"required": ["text", "target_lang"],
"additionalProperties": False
}
}
}
]
def agent_chat(user_message: str) -> str:
messages = [
{"role": "system", "content": "你是一个个人助手,可以搜索信息、计算和翻译。"},
{"role": "user", "content": user_message}
]
for _ in range(5):
response = client.chat.completions.create(
model="qwen2.5:7b",
messages=messages,
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
if msg.content and not msg.tool_calls:
return msg.content
if msg.tool_calls:
messages.append(msg)
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = tool_map[tc.function.name](**args)
print(f" → {tc.function.name}({args}) = {result}")
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps({"result": result}, ensure_ascii=False)
})
return "处理超时"
# 测试
print(agent_chat("帮我算一下(128+256)*3等于多少"))
print(agent_chat("Python是谁创建的?"))
下一篇文章见:AI系列文章导航目录-持续更新中
更多推荐



所有评论(0)