函数调用功能可以增强模型推理效果或进行其他外部操作,包括信息检索、数据库操作、知识图谱搜索与推理、操作系统、触发外部操作等工具调用场景。

注:市面上的大模型几乎都支持函数调用,不过其稳定性不是很好,有时候无法命中函数。OpenAI的函数调用在使用中会发现对中文的理解不够精准,DeepSeek对函数的支持也不够稳定。各大模型对英文的支持度都比较好,比中文的理解更精准。函数调用功能,测试下来发现智谱大模型最优秀,不管是中文还是英文都能命中函数

上图为OpenAI的函数调用的时序图,其中get_weather函数为本地定义的一个函数,需要与大模型至少进行2轮对话,才可以真正调用函数回答问题。

本教程将介绍如何使用 智谱的ChatGLM 的函数调用功能,实现对模型与外部函数库的连接。

tools 是内容生成 API 中的可选参数,用于向模型提供函数定义。通过此参数,模型能够生成符合用户所提供规范的函数参数。请注意,API 实际上不会执行任何函数调用,仅返回调用函数所需要的参数。开发者可以利用模型输出的参数在应用中执行函数调用。

本教程包括以下3个部分:

  1. 如何使用 Chat Completion 接口向模型描述外部函数。
  2. 如何与模型交互,触发模型对函数的调用。
  3. 如何使用模型生成的结果调用外部函数。

如何描述外部函数

假设我们要创建一个具备查询航班功能的聊天机器人。我们定义如下两个外部函数供模型选择调用:

  1. 查询两地之间某日航班号函数:get_flight_number(departure: str, destination: str, date: str)
  2. 查询某航班某日票价函数:get_ticket_price(flight_number: str, date: str)

描述函数功能

为了向模型描述外部函数库,需要向 tools 字段传入可以调用的函数列表。参数如下表:

参数名称 类型 是否必填 参数说明
type String 设置为function
function Object
name String 函数名称
description String 用于描述函数功能。模型会根据这段描述决定函数调用方式。
parameters Object parameters字段需要传入一个 Json Schema 对象,以准确地定义函数所接受的参数。

示例:(注 代码都是使用Python编写)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_flight_number",
            "description": "根据始发地、目的地和日期,查询对应日期的航班号",
            "parameters": {
                ......
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_ticket_price",
            "description": "查询某航班在某日的票价",
            "parameters": {
                ......
            },
        }
    },
]

此处先省略parameters参数,我们将在下一节介绍如何描述函数所需参数。

编写函数参数列表的 JSON 描述

为了准确定义函数的参数列表,在编写参数列表的 JSON Schema 时建议最少包含以下字段:

  • description :说明函数方法的用途。
  • type :定义 JSON 数据的数据类型约束。
  • properties:一个Object,其中的每个属性代表要定义的 JSON 数据中的一个键。
  • required:指定哪些属性在数据中必须被包含。
  • enum:如果一个属性是枚举类型,则此字段应当设置为枚举值的数组。

则完整的tools字段设置为:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_flight_number",
            "description": "根据始发地、目的地和日期,查询对应日期的航班号",
            "parameters": {
                "type": "object",
                "properties": {
                    "departure": {
                        "description": "出发地",
                        "type": "string"
                    },
                    "destination": {
                        "description": "目的地",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": [ "departure", "destination", "date" ]
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_ticket_price",
            "description": "查询某航班在某日的票价",
            "parameters": {
                "type": "object",
                "properties": {
                    "flight_number": {
                        "description": "航班号",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": [ "flight_number", "date"]
            },
        }
    },
]

函数调用选择

在 tools 参数中,如果填写了 functions 参数,则默认情况下模型将决定何时适合使用其中一个函数。 如果要控制模型如何选择函数调用,需要设置 tool_choice 参数。参数默认值为auto,此时模型根据上下文信息自行选择是否返回函数调用。还可以通过将 tool_choice 参数设置为 “none” 来强制 API 不返回任何函数的调用。目前函数调用仅支持 auto 模式。

Function Call 流程实践

本节将以上文定义的具备查询航班功能的聊天机器人为例,介绍如何与模型对话完成函数调用。

初始化函数定义和client:

from zhipuai import ZhipuAI
client = ZhipuAI(api_key="")
messages = []
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_flight_number",
            "description": "根据始发地、目的地和日期,查询对应日期的航班号",
            "parameters": {
                "type": "object",
                "properties": {
                    "departure": {
                        "description": "出发地",
                        "type": "string"
                    },
                    "destination": {
                        "description": "目的地",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": [ "departure", "destination", "date" ]
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_ticket_price",
            "description": "查询某航班在某日的票价",
            "parameters": {
                "type": "object",
                "properties": {
                    "flight_number": {
                        "description": "航班号",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": [ "flight_number", "date"]
            },
        }
    },
]
messages = []

我们想查询2024年1月20日从北京前往上海的航班。我们向模型提供这个信息:

messages = []
messages.append({"role": "user", "content": "帮我查询从2024年1月20日,从北京出发前往上海的航班"})
response = client.chat.completions.create(
    model="glm-4",  # 填写需要调用的模型名称
    messages=messages,
    tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())

此时模型成功触发对get_flight_number函数的调用,参数为:date=“2024-01-20”,departure=“北京”,destination=“上海”

content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8252663420321749719', function=Function(arguments='{"date":"2024-01-20","departure":"北京","destination":"上海"}', name='get_flight_number'), type='function')]

现在,清空消息历史。我们尝试提供信息,触发模型对get_ticket_price函数的调用。

messages = []
messages.append({"role": "system", "content": "不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息"})
messages.append({"role": "user", "content": "帮我查询2024年1月20日1234航班的票价"})
response = client.chat.completions.create(
    model="glm-4",  # 填写需要调用的模型名称
    messages=messages,
    tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())

此时模型成功触发对get_ticket_price函数的调用,参数为:date=“2024-01-20”,flight_number=“1234”

content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8252648611274312180', function=Function(arguments='{"date":"2024-01-20","flight_number":"1234"}', name='get_ticket_price'), type='function')]

我们也可以强制模型使用特定函数,比如,我们通过设置tool_choice为{“type”: “function”, “function”: {“name”: “get_ticket_price”}}以强制模型生成调用get_ticket_price的参数。

messages = []
messages.append({"role": "system", "content": "不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息"})
messages.append({"role": "user", "content": "帮我查询1234航班的票价"})
response = client.chat.completions.create(
    model="glm-4",  # 填写需要调用的模型名称
    messages=messages,
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "get_ticket_price"}},
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())

此时模型被强制触发对get_ticket_price函数的调用,参数为:date=“2022-01-01”,flight_number=“1234”。注意到此时模型假设了一个date。

content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8252663214163297577', function=Function(arguments='{"date":"2022-01-01","flight_number":"1234"}', name='get_ticket_price'), type='function')]

我们也可以强制模型不调用函数。需要设置tool_choice为none。

使用模型生成的参数调用函数

将所需的函数实现

def get_flight_number(date:str , departure:str , destination:str):
    flight_number = {
        "北京":{
            "上海" : "1234",
            "广州" : "8321",
        },
        "上海":{
            "北京" : "1233",
            "广州" : "8123",
        }
    }
    return { "flight_number":flight_number[departure][destination] }
def get_ticket_price(date:str , flight_number:str):
    return {"ticket_price": "1000"}

定义处理 Function call 的函数:

def parse_function_call(model_response,messages):
    # 处理函数调用结果,根据模型返回参数,调用对应的函数。
    # 调用函数返回结果后构造tool message,再次调用模型,将函数结果输入模型
    # 模型会将函数调用结果以自然语言格式返回给用户。
    if model_response.choices[0].message.tool_calls:
        tool_call = model_response.choices[0].message.tool_calls[0]
        args = tool_call.function.arguments
        function_result = {}
        if tool_call.function.name == "get_flight_number":
            function_result = get_flight_number(**json.loads(args))
        if tool_call.function.name == "get_ticket_price":
            function_result = get_ticket_price(**json.loads(args))
        messages.append({
            "role": "tool",
            "content": f"{json.dumps(function_result)}",
            "tool_call_id":tool_call.id
        })
        response = client.chat.completions.create(
            model="glm-4",  # 填写需要调用的模型名称
            messages=messages,
            tools=tools,
        )
        print(response.choices[0].message)
        messages.append(response.choices[0].message.model_dump())

查询北京到广州的航班:

import json

# 清空对话
messages = []
 
messages.append({"role": "system", "content": "不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息"})
messages.append({"role": "user", "content": "帮我查询1月23日,北京到广州的航班"})
 
response = client.chat.completions.create(
    model="glm-4",  # 填写需要调用的模型名称
    messages=messages,
    tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
 
parse_function_call(response,messages)

返回

content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8282666790542042140', function=Function(arguments='{"date":"2023-01-23","departure":"北京","destination":"广州"}', name='get_flight_number'), type='function')]
content='根据您的要求,我已经查询到了1月23日从北京到广州的航班号,航班号为8321。' role='assistant' tool_calls=None

查询1234航班票价:

messages.append({"role": "user", "content": "这趟航班的价格是多少?"})
response = client.chat.completions.create(
    model="glm-4",  # 填写需要调用的模型名称
    messages=messages,
    tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
 
parse_function_call(response,messages)

返回

content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8282666893621289712', function=Function(arguments='{"date":"2023-01-23","flight_number":"8321"}', name='get_ticket_price'), type='function')]
content='这趟航班的票价为1000元。' role='assistant' tool_calls=None

附录:相关网址

chatgpt官方的api地址:

https://platform.openai.com/docs/guides/function-calling

https://openai.com/index/function-calling-and-other-api-updates/

智谱的api地址:

https://open.bigmodel.cn/dev/howuse/functioncall

deepseek

https://api-docs.deepseek.com/zh-cn/guides/function_calling

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐