Transformers 文档
模板编写
并获得增强的文档体验
开始使用
模板编写
聊天模板是一个 Jinja 模板,存储在 tokenizer 的 chat_template 属性中。Jinja 是一种模板语言,允许您编写类似 Python 的代码和语法。聊天模板执行以下三个角色。
- 打印用
<|
和|>
括起来的角色 (<|user|>
,<|assistant|>
等)。 - 打印消息,后跟一个序列结束符 (
EOS
) token。 - 如果 add_generation_prompt=True,则打印 assistant token,以便模型生成 assistant 响应。
下面显示了一个示例模板。
{%- for message in messages %}
{{- '<|' + message['role'] + |>\n' }}
{{- message['content'] + eos_token }}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|assistant|>\n' }}
{%- endif %}
可以自定义模板以处理更复杂的使用场景。本指南将向您展示如何添加和编辑模板,并包含模板编写技巧。
创建模板
通过编写 Jinja 模板,然后将其设置为 tokenizer 中的聊天模板来创建模板。例如,以下模板将 [ASST]
和 [/ASST]
标签添加到 assistant 消息中。
{%- for message in messages %}
{%- if message['role'] == 'user' %}
{{- bos_token + '[INST] ' + message['content'].strip() + ' [/INST]' }}
{%- elif message['role'] == 'system' %}
{{- '<<SYS>>\\n' + message['content'].strip() + '\\n<</SYS>>\\n\\n' }}
{%- elif message['role'] == 'assistant' %}
{{- '[ASST] ' + message['content'] + ' [/ASST]' + eos_token }}
{%- endif %}
{%- endfor %}
在 tokenizer 中设置模板,下次使用 apply_chat_template() 时,将使用新模板。
template = tokenizer.chat_template
template = template.replace("SYS", "SYSTEM") # Change the system token
tokenizer.chat_template = template # Set the new template
模板保存在 tokenizer_config.json
文件中。使用 push_to_hub() 将其上传到 Hub,以便稍后可以重用它,并确保每个人都为您的模型使用正确的模板。
tokenizer.push_to_hub("model_name")
模板编写技巧
开始编写 Jinja 模板的最简单方法是参考现有模板。在任何聊天模型上使用 print(tokenizer.chat_template)
查看它正在使用的模板。尝试从不调用任何工具或不支持 RAG 的简单模型开始。最后,查看 Jinja 文档,了解有关格式和语法的更多详细信息。
本节整理了一些用于编写清晰高效的 Jinja 模板的最佳实践。
修剪空格
Jinja 会打印文本块之前或之后的任何空格。这对于聊天模板来说可能是一个问题,因为空格的使用应该是故意的。添加 -
以删除块之前的任何空格。
{%- for message in messages %}
{{- message['role'] + message['content'] }}
{%- endfor %}
下面不正确的空格使用示例可能会在输出中引入换行符和缩进。
{% for message in messages %}
{{ message['role'] + message['content'] }}
{% endfor %}
特殊变量
模板内部有五个特殊变量可用。您可以将几乎任何其他参数传递给 apply_chat_template(),它将在模板内部作为变量可用。但是,您应该尽量将变量数量保持在以下五个以内,以便用户更轻松地使用聊天模型,而无需编写自定义代码来处理特定于模型的参数。
messages
包含聊天历史记录,形式为消息字典列表。tools
包含 JSON schema 格式的工具列表。documents
包含文档列表,格式为{"title": 标题, "contents": "内容"}
(专为 RAG 模型设计)。add_generation_prompt
是一个布尔值,用于确定是否在对话末尾添加 assistant 标头。bos_token
和eos_token
是从 tokenizer 的special_tokens_map
中提取的特殊 token。
可调用函数
模板内部有两个可调用函数可用。
raise_exception(msg)
引发TemplateException
。这对于调试或警告用户有关不正确的模板使用非常有用。strftime_now(format_str)
检索特定格式的当前日期和时间,这可能有助于包含在系统消息中。它等效于 Python 中的 datetime.now().strftime(format_str)。
与非 Python Jinja 的兼容性
Jinja 以多种语言实现,它们通常具有相同的语法。在 Python 中编写模板允许您使用 Python 方法,例如字符串上的 lower 或字典上的 items。但是,如果模板在非 Python 实现中使用,例如,当使用 Javascript 或 Rust 部署时,这将不起作用。
进行以下更改以确保跨所有 Jinja 实现的兼容性。
- 将 Python 方法替换为 Jinja 过滤器。例如,将
string.lower()
替换为string|lower
或将dict.items()
替换为dict|dictitems
。大多数更改都遵循相同的模式,除了string.strip()
,它被替换为string|trim
。有关过滤器的完整列表,请参阅 内置过滤器 列表。 - 分别用
true
、false
和none
替换True
、False
和None
(这些是 Python 特有的)。 - 直接呈现字典或列表可能会在其他实现中返回不同的结果。例如,字符串条目可能会从单引号更改为双引号。为避免这种情况,添加 tojson 过滤器以保持一致性。
大型模板
较新的模型或具有 工具调用 和 RAG 等功能的模型需要更大的模板,这些模板可能超过 100 行。在单独的文件中编写较大的模板可能会更容易。单独文件中的行号与模板解析或执行错误中的行号完全对应,从而更容易调试任何潜在问题。
在单独的文件中编写模板并将其提取到聊天模板。
open("template.jinja", "w").write(tokenizer.chat_template)
您也可以将编辑后的模板加载回 tokenizer。
tokenizer.chat_template = open("template.jinja").read()
工具模板
编写工具模板没有特定的格式,但最好遵循标准 API。这确保了模板可以在各种模型中广泛访问,而无需用户编写自定义代码来将工具与您的模型一起使用。
诸如空格和特殊 token 之类的格式是特定于模型的。确保所有内容与模型训练时使用的格式完全匹配。
以下部分列出了用于编写工具模板的标准 API 的元素。
工具定义
Transformers 聊天模板方法允许用户将工具作为 Python 函数或 JSON schema 传递。当传递函数时,会自动生成 JSON schema 并传递给模板。模板中的 tools
变量始终采用 JSON schema 列表。
特定的 token 和工具描述应与您的模型训练时使用的 token 和工具描述相匹配。您的模型不需要理解 JSON schema 输入,因为您的模板可以将 JSON schema 转换为您的模型格式。例如,Command-R 是使用 Python 函数头定义的工具进行训练的,但 Command-R 工具模板接受 JSON schema。该模板在内部转换类型并将输入工具呈现为 Python 标头。
{
"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"]
}
}
}
下面显示了一个在聊天模板中处理工具定义的示例。应更改特定的 token 和工具描述,以匹配模型训练时使用的 token 和工具描述。
{%- if tools %}
{%- for tool in tools %}
{{- '<tool>' + tool['function']['name'] + '\n' }}
{%- for argument in tool['function']['parameters']['properties'] %}
{{- argument + ': ' + tool['function']['parameters']['properties'][argument]['description'] + '\n' }}
{%- endfor %}
{{- '\n</tool>' }}
{%- endif %}
{%- endif %}
工具调用
工具调用(如果存在)是一个带有 “assistant”
角色的列表。这始终是一个列表,即使大多数工具调用模型仅支持单个工具调用,这意味着该列表通常仅包含一个元素。
{
"role": "assistant",
"tool_calls": [
{
"type": "function",
"function": {
"name": "multiply",
"arguments": {
"a": 5,
"b": 6
}
}
}
]
}
下面显示了处理工具调用的常见模式。
{%- if message['role'] == 'assistant' and 'tool_calls' in message %}
{%- for tool_call in message['tool_calls'] %}
{{- '<tool_call>' + tool_call['function']['name'] + '\n' + tool_call['function']['arguments']|tojson + '\n</tool_call>' }}
{%- endif %}
{%- endfor %}
{%- endif %}
工具响应
工具响应是一个消息字典,其中包含 role
、name
(函数名称) 和 content
(工具调用的结果) 键。
{
"role": "tool",
"name": "multiply",
"content": "30"
}
并非所有键都需要在工具响应中使用。例如,如果模型不希望函数名称包含在工具响应中,那么您可以只包含 role
和 content
。
{%- if message['role'] == 'tool' %}
{{- "<tool_result>" + message['content'] + "</tool_result>" }}
{%- endif %}
贡献
通过在 tokenizer 中设置 chat_template
属性并使用 apply_chat_template() 进行测试来添加聊天模板。如果它按预期工作,则可以使用 push_to_hub() 将其上传到 Hub。
即使您不是模型所有者,为具有空聊天模板的模型或使用默认类模板的模型添加模板仍然很有帮助。在模型存储库上打开一个 pull request 以添加模板。
tokenizer.chat_template = template
tokenizer.push_to_hub("model_name")