LLM 为什么记不住你?无状态、messages 和上下文管理

刚开始写 LLM 接口时,我一直有一个很自然的误解:

既然我是在和 AI 连续聊天,那模型应该记得前面说过什么。

比如我先告诉它:

text 复制代码
请记住我叫小林。

下一轮再问:

text 复制代码
请问我的名字是什么?

如果它能回答出来,我们很容易以为"模型记住了我"。

但从接口调用的角度看,事情并不是这样。

大多数 LLM API 本身是无状态的。模型不是一个一直坐在那里等你的聊天对象,而更像一个每次被临时叫醒的计算服务。你把本次请求需要的信息发过去,它根据这次输入生成结果,然后这次请求结束。

下一次再请求,如果你不把前面的内容带上,它就不知道之前发生了什么。

什么是无状态?

无状态,英文叫 stateless。

它的意思是:每一次请求都是独立的,服务端不会默认依赖上一轮请求。

这和 HTTP 协议很像。

你打开一个网页、提交一个表单、调用一个接口,本质上都是一次次独立的 HTTP 请求。HTTP 本身并不会天然记住"这个用户刚才做了什么"。如果服务器想识别你是谁,通常要靠 cookie、token、session、Authorization header 这些机制。

LLM API 也类似。

模型收到一次请求,只能看到这次请求里给它的内容。它并不会自动知道你上一轮说了什么,也不会自动保存所有聊天历史。

所以所谓"连续对话",并不是模型自己保存了记忆,而是应用层每次都把必要的历史对话重新发给模型。

为什么 LLM 要设计成无状态?

直觉上看,有状态好像更方便:用户说过什么,服务端都记住,下次直接接着聊。

但在真实工程里,这会带来很大压力。

LLM 服务面对的是大量用户请求。如果服务端要长期保存每个用户的完整对话状态,就会出现几个问题:

  • 服务器压力变大
  • 扩容更困难
  • 请求很难分发到任意机器
  • 故障恢复更复杂
  • 隐私和数据管理成本更高

无状态的好处是,每一次请求都可以被任意一台服务器处理。

只要请求本身带齐了必要信息,后端就不需要关心"之前是哪台机器处理了这个用户"。这对高并发、高可用、水平扩展都很重要。

所以无状态不是 LLM 的缺陷,而是一种很常见的后端设计。

真正的问题是:既然模型不记得,我们就要自己决定,这次请求里应该带什么。

messages 才是模型看到的上下文

在 Chat Completions 这类接口里,我们经常会传一个 messages 数组:

js 复制代码
const messages = [
  { role: "system", content: "你是一个严谨的助手" },
  { role: "user", content: "请记住我叫小林" },
  { role: "assistant", content: "好的,我记住了,你叫小林。" },
  { role: "user", content: "请问我的名字是什么?" }
];

这里最关键的一点是:

模型不是从某个隐藏记忆里知道"我叫小林",而是因为这句话仍然在 messages 里面。

也就是说,messages 就是模型这一次能看到的上下文。

如果第二次请求只发这一句:

js 复制代码
[
  { role: "user", content: "请问我的名字是什么?" }
]

模型就没有可靠依据知道你是谁。

它可能会猜,也可能会说不知道。无论哪种,本质上都不是"忘了",而是你这次请求没有给它上下文。

一个最小的 chatHistory 例子

下面这个最小 demo,就是用 chatHistory 来模拟连续对话:

js 复制代码
const chatHistory = [
  { role: 'system', content: '你是一个严谨的助手' }
];

chatHistory.push({
  role: 'user',
  content: '请记住我叫小林'
});

const response = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: chatHistory
});

chatHistory.push({
  role: 'assistant',
  content: response.choices[0].message.content
});

第一轮请求结束后,代码会把模型的回复也放回 chatHistory

然后第二轮再问:

js 复制代码
chatHistory.push({
  role: 'user',
  content: '请问我的名字是什么?'
});

const response2 = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: chatHistory
});

看起来像模型"记住了",其实是程序把前面的对话保存到了数组里,并且下一次请求时又整体传了过去。

这个细节非常重要。

很多人刚开始写聊天应用时,只保存用户消息,不保存模型回复。这样上下文就会断掉一半。因为模型下一轮不仅需要知道用户说过什么,也需要知道自己上一轮回答过什么。

对话历史应该是一条完整链路:

text 复制代码
system 规则
user 第一次输入
assistant 第一次回复
user 第二次输入
assistant 第二次回复
...

这条链越完整,模型越能理解当前对话发展到哪里。

chatHistory 不能无限变长

既然每次都带上历史,那能不能把所有对话永久塞进 messages

理论上简单,工程上不现实。

因为 messages 越长,token 开销越大。

token 变多以后,会带来几个直接问题:

  • 请求成本变高
  • 响应速度变慢
  • 更容易超过上下文窗口
  • 旧信息干扰新任务
  • 模型注意力被无关内容分散

所以聊天系统不能只会"追加历史",还要学会"管理历史"。

这就从简单调用 API,进入了上下文工程。

LRU 思路:最近的不一定最重要,但通常更有用

LRU 这个概念,可以帮助理解聊天历史裁剪。

LRU 是 Least Recently Used,意思是最近最少使用的内容优先淘汰。

放到聊天上下文里,可以粗略理解成:

最近聊到的内容更可能马上继续用,太久之前、又不重要的内容,可以优先压缩或移除。

比如用户一直在调试一个报错,最近 10 轮里出现的错误信息、代码片段、修改记录,通常比很早之前的一句闲聊更重要。

但这里也不能机械地按时间删除。

有些信息虽然很早,但非常关键:

text 复制代码
这个项目用 Vue 3。
接口统一走 /api。
不要改动用户已有代码。
所有回答用中文。

这些信息不能因为"旧"就删掉。

所以真实的上下文管理,通常要结合几种策略:

  • 最近对话直接保留
  • 重要事实长期保存
  • 长对话定期总结
  • 项目资料按需检索
  • 固定规则放进 system prompt 或项目规则文件

无状态把责任交给了应用层

理解无状态以后,再看很多 AI 应用,就会清楚很多。

ChatGPT、Cursor、Claude Code、Codex 这类工具之所以看起来很懂当前任务,并不是因为底层模型天然记得一切,而是它们在模型外面做了大量上下文管理。

比如:

  • 当前文件内容
  • 相关代码片段
  • 终端输出
  • 用户最近的指令
  • 项目规则文件
  • 工具执行结果
  • RAG 检索到的资料
  • MCP 提供的外部上下文

这些东西都会被选择性地组织进模型当前这次请求里。

所以 AI 工程里有一句很核心的话:

模型负责生成,应用负责给上下文。

模型越强,确实越能推理;但如果上下文给错了、给少了、给乱了,它还是会跑偏。

从 Prompt 到 Context,再到 Harness

早期大家很关注 Prompt Engineering,也就是怎么把一句提示词写好。

但当你开始做真实 AI 应用时,会发现一句 prompt 远远不够。

因为模型本身有几个结构性限制:

  • 无状态:不会自动记住历史
  • 有上下文窗口:不能无限塞内容
  • 概率生成:同样输入也可能有不同输出
  • 不能直接操作外部世界:需要工具和 runtime

所以后面会出现 Context Engineering。

它关心的不是"怎么问一句话",而是"这次请求应该带哪些背景、规则、资料、历史和工具结果"。

再往后是 Harness Engineering。

也就是在模型外面搭一套更完整的工程系统:记忆、工具、循环、校验、权限、规则、上下文压缩,让模型的能力变得更稳定、更可控。

无状态就是这条线的起点。

因为只要模型不自动记得,开发者就必须学会设计记忆。

总结

LLM API 通常是无状态的。

它不会天然记得上一轮对话。我们看到的"连续聊天",本质上是应用层每次都把必要历史放进 messages,让模型在本次请求里重新看到上下文。

所以写 AI 应用时,不能只会调用接口,还要想清楚:

  • 哪些历史要带上
  • 哪些内容可以删掉
  • 哪些信息应该总结
  • 哪些资料应该检索
  • 哪些规则应该固定保存

无状态让服务更容易扩展,也把上下文管理这件事交给了开发者。

真正的 AI 工程能力,往往就藏在这些取舍里。

相关推荐
如此这般英俊1 小时前
手搓Claude Code-第六章 subagent
数据结构·人工智能·python·语言模型·自然语言处理
EAI-Robotics2 小时前
机器人操作鲁棒性:当机械手遇上真实世界的不确定性
人工智能·算法·机器人
shushangyun_2 小时前
2026智能采购商城系统选型指南:如何引领企业数字化采购升级
java·大数据·数据库·人工智能·机器学习
Hilaku2 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
沐籽李2 小时前
Roche 团队如何设计酸性 pH 激活的 anti-CD3 抗体
人工智能·aidd·抗体设计·ph敏感抗体
aaaa954726652 小时前
2026最新5款AI编程工具平替实测合集|Claude Code低成本迭代深度对比
人工智能·microsoft
古夕2 小时前
多应用同域部署下 Favicon 异常问题排查与治理
前端·javascript
Larcher3 小时前
从零实现 RAG 语义搜索——让 AI 听懂你的"弦外之音"
人工智能
Larcher3 小时前
从零搭建文件读取 MCP 服务——让 AI 拥有读文件的能力
人工智能