写在前面:今天学了一个让我"恍然大悟"的知识点——LLM 是"无状态"的。以前我以为 ChatGPT 记得我们聊过什么,是因为它"聪明"。结果老师说:不,它根本不记得,是你每次手动把聊天记录发给它,它才"假装"记得。我听完整个人都不好了——原来我一直被 AI "骗"了!
一、无状态:HTTP 的"健忘症"
1.1 什么是无状态?
老师说:
"调用 LLM 接口的本质是什么?HTTP 调用,算力生成结果。"
LLM 接口和普通的 RESTful API 一样,都是基于 HTTP 协议的。而 HTTP 协议本身是无状态的。
什么是"无状态"?
"每次请求都是独立的,不依赖于之前的请求。服务器不需要维护请求状态,只需要处理当前请求即可。"
通俗地说:服务器对每一个请求都"一视同仁",不管你是谁、之前聊过什么。
这就像你去理发店:
- 有状态:Tony 老师记得你上次剪了什么发型,这次直接按上次的来。
- 无状态:每次去都是新顾客,你得重新描述"两边推短、上面留长"。
1.2 为什么 LLM 要无状态?
老师说:
"有状态?你是谁?LLM 服务器压力太大了。"
如果 LLM 服务器要维护每个用户的对话状态:
- 几亿用户同时在线,服务器内存直接爆炸。
- 某台服务器挂了,用户状态就丢了,体验极差。
- 无法水平扩展——新加的服务器没有老用户的状态。
无状态的好处:
| 特性 | 说明 |
|---|---|
| 公平 | 所有人一视同仁,没有"VIP 记忆" |
| 可扩展 | 随便加服务器,每台都能处理任何请求 |
| 高可用 | 某台挂了,请求自动转到其他机器 |
| 简单 | 服务器不需要维护复杂的会话状态 |
"所有人都公平"——无状态的核心哲学。
二、LLM 的"记忆"骗局:chatHistory 的真相
2.1 LLM 真的不记得你
看这段代码:
const chatHistory = [
{ role: "system", content: "你是一个严谨的助手" }
];
async function testStateless() {
// 第一次请求:告诉模型我的名字
chatHistory.push({
role: "user",
content: "请记住我的名字叫字节Q"
});
const response = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: chatHistory // 带上全部历史
});
// 把模型回复也加入历史
chatHistory.push({
role: "assistant",
content: response.choices[0].message.content
});
// 第二次请求:问它我叫什么
chatHistory.push({
role: "user",
content: "请问我的名字是什么?"
});
const response2 = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: chatHistory // 再次带上全部历史
});
}
关键发现:每次请求都要带上 messages: chatHistory。
LLM 根本不记得你之前说过什么。是你每次手动把聊天记录发过去,它才能"假装"记得。
这就像你和一个金鱼朋友聊天:
- 金鱼只有 7 秒记忆(LLM 无状态)。
- 但你每次聊天前,都把之前的对话写在纸条上给金鱼看(
chatHistory)。 - 金鱼看了纸条,说"哦对,你叫字节Q"。
- 实际上金鱼根本没记住,它只是读了纸条。
2.2 代码演示:不带历史会怎样?
如果你第二次请求不带历史:
// 错误示范:不带 chatHistory
const response2 = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: [
{ role: "user", content: "请问我的名字是什么?" }
]
});
LLM 会一脸懵:"你是谁?我们认识吗?"
因为这次请求里没有任何上下文,LLM 只能根据当前问题回答——它根本不知道"你"是谁。
三、chatHistory 的隐患:Token 大爆炸
3.1 messages 越来越大,钱包越来越瘪
老师说:
"messages 越来越大,token 开销越大。"
每次请求都要带上全部历史,这意味着:
- 聊得越久,
chatHistory越长。 - 每次请求的 Token 数 = 历史 Token + 新问题 Token。
- Token 就是钱,聊久了钱包受不了。
举个例子:
第 1 轮:历史 100 Token + 新问题 20 Token = 120 Token
第 2 轮:历史 120 Token + 新问题 20 Token = 140 Token
第 3 轮:历史 140 Token + 新问题 20 Token = 160 Token
...
第 50 轮:历史 1000+ Token + 新问题 20 Token = 1020+ Token
Token 消耗呈线性增长,钱包呈线性出血。
3.2 LRU 策略:给聊天记录"减肥"
老师说了一个解决方案:
"LRU:一次对话一直聊?任务还没完成。Tokens 开销变大,最近在聊的留下,久远了的可以适当删除。"
LRU(Least Recently Used,最近最少使用) 是一种缓存淘汰策略:
| 策略 | 说明 | 比喻 |
|---|---|---|
| 保留最近 N 条 | 只保留最近的 10-20 条对话 | 像微信聊天记录,只显示最近几条 |
| 按 Token 限制 | 总 Token 不超过某个上限 | 像手机存储满了,自动删旧照片 |
| 摘要压缩 | 把久远对话压缩成摘要 | 像写会议纪要,把长篇大论缩成几句话 |
// 简单的 LRU 实现:只保留最近 10 条
const MAX_HISTORY = 10;
function addMessage(chatHistory, message) {
chatHistory.push(message);
if (chatHistory.length > MAX_HISTORY) {
// 保留 system 提示词,删除最早的用户/助手对话
const systemMessages = chatHistory.filter(m => m.role === 'system');
const otherMessages = chatHistory.filter(m => m.role !== 'system');
const recentMessages = otherMessages.slice(-(MAX_HISTORY - systemMessages.length));
return [...systemMessages, ...recentMessages];
}
return chatHistory;
}
这就像你的衣柜:空间有限,只能放最近常穿的衣服,旧衣服要么扔掉,要么收进箱子(摘要)。
四、从 Prompt Engineering 到 Loop Engineering:AI 协作的三次升级
4.1 三次升级路线
老师总结了 AI 协作方式的演进:
Prompt Engineering(提示词工程)
↓
Context Engineering(上下文工程)
↓
Loop Engineering(循环工程)
4.2 Prompt Engineering:抽卡式聊天
"历史对话、知识库 claude.md、agent.md 上下文。抽卡,prompt 质量或设计只能提升抽到金卡的概率,不是特别可控。"
Prompt Engineering 就是"写好提示词,让 AI 一次性给出好答案"。
但问题是:
- 结果不可控——同样的 prompt,每次答案可能不一样。
- 没有记忆——每次对话都是新的开始。
- 像抽卡——prompt 写得好,只是提高了"抽到好答案"的概率。
4.3 Context Engineering:给 AI "喂"上下文
"RAG:LLM 不懂、没有,更加优质的 MCP、skill。"
Context Engineering 的核心:让 LLM 拥有更丰富的上下文。
| 技术 | 作用 |
|---|---|
| RAG | 检索增强生成,从知识库中检索相关信息 |
| MCP | 模型上下文协议,标准化上下文传递 |
| Skill | 预定义的技能模板,提升输出质量 |
Context Engineering 解决的是"LLM 不知道"的问题。 通过外部知识库、文档、技能,让 LLM 拥有更专业的知识。
4.4 Loop Engineering:让 AI 自己迭代
"Harness AI 工程。"
Loop Engineering 就是上一节课学的 AI Loop——让 AI 自己生成、检查、循环,直到满足条件。
| 阶段 | 核心 | 解决的问题 |
|---|---|---|
| Prompt Engineering | 写好提示词 | 一次性输出质量 |
| Context Engineering | 丰富上下文 | LLM 知识不足 |
| Loop Engineering | 自动化循环 | 结果不可控 |
从"抽卡"到"喂知识"再到"自动迭代",AI 协作方式在持续进化。
五、总结:理解无状态,才能用好 LLM
| 知识点 | 说明 |
|---|---|
| 无状态(Stateless) | 每次请求独立,服务器不维护状态 |
| HTTP 无状态 | LLM 接口基于 HTTP,天然无状态 |
| chatHistory | 手动维护对话历史,让 LLM "假装"有记忆 |
| Token 开销 | 历史越长,每次请求消耗的 Token 越多 |
| LRU 策略 | 保留最近对话,删除久远内容 |
| Prompt Engineering | 优化提示词,提升一次性输出质量 |
| Context Engineering | 丰富上下文,解决知识不足问题 |
| Loop Engineering | 自动化循环,解决结果不可控问题 |
理解 LLM 的无状态本质,是正确使用它的前提。 不要幻想 LLM 会"记住"你——它不会。你要做的是:设计好 chatHistory 的管理策略,控制 Token 开销,用 Context 和 Loop 弥补无状态的不足。
写在最后
今天最大的收获,是打破了"LLM 有记忆"的幻觉。它每次回答你,都是基于你发送的完整上下文,而不是因为它"记得"。这个认知让我对 AI 的理解更深了一层——它不是人,不要用人性的方式去理解它。
下次面试官问你:"LLM 为什么是无状态的?"
你可以淡定地说:
"LLM 接口基于 HTTP 协议,HTTP 是无状态协议。每次请求都是独立的,服务器不维护任何会话状态。这样设计的好处是公平性、可扩展性和高可用性——所有请求一视同仁,服务器可以水平扩展,某台挂了不影响其他请求。为了让 LLM '记得'对话历史,需要客户端手动维护 chatHistory,每次请求带上全部 messages。但这也带来 Token 开销问题,需要通过 LRU 等策略控制历史长度。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。