Function Calling:让大模型学会调用外部工具
·
文章目录
大语言模型虽然强大,但它有几个天生的"短板":不会算复杂数学、无法获取实时信息、不能直接操作数据库。那怎么办?Function Calling就是解决方案——让模型在需要的时候,主动调用我们写好的函数,把"思考"和"执行"完美结合起来。
这篇文章会通过5个由浅入深的案例,带你彻底搞懂Function Calling。
案例一:最简单的Function Calling——查询天气
先从一个最经典的场景开始:让模型调用天气查询工具。
1.1 定义工具
我们需要告诉模型:有一个叫get_current_weather的工具,当你需要查天气时,就用它。
from openai import OpenAI
import random
client = OpenAI(
api_key="sk-xxx",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 定义工具列表(告诉模型有哪些工具可用)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "当你想查询指定城市的天气时非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或县区,比如北京市、杭州市、余杭区等。",
}
},
"required": ["location"],
},
},
},
]
# 实现工具函数(这里用随机模拟,实际应该调天气API)
def get_current_weather(arguments):
weather_conditions = ["晴天", "多云", "雨天"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"{location}今天是{random_weather}。"
1.2 定义模型调用函数
def get_response(messages):
completion = client.chat.completions.create(
model="qwen-plus",
messages=messages,
tools=tools, # 把工具列表传给模型
)
return completion
1.3 主流程:判断是否需要调用工具
USER_QUESTION = "合肥天气咋样"
messages = [{"role": "user", "content": USER_QUESTION}]
# 第一次调用模型
response = get_response(messages)
assistant_output = response.choices[0].message
if assistant_output.content is None:
assistant_output.content = ""
messages.append(assistant_output)
# 判断是否要调用工具
if assistant_output.tool_calls is None:
print(f"无需调用天气查询工具,直接回复:{assistant_output.content}")
else:
# 进入工具调用循环
while assistant_output.tool_calls is not None:
tool_call = assistant_output.tool_calls[0]
tool_call_id = tool_call.id
func_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"正在调用工具 [{func_name}],参数:{arguments}")
# 执行对应的函数
if func_name == "get_current_weather":
tool_result = get_current_weather(arguments)
else:
tool_result = f"未知工具:{func_name}"
print(f"工具返回:{tool_result}")
# 把工具返回结果加入对话
tool_message = {
"role": "tool",
"tool_call_id": tool_call_id,
"content": tool_result,
}
messages.append(tool_message)
# 再次调用模型,生成最终回答
response = get_response(messages)
assistant_output = response.choices[0].message
if assistant_output.content is None:
assistant_output.content = ""
messages.append(assistant_output)
if assistant_output.tool_calls is None:
break
print(f"助手最终回复:{assistant_output.content}")
运行结果:
正在调用工具 [get_current_weather],参数:{'location': '合肥'}
工具返回:合肥今天是晴天。
助手最终回复:合肥今天是晴天。
案例二:JSON格式提取——让模型帮你整理信息
有时候我们需要模型从自然语言中提取结构化数据,比如从一段话中提取联系人信息。
from openai import OpenAI
import json
client = OpenAI(
api_key="sk-xxx",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def get_completion(messages, model="qwen-plus"):
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0,
tools=[
{
"type": "function",
"function": {
"name": "add_contact",
"description": "添加联系人",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "联系人姓名"},
"address": {"type": "string", "description": "联系人地址"},
"tel": {"type": "string", "description": "联系人电话"},
},
},
},
}
],
)
return response.choices[0].message
prompt = "帮我寄给陆天宇,地址是合肥市经开区英唐工业园,电话15156028147。"
messages = [
{"role": "system", "content": "你是一个联系人录入员。"},
{"role": "user", "content": prompt},
]
response = get_completion(messages)
print("====GPT回复====")
print(response)
# 解析函数参数
args = json.loads(response.tool_calls[0].function.arguments)
print("====提取的信息====")
print(f"姓名:{args['name']}")
print(f"地址:{args['address']}")
print(f"电话:{args['tel']}")
运行结果:
====提取的信息====
姓名:陆天宇
地址:合肥市经开区英唐工业园
电话:15156028147
💡 使用Function Calling来提取结构化数据,比直接用提示词要求输出JSON更稳定可靠!
案例三:多个工具配合——解决复杂问题
现实场景往往需要多个工具配合。比如用户说:“我在合肥英唐工业园附近,想找麦当劳”。这需要先获取坐标,再搜索附近POI。
3.1 定义两个工具
from openai import OpenAI
import json
import requests
client = OpenAI(
api_key="sk-xxx",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
amap_key = "你的高德地图API Key"
tools = [
{
"type": "function",
"function": {
"name": "get_location_coordinate",
"description": "根据POI名称,获得POI的经纬度坐标",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "POI名称,必须是中文"},
"city": {"type": "string", "description": "POI所在的城市名,必须是中文"},
},
"required": ["location", "city"],
},
},
},
{
"type": "function",
"function": {
"name": "search_nearby_pois",
"description": "搜索给定坐标附近的POI",
"parameters": {
"type": "object",
"properties": {
"longitude": {"type": "string", "description": "中心点的经度"},
"latitude": {"type": "string", "description": "中心点的纬度"},
"keyword": {"type": "string", "description": "目标POI的关键字"},
},
"required": ["longitude", "latitude", "keyword"],
},
},
},
]
3.2 实现工具函数
def get_location_coordinate(location, city):
url = f"https://restapi.amap.com/v5/place/text?key={amap_key}&keywords={location}®ion={city}"
print(f"[请求] 获取坐标: {url}")
r = requests.get(url)
result = r.json()
if "pois" in result and result["pois"]:
return result["pois"][0]
return None
def search_nearby_pois(longitude, latitude, keyword):
url = f"https://restapi.amap.com/v5/place/around?key={amap_key}&keywords={keyword}&location={longitude},{latitude}"
print(f"[请求] 搜索附近POI: {url}")
r = requests.get(url)
result = r.json()
ans = ""
if "pois" in result and result["pois"]:
for i in range(min(3, len(result["pois"]))):
name = result["pois"][i]["name"]
address = result["pois"][i]["address"] or "地址未知"
distance = result["pois"][i]["distance"] or "未知"
ans += f"{name}\n{address}\n距离:{distance}米\n\n"
return ans
3.3 主流程:支持多轮工具调用
prompt = "我想在合肥英唐工业园附近吃麦当劳,给我推荐几个"
messages = [
{"role": "system", "content": "你是一个地图通,你可以找到任何地址。"},
{"role": "user", "content": prompt},
]
response = get_completion(messages)
messages.append(response)
# 循环处理工具调用
while response.tool_calls is not None:
for tool_call in response.tool_calls:
args = json.loads(tool_call.function.arguments)
if tool_call.function.name == "get_location_coordinate":
print("Call: get_location_coordinate")
result = get_location_coordinate(**args)
elif tool_call.function.name == "search_nearby_pois":
print("Call: search_nearby_pois")
result = search_nearby_pois(**args)
else:
result = "未知的工具调用"
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": tool_call.function.name,
"content": str(result),
})
response = get_completion(messages)
messages.append(response)
print("=====最终回复=====")
print(response.content)
运行结果(模拟):
Call: get_location_coordinate
[请求] 获取坐标: https://restapi.amap.com/v5/place/text?key=xxx&keywords=英唐工业园®ion=合肥
Call: search_nearby_pois
[请求] 搜索附近POI: https://restapi.amap.com/v5/place/around?key=xxx&keywords=麦当劳&location=117.22,31.82
=====最终回复=====
在合肥英唐工业园附近找到以下麦当劳:
1. 麦当劳(合肥经开区店)
地址:经开区繁华大道与翡翠路交叉口
距离:约500米
案例四:让模型操作数据库——SQL生成与执行
这个案例展示了如何让大模型根据自然语言生成SQL并执行查询。
4.1 定义数据库表结构
import sqlite3
database_schema_string = """
CREATE TABLE orders (
id INT PRIMARY KEY NOT NULL,
student_id STR NOT NULL,
paper_id STR NOT NULL,
mark DECIMAL(10,3) NOT NULL,
graduate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
# 创建内存数据库
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute(database_schema_string)
# 插入测试数据
mock_data = [
(1, "Tom", "A", 91.00, "2021-10-12 "),
(2, "Lucy", "B", 87.50, "2022-10-16 "),
(3, "Jack", "C", 90.25, "2023-10-17 "),
(4, "Paul", "D", 86.75, "2024-10-20 "),
(5, "Bob", "E", 55.00, "2025-10-28 "),
]
for record in mock_data:
cursor.execute(
"INSERT INTO orders (id, student_id, paper_id, mark, graduate_time) VALUES (?, ?, ?, ?, ?)",
record,
)
conn.commit()
4.2 定义工具并调用
def ask_database(query):
cursor.execute(query)
records = cursor.fetchall()
return records
prompt = "哪个学生毕业时间最晚?在什么时候?"
messages = [
{"role": "system", "content": "基于 order 表回答用户问题"},
{"role": "user", "content": prompt},
]
response = get_sql_completion(messages)
messages.append(response)
if response.tool_calls is not None:
tool_call = response.tool_calls[0]
if tool_call.function.name == "ask_database":
args = json.loads(tool_call.function.arguments)
print("====生成的SQL====")
print(args["query"])
result = ask_database(args["query"])
print("====查询结果====")
print(result)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": "ask_database",
"content": str(result),
})
response = get_sql_completion(messages)
print("====最终回复====")
print(response.content)
conn.close()
运行结果:
====生成的SQL====
SELECT student_id, graduate_time FROM orders ORDER BY graduate_time DESC LIMIT 1;
====查询结果====
[('Bob', '2025-10-28 ')]
====最终回复====
毕业时间最晚的学生是Bob,毕业时间为2025年10月28日。
案例五:简单加法器——让模型调用计算工具
最后一个案例演示如何让模型调用加法工具进行计算。
from openai import OpenAI
import json
from math import *
client = OpenAI(
api_key="sk-xxx",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def get_completion(messages, model="qwen-plus"):
response = client.chat.completions.create(
model=model,
messages=messages,
tools=[
{
"type": "function",
"function": {
"name": "sum",
"description": "加法器,计算一组数的和,只能运用于加法操作",
"parameters": {
"type": "object",
"properties": {
"numbers": {"type": "array", "items": {"type": "number"}}
},
},
},
}
],
)
return response.choices[0].message
prompt = "桌上有 2 个苹果,四个桃子和 3 本书,一共有几个水果?"
messages = [
{"role": "system", "content": "你是一个数学家,当需要进行加法操作时调用sum工具"},
{"role": "user", "content": prompt},
]
response = get_completion(messages)
messages.append(response)
if response.tool_calls is not None:
tool_call = response.tool_calls[0]
if tool_call.function.name == "sum":
args = json.loads(tool_call.function.arguments)
result = sum(args["numbers"]) # 调用Python内置sum函数
print("=====函数返回=====")
print(result)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": "sum",
"content": str(result),
})
print("=====最终回复=====")
print(get_completion(messages).content)
运行结果:
=====函数返回=====
6
=====最终回复=====
桌上有2个苹果和4个桃子,一共是6个水果。
更多推荐
所有评论(0)