3、AI 如何理解对话
1 从聊天界面到模型输入,发生了什么?
当你打开一个 AI 对话产品,比如某个智能客服或者 AI 助手,你看到的是一条一条整整齐齐的聊天气泡。你发一句,它回一句,感觉就像在和一个真人聊天。
但你有没有想过,这些聊天消息背后,AI 模型实际上"看到"的是什么?
这里有一个很重要的事实,很多人不知道:
AI 模型并不像人一样"记住"对话。每次它回复你,都是把从头到尾所有的聊天记录拼成一段完整的文字,然后一次性读进去,再生成下一句回复。
你在界面上看到的一条条聊天气泡,其实只是一种视觉上的呈现方式。在它背后,所有的消息早就被悄悄地拼成了一大段文字,塞进了模型里。
举个生活中的类比:你和一位秘书沟通,你以为每次说一句话,秘书都在实时理解。但实际上,秘书每次回复之前,都会把你们从开始到现在所有说过的话重新看一遍,然后再给你答复。
这就是 AI 模型工作的真实方式。
那么,问题来了:这些消息是怎么被拼在一起的?模型怎么知道哪句话是用户说的,哪句话是它自己之前说的?
答案就是------聊天模板(Chat Templates)和特殊 Token(Special Tokens) 。
2 什么是特殊 Token?
在正式讲聊天模板之前,我们需要先理解一个概念:特殊 Token(Special Tokens) 。
普通的文字,比如"你好"、"请问",都是正常的内容。而特殊 Token 是一些有着特定"功能"的符号或标记,它们不是用来表达内容的,而是用来告诉模型:"这里是对话的开始"、"这里是用户说的话"、"这里是回复的结束"......
你可以把特殊 Token 想象成一篇文章里的标点符号和标题格式。文章里的文字是内容,而逗号、句号、加粗标题这些东西,是帮你理解文章结构的"格式标记"。特殊 Token 对于 AI 模型来说,就起到这样的作用。
不同的 AI 模型使用的特殊 Token 是不一样的,就好像不同国家有不同的书写格式一样。这就是为什么我们需要聊天模板来帮忙"翻译"和格式化。
3 消息的三种角色
在 AI 对话系统中,所有的消息都会被归类成三种角色:
3.1 系统消息(System Message)
系统消息是在对话开始之前,给 AI 设定"人设"和"规则"的一段话。它就像是在正式开工之前,给员工发的一份工作手册。
系统消息不是用户说的,也不是 AI 回复的,它是由开发者或者产品运营人员提前写好的,用来规定 AI 应该怎么表现。
举个例子:
如果你在做一个餐厅点餐的 AI 助手,你可能会写这样一条系统消息:
json
系统消息 = {
"role": "system",
"content": "你是一位专业的餐厅点餐助手,名叫小惠。你熟悉菜单上的所有菜品,能够根据顾客的口味和需求给出推荐,始终保持热情、耐心的服务态度。"
}
有了这条系统消息,AI 就知道自己叫"小惠",是餐厅助手,要热情耐心。
如果你把系统消息改成:
json
系统消息 = {
"role": "system",
"content": "你是一位严肃的法律顾问助手,只回答与法律相关的问题,对于其他话题一律拒绝回答。"
}
同一个 AI 模型,立刻就变成了一个法律顾问助手,拒绝帮你推荐菜。
这说明什么?说明系统消息是控制 AI 行为最直接的手段。在实际的 AI 智能体开发中,系统消息还会包含:AI 可以使用哪些工具、遇到什么情况应该怎么处理、回复格式有什么要求等等。
3.2 用户消息(User Message)
用户消息就是使用者发给 AI 的内容,这个最好理解,就是聊天框里你输入的那些话。
json
用户消息 = {
"role": "user",
"content": "你们今天有什么推荐的套餐吗?"
}
3.3 助手消息(Assistant Message)
助手消息是 AI 回复的内容。在多轮对话中,AI 之前说过的话也会被保存下来,作为"助手消息"拼进下一次的输入里,这样 AI 才能"记住"之前聊了什么。
json
助手消息 = {
"role": "assistant",
"content": "您好!今天我们推荐的是招牌三人套餐,包含一道主菜、两道配菜和一份例汤,非常划算!请问您今天几位用餐呢?"
}
4 多轮对话是如何工作的?
现在我们来看一个完整的多轮对话例子,你就能明白为什么需要把所有消息都拼在一起了。
假设有人在用餐厅点餐助手"小惠":
css
对话记录 = [ { "role": "system", "content": "你是一位专业的餐厅点餐助手,名叫小惠,始终保持热情耐心的服务态度。" }, { "role": "user", "content": "你好,我想点餐" }, { "role": "assistant", "content": "您好!欢迎光临,我是小惠,很高兴为您服务!请问您今天想吃什么口味的菜肴呢?" }, { "role": "user", "content": "我不吃辣,有什么推荐吗?" },]
当用户第二次说话"我不吃辣,有什么推荐吗?"的时候,模型需要知道:
- 自己是谁(系统消息告诉它)
- 用户之前说了什么(第一条用户消息)
- 自己之前回复了什么(助手消息)
- 用户现在说了什么(第二条用户消息)
只有把这四条消息全部拼在一起,模型才能理解"不吃辣"这个上下文,给出合适的推荐。否则,模型根本不知道"我"是谁,"不吃辣"是在聊什么。
这就是为什么所有历史消息必须每次都完整地传给模型。
5 聊天模板:把消息列表变成模型能读懂的文字
我们已经知道,所有消息要拼成一段文字输入给模型。但怎么拼?拼的格式是什么?不同的模型有不同的要求,这时候就需要**聊天模板(Chat Template)**来做这个转换工作。
聊天模板就是一套格式规则,它规定了:
- 每条消息前面加什么标记
- 每条消息结束后加什么标记
- 系统消息、用户消息、助手消息分别怎么区分
5.1 基础模型和指令模型的区别
在深入聊天模板之前,有一个概念需要先了解:
基础模型(Base Model): 就是用大量文字数据训练出来的"原始"模型,它的能力是预测下一个词是什么,没有经过专门的对话训练,你直接问它问题,它可能只会续写你的句子,而不是回答你。
指令模型(Instruct Model): 是在基础模型的基础上,进一步用对话数据训练过的模型,它懂得如何回答问题、遵循指令、进行多轮对话。
比如同一个模型家族里,SmolLM2-135M 是基础模型,SmolLM2-135M-Instruct 是指令模型。我们在实际使用中,对话场景下一般都用指令模型。
聊天模板就是为指令模型设计的。因为指令模型在训练的时候,就是按照特定的格式喂入数据的,所以使用的时候,我们也必须用同样的格式,模型才能正确理解。
5.2 不同模型,格式不同
这里用同一段对话来演示不同模型的格式差异:
对话内容(以在线购物客服为例):
css
对话 = [ {"role": "user", "content": "我的快递怎么还没到?"}, {"role": "assistant", "content": "非常抱歉给您带来不便,请问您能提供一下您的订单编号吗?"}, {"role": "user", "content": "订单号是 DD-20240315-88"},]
SmolLM2 模型会把它格式化成这样:
sql
<|im_start|>system
你好,我是SmolLM,一个由Hugging Face训练的助人AI助手。<|im_end|>
<|im_start|>user
我的快递怎么还没到?<|im_end|>
<|im_start|>assistant
非常抱歉给您带来不便,请问您能提供一下您的订单编号吗?<|im_end|>
<|im_start|>user
订单号是 DD-20240315-88<|im_end|>
<|im_start|>assistant
你可以看到:
<|im_start|>表示一条消息开始
<|im_end|>表示一条消息结束
- 最后以
<|im_start|>assistant结尾,意思是"现在轮到你(助手)说话了"
而另一款模型(Llama 3.2)会把同样的对话格式化成:
sql
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
知识更新日期:2023年12月
今日日期:2026年6月10日
<|eot_id|><|start_header_id|>user<|end_header_id|>
我的快递怎么还没到?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
非常抱歉给您带来不便,请问您能提供一下您的订单编号吗?<|eot_id|><|start_header_id|>user<|end_header_id|>
订单号是 DD-20240315-88<|eot_id|><|start_header_id|>assistant<|end_header_id|>
格式完全不同!标记符号也不一样。这就是为什么用错了聊天模板,模型可能完全读不懂你的输入,就像你给一位只懂中文的人发了一份日文说明书一样。
5.3 聊天模板长什么样?
聊天模板本质上是一段用 Jinja2 语法写的格式化脚本(Jinja2 是一种常见的模板语言,你可以先把它理解成"按规则填空的脚本")。
以 SmolLM2 的聊天模板为例(简化版):
css
{% for message in messages %}
{% if loop.first and messages[0]['role'] != 'system' %}
<|im_start|>system
你是小惠,一位专业的餐厅点餐助手
<|im_end|>
{% endif %}
<|im_start|>{{ message['role'] }}
{{ message['content'] }}<|im_end|>
{% endfor %}
这段模板的逻辑很简单,翻译成人话就是:
遍历每一条消息,如果第一条不是系统消息,就自动加一条默认的系统消息。然后对每条消息,按照 <|im_start|>角色\n内容<|im_end|> 的格式输出。
完整示例:
给定这些消息:
css
消息列表 = [ {"role": "system", "content": "你是小惠,一位专业的餐厅点餐助手。"}, {"role": "user", "content": "你们有哪些素食选项?"}, {"role": "assistant", "content": "我们有多款素食菜肴,例如清炒时蔬、豆腐羹、素春卷......"}, {"role": "user", "content": "豆腐羹怎么做的?"},]
经过聊天模板处理后,变成:
sql
<|im_start|>system
你是小惠,一位专业的餐厅点餐助手。<|im_end|>
<|im_start|>user
你们有哪些素食选项?<|im_end|>
<|im_start|>assistant
我们有多款素食菜肴,例如清炒时蔬、豆腐羹、素春卷......<|im_end|>
<|im_start|>user
豆腐羹怎么做的?<|im_end|>
<|im_start|>assistant
这个拼好的长字符串,就是最终输入给 AI 模型的内容。
6 用代码实现消息到提示的转换
了解了原理,我们再来看看在实际开发中怎么操作。好消息是,你不需要手动写那些特殊 Token,transformers 库里的标记器(Tokenizer)已经帮你封装好了。
python
from transformers import AutoTokenizer
# 加载模型对应的标记器
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM2-1.7B-Instruct")
# 准备消息列表
消息列表 = [
{"role": "system", "content": "你是小惠,一位专业的在线购物客服助手,负责帮助用户解决订单和物流问题。"},
{"role": "user", "content": "你好,我的包裹已经三天没有更新物流了"},
{"role": "assistant", "content": "您好!非常抱歉听到这个情况。请问您能告诉我您的订单编号吗?我来帮您查询一下。"},
{"role": "user", "content": "订单编号是 DD-20240801-66688"},
]
# 使用 apply_chat_template 生成最终的提示文本
最终提示 = tokenizer.apply_chat_template(
消息列表,
tokenize=False, # 只返回字符串,不做分词处理
add_generation_prompt=True # 在末尾加上助手开始回复的标记
)
print(最终提示)
运行之后,你会得到一段格式化好的长字符串,这就是可以直接输入给模型的提示(Prompt)。
这里有两个参数值得注意:
tokenize=False:意思是先给我看格式化后的字符串,不要直接转成数字编码。
add_generation_prompt=True:在最后自动加上<|im_start|>assistant,告诉模型"现在该你说话了"。
7 整体流程梳理
学到这里,我们来做一个完整的流程梳理,把所有知识点串联起来:
markdown
开发者写系统消息
↓
用户发送消息(用户消息)
↓
把系统消息 + 历史对话 + 最新用户消息,整理成消息列表
↓
聊天模板把消息列表格式化成一段带特殊Token的字符串(提示/Prompt)
↓
这段字符串输入给AI模型
↓
模型生成回复文字
↓
把回复显示在聊天界面,同时存入历史对话,等待下一轮
每一次用户发新消息,这个流程就循环一次。而且每次都是把所有历史消息重新拼一遍,不是只传最新那条。
8 为什么这些对智能体开发很重要?
你可能会问:我只是想开发一个 AI 应用,这些底层的东西我需要懂吗?
答案是:非常需要。 原因如下:
第一,系统消息是你控制 AI 行为的核心手段。 你想让 AI 扮演什么角色、遵守什么规则、有什么能力限制,都是通过系统消息实现的。不懂这个,你就不知道怎么"驯服"AI。
第二,对话历史的管理直接影响效果和成本。 每次对话都要把历史记录全部传进去,历史越长,消耗的计算资源越多。在实际产品中,你需要考虑什么时候该截断历史、怎么压缩历史,这些都建立在理解底层机制的基础上。
第三,用错聊天模板会导致模型表现很差。 如果你用了错误的格式,模型可能完全无法理解你的输入,给出乱七八糟的回答,而你可能完全不知道原因出在哪里。
第四,AI 智能体需要更复杂的系统消息。 当 AI 不只是聊天,而是要去调用工具、执行任务的时候,系统消息里还需要包含工具的描述、执行格式的规范等等,这些都依赖于对消息结构的深刻理解。
9 常见误区澄清
最后,我们来澄清几个初学者常见的误解:
误区一:"AI 模型有记忆,它记得我之前说的话"
真相:模型本身没有记忆。它之所以"记得",是因为每次都把历史对话重新传入了。如果你清空了对话历史,它就完全不知道之前聊了什么。
误区二:"只要中文说得够清楚,模型肯定能理解"
真相:模型理解的前提是格式正确。如果聊天模板用错了,即使内容再清晰,模型也可能解读错误。
误区三:"系统消息是可选的,不写也没关系"
真相:不写系统消息,模型会按照训练时的默认行为运行,这可能不符合你的业务需求。在智能体开发中,系统消息几乎是必写的。
误区四:"不同的 AI 模型可以通用同一套聊天模板"
真相:每个模型都有自己的聊天模板格式。用错模板就好比把一台设备的充电器插到另一台设备上------可能不兼容,严重时会出问题。应该始终使用模型对应的模板,或者直接使用 apply_chat_template 让库自动处理。