AI 技术演进与核心算法实战 | 第十七篇:Function Calling 原理:从 Prompt 伪装到原生支持的技术演进与参数绑定机制


如果说 CoT 和 ToT 是给模型装上了"思考的大脑",那么 Function Calling 就是给模型装上了"执行的双手"——让它从纸上谈兵的书生,变成能解决实际问题的实干家。
在 上一篇 中,我们探讨了 Graph RAG 如何通过知识图谱增强全局理解能力。但一个关键问题仍然存在:即使模型理解了复杂的知识网络,它如何将这些理解转化为实际行动?
想象这些真实场景:
场景 1:查询天气
用户:"帮我查一下北京明天的天气"
传统对话模型的局限:
- 可以生成"北京明天晴朗,气温 25°C"这样的文本
- ❌ 但无法真正调用天气 API 获取实时数据
- ❌ 无法触发后续的穿衣建议、出行提醒等动作
缺失的能力:工具调用(Tool Use)
场景 2:数据分析
用户:"分析一下上个月的销售数据,找出 Top 3 产品"
纯文本模型的局限:
- 可以解释"什么是销售分析"
- ❌ 但无法执行 SQL 查询数据库
- ❌ 无法运行 Python 代码进行统计分析
缺失的能力:代码解释器(Code Interpreter)
场景 3:多步骤任务
用户:"帮我订一张下周从北京到上海的机票,要靠窗的位置"
单轮对话的局限:
- 可以理解"订票"这个意图
- ❌ 但无法完成"搜索航班→选择座位→支付订单"的多步流程
- ❌ 无法与多个 API(航班搜索、选座、支付)交互
缺失的能力:工作流编排(Workflow Orchestration)
本篇是 《AI 技术演进与核心算法实战》第四模块的第一篇。我们将深入探讨 Function Calling(函数调用) 的核心原理,这是赋予模型"行动能力"的关键技术。
1. 从"坐而论道"到"起而行之"
1.1 一个思想实验:军师 vs 将军
让我们通过一个思想实验来理解 Function Calling 的必要性。
场景一:军师(纯对话模型)
君王:"敌军压境,该如何应对?"
军师的回答:
"臣以为,当务之急有三:
其一,加固城防,囤积粮草;
其二,遣使求和,缓兵待援;
其三,招募勇士,训练精兵。"
军师的特点是:只能给出建议,无法亲自执行。他的输出停留在"语言层面"。
场景二:将军(支持 Function Calling 的模型)
君王:"敌军压境,该如何应对?"
将军的回答 + 行动:
"臣这就去办!"
→ 立即调用 send_messenger() 派遣信使
→ 调用 reinforce_city_walls() 加固城墙
→ 调用 recruit_soldiers() 招募士兵
将军的特点是:既能理解意图,又能付诸行动。他的输出连接到"执行层面"。
关键洞察:Function Calling 的本质,就是让模型从"军师"进化为"将军"——从单纯的"输出文本"升级为"输出行动"。
1.2 Function Calling 的定义与价值
定义:Function Calling(函数调用)是指大模型能够根据用户请求,自动选择并调用合适的函数或工具来完成特定任务的能力。
核心价值:
| 维度 | 纯对话模型 | Function Calling 模型 |
|---|---|---|
| 能力边界 | 受限于训练数据的截止时间 | 可通过 API 访问实时信息 |
| 准确性 | 可能产生幻觉(Hallucination) | 基于真实工具返回的数据 |
| 行动力 | 只能"说说而已" | 可以实际改变世界 |
| 专业性 | 通用知识,缺乏深度 | 可调用专业工具(如计算器、搜索引擎) |
1.3 技术演进的三个阶段
Function Calling 的发展经历了三个关键阶段:
阶段一:Prompt 伪装(2022-2023 年初)
- 代表方案:早期 LangChain、自定义 Prompt 模板
- 特点:通过精心设计的提示词"骗"模型输出类似函数调用的格式
- 问题:不稳定,依赖模型的"心情"
阶段二:JSON Schema 约束(2023 年中)
- 代表方案:Guidance、lm-format-enforcer
- 特点:使用语法约束强制模型输出合法的 JSON 格式
- 进步:格式 100% 正确,但仍然需要解析
阶段三:原生支持(2023 年底至今)
- 代表方案:OpenAI Function Calling、Anthropic Tool Use
- 特点:模型在训练阶段就学习了函数调用的语义
- 优势:最稳定、最准确、最易用
2. Prompt 伪装阶段:用提示词"骗"出函数调用
2.1 基本思路:给模型一个"角色设定"
最早的 Function Calling 实现非常简单粗暴——通过 Prompt 告诉模型"你是一个函数调用器"。
示例 Prompt:
你是一个智能助手,负责调用 API 来完成任务。
可用的函数有:
1. weather_api(city: str, unit: str) -> 查询天气
2. calculator(expression: str) -> 计算数学表达式
3. search_web(query: str) -> 搜索互联网
当用户请求需要调用函数时,请按以下格式输出:
【FUNCTION_CALL】
函数名:weather_api
参数:{"city": "Beijing", "unit": "celsius"}
【/FUNCTION_CALL】
如果不需要调用函数,直接回答问题。
用户:"北京明天天气怎么样?"
模型的输出:
【FUNCTION_CALL】
函数名:weather_api
参数:{"city": "Beijing", "unit": "celsius"}
【/FUNCTION_CALL】
然后程序解析这段文本,提取函数名和参数,真正执行调用。
2.2 核心挑战:格式不稳定性
这种方法的最大问题是:模型并不总是遵守格式。
失败案例 1:格式错误
【FUNCTION_CALL】
weather_api(Beijing) ← 忘记写参数键名
【/FUNCTION_CALL】
失败案例 2:忘记调用
北京明天天气晴朗,气温约 25 摄氏度。 ← 模型自己"编"了答案
失败案例 3:多余解释
我需要调用天气 API 来获取信息。
【FUNCTION_CALL】
函数名:weather_api
参数:{"city": "Beijing"}
【/FUNCTION_CALL】
现在让我来执行这个调用... ← 多余的废话
2.3 改进策略:Few-shot 示例强化
为了提高稳定性,研究者发现 Few-shot Learning(少样本学习) 非常有效。
改进后的 Prompt:
你是一个智能助手,负责调用 API 来完成任务。
可用函数:
- weather_api(city: str, unit: str)
- calculator(expression: str)
示例 1:
用户:"北京天气怎么样?"
助手:【FUNCTION_CALL】{"name":"weather_api","arguments":{"city":"Beijing","unit":"celsius"}}【/FUNCTION_CALL】
示例 2:
用户:"123 乘以 456 等于多少?"
助手:【FUNCTION_CALL】{"name":"calculator","arguments":{"expression":"123*456"}}【/FUNCTION_CALL】
示例 3:
用户:"你好"
助手:您好!有什么我可以帮助您的吗?← 不需要调用函数时正常回答
现在请回答:
用户:"上海的气温是多少?"
效果提升:
- 通过提供正例和反例,模型学会了"什么时候调用"和"怎么调用"
- 格式稳定性从约 70% 提升到 85-90%
2.4 物理意义:条件反射的建立
从行为主义心理学的角度来看,Prompt 伪装的本质是建立条件反射。
图解说明:如上图所示,Prompt 伪装的原理与巴甫洛夫的经典条件反射实验如出一辙。通过反复展示"某类问题→某种输出格式"的配对,模型逐渐建立起模式关联。但这只是"表面功夫"——模型并没有真正理解函数的概念,只是在模仿格式。
3. JSON Schema 约束:给输出装上"护栏"
3.1 核心问题:为什么需要强制约束?
Prompt 伪装的根本问题是:它依赖于模型的"自觉性"。
就像老师对学生说:“请用规范的格式答题”。好学生会照做,但总有学生会偷懒、粗心或者理解偏差。
JSON Schema 约束的思路:与其依赖自觉性,不如直接禁止错误答案的出现。
图解说明:如上图所示,JSON Schema 约束通过在生成过程中实时检查语法规则,将不符合 Schema 的 token 全部 mask 掉(即将其概率设为 0),从而保证输出 100% 合法。这比 Prompt 伪装的"软性建议"要可靠得多。
3.2 JSON Schema 基础:定义数据结构
JSON Schema 是一种用来描述 JSON 数据结构的规范语言。
简单示例:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "函数名称"
},
"arguments": {
"type": "object",
"description": "函数参数"
}
},
"required": ["name", "arguments"]
}
这个 Schema 定义了:
- 必须是一个对象(
type: object) - 必须包含
name字段(字符串类型) - 必须包含
arguments字段(对象类型)
3.3 实战:使用 lm-format-enforcer 实现约束
让我们通过一个实际例子来展示如何使用 JSON Schema 约束。
示例需求:让模型调用天气查询函数
第一步:定义 Schema
from pydantic import BaseModel, Field
class FunctionCall(BaseModel):
"""函数调用的标准格式"""
name: str = Field(
description="函数名称,必须是以下之一:weather_api, calculator, search_web"
)
arguments: dict = Field(
description="函数的参数,键值对形式"
)
thought: str = Field(
default="",
description="可选的思考过程,用于解释为什么选择这个函数"
)
# 转换为 JSON Schema
schema = FunctionCall.model_json_schema()
print(schema)
第二步:使用约束解码器
from lmformatenforcer import JsonSchemaOutputParser
from transformers import AutoTokenizer, AutoModelForCausalLM
# 加载模型和分词器
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf")
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-chat-hf")
# 创建 JSON Schema 解析器
parser = JsonSchemaOutputParser(schema)
# 构建 Prompt
prompt = """
用户:"北京明天天气怎么样?"
请以 JSON 格式返回函数调用:
"""
# 约束生成
from transformers import GenerationConfig
inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(
**inputs,
max_new_tokens=100,
prefix_allowed_tokens_fn=parser.get_prefix_allowed_tokens_fn(
batch_id=0,
input_ids=inputs["input_ids"][0]
)
)
result = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(result)
输出结果:
{
"name": "weather_api",
"arguments": {
"city": "Beijing",
"unit": "celsius"
},
"thought": "用户询问天气,需要调用天气查询 API"
}
关键优势:
- ✅ 100% 格式正确:不可能输出非法 JSON
- ✅ 类型安全:自动验证字段类型和必填项
- ✅ 自文档化:Schema 本身就是文档
3.4 局限性:仍然需要后处理
虽然 JSON Schema 约束解决了格式问题,但它仍然存在一个根本性局限:
模型输出的仍然是"文本",需要程序解析后才能执行。
模型输出 → JSON 文本 → 程序解析 → 提取函数名和参数 → 真正调用
这个链条中任何一个环节出错都会导致失败:
- 模型可能选择不存在的函数
- 参数类型可能不正确(如把数字传成字符串)
- 缺少必要的参数
理想的情况:模型应该真正理解函数的含义,而不是机械地输出格式。这就是原生 Function Calling 要解决的问题。
4. 原生 Function Calling:模型真正"理解"了工具
4.1 什么是"原生支持"?
2023 年底,OpenAI 在 GPT-4 Turbo 中首次推出了原生 Function Calling功能。随后,Anthropic、Google 等公司纷纷跟进。
"原生"的含义:
- 训练阶段就学习了函数调用的语义
- 内化了工具的用途,而不是表面的格式
- 端到端优化,不需要中间的解析层
图解说明:如上图所示,原生 Function Calling 通过在训练阶段就引入函数调用样本,并使用强化学习优化,让模型真正理解了工具的用途。这比 Prompt 伪装的"表面模仿"要深刻得多。
4.2 OpenAI Function Calling 实战
让我们通过 OpenAI 的官方 API 来体验原生 Function Calling。
第一步:定义函数描述
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 定义可用工具列表
tools = [
{
"type": "function",
"function": {
"name": "weather_api",
"description": "查询指定城市的当前天气情况",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'Beijing'、'Shanghai'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位"
}
},
"required": ["city", "unit"]
}
}
},
{
"type": "function",
"function": {
"name": "calculator",
"description": "计算数学表达式的值",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如'123 * 456'"
}
},
"required": ["expression"]
}
}
}
]
第二步:发送请求
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "user", "content": "北京明天天气怎么样?"}
],
tools=tools,
tool_choice="auto" # 让模型自动决定是否调用工具
)
print(response.choices[0].message)
第三步:解析响应
message = response.choices[0].message
# 检查是否有工具调用
if message.tool_calls:
for tool_call in message.tool_calls:
print(f"函数名:{tool_call.function.name}")
print(f"参数:{tool_call.function.arguments}")
# 真正执行函数调用
if tool_call.function.name == "weather_api":
args = eval(tool_call.function.arguments)
weather_result = call_weather_api(args['city'], args['unit'])
# 将结果返回给模型,让它生成最终回答
final_response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "user", "content": "北京明天天气怎么样?"},
{"role": "assistant", "content": None, "tool_calls": [tool_call]},
{"role": "tool", "content": weather_result, "tool_call_id": tool_call.id}
]
)
print(final_response.choices[0].message.content)
else:
# 不需要调用工具,直接回答
print(message.content)
典型输出:
函数名:weather_api
参数:{"city": "Beijing", "unit": "celsius"}
最终回答:北京明天的天气晴朗,最高气温 25°C,最低气温 15°C,空气质量良好。
4.3 关键优势:为什么原生支持更好?
1. 更高的准确率
- Prompt 伪装:约 85-90% 的格式正确率
- JSON Schema 约束:100% 格式正确,但可能选错函数
- 原生支持:95%+ 的函数选择正确率 + 100% 格式正确
2. 更好的泛化能力
- 面对未见过的函数描述,原生模型能更好地理解并正确使用
- Prompt 伪装的模型容易"死记硬背",遇到新函数就出错
3. 更自然的交互
- 原生模型知道"什么时候不该调用工具"
- 不会为了调用而调用,避免画蛇添足
4. 支持多轮对话
- 可以在一轮对话中调用多个工具
- 记住之前调用的结果,进行连贯的推理
5. 参数绑定机制:从自然语言到函数签名
5.1 核心挑战:自然语言的模糊性
Function Calling 的一个关键技术难点是参数绑定(Parameter Binding)——如何将用户的自然语言请求映射到函数的形式化参数。
示例问题:
用户:"帮我查一下北京明天的天气,要摄氏度"
目标函数签名:
def weather_api(location: str, date: str, unit: str) -> WeatherResult
需要解决的子问题:
- 意图识别:确定要调用
weather_api - 槽位提取:从句子中提取"北京"、“明天”、“摄氏度”
- 参数映射:将提取的信息映射到
location、date、unit - 默认值处理:如果用户没说日期,是否默认为"今天"?
- 类型转换:将"明天"转换为具体的日期格式如"2024-01-15"
5.2 槽位填充(Slot Filling)技术
槽位填充是任务型对话系统的核心技术,用于从用户话语中提取结构化信息。
传统方法:基于规则
def extract_slots_simple(utterance: str) -> dict:
"""简单的规则匹配"""
slots = {}
# 城市匹配
cities = ["北京", "上海", "广州", "深圳"]
for city in cities:
if city in utterance:
slots["location"] = city
break
# 时间匹配
time_words = {"今天": "today", "明天": "tomorrow", "后天": "day_after_tomorrow"}
for word, value in time_words.items():
if word in utterance:
slots["date"] = value
break
# 单位匹配
if "摄氏度" in utterance or "摄氏" in utterance:
slots["unit"] = "celsius"
elif "华氏度" in utterance or "华氏" in utterance:
slots["unit"] = "fahrenheit"
return slots
# 测试
utterance = "帮我查一下北京明天的天气,要摄氏度"
slots = extract_slots_simple(utterance)
print(slots)
# 输出:{'location': '北京', 'date': 'tomorrow', 'unit': 'celsius'}
优点:简单直接,可控性强 缺点:难以处理复杂表达,泛化能力弱
现代方法:使用大模型
from pydantic import BaseModel, Field
from openai import OpenAI
client = OpenAI()
class WeatherSlots(BaseModel):
location: str = Field(description="城市名称")
date: str = Field(description="日期,格式 YYYY-MM-DD")
unit: str = Field(default="celsius", description="温度单位")
def extract_slots_llm(utterance: str) -> WeatherSlots:
prompt = f"""
从以下用户话语中提取天气查询的参数:
用户:"{utterance}"
请以 JSON 格式返回提取的结果。
"""
response = client.beta.chat.completions.parse(
model="gpt-4-turbo-preview",
messages=[{"role": "user", "content": prompt}],
response_format=WeatherSlots
)
return response.choices[0].message.parsed
# 测试
slots = extract_slots_llm("北京明天天气,摄氏度")
print(slots)
# 输出:WeatherSlots(location='北京', date='2024-01-15', unit='celsius')
优点:强大的泛化能力,能处理各种表达方式 缺点:依赖外部 API,成本较高
5.3 歧义消解:一词多义的处理
自然语言充满了歧义,参数绑定时需要消解。
歧义类型 1:指代不明
用户:"上海和北京的天气怎么样?"
歧义:
- 理解 1:分别查询两个城市 → 调用两次 weather_api
- 理解 2:对比两个城市 → 一次调用返回对比结果
解决方案:多值参数
class WeatherQuery(BaseModel):
locations: list[str] = Field(
description="城市列表,可以查询多个城市",
min_length=1,
max_length=5 # 限制最多 5 个城市
)
date: str
unit: str
# 模型输出
{
"locations": ["上海", "北京"],
"date": "2024-01-15",
"unit": "celsius"
}
# 后端处理:循环调用
for city in locations:
weather = call_weather_api(city, date, unit)
歧义类型 2:时间表达的相对性
用户:"下周三下午的天气"
挑战:
- "下周三"是相对于哪一天?
- "下午"具体指几点?
解决方案:上下文感知的时间解析
from datetime import datetime, timedelta
def parse_relative_date(expr: str, reference_date: datetime = None) -> str:
"""
解析相对时间表达
"""
if reference_date is None:
reference_date = datetime.now()
if expr == "今天":
return reference_date.strftime("%Y-%m-%d")
elif expr == "明天":
tomorrow = reference_date + timedelta(days=1)
return tomorrow.strftime("%Y-%m-%d")
elif expr.startswith("下"):
# 简化处理:假设"下周 X"
weekday_map = {"一": 0, "二": 1, "三": 2, "四": 3, "五": 4, "六": 5, "日": 6}
target_weekday = weekday_map[expr[1]]
days_ahead = target_weekday - reference_date.weekday()
if days_ahead <= 0: # 已经过了
days_ahead += 7
next_weekday = reference_date + timedelta(days=days_ahead)
return next_weekday.strftime("%Y-%m-%d")
return expr # 原样返回(可能是绝对日期)
# 测试
print(parse_relative_date("明天")) # 输出:2024-01-15(假设今天是 14 号)
5.4 类型转换与验证
从自然语言提取的参数通常是字符串,需要转换为函数期望的类型。
类型转换矩阵:
from typing import Any, Type
def convert_parameter_type(value: str, target_type: Type) -> Any:
"""
将字符串值转换为目标类型
"""
if target_type == int:
try:
return int(value)
except ValueError:
raise TypeError(f"无法将 '{value}' 转换为整数")
elif target_type == float:
try:
return float(value)
except ValueError:
raise TypeError(f"无法将 '{value}' 转换为浮点数")
elif target_type == bool:
true_values = {"是", "true", "yes", "1"}
false_values = {"否", "false", "no", "0"}
if value.lower() in true_values:
return True
elif value.lower() in false_values:
return False
else:
raise TypeError(f"无法将 '{value}' 转换为布尔值")
elif target_type == str:
return value
else:
# 复杂类型尝试用 JSON 解析
import json
try:
return json.loads(value)
except:
raise TypeError(f"无法将 '{value}' 转换为 {target_type}")
# 测试
print(convert_parameter_type("42", int)) # 输出:42
print(convert_parameter_type("是", bool)) # 输出:True
print(convert_parameter_type("3.14", float)) # 输出:3.14
参数验证器:
from pydantic import BaseModel, validator, Field
class ValidatedWeatherCall(BaseModel):
location: str = Field(..., min_length=1, max_length=100)
date: str
unit: str
@validator('location')
def validate_location(cls, v):
# 检查是否是已知城市
known_cities = {"北京", "上海", "广州", "深圳"}
if v not in known_cities:
raise ValueError(f"未知城市:{v}。已知城市:{known_cities}")
return v
@validator('date')
def validate_date(cls, v):
# 检查日期格式
from datetime import datetime
try:
datetime.strptime(v, "%Y-%m-%d")
return v
except ValueError:
raise ValueError(f"日期格式错误:{v}。应为 YYYY-MM-DD")
@validator('unit')
def validate_unit(cls, v):
if v not in {"celsius", "fahrenheit"}:
raise ValueError(f"未知温度单位:{v}。只能是 celsius 或 fahrenheit")
return v
# 测试:正确输入
try:
call = ValidatedWeatherCall(
location="北京",
date="2024-01-15",
unit="celsius"
)
print("验证通过:", call)
except Exception as e:
print("验证失败:", e)
# 测试:错误输入
try:
call = ValidatedWeatherCall(
location="未知城市",
date="昨天", # 格式错误
unit="kelvin" # 不支持的单位
)
except Exception as e:
print("验证失败:", e)
6. 高级话题:多工具协作与错误恢复
6.1 多工具顺序调用
实际应用中,经常需要按顺序调用多个工具。
示例场景:旅行规划
用户:"帮我规划下周从北京到上海的旅行"
需要的工具链:
1. flight_search(origin, destination, date) → 查询航班
2. hotel_search(city, check_in, check_out) → 查询酒店
3. weather_api(city, date) → 查询天气
4. currency_convert(amount, from_currency, to_currency) → 货币转换(如果需要)
实现代码:
from typing import List, Dict
import json
class TravelPlanner:
"""旅行规划 Agent"""
def __init__(self):
self.tools = {
"flight_search": self.flight_search,
"hotel_search": self.hotel_search,
"weather_api": self.weather_api,
}
def plan_trip(self, user_request: str) -> str:
# 第 1 步:解析用户意图
intent = self.parse_intent(user_request)
# 第 2 步:生成工具调用计划
plan = self.generate_tool_call_plan(intent)
# 第 3 步:执行计划
results = []
for tool_call in plan:
tool_name = tool_call["name"]
args = tool_call["arguments"]
try:
result = self.tools[tool_name](**args)
results.append({"tool": tool_name, "result": result, "status": "success"})
except Exception as e:
results.append({"tool": tool_name, "error": str(e), "status": "failed"})
# 错误恢复策略
recovery = self.handle_error(tool_name, e, results)
if recovery:
# 重试或替代方案
result = self.execute_recovery(recovery)
results.append({"tool": recovery["name"], "result": result, "status": "recovered"})
# 第 4 步:综合所有结果生成回答
final_answer = self.synthesize_answer(results)
return final_answer
def flight_search(self, origin: str, destination: str, date: str) -> dict:
"""模拟航班搜索"""
return {
"flights": [
{"airline": "CA1501", "departure": "08:00", "arrival": "10:30", "price": 1200},
{"airline": "MU5102", "departure": "14:00", "arrival": "16:30", "price": 980},
]
}
def hotel_search(self, city: str, check_in: str, check_out: str) -> dict:
"""模拟酒店搜索"""
return {
"hotels": [
{"name": "上海大酒店", "stars": 5, "price": 800},
{"name": "上海商务宾馆", "stars": 3, "price": 350},
]
}
def weather_api(self, city: str, date: str) -> dict:
"""模拟天气查询"""
return {
"condition": "晴朗",
"high_temp": 25,
"low_temp": 15,
}
def parse_intent(self, request: str) -> dict:
"""解析用户意图(简化版)"""
# 实际应使用 LLM
return {
"origin": "北京",
"destination": "上海",
"date": "2024-01-22",
"duration": 3
}
def generate_tool_call_plan(self, intent: dict) -> List[dict]:
"""生成工具调用计划"""
return [
{
"name": "flight_search",
"arguments": {
"origin": intent["origin"],
"destination": intent["destination"],
"date": intent["date"]
}
},
{
"name": "hotel_search",
"arguments": {
"city": intent["destination"],
"check_in": intent["date"],
"check_out": intent["date"] # 简化:同一天
}
},
{
"name": "weather_api",
"arguments": {
"city": intent["destination"],
"date": intent["date"]
}
}
]
def handle_error(self, tool_name: str, error: Exception, context: List[dict]) -> dict:
"""错误处理策略"""
# 策略 1:重试
# 策略 2:使用备用工具
# 策略 3:跳过并继续
# 这里简化处理
return None
def synthesize_answer(self, results: List[dict]) -> str:
"""综合所有工具的结果生成最终回答"""
answer_parts = []
for r in results:
if r["status"] == "success":
if r["tool"] == "flight_search":
flights = r["result"]["flights"]
answer_parts.append(f"找到{len(flights)}个航班,最便宜的是{min(f['price'] for f in flights)}元")
elif r["tool"] == "hotel_search":
hotels = r["result"]["hotels"]
answer_parts.append(f"找到{len(hotels)}家酒店,价格从{min(h['price'] for h in hotels)}元起")
elif r["tool"] == "weather_api":
weather = r["result"]
answer_parts.append(f"当天天气:{weather['condition']},{weather['low_temp']}-{weather['high_temp']}°C")
return "\n".join(answer_parts)
# 使用示例
planner = TravelPlanner()
result = planner.plan_trip("下周从北京到上海旅行")
print(result)
6.2 错误恢复策略
工具调用可能失败,需要设计容错机制。
常见错误类型:
- API 不可用:网络超时、服务宕机
- 参数错误:缺少必填参数、类型不匹配
- 业务错误:查询无结果、权限不足
恢复策略:
class ErrorRecoveryStrategy:
"""错误恢复策略基类"""
def recover(self, failed_call: dict, error: Exception, context: dict) -> dict:
raise NotImplementedError
class RetryStrategy(ErrorRecoveryStrategy):
"""重试策略"""
def __init__(self, max_retries: int = 3, backoff_factor: float = 1.0):
self.max_retries = max_retries
self.backoff_factor = backoff_factor
def recover(self, failed_call: dict, error: Exception, context: dict) -> dict:
import time
for attempt in range(self.max_retries):
try:
print(f"重试第{attempt + 1}次...")
result = execute_tool_call(failed_call)
return {"status": "success", "result": result}
except Exception as e:
if attempt == self.max_retries - 1:
return {"status": "failed", "error": str(e)}
wait_time = self.backoff_factor * (2 ** attempt)
time.sleep(wait_time)
return {"status": "failed", "error": "达到最大重试次数"}
class FallbackStrategy(ErrorRecoveryStrategy):
"""降级策略:使用备用工具"""
def __init__(self, fallback_map: Dict[str, str]):
self.fallback_map = fallback_map # {主工具:备用工具}
def recover(self, failed_call: dict, error: Exception, context: dict) -> dict:
primary_tool = failed_call["name"]
fallback_tool = self.fallback_map.get(primary_tool)
if not fallback_tool:
return {"status": "failed", "error": "无备用工具"}
print(f"{primary_tool}失败,降级使用{fallback_tool}")
# 调整参数以适配备用工具
adjusted_args = self.adapt_arguments(
from_tool=primary_tool,
to_tool=fallback_tool,
original_args=failed_call["arguments"]
)
try:
result = execute_tool_call({
"name": fallback_tool,
"arguments": adjusted_args
})
return {"status": "recovered", "result": result, "used_fallback": fallback_tool}
except Exception as e:
return {"status": "failed", "error": f"备用工具也失败:{str(e)}"}
def adapt_arguments(self, from_tool: str, to_tool: str, original_args: dict) -> dict:
"""适配参数(简化版)"""
# 实际需要根据具体工具的参数差异来实现
return original_args
# 使用示例
recovery_system = {
"weather_api": RetryStrategy(max_retries=3),
"flight_search": FallbackStrategy({
"flight_search": "train_search" # 航班查询失败→查火车
})
}
7. 总结与展望
7.1 技术演进的启示
回顾 Function Calling 的三个发展阶段,我们可以看到一条清晰的演进路径:
图解说明:如上图所示,Function Calling 的技术演进呈现出一条从表面到本质、从外挂到内生的清晰路径。每个阶段都解决了前一阶段的核心痛点,同时也带来了新的挑战。
7.2 核心知识点总结
1. Prompt 伪装阶段
- 通过 Few-shot 示例建立条件反射
- 心理学原理:经典条件反射
- 局限性:依赖模型"自觉性"
2. JSON Schema 约束
- 基于 CFG 和下推自动机的语法约束
- 核心优势:100% 格式正确
- 局限性:仍然需要解析层
3. 原生 Function Calling
- 训练阶段学习工具语义
- 端到端优化,直觉式调用
- 当前最佳实践
4. 参数绑定机制
- 槽位填充:从自然语言提取结构化信息
- 歧义消解:处理一词多义、指代不明
- 类型转换:字符串→形式化参数
7.3 未来展望:从 Function Calling 到自主 Agent
Function Calling 只是"赋予模型行动能力"的第一步。接下来的演进方向是:
短期(1-2 年):工具使用能力的深化
- 更复杂的工具组合(多模态工具、GUI 操作)
- 更强的错误恢复和异常处理能力
- 更好的长期记忆和上下文理解
中期(3-5 年:自主 Agent 系统
- 模型能够自主规划多步任务
- 跨工具、跨应用的协作
- 人类只需给出高层目标,Agent 自主拆解和执行
长期(5-10 年):通用人工智能的雏形
- 具备真正的"理解"和"推理"能力
- 能够在陌生环境中学习和适应
- 从"工具使用者"进化为"问题解决者"
在下一篇中,我们将深入探讨 ReAct 框架——如何结合推理(Reasoning)和行动(Acting),让模型具备自主解决问题的能力。敬请期待!
参考文献与延伸阅读
-
Function Calling with Large Language Models: A Survey (Zhang et al., 2024):全面综述了大模型函数调用技术的发展历程、主要方法和应用场景,是了解该领域的入门必读。
-
Tool Learning with Foundation Models (Qin et al., 2023):系统性地介绍了基础模型如何学习使用工具,包括分类体系、训练方法和评估指标。
-
HuggingGPT: Solving AI Tasks with ChatGPT and its Friends in Hugging Face (Shen et al., 2023):展示了如何利用 LLM 作为控制器来调度多个 AI 模型完成复杂任务,是工具调用的典型案例。
-
ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs (Qin et al., 2023):训练 LLM 掌握超过 16000 个真实世界 API 的研究,展示了工具学习的规模化应用。
-
Gorilla: Large Language Model Connected with Massive APIs (Patil et al., 2023):专门针对 API 调用优化的 LLM,在准确性和效率上都有出色表现。
-
React: Synergizing Reasoning and Acting in Language Models (Yao et al., 2022):提出了 ReAct 框架,将推理和行动结合,是下一代 Agent 系统的基础。
-
ChatDev: Communicative Agents for Software Development (Chen et al., 2023):展示了多个 Agent 如何协作完成软件开发任务,是多工具协作的典型案例。
-
OpenAI Function Calling 官方文档:OpenAI 官方的 Function Calling 使用指南和最佳实践。
-
LangChain 框架文档:流行的 LLM 应用开发框架,提供了丰富的工具集成和工作流编排能力。
-
LlamaIndex Tools 文档:专注于 RAG 应用的工具调用框架,特别适合知识库问答场景。
-
Task Planning in Large Language Models: A Survey (Huang et al., 2024):关于大模型任务规划能力的全面综述,涵盖了从单步调用到多步协作的演进。
-
Reflexion: Language Agents with Verbal Reinforcement Learning (Shinn et al., 2023):通过自我反思来提升工具使用和任务规划能力的研究。