Skip to content

Latest commit

 

History

History
312 lines (268 loc) · 10.2 KB

函数调用.md

File metadata and controls

312 lines (268 loc) · 10.2 KB

函数调用

什么是函数调用?

大语言模型(LLM)的函数调用功能使模型能够与外部工具或API进行交互,扩展其能力以处理复杂任务。例如,模型可以调用外部函数来获取实时数据、执行计算或访问数据库。

使用函数调用进行推理

由于函数调用本质上是通过提示工程实现的,您可以手动构建TeleChat2模型的输入。但是,支持函数调用的框架可以帮助您完成所有繁重的工作。

接下来,我们将介绍(通过专用的函数调用模板)使用

  • vLLM

如果您熟悉OpenAI API的使用,您也可以直接使用适用于TeleChat2的OpenAI兼容API服务。然而,并非所有服务都支持TeleChat2的函数调用。目前,支持的解决方案包括由vLLM提供的自托管服务。

案例

我们同样通过一个示例来展示推理的使用方法。假设我们使用的编程语言是Python 3.11。

场景:假设我们要询问模型某个地点的温度。通常,模型会回答无法提供实时信息。但我们有两个工具,可以分别获取城市的当前温度和指定日期的温度,我们希望模型能够利用这些工具。

为了这个示例案例,您可以使用以下代码:

准备代码

# function call opensource demo
def get_phone_number(name: str) -> str:
    """Get phone number by name.

    Args:
        name: Name of a person.

    Returns:
        The name and his phone number in a dict.
    """
    return {
        "name": name,
        "phone_number": "1234567890",
    }


def get_email_address(name: str) -> str:
    """Get email address by name.

    Args:
        name: Name of a person.

    Returns:
        The name and his email address in a dict.
    """
    return {
        "name": name,
        "email_address": "[email protected]",
    }


def exec_function_call(func_name, param_dict):
    if func_name == "get_phone_number":
        return get_phone_number(**param_dict)
    elif func_name == "get_email_address":
        return get_email_address(**param_dict)


TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_phone_number",
            "description": "Get phone number by name.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of a person.",
                    }
                },
                "required": ["name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_email_address",
            "description": "Get email address by name.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of a person.",
                    }
                },
                "required": ["name"]
            }
        }
    }
]

工具应使用JSON Schema进行描述,消息应包含尽可能多的有效信息。您可以在下面找到工具和消息的解释:

示例工具

工具应使用以下JSON进行描述:

[
    {
        "type": "function",
        "function": {
            "name": "get_phone_number",
            "description": "Get phone number by name.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of a person.",
                    }
                },
                "required": ["name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_email_address",
            "description": "Get email address by name.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of a person.",
                    }
                },
                "required": ["name"]
            }
        }
    }
]

对于每个工具,它是一个具有两个字段的JSON object:

type:string,用于指定工具类型,目前仅"function"有效

function:object,详细说明了如何使用该函数

对于每个function,它是一个具有三个字段的JSON object:

name:string 表示函数名称

description:string 描述函数用途

parameters:JSON Schema,用于指定函数接受的参数。请参阅链接文档以了解如何构建JSON Schema。值得注意的字段包括type、required和enum。

大多数框架使用“工具”格式,有些可能使用“函数”格式。根据命名,应该很明显应该使用哪一个。

示例消息

我们的查询是What's the temperature in San Francisco now? How about tomorrow?。由于模型不知道当前日期,更不用说明天了,我们应该在输入中提供日期。在这里,我们决定在默认系统消息 “你是中国电信星辰语义大模型,英文名是TeleChat,你是由中电信人工智能科技有限公司和中国电信人工智能研究院(TeleAI)研发的人工智能助手。” 之后的系统消息中提供该信息。您可以在应用程序代码中将日期附加到用户消息。

[
     {"role": "user", "content": "May I have Bill's phone number please?"},
]

vLLM

vLLM 是一个快速且易于使用的库,用于大型语言模型的推理和部署。它使用 transformers 中的分词器来格式化输入,因此我们在准备输入时应该不会遇到任何问题。此外,vLLM 还实现了辅助函数,以便在支持的情况下自动解析生成的工具调用。

工具支持自 v0.6.0 版本起已在 vllm 中可用。请确保安装了一个支持工具调用的版本。更多信息,请查阅 vLLM 文档

在本指南中,我们使用的是 v0.6.1.post2 版本。我们将使用 vllm 提供的 OpenAI 兼容 API,并通过 openai Python 库的 API 客户端来进行操作。

准备工作

对于 TeleChat2,tokenizer_config.json 中的聊天模板已经包含了对 Hermes 风格工具调用的支持。我们只需要启动一个由 vLLM 提供的 OpenAI 兼容 API 即可:

vllm serve TeleChat2/TeleChat2-7B \
    --enable-auto-tool-choice \
    --tool-call-parser hermes

输入与[准备代码](#### 准备代码)中的相同:

tools = TOOLS
messages = MESSAGES[:]

我们先初始化API客户端:

from openai import OpenAI

openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"

client = OpenAI(
    api_key=openai_api_key,
    base_url=openai_api_base,
)

model_name = "TeleChat2/TeleChat2-7B"

工具调用和工具结果

我们可以使用create chat completions endpoint直接查询底层API:

response = client.chat.completions.create(
    model=model_name,
    messages=messages,
    tools=tools,
    temperature=0.7,
    top_p=0.8,
    max_tokens=512,
    extra_body={
        "skip_special_tokens": False,
        "spaces_between_special_tokens": False,
        "repetition_penalty": 1.05,
    },
)

vLLM应当可以为我们解析工具调用,回复的主要字段(response.choices[0])应如下所示:

Choice(
    finish_reason='tool_calls', 
    index=0, 
    logprobs=None, 
    message=ChatCompletionMessage(
        content=None, 
        role='assistant', 
        function_call=None, 
        tool_calls=[
            ChatCompletionMessageToolCall(
                id='chatcmpl-tool-d544ca22e8634bea9587d19f9b9a948c', 
                function=Function(arguments='{"name": "Bill"}', name='get_phone_number'), 
                type='function',
            ), 
        ],
    ), 
    stop_reason=None,
)

如前所述,有可能存在边界情况,模型生成了工具调用但格式不良也无法被解析。对于生产代码,我们需要尝试自行解析。 随后,我们可以调用工具并获得结果,然后将它们加入消息中:

messages.append(response.choices[0].message.model_dump())

if tool_calls := messages[-1].get("tool_calls", None):
    for tool_call in tool_calls:
        call_id: str = tool_call["id"]
        if fn_call := tool_call.get("function"):
            fn_name: str = fn_call["name"]
            fn_args: dict = json.loads(fn_call["arguments"])
        
            fn_res: str = str(exec_function_call(fn_name, fn_args))

            messages.append({
                "role": "tool",
                "content": fn_res,
                "tool_call_id": call_id,
            })

这里需要注意OpenAI API使用tool_call_id字段来识别工具结果和工具调用间的联系。

现在消息如下:

[
    {
        "role": "user",
        "content": "May I have Bill's phone number please?"
    },
    {
        "content": null,
        "role": "assistant",
        "function_call": null,
        "tool_calls": [
            {
                "id": "chatcmpl-tool-d544ca22e8634bea9587d19f9b9a948c",
                "function": {
                    "arguments": "{\"name\": \"Bill\"}",
                    "name": "get_phone_number"
                },
                "type": "function"
            }
        ]
    },
    {
        "role": "tool",
        "content": "{'name': 'Bill', 'phone_number': '1234567890'}",
        "tool_call_id": "chatcmpl-tool-d544ca22e8634bea9587d19f9b9a948c"
    }
]

最终响应

让我们再次查询接口,以给模型提供工具结果并获得回复:

response = client.chat.completions.create(
    model=model_name,
    messages=messages,
    tools=tools,
    temperature=0.7,
    top_p=0.8,
    max_tokens=512,
    extra_body={
        "repetition_penalty": 1.05,
        "skip_special_tokens": False,
        "spaces_between_special_tokens": False,
    },
)

最终结果如下所示

Sure, here is Bill's phone number: 1234567890.