ChatML 是什么:Hermes 聊天模板格式入门指南

ChatML 格式的由来和具体用法,system/user/assistant 三种角色的作用,以及为什么 Hermes 选择 ChatML 作为默认聊天模板。

目录

  1. 为什么需要聊天模板
  2. ChatML 的由来
  3. ChatML 长什么样
    1. 特殊标记
    2. 三种角色
    3. 完整结构
  4. 多轮对话的格式
  5. System Prompt 的作用
    1. 定义身份
    2. 设定行为规则
    3. 控制输出格式
  6. 为什么 Hermes 选择 ChatML
    1. Llama 格式
    2. Alpaca 格式
    3. Vicuna 格式
  7. 实际使用中你需要注意什么
    1. 大多数情况下你不需要手写 ChatML
    2. 什么时候需要关心 ChatML
    3. 常见的格式错误
  8. ChatML 与函数调用
  9. Jinja2 模板与 Hugging Face
  10. 写在最后

为什么需要聊天模板

你有没有想过这样一个问题:当你在 ChatGPT 或者其他聊天界面里打字对话的时候,你输入的文字到底是怎么传给模型的?

你可能觉得——不就是把我打的字直接喂给模型吗?

没那么简单。

大语言模型本质上是一个「文本续写器」——给它一段文本,它预测下一个最可能出现的 token(关于 token 的概念,可以看 Token 到底是什么 这篇)。它并不天然理解「这段话是用户说的」「那段话是AI回复的」这种对话结构。

所以,你的输入在传给模型之前,需要被包装成一种特定的格式,告诉模型:这里是系统指令、这里是用户说的话、这里是你之前的回复——现在该你继续说了。

这种格式,就叫做聊天模板(Chat Template)

ChatML,就是其中一种非常流行的聊天模板格式,也是 Hermes 系列模型默认使用的格式。

ChatML 的由来

ChatML 最早是 OpenAI 提出的。没错,就是做 GPT 系列的那个 OpenAI。

2023 年,OpenAI 在其 API 文档中定义了一种叫 ChatML(Chat Markup Language)的格式,用来结构化多轮对话。虽然 OpenAI 后来并没有大力推广这个格式的标准化,但它的设计简洁直观,很快被开源社区采纳。

Nous Research 的 Hermes 系列从 Hermes 2 开始就采用 ChatML 作为默认聊天模板,并且一直延续至今。在开源社区里,ChatML 已经成为了最广泛使用的聊天模板格式之一。

ChatML 长什么样

直接看一个例子,一切就明白了:

1
2
3
4
5
<|im_start|>system
你是一个专业的Python编程助手,用中文回答问题。<|im_end|>
<|im_start|>user
怎么用Python读取一个CSV文件?<|im_end|>
<|im_start|>assistant

这就是一个最基本的 ChatML 格式的对话。拆开来看:

特殊标记

  • <|im_start|>:一条消息的开始标记(im = internal message)
  • <|im_end|>:一条消息的结束标记

这两个标记是特殊的 token,不是普通文本。模型在训练时专门学习了这些标记的含义。

三种角色

紧跟在 <|im_start|> 后面的是角色标识

  • system:系统角色。用来设定模型的行为、身份、规则
  • user:用户角色。就是你说的话
  • assistant:助手角色。就是模型的回复

完整结构

一条消息的完整结构是:

1
2
<|im_start|>角色名
消息内容<|im_end|>

注意:角色名后面有一个换行符,然后才是消息内容。消息内容结束后紧跟 <|im_end|>

多轮对话的格式

实际使用中,对话往往不止一轮。来看一个多轮对话的例子:

1
2
3
4
5
6
7
8
9
<|im_start|>system
你是一个友好的AI助手。<|im_end|>
<|im_start|>user
今天北京天气怎么样?<|im_end|>
<|im_start|>assistant
抱歉,我无法获取实时天气数据。建议你查看天气预报应用获取最新信息。<|im_end|>
<|im_start|>user
那你能做什么?<|im_end|>
<|im_start|>assistant

整个对话历史会被拼接成一个长字符串,按时间顺序排列。最后一条消息以 <|im_start|>assistant 结尾(没有 <|im_end|>),这就是在告诉模型:「轮到你说话了,请继续生成文本。」

模型看到这个格式,就知道:

  1. 我的角色设定是「友好的AI助手」
  2. 用户先问了天气,我回复了无法获取
  3. 用户又问了我能做什么,现在该我回答了

System Prompt 的作用

三种角色中,system 可能是最容易被忽视但最重要的。

System prompt 就是你给模型的「人设说明书」。你可以用它来:

定义身份

1
2
<|im_start|>system
你是一位资深的Linux运维工程师,有15年工作经验。回答问题时要专业但通俗易懂。<|im_end|>

设定行为规则

1
2
3
4
5
6
<|im_start|>system
你是一个代码审查助手。对于用户提交的代码:
1. 先指出潜在的bug和安全问题
2. 然后给出改进建议
3. 最后给出修改后的完整代码
回答使用中文,代码注释使用英文。<|im_end|>

控制输出格式

1
2
<|im_start|>system
你是一个JSON数据生成器。无论用户问什么,都以合法的JSON格式回复。不要包含任何JSON之外的文本。<|im_end|>

Hermes 特别擅长遵从 system prompt。 这是 Hermes 系列的一个核心卖点——很多模型虽然也支持 system prompt,但在实际使用中经常「忘记」或者不严格遵守。Hermes 在这方面的表现明显更好,这和它的「个体对齐」训练理念有关。具体可以看 Hermes 的个体对齐哲学 那篇。

为什么 Hermes 选择 ChatML

现在开源社区里的聊天模板格式不止 ChatML 一种,常见的还有:

Llama 格式

1
2
3
4
5
<s>[INST] <<SYS>>
你是一个有帮助的助手。
<</SYS>>

你好 [/INST] 你好!有什么可以帮你的? </s><s>[INST] 谢谢 [/INST]

Alpaca 格式

1
2
3
4
5
6
Below is an instruction that describes a task...

### Instruction:
你好

### Response:

Vicuna 格式

1
2
USER: 你好
ASSISTANT: 你好!

相比之下,ChatML 有几个明显的优势:

1. 结构清晰

<|im_start|><|im_end|> 是明确的分隔符,不容易和正文内容混淆。你在消息内容里不太可能正好写出 <|im_start|> 这样的字符串(而 Alpaca 格式里 ### Instruction: 在正文中出现的概率就大多了)。

2. 角色扩展性好

ChatML 的角色不是固定死的。除了 system、user、assistant 这三个标准角色,你还可以定义其他角色:

1
2
<|im_start|>tool
{"name": "search", "result": "找到了3个相关结果..."}<|im_end|>

这对做函数调用和工具使用的场景非常有用。Hermes 2 Pro 开始引入的 Function Calling 就依赖于这个扩展能力。

3. 多轮对话处理自然

ChatML 的多轮对话就是把消息一条条拼接起来,逻辑直观,不需要复杂的嵌套结构。

4. 社区生态成熟

因为 Hermes 和很多其他模型的采用,ChatML 在开源社区里的工具支持非常完善。Ollama、llama.cpp、vLLM、text-generation-inference 等主流推理框架都原生支持 ChatML。

实际使用中你需要注意什么

大多数情况下你不需要手写 ChatML

如果你通过 Ollama、Open WebUI 之类的工具使用 Hermes,这些工具会自动帮你处理 ChatML 格式。你只需要正常打字聊天就行,不用操心底层的模板格式。

什么时候需要关心 ChatML

以下几种情况你需要了解 ChatML:

1. 直接调用模型 API 时

如果你用代码直接调用模型的推理接口(比如通过 vLLM 的 completions API),你可能需要自己组装 ChatML 格式的输入。

2. 写 system prompt 时

虽然前端界面会帮你处理格式,但了解 system prompt 在 ChatML 中的位置和作用,有助于你写出更好的 system prompt。

3. Debug 问题时

如果模型的回复出了问题(比如不遵从指令、输出格式混乱),查看实际发送给模型的 ChatML 格式文本往往能帮你发现问题。可能是格式拼装错了,也可能是 system prompt 写得有歧义。

4. 做二次开发或微调时

如果你要基于 Hermes 做进一步的微调训练,你需要确保训练数据的格式和 ChatML 一致。格式不匹配会导致训练效果大幅下降。

常见的格式错误

错误1:忘记 <|im_end|>

1
2
3
4
<|im_start|>system
你是一个助手。
<|im_start|>user ← 这里忘了结束标记
你好<|im_end|>

这会导致模型把 system 消息和 user 消息混在一起理解。

错误2:角色名拼写错误

1
2
<|im_start|>systme   ← 拼错了
你是一个助手。<|im_end|>

模型可能不认识这个角色,导致 system prompt 不生效。

错误3:在非 ChatML 模型上使用 ChatML

不是所有模型都用 ChatML 格式。如果你把 ChatML 格式的文本喂给一个用 Llama 格式训练的模型,模型会把 <|im_start|> 这些标记当成普通文本来处理,输出会很混乱。

一定要确认你用的模型支持什么格式。 Hermes 全系列用 ChatML,但其他模型未必。

ChatML 与函数调用

Hermes 2 Pro 以来的一个重要能力是函数调用(Function Calling),而这个能力的实现就依赖于 ChatML 的扩展性。

一个典型的函数调用流程是这样的:

Step 1:在 system prompt 中定义可用函数

1
2
3
4
5
<|im_start|>system
你是一个有用的助手。你可以使用以下工具:

{"type": "function", "function": {"name": "get_weather", "description": "获取指定城市的天气", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}
<|im_end|>

Step 2:用户提问

1
2
<|im_start|>user
北京今天天气怎么样?<|im_end|>

Step 3:模型输出函数调用

1
2
3
4
<|im_start|>assistant
<tool_call>
{"name": "get_weather", "arguments": {"city": "北京"}}
</tool_call><|im_end|>

Step 4:工具返回结果

1
2
3
4
<|im_start|>tool
<tool_response>
{"temperature": 22, "condition": "晴", "humidity": 45}
</tool_response><|im_end|>

Step 5:模型基于结果回复

1
2
<|im_start|>assistant
北京今天天气不错,晴天,气温22度,湿度45%,适合户外活动。<|im_end|>

整个流程中,ChatML 的角色系统(system、user、assistant、tool)让每个环节都有清晰的标识,模型能准确理解对话的流程和状态。

在 cocoloop 社区里,有开发者做过测试对比,Hermes 在 ChatML 格式下的函数调用准确率明显高于使用其他格式的模型。这不奇怪——Hermes 专门针对 ChatML 做了大量的训练数据优化。

Jinja2 模板与 Hugging Face

如果你用过 Hugging Face 的 Transformers 库,你可能会接触到 Jinja2 聊天模板。这是 Hugging Face 提供的一种机制,让每个模型可以在自己的配置文件中定义聊天模板。

Hermes 模型的 Jinja2 模板大致长这样:

1
2
3
4
5
{% for message in messages %}
<|im_start|>{{ message.role }}
{{ message.content }}<|im_end|>
{% endfor %}
<|im_start|>assistant

这样,当你通过 Transformers 库的 apply_chat_template() 方法处理对话时,它会自动帮你生成正确的 ChatML 格式。

1
2
3
4
5
6
7
8
9
10
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("NousResearch/Hermes-3-Llama-3.1-8B")

messages = [
{"role": "system", "content": "你是一个Python专家"},
{"role": "user", "content": "怎么读取JSON文件?"}
]

text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

写在最后

ChatML 的设计哲学可以总结为两个词:简洁通用

它没有花里胡哨的嵌套结构,也没有复杂的转义规则。三个标记(<|im_start|><|im_end|>、角色名)就搞定了所有事情。

对于刚入门的人来说,你需要记住的核心要点就三个:

  1. ChatML 是一种给模型看的对话格式,有 system/user/assistant 三个基本角色
  2. Hermes 系列模型全线使用 ChatML
  3. 大多数工具(Ollama、Open WebUI 等)会自动处理格式,你不需要手写

更深入的理解可以等你实际上手后再慢慢建立。现在先把概念记住就够了。

参与讨论

对这篇文章有疑问或想法?cocoloop 社区有不少开发者在讨论 Hermes 相关话题,欢迎加入交流。

前往 cocoloop 社区 →