一、 开场:一个让我兴奋的技术趋势
大家好,我是老金。
最近几个月,我明显感觉到一个技术趋势正在加速:
Function Calling(函数调用)正在成为AI应用的标准能力。
OpenAI、Anthropic、Google、百度、阿里……几乎所有大模型厂商都在推进这个能力。
为什么?因为Function Calling是AI Agent从”能聊天”进化到”能干活”的关键桥梁。
今天这篇文章,我想系统性地聊聊:Function Calling的技术原理、最佳实践,以及那些你可能遇到的坑。
二、 什么是Function Calling?
2.1 核心概念
Function Calling是一种让LLM输出结构化函数调用的机制。
简单说:
- 你告诉AI:”这里有一些可用的工具(函数)”
- AI分析用户需求,决定调用哪个函数
- AI输出结构化的函数调用(JSON格式)
- 你执行函数,把结果返回给AI
- AI基于结果继续回答用户
2.2 工作流程图
用户问题
↓
LLM分析 → 决定调用哪个函数
↓
输出结构化JSON:{"function": "get_weather", "args": {"city": "北京"}}
↓
你的代码执行函数调用
↓
返回结果给LLM
↓
LLM生成最终回答
2.3 为什么需要Function Calling?
没有Function Calling之前,让AI”调用工具”是这样的:
用户:帮我查一下北京今天天气
AI:好的,让我为你查询北京今天的天气...
(AI输出了一堆自然语言描述,你需要用正则匹配提取关键信息)
有了Function Calling:
用户:帮我查一下北京今天天气
AI输出(结构化JSON):
{
"name": "get_weather",
"arguments": "{"city": "北京", "date": "今天"}"
}
你的代码:直接解析JSON,调用天气API
结构化输出 = 可靠的程序处理。
三、 主流模型的Function Calling实现
3.1 OpenAI
import openai定义可用函数
functions = [ { "name": "get_weather", "description": "获取指定城市的天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如:北京、上海" }, "date": { "type": "string", "description": "日期,如:今天、明天、2024-01-01" } }, "required": ["city"] } }, { "name": "send_email", "description": "发送邮件", "parameters": { "type": "object", "properties": { "to": {"type": "string", "description": "收件人邮箱"}, "subject": {"type": "string", "description": "邮件主题"}, "body": {"type": "string", "description": "邮件正文"} }, "required": ["to", "subject", "body"] } } ]
调用API
response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "user", "content": "帮我查一下北京今天天气"} ], functions=functions, function_call="auto" # 让模型自己决定是否调用函数 )
处理函数调用
message = response.choices[0].message if message.get("function_call"): function_name = message["function_call"]["name"] arguments = json.loads(message["function_call"]["arguments"])
if function_name == "get_weather": result = get_weather(arguments["city"], arguments.get("date")) # 将结果返回给模型 second_response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "user", "content": "帮我查一下北京今天天气"}, message, { "role": "function", "name": "get_weather", "content": json.dumps(result, ensure_ascii=False) } ] ) print(second_response.choices[0].message.content)3.2 Anthropic Claude
import anthropicclient = anthropic.Anthropic()
定义工具
tools = [ { "name": "get_weather", "description": "获取指定城市的天气信息", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"}, "date": {"type": "string", "description": "日期"} }, "required": ["city"] } } ]
调用API
response = client.messages.create( model="claude-3-opus-20240229", max_tokens=1024, messages=[ {"role": "user", "content": "帮我查一下北京今天天气"} ], tools=tools )
处理工具调用
for block in response.content: if block.type == "tool_use": if block.name == "get_weather": result = get_weather(**block.input)
# 返回结果 second_response = client.messages.create( model="claude-3-opus-20240229", max_tokens=1024, messages=[ {"role": "user", "content": "帮我查一下北京今天天气"}, {"role": "assistant", "content": response.content}, { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result) } ] } ], tools=tools ) print(second_response.content[0].text)3.3 国产模型(以通义千问为例)
from dashscope import Generation定义函数
functions = [ { "name": "get_weather", "description": "获取天气信息", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市"} }, "required": ["city"] } } ]
response = Generation.call( model="qwen-max", messages=[ {"role": "user", "content": "帮我查一下北京今天天气"} ], functions=functions )
处理逻辑类似...
四、 最佳实践
4.1 函数定义的艺术
好的函数定义能让AI更准确地调用,减少错误。
原则1:描述要具体
❌ 差的定义: "city": {"type": "string"}✅ 好的定义: "city": { "type": "string", "description": "城市名称,支持中文和拼音,如:北京、beijing、上海" }
原则2:使用枚举限制
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位" }原则3:设置合理的required
❌ 把所有参数都设为required "required": ["city", "date", "unit", "detail_level"]✅ 只把必须的参数设为required "required": ["city"]
4.2 错误处理
async def execute_function_call(function_name, arguments): """执行函数调用,带完善的错误处理""" try: # 1. 检查函数是否存在 if function_name not in AVAILABLE_FUNCTIONS: return { "error": f"未知函数:{function_name}", "available_functions": list(AVAILABLE_FUNCTIONS.keys()) }# 2. 验证参数 func = AVAILABLE_FUNCTIONS[function_name] validate_arguments(func, arguments) # 3. 执行函数 result = await func(**arguments) # 4. 验证返回值 if result is None: return {"error": "函数执行返回空值"} return result except ValidationError as e: return {"error": f"参数验证失败:{str(e)}"} except Exception as e: logger.exception(f"函数 {function_name} 执行异常") return {"error": f"函数执行异常:{str(e)}"}4.3 多函数调用处理
有时候AI会一次性调用多个函数:
async def handle_multiple_calls(tool_calls): """处理多个函数调用""" results = []# 并行执行 tasks = [] for call in tool_calls: task = execute_function_call(call.name, call.arguments) tasks.append(task) results = await asyncio.gather(*tasks, return_exceptions=True) # 组织返回结果 function_results = [] for call, result in zip(tool_calls, results): if isinstance(result, Exception): function_results.append({ "tool_call_id": call.id, "error": str(result) }) else: function_results.append({ "tool_call_id": call.id, "result": result }) return function_results4.4 Token优化
函数定义会占用Token,需要注意优化:
class FunctionRegistry: def __init__(self): self.all_functions = {} # 所有可用函数 self.common_functions = [] # 常用函数(始终包含)def get_functions_for_context(self, user_message, max_tokens=2000): """根据用户消息选择相关函数,控制Token消耗""" # 1. 始终包含常用函数 selected = self.common_functions.copy() current_tokens = count_tokens(json.dumps(selected)) # 2. 根据用户消息选择相关函数 for name, func in self.all_functions.items(): if name in [f["name"] for f in selected]: continue # 简单的相关性判断(可以换成向量检索) if self._is_relevant(user_message, func): func_tokens = count_tokens(json.dumps(func)) if current_tokens + func_tokens <= max_tokens: selected.append(func) current_tokens += func_tokens return selected def _is_relevant(self, message, func): """判断函数是否与消息相关""" keywords = func.get("description", "").split() return any(kw in message for kw in keywords)五、 常见问题与解决方案
5.1 AI不调用函数
原因:
- 函数描述不够清晰
- 用户问题与函数不匹配
- 模型能力限制
解决方案:
# 强制调用特定函数
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[...],
functions=functions,
function_call={"name": "get_weather"} # 强制调用
)
或者在系统提示中强调
system_prompt = """
你可以使用以下工具:
- get_weather: 获取天气信息
当用户询问天气相关问题时,请务必调用get_weather函数。
"""
5.2 参数解析错误
原因:AI输出的JSON格式不正确。
解决方案:
import json from json_repair import repair_jsondef parse_arguments(arguments_str): """解析函数参数,带修复功能""" try: return json.loads(arguments_str) except json.JSONDecodeError:
尝试修复JSON
try: repaired = repair_json(arguments_str) return json.loads(repaired) except: return None使用
arguments = parse_arguments(message["function_call"]["arguments"])
if arguments is None:让AI重新输出
return "参数格式错误,请重新生成"5.3 函数执行超时
async def execute_with_timeout(func, args, timeout=30): """带超时的函数执行""" try: result = await asyncio.wait_for( func(**args), timeout=timeout ) return result except asyncio.TimeoutError: return {"error": f"函数执行超时({timeout}秒)"}六、 写在最后
Function Calling是AI Agent能力的基石。
掌握它,你就能让AI从”聊天机器人”进化为”能干活的智能助手”。
最后分享几点心得:
- 定义要清晰:好的函数定义是成功的一半
- 错误处理要完善:AI不一定每次都正确,要有兜底方案
- 注意Token消耗:函数定义会占用上下文窗口
- 持续优化:根据实际调用情况优化函数设计
如果你在实践Function Calling时遇到问题,欢迎在评论区交流。
我是技术老金,我们下期见!
📌 往期精彩回顾