Article cover

大模型Function Calling详解:原理、代码实现与局限性分析

Author avatar
zan
#技术#编程#Web开发

大模型Function Calling的出现背景

大模型受限于训练数据的时效性,对实时场景的回答效果并不好。早期的一些模型,在面对数学等需要精确计算的场景,效果可能也不好。Function Calling的出现,使大模型可以调用外部工具, 通过工具获取数据或执行特定任务,以扬长避短。

Function Calling的处理流程

Function Calling的流程比较简单,归纳下来主要是下面6个大步骤。

  1. 函数定义:定义并实现函数,明确函数名,函数功能描述,参数,参数类型定义

  2. 请求大模型并注册函数: 在api调用大模型时,通过tools参数将函数的定义信息传递给大模型

  3. 大模型判断与选择: 大模型收到用户请求,并决定是否调用函数,当需要调用函数时,大模型会在回包中返回需要调用的函数名称,函数的参数等信息

  4. 执行函数: 本地代码解析大模型回包,当大模型返回需要执行函数的命令时,本地代码按照大模型的函数名,参数,执行函数调用

  5. 返回结果: 将函数结果发送给大模型,大模型生成最终的回答

  6. 将处理过程用时许表示之后,则如下图所示:

    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. 1. function calling要求函数的定义清晰准确,否则大模型生成的函数名,函数参数可能不正确,导致function calling失败,或者计算结果出现错误。

  2. 2. 函数调用会导致API使用成本增加,整体延时略有上升,对服务的稳定性也带来了一些新的挑战。

  3. 3. 最后Function Calling的代码维护比较麻烦,每个Function Calling都要提前定义之后才能使用,另外Function Calling的相关代码复用暂时也没有比较好的实践。

Author avatar

关于作者

全栈开发者,技术爱好者,分享Web开发与编程心得

评论