工具使用
聊天模型通常经过训练以支持“函数调用”或“工具使用”。工具是由用户提供的函数,模型可以在其响应过程中选择调用这些工具。例如,模型可以访问一个计算器工具来执行算术运算,而无需在内部自行完成计算。
本指南将演示如何定义工具、如何将工具传递给聊天模型,以及当模型调用工具时如何处理其输出。
传递工具
当模型支持工具调用时,请将函数传入 apply_chat_template() 的 tools 参数。
工具以 JSON schema 或 Python 函数的形式传入。如果传入 Python 函数,
系统会解析参数、参数类型和函数 docstring,以自动生成 JSON schema。
虽然传入 Python 函数非常方便,但解析器只能处理 Google 风格的文档字符串。有关如何格式化可直接用于工具的函数,请参阅以下示例。
def get_current_temperature(location: str, unit: str):
"""
Get the current temperature at a location.
Args:
location: The location to get the temperature for, in the format "City, Country"
unit: The unit to return the temperature in. (choices: ["celsius", "fahrenheit"])
"""
return 22. # A real function should probably actually get the temperature!
def get_current_wind_speed(location: str):
"""
Get the current wind speed in km/h at a given location.
Args:
location: The location to get the wind speed for, in the format "City, Country"
"""
return 6. # A real function should probably actually get the wind speed!
tools = [get_current_temperature, get_current_wind_speed]您可以选择在文档字符串中添加 Returns: 块,并在函数头部添加返回类型,但大多数模型不会使用这些信息。解析器还会忽略函数内部的实际代码!
真正重要的是函数名、参数名、参数类型,以及描述函数用途及其参数用途的文档字符串。这些内容构成了模型在决定是否调用该工具时所使用的“签名”。
工具调用示例
加载一个支持工具调用的模型和分词器,例如 NousResearch/Hermes-2-Pro-Llama-3-8B,如果您的硬件支持,也可以考虑更大的模型,如 Command-R 和 Mixtral-8x22B。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
checkpoint = "NousResearch/Hermes-2-Pro-Llama-3-8B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, dtype="auto", device_map="auto")创建聊天历史记录。
messages = [
{"role": "system", "content": "You are a bot that responds to weather queries. You should reply with the unit used in the queried location."},
{"role": "user", "content": "Hey, what's the temperature in Paris right now?"}
]接下来,将 messages 和工具列表传入 apply_chat_template()。对聊天进行分词并生成回复。
inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
outputs = model.generate(**inputs.to(model.device), max_new_tokens=128)
print(tokenizer.decode(outputs[0][len(inputs["input_ids"][0]):]))<tool_call>
{"arguments": {"location": "Paris, France", "unit": "celsius"}, "name": "get_current_temperature"}
</tool_call><|im_end|>该聊天模型调用了 get_current_temperature 工具,并使用了 docstring 中指定的正确参数。它根据 Paris 推断出位置为法国,并推断出温度单位应使用摄氏度。
模型实际上无法自行调用工具。它只是请求一次工具调用,而由你负责处理该调用,并将调用及其结果追加到聊天历史中。对于支持响应解析的模型,响应解析会自动完成,你只需使用parse_response()来提取工具调用即可。对于其他模型,你需要手动将输出字符串转换为工具调用字典。
无论采用哪种方法,工具调用都应放在 assistant 消息的 tool_calls 键中。这是推荐的 API,大多数支持工具调用的模型的聊天模板都应支持此方式。
尽管
tool_calls与 OpenAI API 类似,但 OpenAI API 使用 JSON 字符串作为其tool_calls的格式。如果在 Transformers 中使用该格式,可能会引发错误或导致模型出现异常行为,因为 Transformers 期望接收一个 dict。
tool_call = {"name": "get_current_temperature", "arguments": {"location": "Paris, France", "unit": "celsius"}}
messages.append({"role": "assistant", "tool_calls": [{"type": "function", "function": tool_call}]})将工具响应以 tool 角色追加到聊天历史中。
messages.append({"role": "tool", "content": "22"}) # Note that the returned content is always a string!最后,让模型读取工具响应并回复用户。
inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
out = model.generate(**inputs.to(model.device), max_new_tokens=128)
print(tokenizer.decode(out[0][len(inputs["input_ids"][0]):]))The temperature in Paris, France right now is 22°C.<|im_end|>
尽管助手消息中的键名为
tool_calls,但在大多数情况下,模型一次只发出一个工具调用。一些较旧的模型会同时发出多个工具调用,但这是一个复杂得多的过程,因为你需要同时处理多个工具响应并加以区分,通常需要使用工具调用 ID。请参阅模型卡片以了解模型对工具调用的具体格式要求。
JSON 模式
定义工具的另一种方式是传入 JSON schema。
您也可以手动调用将 Python 函数转换为 JSON schema 的底层函数,然后检查或编辑生成的 schema。通常无需这样做,但了解其底层机制会很有帮助。对于需要访问 JSON schema 以渲染工具定义的聊天模板作者而言,这一点尤为重要。
apply_chat_template() 方法使用 get_json_schema 函数将 Python 可调用对象转换为 JSON schema。这包括方法:self 和 cls 被视为隐式接收者参数,会被忽略。
from transformers.utils import get_json_schema
def multiply(a: float, b: float):
"""
A function that multiplies two numbers
Args:
a: The first number to multiply
b: The second number to multiply
"""
return a * b
schema = get_json_schema(multiply)
print(schema){
"type": "function",
"function": {
"name": "multiply",
"description": "A function that multiplies two numbers",
"parameters": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "The first number to multiply"
},
"b": {
"type": "number",
"description": "The second number to multiply"
}
},
"required": ["a", "b"]
}
}
}我们在此不会深入介绍 JSON schema 本身的细节,因为它在别处已有非常详尽的文档。不过,我们会提到,您可以将 JSON schema 字典传递给 apply_chat_template() 的 tools 参数,而无需使用 Python 函数:
# A simple function that takes no arguments
current_time = {
"type": "function",
"function": {
"name": "current_time",
"description": "Get the current local time as a string.",
"parameters": {
'type': 'object',
'properties': {}
}
}
}
# A more complete function that takes two numerical arguments
multiply = {
'type': 'function',
'function': {
'name': 'multiply',
'description': 'A function that multiplies two numbers',
'parameters': {
'type': 'object',
'properties': {
'a': {
'type': 'number',
'description': 'The first number to multiply'
},
'b': {
'type': 'number', 'description': 'The second number to multiply'
}
},
'required': ['a', 'b']
}
}
}
model_input = tokenizer.apply_chat_template(
messages,
tools = [current_time, multiply]
)