一、Function Calling 概述
Function Calling 是一项允许大型语言模型(如 GPT 系列)在生成文本的过程中调用外部函数或服务的功能。它使得开发者能够通过自然语言指令触发预定义的函数,将复杂的操作封装在函数中,然后通过简单的自然语言调用这些函数,从而实现更复杂、更动态的任务处理。这一功能大大增强了大模型的应用潜力,使其可以与外部系统进行交互,处理超出自身知识范围的问题。
(一)核心作用
- 扩展模型能力:通过调用外部函数,模型可以处理更复杂的任务,如查询数据库、调用 API、执行计算等,突破了纯文本生成的限制。
- 动态任务处理:模型可以根据用户请求动态选择需要调用的函数,实现灵活的任务处理。
- 提高准确性:通过调用外部函数获取准确的数据或执行精确的计算,减少模型“编造”信息的可能性。
(二)应用场景
- 数据查询:调用外部函数查询数据库或 API,获取实时数据。例如,用户问“今天的天气怎么样?”,模型调用天气 API 获取实时天气数据。
- 计算任务:调用外部函数执行复杂的计算任务。例如,用户问“计算 12345 的平方根。”,模型调用计算函数返回结果。
- 任务自动化:调用外部函数执行自动化任务。例如,用户问“发送一封邮件给张三。”,模型调用邮件发送函数完成任务。
- 知识增强:调用外部函数从知识库中检索信息。例如,用户问“谁是美国的第一任总统?”,模型调用知识库查询函数返回答案。
- 智能家居控制:在智能家居系统中,定义函数来控制各个设备,并通过自然语言指令进行控制。
- 旅游服务助手:构建旅游服务助手,通过 API 连接到数据库中的产品目录,获取商品列表和进行商品购买;通过检索增强生成(RAG)连接到存储和管理文档数据的存储系统,从非结构化文本中获取相关信息。
二、Function Calling 数据结构
(一)函数定义的数据结构
在使用 Function Calling 时,需要向模型描述可用的函数。函数定义通常使用 JSON Schema 来描述函数的名称、描述、参数等信息,以下是一个示例:
{
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
- name:函数的名称,用于标识函数。
- description:函数的描述,用于说明函数的作用。
- parameters:函数的参数,使用 JSON Schema 描述参数的类型、属性等信息。
- type:参数的类型,如
object、string、integer等。 - properties:参数的属性,每个属性对应一个参数。
- required:必需的参数列表。
- type:参数的类型,如
(二)模型生成的函数调用数据结构
当模型决定调用函数时,会生成一个包含函数调用所需参数的结构化输出,通常是一个 JSON 对象,例如:
{
"tool_calls": [
{
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"Boston, MA\"}"
}
}
]
}
- tool_calls:一个数组,包含模型建议调用的函数信息。
- function:函数调用的详细信息。
- name:要调用的函数名称。
- arguments:函数调用所需的参数,以字符串形式存在的 JSON 对象。
- function:函数调用的详细信息。
三、Function Calling 使用例子
(一)使用 OpenAI API 进行天气查询的例子
以下是一个使用 OpenAI API 进行天气查询的完整例子:
import requests
import os
import json
# 配置 OpenAI API 请求的 URL 和认证信息
url = "https://api.openai.com/v1/chat/completions"
auth = ('', os.getenv('OPENAI_API_KEY'))
headers = {
"Content-Type": "application/json"
}
# 定义函数描述
functions = [
{
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
]
# 定义用户请求
messages = [
{"role": "user", "content": "What is the weather like in Boston?"}
]
# 构建请求数据
request_data = {
"model": "gpt-3.5-turbo-0613",
"messages": messages,
"functions": functions
}
# 发送请求
response = requests.post(url, auth=auth, headers=headers, data=json.dumps(request_data))
# 解析响应
response_data = response.json()
if 'choices' in response_data and len(response_data['choices']) > 0:
message = response_data['choices'][0]['message']
if 'function_call' in message:
function_call = message['function_call']
function_name = function_call['name']
function_args = json.loads(function_call['arguments'])
print(f"Function name: {function_name}")
print(f"Function arguments: {function_args}")
# 这里可以根据函数名和参数调用实际的天气 API
# 假设调用天气 API 后得到结果
weather_result = "22 degrees Celsius, sunny"
# 将函数调用结果再次发送给模型
messages.append(message)
messages.append({
"role": "function",
"name": function_name,
"content": weather_result
})
# 再次发送请求获取最终回复
request_data = {
"model": "gpt-3.5-turbo-0613",
"messages": messages
}
response = requests.post(url, auth=auth, headers=headers, data=json.dumps(request_data))
final_response = response.json()
print(f"Final response: {final_response['choices'][0]['message']['content']}")
(二)通义千问大模型的 Function Call 例子
from dashscope import Generation
from datetime import datetime
import random
import json
import requests
# 定义工具列表
tools = [
# 工具 1:获取当前时刻的时间
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "当你想知道现在的时间时非常有用。",
"parameters": {} # 因为获取当前时间无需输入参数,因此 parameters 为空字典
}
},
# 工具 2:获取指定城市的天气
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "当你想查询指定城市的天气时非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或县区,比如北京市、杭州市、余杭区等。"
}
},
"required": ["location"]
}
}
}
]
# 基于外部网站的天气查询工具
def get_current_weather(location):
api_key = "9fa42a532a9338bf217afd7ac47a565a" # 替换为你自己的 OpenWeatherMap API 密钥
url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
weather = data["weather"][0]["description"]
temp = data["main"]["temp"]
return json.dumps({"location": location, "weather": weather, "temperature": temp})
else:
return json.dumps({"location": location, "error": "Unable to fetch weather data"})
# 查询当前时间的工具
def get_current_time():
# 获取当前日期和时间
current_datetime = datetime.now()
# 格式化当前日期和时间
formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
# 返回格式化后的当前时间
return f"当前时间:{formatted_time}。"
# 封装模型响应函数
def get_response(messages):
response = Generation.call(
model='qwen-plus',
messages=messages,
tools=tools,
seed=random.randint(1, 10000), # 设置随机数种子
result_format='message' # 将输出设置为 message 形式
)
return response
# 主函数
def call_with_messages():
print('\n')
messages = [
{
"content": input('请输入:'), # 提问示例:"现在几点了?" "一个小时后几点" "北京天气如何?"
"role": "user"
}
]
# 模型的第一轮调用
first_response = get_response(messages)
assistant_output = first_response.output.choices[0].message
print(f"\n大模型第一轮输出信息:{first_response}\n")
messages.append(assistant_output)
if 'tool_calls' not in assistant_output: # 如果模型判断无需调用工具,则将 assistant 的回复直接打印出来,无需进行模型的第二轮调用
print(f"最终答案:{assistant_output.content}")
return
# 如果模型选择的工具是 get_current_weather
elif assistant_output['tool_calls'][0]['function']['name'] == 'get_current_weather':
tool_call = assistant_output['tool_calls'][0]
function_name = tool_call['function']['name']
function_args = json.loads(tool_call['function']['arguments'])
location = function_args['location']
result = get_current_weather(location)
messages.append({
"role": "function",
"name": function_name,
"content": result
})
# 模型的第二轮调用
second_response = get_response(messages)
print(f"最终答案:{second_response.output.choices[0].message.content}")
elif assistant_output['tool_calls'][0]['function']['name'] == 'get_current_time':
tool_call = assistant_output['tool_calls'][0]
function_name = tool_call['function']['name']
result = get_current_time()
messages.append({
"role": "function",
"name": function_name,
"content": result
})
# 模型的第二轮调用
second_response = get_response(messages)
print(f"最终答案:{second_response.output.choices[0].message.content}")
if __name__ == '__main__':
call_with_messages()
四、Function Calling 返回值
(一)模型生成的函数调用返回值
当模型决定调用函数时,会返回一个包含函数调用信息的 JSON 对象,如上述例子中的 function_call 字段:
{
"name": "get_current_weather",
"arguments": "{\"location\": \"Boston, MA\"}"
}
- name:要调用的函数名称。
- arguments:函数调用所需的参数,以字符串形式存在的 JSON 对象。
(二)函数执行后的返回值
函数执行后的返回值取决于具体的函数实现。例如,天气查询函数可能返回一个包含天气信息的 JSON 对象:
{"location": "Boston, MA", "weather": "sunny", "temperature": 22}
五、支持 Function Calling 的模型
目前,多个先进的 AI 大模型已经支持 Function Calling 功能,包括但不限于:
- OpenAI 的 GPT 系列:从 GPT - 3.5 开始引入了 Function Calling 的功能,支持的模型有 gpt - 4o、gpt - 4o - 2024 - 05 - 13、gpt - 4 - turbo、gpt - 4 - turbo - 2024 - 04 - 09、gpt - 4 - turbo - preview、gpt - 4 - 0125 - preview、gpt - 4 - 1106 - preview、gpt - 4、gpt - 4 - 0613、gpt - 3.5 - turbo、gpt - 3.5 - turbo - 0125、gpt - 3.5 - turbo - 1106 和 gpt - 3.5 - turbo - 0613 等。
- 阿里的 Qwen 系列:大部分的 Qwen 系列模型都支持 Function Calling,如通义千问 - Max、通义千问 - Plus(非思考模式)、通义千问 - Turbo(非思考模式)、通义千问 - Long、Qwen3(非思考模式)、Qwen2.5、Qwen2、Qwen1.5 等。
- DeepSeek 系列:硅基流动平台上的满血版和蒸馏版模型也明确支持 Function Calling。
- 智谱系列:智谱 AI 的大模型实测也支持 Function Calling。
- Anthropic 的 Claude:Claude 模型同样支持 Function Calling,这使得它能够与外部系统更紧密地集成。
- 谷歌的 Gemini API:最近也开始支持函数调用。
六、Function Calling 的工作流程
(一)基本流程
- 用户输入请求:用户通过自然语言向模型提出问题或请求。这些问题或请求可能需要调用外部函数来获取答案或执行某些操作。
- 模型解析意图:模型接收到用户输入后,会解析并理解输入内容。模型会根据其训练数据和算法判断是否需要调用函数,并确定要调用的函数及其参数。
- 生成函数调用:如果模型确定需要调用函数,它会生成一个包含函数调用所需参数的结构化输出。这通常是一个 JSON 对象,其中包含函数名、参数列表等信息。这个 JSON 对象是以字符串形式存在的,需要在实际调用函数之前进行解析。
- 函数调用执行:在代码中,需要解析这个字符串化的 JSON 对象,将其转换为有效的数据结构(如字典或对象),并使用这些参数调用相应的函数。这个过程是在代码环境中完成的,而不是在模型内部。模型只是提供了调用函数所需的参数和信息。
- 处理函数结果:函数调用执行完成后,需要将函数的结果返回给模型。这通常通过将结果附加到模型中再次调用模型来实现。模型会接收并处理这些结果,然后生成一个自然语言回复给用户,总结或解释函数调用的结果。
(二)以 OpenAI API 为例的详细流程
- 代码调用 LLM API:应用程序向 API 发送请求,其中包含提示(prompt)以及 LLM 可以调用的函数的定义。
- LLM 做出决策:LLM 会根据提示和函数定义,决定是直接回复用户,还是“可以调用”一个或多个函数。
- LLM API 返回结果:API 会将 LLM 的决策返回给应用程序,其中包含需要调用的函数名称以及对应的参数。
- 执行函数:应用程序根据 LLM API 返回的信息,调用指定的函数,并传入对应的参数。
- 再次调用 LLM API:应用程序将函数执行的结果以及原始提示再次发送给 LLM API,以便 LLM 能够结合新的信息生成更准确的回复。
七、使用 Function Calling 的注意事项
(一)函数定义方面
需要正确定义函数及其参数,确保模型能够生成正确的函数调用。函数的描述要清晰准确,参数的类型和属性要使用 JSON Schema 进行规范描述。
(二)错误处理方面
需要处理函数调用过程中可能出现的错误,如 API 调用失败、参数错误等。可以在代码中添加适当的错误处理逻辑,确保程序的健壮性。
(三)安全性方面
需要确保函数调用的安全性,防止恶意请求或数据泄露。例如,在调用外部 API 时,要对用户输入进行严格的验证和过滤,避免 SQL 注入、跨站脚本攻击等安全问题。
(四)令牌方面
函数在底层被注入到系统消息中,并且模型已经对其进行了训练。这意味着函数会计入模型的上下文限制,并且会被视为输入令牌进行计费。如果遇到上下文限制,建议限制函数的数量或者为函数参数提供的文档的长度,也可以使用微调来减少所使用的令牌数量。
(五)模型选择方面
并不是所有的 LLM 模型都支持 Function Calling 功能。在选择模型时,需要确保所选模型经过了相关训练以支持该功能。对于不支持的模型,整合 LangChain 框架有机会实现 Function Calling 的效果,但可能在精确度上会有落差。
综上所述,Function Calling 是一项强大的功能,它为大模型与外部系统的交互提供了便利,扩展了大模型的应用场景和能力。通过合理使用 Function Calling,可以构建出更加智能、灵活的应用程序。