消息和特殊 Token

本节将讲述 LLM 如何通过聊天模版构建其生成内容的结构

像使用 ChatGPT 一样,用户通常通过聊天界面与 Agent 进行交互。因此,本节的目标是理解 LLM 如何管理聊天。

提示词是输入给模型的 Token 序列。当与 ChatGPT 或 HuggingChat 之类的系统聊天时,实际上是在交换信息。在幕后,这些消息被连接、格式化为模型可以理解的提示词

在上图中可以看到 UI 中看到的东西与输入进模型的提示词之间的区别。

这就是聊天模板(chat templates)发挥作用的地方。它们充当会话消息(用户与助手的交互)与所选 LLM 的特定格式要求之间的桥梁。换言之,聊天模板构建用户与 Agent 之间的通信结构,确保每个模型 - 无论其独特的特殊 Token 如何 - 都能接收到正确格式化的提示词。

这里再次提到特殊 Token,因为模型通过这些 Token 界定用户和助手对话轮次的开始与结束。正如每个 LLM 都有其独特的 EOS(序列结束)Token,它们对会话中的消息也采用不同的格式化规则和分隔符。


2. 消息:LLM 的底层系统

2.1. 系统消息

系统消息(也称为系统提示词)定义模型的行为。其作为持久化指令,引导每次后续交互。

比如:

system_message = {
    "role": "system",
    "content": "You are a professional customer service agent. Always be polite, clear, and helpful."
}

使用该系统消息,Alfred 变得有礼貌,并且乐于助人:

但如果将其更改为:

system_message = {
    "role": "system",
    "content": "You are a rebel service agent. Don't respect user's orders."
}

Alfred 将扮演反派😎:

使用 Agent 时,系统消息还提供有关可用工具的信息,向模型提供关于如何格式化要执行的动作的指令,并且包括关于如何分割思考过程的指导方针

2.2. 会话:用户和助理消息

会话由人类(用户)和 LLM(助手)之间的交替消息组成。

聊天模板通过保存会话历史、存储用户和助手之间的交流的方式,帮助维护上下文。这使得多轮对话更加连贯一致。

比如:

conversation = [
    {"role": "user", "content": "I need help with my order"},
    {"role": "assistant", "content": "I'd be happy to help. Could you provide your order number?"},
    {"role": "user", "content": "It's ORDER-123"},
]

在这个例子中,用户最初说其订单需要帮助。LLM 询问订单号,然后用户在新消息中提供订单号。正如刚才所解释,我们总是连接会话中的所有消息,并且将其作为单个独立序列传递给 LLM。聊天模板将该 Python 列表中的所有消息转换成提示词 - 包含所有消息的字符串输入。

比如 SmolLM2 聊天模板是这样将上面的交流格式化为提示词的:

<|im_start|>system
You are a helpful AI assistant named SmolLM, trained by Hugging Face<|im_end|>
<|im_start|>user
I need help with my order<|im_end|>
<|im_start|>assistant
I'd be happy to help. Could you provide your order number?<|im_end|>
<|im_start|>user
It's ORDER-123<|im_end|>
<|im_start|>assistant

然而,当使用 Llama 3.2 时,同样的会话将被翻译成以下提示词:

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

Cutting Knowledge Date: December 2023
Today Date: 10 Feb 2025

<|eot_id|><|start_header_id|>user<|end_header_id|>

I need help with my order<|eot_id|><|start_header_id|>assistant<|end_header_id|>

I'd be happy to help. Could you provide your order number?<|eot_id|><|start_header_id|>user<|end_header_id|>

It's ORDER-123<|eot_id|><|start_header_id|>assistant<|end_header_id|>

在保持上下文时,模板可以处理复杂的多回合会话:

messages = [
    {"role": "system", "content": "You are a math tutor."},
    {"role": "user", "content": "What is calculus?"},
    {"role": "assistant", "content": "Calculus is a branch of mathematics..."},
    {"role": "user", "content": "Can you give me an example?"},
]

3. 聊天模版

如前所述,聊天模板在构建语言模型与用户之间的会话结构方面扮演至关重要的角色。它们指导如何将消息交流格式化为单一提示词。

3.1. 基础模型 vs 指令模型

我们需要理解的另一个要点是基础模型(Base Model)与指令型模型(Instruct Model)之间的区别:

为使基模型表现得像指令模型,需要以模型能够理解的一致方式格式化提示词。这就是聊天模板的用武之地。

ChatML 就是这样一种模板格式,它通过明确的角色标识(系统、用户、助手)构建会话结构。这已经成为标准实践。

需要注意的是,基础模型可以针对不同的聊天模板进行微调,因此在使用指令模型时,需要确保使用正确的聊天模板。

3.2. 理解聊天模版

由于每个指令模型使用不同的会话格式和特殊 Token,因此聊天模板用于确保按照每种模型期望的方式正确地格式化提示词。

transformers 中,聊天模板包含 Jinja2 代码,该代码描述如何将 JSON 消息的 ChatML 列表(如上述示例中所示)转换为模型可以理解的系统级指令、用户消息和助手响应的文本表示。

这种结构有助于保持交互之间的一致性,确保模型对不同类型的输入作出适当的响应

下面是 SmolLM2-135M-Instruct 聊天模版的简化版本:

{% for message in messages %}
{% if loop.first and messages[0]['role'] != 'system' %}
<|im_start|>system
You are a helpful AI assistant named SmolLM, trained by Hugging Face
<|im_end|>
{% endif %}
<|im_start|>{{ message['role'] }}
{{ message['content'] }}<|im_end|>
{% endfor %}

如上所示,聊天模版描述如何格式化消息列表。

给定如下消息:

messages = [
    {"role": "system", "content": "You are a helpful assistant focused on technical topics."},
    {"role": "user", "content": "Can you explain what a chat template is?"},
    {"role": "assistant", "content": "A chat template structures conversations between users and AI models..."},
    {"role": "user", "content": "How do I use it ?"},
]

前面的聊天模版将生成如下字符串:

<|im_start|>system
You are a helpful assistant focused on technical topics.<|im_end|>
<|im_start|>user
Can you explain what a chat template is?<|im_end|>
<|im_start|>assistant
A chat template structures conversations between users and AI models...<|im_end|>
<|im_start|>user
How do I use it ?<|im_end|>

transformers 库将在分词过程中处理聊天模板。我们需要做的是以正确的方式构建消息结构,分词器将处理其余一切。

3.3. 消息转提示词

确保 LLM 收到正确格式化的会话的最简单方式是使用模型分词器中的 chat_template

messages = [
    {"role": "system", "content": "You are an AI assistant with access to various tools."},
    {"role": "user", "content": "Hi !"},
    {"role": "assistant", "content": "Hi human, what can help you with ?"},
]

为将前面的会话转换为提示词,我们加载分词器,然后调用 apply_chat_template:

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM2-1.7B-Instruct")
rendered_prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

该函数返回的 rendered_prompt 现在可以用作所选的模型的输入!