TellWorldAI 世界档案
问档案English

首页›实践›实践

实践

聊天模板与特殊 token:智能体底层怎么拼提示词

聊天消息到提示词的转换:三种角色、各家特殊 token 的差异、ChatML 模板与 apply_chat_template 的用法。

聊天界面给人的感觉是「模型记得我们聊过什么」,但这是个 UI 抽象:所有对话消息在送进模型之前都会拼成一条完整提示词,模型每次都从头读完整段对话,它从不「记得」任何东西 [1]。理解这一点,才能理解智能体的底层运作方式——所谓对话历史,就是反复重拼的提示词 [1]。

聊天模板:消息与模型之间的桥

聊天模板的作用是把「用户和助手的轮流发言」翻译成「当前这个模型期望的格式」。每家模型都有自己的特殊 token 来标记轮次的开始和结束——就像每家模型有自己的 EOS token 一样,格式规则和分隔符也各不相同 [1]。模板保证不管底层是哪家模型,上层构造的消息列表都能得到正确格式的提示词 [1]。

三种角色

消息体系里最先要理解的是 system 角色:它定义模型该怎么表现,作为持久指令作用于之后的每一轮交互 [1]。换一条 system 消息,同一个模型的表现就完全不同——礼貌版 [1]:

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

换成叛逆版,行为立刻反转 [1]:

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

到了智能体场景,system 消息的职责进一步扩展:告诉模型有哪些工具可用、规定动作该怎么格式化、给出思考过程怎么分段的要求——智能体的行为边界大半写在 system 消息里 [1]。

user 和 assistant 交替构成对话。模板靠保留历史来维持上下文,让多轮对话连贯 [1]:

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"},
]

这三条消息最终会被模板转成一个包含全部内容的字符串提示词 [1]。

同一段对话,两种渲染

看同一段对话在不同模型下的样子,特殊 token 的差异一目了然。SmolLM2 用 ChatML 风格的 <|im_start|> 和 <|im_end|> [1]:

<|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|> 和 <|end_header_id|> 包裹,轮次以 <|eot_id|> 结束,system 段里还带着知识截止日期和当天日期两个字段 [1]:

<|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|>

两个渲染结果的差异说明了一件事:消息列表是通用语,提示词是方言。多轮对话再复杂,模板都能维持上下文的完整结构 [1]:

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?"},
]

基座模型与指令模型

Base Model 只在原始文本上训练预测下一个 token;Instruct Model 则专门微调过,会遵循指令、进行对话——SmolLM2-135M 是基座,SmolLM2-135M-Instruct 是它的指令版 [1]。想让基座模型表现出指令模型的样子,就得用模型能理解的固定格式来构造提示词,这正是聊天模板的用武之地 [1]。ChatML 就是这类格式之一,用清晰的 system、user、assistant 角色标记组织对话——如今各 AI API 的通行做法 [1]。

有个容易踩的坑:同一个基座模型可能被用不同的聊天模板微调过,用指令模型时必须确认模板用对了——模板错了,模型要么答非所问要么胡乱续写 [1]。

模板的实现与使用

transformers 里聊天模板是一段 Jinja2 代码,负责把 ChatML 格式的 JSON 消息列表转成模型能理解的文本形式。下面是 SmolLM2-135M-Instruct 模板的简化版,逻辑是逐条消息渲染角色和内容,首条消息不是 system 时自动补一条默认的 system 段 [1]:

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

喂进这组消息 [1]:

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 ?"},
]

模板产出这串文本 [1]:

<|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 在分词过程中自动处理聊天模板,把消息按正确结构组织好即可 [1]。标准做法是加载 tokenizer 后调 apply_chat_template,tokenize=False 表示返回文本而不是 token 序列,add_generation_prompt=True 会在末尾补上 assistant 轮次的开始标记,让模型直接续写回复 [1]:

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 ?"},
]
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 直接可用作模型输入 [1]。还有个值得知道的细节:平时调用各家 API 时传的 ChatML 格式消息,后端执行的就是 apply_chat_template 这一整套转换——你在 API 层看到的抽象,和模型层看到的提示词,中间就隔着这个函数 [1]。

理解了消息结构,智能体与环境的交互方式——工具——就有了承载的容器:工具描述进 system 消息,工具调用走 assistant 轮次,工具结果以新角色回填,整套机制都建立在聊天模板之上 [1]。

本文改编自 Hugging Face Agents Course 的 Messages and Special Tokens 一节[1],原文以 Apache-2.0 许可证发布。

来源

  1. 1
    Messages and Special Tokens | Hugging Face Agents CourseHugging Face agents-course (Apache-2.0)

引用本页: 聊天模板与特殊 token:智能体底层怎么拼提示词. TellWorld, https://www.tellworld.net/zh/practice/practice-agent-messages-special-tokens. 更新于 2026-11-14

相关动态