大模型Function Calling详解:原理、代码实现与局限性分析
大模型Function Calling的出现背景
大模型受限于训练数据的时效性,对实时场景的回答效果并不好。早期的一些模型,在面对数学等需要精确计算的场景,效果可能也不好。Function Calling的出现,使大模型可以调用外部工具, 通过工具获取数据或执行特定任务,以扬长避短。
Function Calling的处理流程
Function Calling的流程比较简单,归纳下来主要是下面6个大步骤。
函数定义:定义并实现函数,明确函数名,函数功能描述,参数,参数类型定义
请求大模型并注册函数: 在api调用大模型时,通过tools参数将函数的定义信息传递给大模型
大模型判断与选择: 大模型收到用户请求,并决定是否调用函数,当需要调用函数时,大模型会在回包中返回需要调用的函数名称,函数的参数等信息
执行函数: 本地代码解析大模型回包,当大模型返回需要执行函数的命令时,本地代码按照大模型的函数名,参数,执行函数调用
返回结果: 将函数结果发送给大模型,大模型生成最终的回答
将处理过程用时许表示之后,则如下图所示:
sequenceDiagram autonumber participant User as 👤 用户 participant Dev as 🖥️ 开发者/后端系统 participant LLM as 🧠 大模型 User ->> Dev: 提出需求 Note over Dev: 📝 函数定义:<br/>• 函数名称<br/>• 功能描述<br/>• 参数及类型(JSON Schema) Dev ->> LLM: 发送函数定义和用户问题 rect rgb(240, 248, 255) Note over LLM: 🤔 判断与选择:<br/>分析是否需要调用函数 Note over LLM: ⚙️ 生成函数参数:<br/>构造符合要求的JSON结构 end LLM ->> Dev: 函数调用请求 rect rgb(245, 245, 245) Note over Dev: 🚀 执行函数:<br/>根据参数执行相应操作 end Dev ->> LLM: 返回函数执行结果 rect rgb(240, 248, 255) Note over LLM: 💬 生成最终回复:<br/>结合函数结果生成自然语言回答 end LLM ->> User: 提供最终回答Function Calling的调用案例:
下面以Deepseek为例,通过简单的案例展示Function Calling的使用。
环境准备:
首先安装 openai的sdk,国内的大多数模型都兼容openai的接口,可以直接使用openai提供的 sdk访问。
# 推荐使用虚拟环境
python -m venv .venv
source .venv/bin/activate
pip install openai
编码业务逻辑
以对一个数组求和为例,实现的代码如何:
import os
import json
from openai import OpenAI
client = OpenAI(
api_key= os.environ.get("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 定义函数,对数组进行求和
def sum_array(arr=[]):
return sum(arr)
function_map = {
"sum_array": sum_array
}
tools = [{
"type": "function",
"function": {
"name": "sum_array",
"description": "对数组中的内容进行求和",
"parameters": {
"type": "object",
"properties": {
"arr": {
"type": "array",
"items": {
"type": "number"
},
"description": "需要求和的数字数组"
}
},
"required": ["arr"]
},
"strict": True
}
}]
def request_deepseek(messages):
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
return response.choices[0].message
messages = [{"role": "user", "content": "计算1+2+89+76+67+220的值"}]
response = request_deepseek(messages)
messages.append(response)
if len(response.tool_calls) > 0:
tool = response.tool_calls[0]
if tool.function.name in function_map:
arguments = json.loads(tool.function.arguments)
func_result = function_map[tool.function.name](arguments["arr"])
print(f"函数调用结果: {func_result}")
messages.append({"role": "tool", "tool_call_id": tool.id, "content": f'{func_result}'})
response = request_deepseek(messages)
print(response.content)
#下面是deepseek的回包内容
ChatCompletionMessage(
content='',
role='assistant',
tool_calls=[
ChatCompletionMessageToolCall(
id='call_0_d0fb7475-0a3d-46af-84cf-b33ce6ae931f',
function=Function(
arguments='{"arr":[1,2,89,76,67,220]}',
name='sum_array'),
type='function',
index=0)
])
至此,函数调用的整个流程已经跑通,整个流程不是特别复杂。
使用function calling的过程中发现function calling也存在一些局限性,例如:
1. function calling要求函数的定义清晰准确,否则大模型生成的函数名,函数参数可能不正确,导致function calling失败,或者计算结果出现错误。
2. 函数调用会导致API使用成本增加,整体延时略有上升,对服务的稳定性也带来了一些新的挑战。
3. 最后Function Calling的代码维护比较麻烦,每个Function Calling都要提前定义之后才能使用,另外Function Calling的相关代码复用暂时也没有比较好的实践。