Transformers 文档

模板编写

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

模板编写

聊天模板是一个存储在分词器 chat_template 属性中的 Jinja 模板。Jinja 是一种模板语言,允许您编写类似 Python 的代码和语法。聊天模板执行以下三个作用。

  1. 打印用 <||> 包裹的角色(<|user|><|assistant|> 等)。
  2. 打印消息,后跟一个序列结束(EOS)标记。
  3. 如果 add_generation_prompt=True,则打印助手标记,以便模型生成助手响应。

一个示例如下所示。

{%- for message in messages %}
    {{- '<|' + message['role'] + |>\n' }}
    {{- message['content'] + eos_token }}
{%- endfor %}
{%- if add_generation_prompt %}
    {{- '<|assistant|>\n' }}
{%- endif %}

该模板可以自定义以处理更复杂的用例。本指南将向您展示如何添加和编辑模板,并包含模板编写技巧。

创建模板

通过编写 Jinja 模板,然后将其设置为分词器中的聊天模板来创建模板。例如,以下模板向助手消息添加 [ASST][/ASST] 标签。

{%- 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 %}

在分词器中设置模板,下次使用 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 模式格式的工具列表。
  • documents 包含文档列表,格式为 {"title": Title, "contents": "Contents"} (专为 RAG 模型设计)。
  • add_generation_prompt 是一个布尔值,用于确定是否在对话结束时添加助手标题。
  • bos_tokeneos_token 是从分词器的 special_tokens_map 中提取的特殊标记。

可调用函数

模板中有两个可调用函数可用。

  • 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。有关过滤器的完整列表,请参阅 内置过滤器 列表。
  • TrueFalseNone(这些是 Python 特有的)分别替换为 truefalsenone
  • 直接渲染字典或列表可能会在其他实现中返回不同的结果。例如,字符串条目可能会从单引号变为双引号。为避免这种情况,请添加 tojson 过滤器以保持一致性。

大型模板

较新的模型或具有 工具调用RAG 等功能的模型需要更大的模板,长度可能超过 100 行。在单独的文件中编写大型模板可能更容易。单独文件中的行号与模板解析或执行错误中的行号完全对应,从而更容易调试任何潜在问题。

将模板写入单独的文件并将其提取到聊天模板中。

open("template.jinja", "w").write(tokenizer.chat_template)

您也可以将编辑过的模板重新加载到分词器中。

tokenizer.chat_template = open("template.jinja").read()

工具模板

没有编写工具模板的特定格式,但最好遵循标准 API。这确保了模板在模型之间广泛可用,而无需用户编写自定义代码来将工具与您的模型一起使用。

空白和特殊标记等格式是模型特定的。确保一切都与模型训练时使用的格式完全匹配。

以下部分列出了用于编写工具模板的标准 API 元素。

工具定义

Transformers 聊天模板方法允许用户将工具作为 Python 函数或 JSON 模式传递。当传递函数时,会自动生成 JSON 模式并传递给模板。模板中的 tools 变量总是接受 JSON 模式列表。

特定的标记和工具描述应与您的模型训练时使用的标记和工具描述相匹配。您的模型不需要理解 JSON 模式输入,因为您的模板可以将 JSON 模式转换为您的模型格式。例如,Command-R 是用 Python 函数头定义的工具进行训练的,但 Command-R 工具模板接受 JSON 模式。模板在内部转换类型并将输入工具渲染为 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"]
    }
  }
}

下面显示了一个在聊天模板中处理工具定义的示例。特定的标记和工具描述应更改为与模型训练时使用的标记和工具描述相匹配。

{%- 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 %}

工具响应

工具响应是一个带有 rolename(函数名称)和 content(工具调用结果)键的消息字典。

{
  "role": "tool",
  "name": "multiply",
  "content": "30"
}

并非所有键都需要在工具响应中使用。例如,如果模型不期望函数名称包含在工具响应中,那么您只需包含 rolecontent

{%- if message['role'] == 'tool' %}
    {{- "<tool_result>" + message['content'] + "</tool_result>" }}
{%- endif %}

贡献

通过在分词器中设置 chat_template 属性并使用 apply_chat_template() 进行测试来添加聊天模板。如果它按预期工作,那么您可以使用 push_to_hub() 将其上传到 Hub。

即使您不是模型所有者,为具有空聊天模板或使用默认类模板的模型添加模板也很有帮助。在模型仓库上打开 拉取请求 以添加模板。

tokenizer.chat_template = template
tokenizer.push_to_hub("model_name")
< > 在 GitHub 上更新