写在前面
你每天都在和 ChatGPT、Claude 对话,但你有没有想过:
- 它是怎么"记住"你前面说的话的?
- "系统提示词"到底是什么?它凭什么能改变 AI 的行为?
- 为什么有时候聊着聊着,AI 就"忘了"之前的内容?
- 你发的每条消息,在 AI 眼里到底长什么样?
这篇文章会揭开你与 LLM 交互时的所有底层机制。看完之后,你会真正理解"对话"这件事在技术层面是怎么运作的。
第一章:你以为的对话 vs 真实的对话
1.1 你以为的样子
你打开 ChatGPT,输入一句话,AI 回复一句话。你再输入,AI 再回复。看起来就像两个人在微信聊天——一来一回,自然流畅。
你可能以为 AI 那边有一个"对话状态",它一直"在线",记着你们聊过的所有内容。
1.2 真实的样子
LLM 没有任何持久状态。每次你发消息,它都是从零开始。
真相是:每次你点击"发送",系统都会把整个对话历史打包成一个完整的消息列表,从头到尾发给模型。模型读完所有内容,生成回复,然后——彻底忘记一切。
你以为的过程:
你:你好 → AI 记住了
你:我叫小明 → AI 记住了
你:我叫什么? → AI 从记忆中找到答案:"小明"
真实的过程:
第 1 次调用:
发送:[{你:你好}]
AI 回复:"你好!"
AI 立刻忘记一切
第 2 次调用:
发送:[{你:你好}, {AI:你好!}, {你:我叫小明}]
AI 回复:"你好小明!"
AI 立刻忘记一切
第 3 次调用:
发送:[{你:你好}, {AI:你好!}, {你:我叫小明}, {AI:你好小明!}, {你:我叫什么?}]
AI 回复:"你叫小明。"
AI 立刻忘记一切
AI 之所以"记得"你叫小明,不是因为它有记忆,而是因为"我叫小明"这句话每次都被重新发给了它。
💡 核心真相
LLM 没有记忆。它的"记忆"是靠每次把完整对话历史重新发送来实现的。所谓的"对话",本质上是一次又一次独立的"阅读理解"。
第二章:消息列表——对话的真实数据结构
2.1 一切都是消息列表
当你和 AI 对话时,底层传输的数据结构是一个消息列表(messages array)。每条消息有两个核心字段:角色(role)和内容(content)。
[
{"role": "system", "content": "你是一个友好的助手。"},
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮你的?"},
{"role": "user", "content": "帮我写一首诗"}
]
这就是 AI 看到的全部信息。没有别的。没有隐藏的状态,没有数据库查询,没有上一次会话的残留。就是这个列表。
2.2 三种角色
LLM 的消息系统中有三种角色,每种角色有不同的含义和优先级:
┌─────────────────────────────────────────────────────┐
│ 消息列表 │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ system(系统消息) │ │
│ │ "你是一个专业的翻译助手,只做中英互译" │ │
│ │ ← 设定 AI 的身份、行为规则、边界 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ user(用户消息) │ │
│ │ "请把这段话翻译成英文:今天天气真好" │ │
│ │ ← 用户的输入 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ assistant(助手消息) │ │
│ │ "The weather is really nice today." │ │
│ │ ← AI 之前的回复(历史记录) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ user(用户消息) │ │
│ │ "再翻译一句:我很开心" │ │
│ │ ← 用户的新输入 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ → AI 基于以上所有消息,生成下一条 assistant 回复 │
└─────────────────────────────────────────────────────┘
| 角色 | 英文 | 谁写的 | 作用 |
|---|---|---|---|
| system | system | 开发者 | 设定 AI 的身份、规则、行为边界 |
| user | user | 用户 | 用户的输入和请求 |
| assistant | assistant | AI | AI 之前的回复(作为历史记录回传) |
2.3 为什么要区分角色?
因为 LLM 在训练时就学会了根据角色来调整行为:
- 看到
system消息 → "这是我的行为准则,我应该遵守" - 看到
user消息 → "这是用户在跟我说话,我应该回应" - 看到
assistant消息 → "这是我之前说过的话,我应该保持一致"
如果没有角色区分,模型就无法分辨"谁在说什么",对话会变得混乱。
第三章:系统提示词——AI 的"人格设定"
3.1 系统提示词是什么?
系统提示词(System Prompt)是消息列表中 role: "system" 的那条消息。它通常是列表中的第一条消息,用来告诉 AI:
- 你是谁
- 你应该怎么表现
- 你不能做什么
- 你应该用什么风格回答
3.2 系统提示词的实际例子
一个简单的翻译助手:
{
"role": "system",
"content": "你是一个专业的中英翻译助手。用户发送中文时翻译成英文,发送英文时翻译成中文。只做翻译,不要解释,不要闲聊。"
}
ChatGPT 的系统提示词(简化版):
{
"role": "system",
"content": "You are ChatGPT, a large language model trained by OpenAI. You are helpful, harmless, and honest. Current date: 2026-04-27. You can browse the web, run code, and generate images..."
}
Claude 的系统提示词(简化版):
{
"role": "system",
"content": "You are Claude, made by Anthropic. You are helpful, harmless, and honest. You think step by step. You refuse harmful requests..."
}
3.3 系统提示词为什么有效?
系统提示词之所以能"控制"AI 的行为,是因为:
① 训练阶段就学会了"听从系统指令"
在对齐训练(RLHF)阶段,模型被大量训练了"遵循 system 消息中的指令"这个行为模式。模型学到了:system 消息 = 我的行为准则。
② 它是上下文的一部分
系统提示词和用户消息一样,都是输入给模型的文本。模型在生成每个词时,都会通过注意力机制"看到"系统提示词。它就像一个持续存在的"背景指令"。
③ 位置优势
系统提示词通常在消息列表的最前面。在注意力机制中,前面的内容对后面的生成有持续影响。
3.4 系统提示词的局限
系统提示词不是万能的:
- 不是绝对约束:用户可以通过精心构造的输入"绕过"系统提示词(提示注入攻击)
- 会被稀释:对话越长,系统提示词在整个上下文中的占比越小,影响力会减弱
- 模型可能"忘记":在非常长的对话中,模型可能不再严格遵守系统提示词
这也是为什么一些产品会在对话中间重复注入系统提示词的关键部分,以强化约束。
第四章:LLM 的"记忆"——一个精心维护的幻觉
4.1 短期记忆:上下文窗口
LLM 唯一的"记忆"就是上下文窗口——每次调用时发送给它的那个消息列表。
上下文窗口 = 系统提示词 + 所有历史消息 + 当前用户输入
┌──────────────────────────────────────────────┐
│ 上下文窗口(如 128K tokens) │
│ │
│ [system] 你是一个助手... │
│ [user] 你好 │
│ [assistant] 你好! │
│ [user] 我叫小明 │
│ [assistant] 你好小明! │
│ [user] 帮我写代码 │
│ [assistant] 好的,这是代码... │
│ ... (越来越多的历史消息) │
│ [user] 我叫什么? ← 当前输入 │
│ │
│ 只要"我叫小明"还在窗口内,AI 就"记得" │
└──────────────────────────────────────────────┘
上下文窗口满了怎么办?
当对话历史超过上下文窗口的容量时,必须做取舍:
| 策略 | 做法 | 优缺点 |
|---|---|---|
| 截断 | 丢弃最早的消息 | 简单,但会丢失早期信息 |
| 摘要压缩 | 用 LLM 把旧对话压缩成摘要 | 保留关键信息,但有信息损失 |
| 滑动窗口 | 保留系统提示 + 最近 N 轮对话 | 最常用的折中方案 |
4.2 长期记忆:外部存储
如果你希望 AI "记住"跨会话的信息(比如你的偏好、之前的项目),就需要外部记忆系统:
用户说:"我喜欢简洁的代码风格"
│
▼
记忆系统存储:
{用户偏好: "简洁代码风格", 时间: "2026-04-27"}
│
......(几天后,新的对话)......
│
▼
新对话开始时,系统从记忆中检索相关信息,
注入到系统提示词中:
"该用户偏好简洁的代码风格,请据此调整回复。"
这就是 ChatGPT 的"Memory"功能、Claude 的"Project Knowledge"等功能的原理——不是模型本身有了记忆,而是外部系统帮它"记笔记",下次对话时把笔记塞进上下文。
4.3 记忆的层次结构
┌─────────────────────────────────────────┐
│ 第 1 层:上下文窗口内的对话历史 │ ← 最可靠,但容量有限
│ (当前会话中的所有消息) │
├─────────────────────────────────────────┤
│ 第 2 层:摘要/压缩后的历史 │ ← 有信息损失,但节省空间
│ (旧对话被压缩成几句话的摘要) │
├─────────────────────────────────────────┤
│ 第 3 层:外部记忆存储 │ ← 跨会话持久化
│ (向量数据库、文件、数据库) │
├─────────────────────────────────────────┤
│ 第 4 层:RAG 检索的知识 │ ← 按需加载的外部知识
│ (文档、网页、API 返回的数据) │
└─────────────────────────────────────────┘
第五章:一次完整的 API 调用长什么样
5.1 你在 ChatGPT 界面点击"发送"后发生了什么
让我们追踪一次完整的交互过程:
第 1 步:用户在界面输入 "帮我用 Python 写一个快速排序"
第 2 步:客户端(浏览器/App)构造消息列表
[
{"role": "system", "content": "You are ChatGPT...(几千字的系统提示词)"},
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮你的?"},
{"role": "user", "content": "帮我用 Python 写一个快速排序"}
]
第 3 步:发送 HTTP 请求到 API
POST https://api.openai.com/v1/chat/completions
{
"model": "gpt-4",
"messages": [上面的消息列表],
"temperature": 0.7,
"stream": true
}
第 4 步:模型处理
- 把所有消息拼接成一个 token 序列
- 通过 Transformer 的多层注意力处理
- 逐 token 生成回复
第 5 步:流式返回
"好" → "的" → "," → "这是" → "一个" → "快速" → "排序" → ...
(一个 token 一个 token 地返回,所以你看到文字是逐渐出现的)
第 6 步:客户端把 AI 的回复追加到消息列表
[
{"role": "system", "content": "..."},
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮你的?"},
{"role": "user", "content": "帮我用 Python 写一个快速排序"},
{"role": "assistant", "content": "好的,这是一个快速排序的实现..."} ← 新增
]
第 7 步:等待用户下一次输入,重复上述过程
5.2 流式输出(Streaming)
你注意到 ChatGPT 的回复是一个字一个字"打"出来的,而不是等全部生成完再一次性显示。这就是流式输出。
非流式:等 10 秒 → 一次性显示完整回复(用户体验差)
流式: 第 0.1 秒 → "好"
第 0.2 秒 → "好的"
第 0.3 秒 → "好的,"
第 0.4 秒 → "好的,这是"
...
(用户立刻看到回复在生成,体验好)
流式输出不改变任何技术原理,只是改变了传输方式——模型每生成一个 token 就立刻发送给客户端,而不是等全部生成完。
第六章:多轮对话的代价——Token 经济学
6.1 每轮对话都在重复发送历史
因为 LLM 没有记忆,每次调用都要发送完整历史。这意味着:
第 1 轮:发送 100 tokens(系统提示 + 第 1 条消息)
第 2 轮:发送 250 tokens(系统提示 + 第 1-2 条消息 + 第 1 条回复)
第 3 轮:发送 500 tokens(系统提示 + 第 1-3 条消息 + 第 1-2 条回复)
第 10 轮:发送 5000 tokens
第 50 轮:发送 50000 tokens
...
对话越长,每次调用的成本越高。 这就是为什么 AI 产品都有对话长度限制,以及为什么长对话会变慢——不是 AI 变笨了,是要处理的输入变多了。
6.2 Token 计费模型
API 按 token 数量计费,分为输入 token(你发给模型的)和输出 token(模型生成的):
费用 = 输入 tokens × 输入单价 + 输出 tokens × 输出单价
一个 50 轮的对话,最后几轮的输入可能有几万个 token,即使用户只问了一句简单的话。这就是多轮对话的隐性成本。
6.3 为什么有时候 AI 会"忘记"之前说的话
现在你能理解了:
- 对话太长,早期消息被截断 → AI 真的看不到了
- 上下文窗口快满了,系统自动压缩历史 → 细节丢失
- 注意力稀释 → 上下文太长时,模型对早期内容的"注意力"会减弱
第七章:角色扮演的原理——AI 是怎么"变成"不同角色的
7.1 角色扮演 = 系统提示词 + 对话历史的引导
当你让 AI "扮演一个海盗"时,技术上发生的事情是:
[
{
"role": "system",
"content": "你是一个 17 世纪的海盗船长。你说话粗犷豪放,喜欢用航海术语,经常提到宝藏和冒险。你的名字叫红胡子杰克。"
},
{
"role": "user",
"content": "你好啊"
}
]
模型会回复类似:
"嘿!欢迎登上我的船,小伙子!我是红胡子杰克,这片海域最令人闻风丧胆的船长!你是来寻宝的,还是来送命的?哈哈哈!"
7.2 为什么 AI 能扮演角色?
因为 LLM 的训练数据中包含了海量的小说、剧本、对话、角色扮演文本。模型学会了:
- 不同角色有不同的说话风格
- 给定一个角色设定,应该生成符合该角色的文本
- 保持角色一致性(如果设定是海盗,就不应该突然说出现代科技术语)
本质上,角色扮演就是条件文本生成——系统提示词提供了"条件",模型在这个条件下生成最可能的文本。
7.3 AI 是怎么分辨"谁在说什么"的
模型通过消息中的 role 字段来分辨:
模型看到的 token 序列(简化):
<|system|> 你是一个翻译助手... <|end|>
<|user|> 请翻译:今天天气真好 <|end|>
<|assistant|> The weather is really nice today. <|end|>
<|user|> 再翻译:我很开心 <|end|>
<|assistant|> ← 模型从这里开始生成
这些 <|system|>、<|user|>、<|assistant|> 是特殊标记(special tokens),在训练时就被嵌入到模型中。模型学会了:
<|system|>后面的内容 = 行为准则<|user|>后面的内容 = 需要回应的输入<|assistant|>后面的内容 = 我应该生成的回复
不同的模型使用不同的特殊标记格式(也叫 chat template),但原理相同。
第八章:提示工程——与 LLM 高效沟通的技巧
8.1 为什么提示词很重要?
LLM 的输出质量高度依赖输入的质量。同一个问题,不同的问法会得到截然不同的回答:
差的提示: "写个网站"
好的提示: "用 React + TypeScript 写一个待办事项网站,
要求有添加、删除、标记完成功能,
使用 Tailwind CSS 做样式,
代码要有注释。"
8.2 核心提示技巧
① 明确角色
"你是一个有 10 年经验的 Python 后端工程师。"
② 明确任务
"请审查以下代码,找出潜在的安全漏洞,并给出修复建议。"
③ 提供示例(Few-shot)
"请按以下格式输出:
输入:苹果 → 输出:水果
输入:狗 → 输出:动物
输入:钢琴 → 输出:?"
④ 指定输出格式
"请用 JSON 格式输出,包含 name、age、city 三个字段。"
⑤ 分步引导
"请按以下步骤完成:
1. 先分析需求
2. 设计数据结构
3. 编写核心逻辑
4. 添加错误处理
5. 编写测试"
第九章:全景回顾——一次对话的完整生命周期
用户打开 ChatGPT
│
▼
┌─────────────────────────────────────────────┐
│ 客户端加载系统提示词 │
│ messages = [{role: "system", content: ...}] │
└──────────────────┬──────────────────────────┘
│
用户输入第 1 条消息
│
▼
┌─────────────────────────────────────────────┐
│ messages.append({role: "user", content: …}) │
│ │
│ 发送 messages 到 LLM API │
│ │
│ LLM 读取所有消息 │
│ ├── 注意力机制处理每个 token │
│ ├── 根据 system 消息确定行为模式 │
│ ├── 根据 user 消息理解当前请求 │
│ ├── 根据 assistant 消息保持一致性 │
│ └── 逐 token 生成回复 │
│ │
│ 流式返回回复 │
│ │
│ messages.append({role: "assistant", …}) │
└──────────────────┬──────────────────────────┘
│
用户输入第 2 条消息
│
▼
重复上述过程(messages 越来越长)
│
▼
当 messages 接近上下文窗口上限时
├── 截断早期消息
├── 或压缩历史为摘要
└── 或开始新对话
💡 一句话总结
你与 LLM 的每次"对话",本质上是把一个不断增长的消息列表反复发给一个无状态的模型。系统提示词定义了 AI 的行为,角色标记让模型分辨谁在说话,而"记忆"只是每次重新发送历史消息的副产品。理解了这些,你就理解了与 AI 交互的全部底层机制。
附录:关键术语速查表
| 术语 | 英文 | 一句话解释 |
|---|---|---|
| 消息列表 | Messages Array | 发送给 LLM 的对话历史,包含所有角色的消息 |
| 系统提示词 | System Prompt | 设定 AI 身份和行为规则的特殊消息 |
| 角色 | Role | 消息的发送者标识:system / user / assistant |
| 上下文窗口 | Context Window | LLM 一次能处理的最大 token 数量 |
| 流式输出 | Streaming | 模型边生成边返回,而非等全部完成 |
| Token | Token | 模型处理文本的最小单位 |
| 提示工程 | Prompt Engineering | 优化输入以获得更好输出的技巧 |
| 提示注入 | Prompt Injection | 通过用户输入绕过系统提示词约束的攻击 |
| Chat Template | Chat Template | 不同模型用来区分角色的特殊标记格式 |
| Few-shot | Few-shot Prompting | 在提示中提供少量示例来引导模型输出 |