大语言模型(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 是一个快速且易于使用的库,用于大型语言模型的推理和部署。它使用 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.