你真的理解 LLM 的"无状态"吗?从一段代码讲起

为什么每次跟 ChatGPT 聊天,它都能"记住"之前说过的话?是服务器存了我们的聊天记录吗?

从一个实验开始

先来看一段代码,我们让 DeepSeek 记住我们的名字,然后再问它:

javascript 复制代码
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: process.env.DEEPSEEK_API_BASE_URL,
});

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

async function chat() {
  // 第一次请求:告诉模型我的名字
  chatHistory.push({ role: 'user', content: '请记住我叫字节谢' });
  const res1 = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: chatHistory,
  });
  chatHistory.push({ role: 'assistant', content: res1.choices[0].message.content });

  // 第二次请求:问模型我叫什么
  chatHistory.push({ role: 'user', content: '请问我的名字是什么?' });
  const res2 = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: chatHistory,
  });
  chatHistory.push({ role: 'assistant', content: res2.choices[0].message.content });

  console.log(res2.choices[0].message.content);
}

chat();

运行结果:

arduino 复制代码
你的名字叫"字节谢"。

看起来模型"记住"了,对吧?但仔细看代码 ------ 我们每次调用 API 都把完整的 chatHistory 传了过去

这就是 LLM 的无状态本质。


一、什么是"无状态"?

1.1 先理解 HTTP 的无状态

LLM API 本质上是 HTTP 调用 。而 HTTP 协议天然就是无状态协议

知识点:HTTP 的无状态性

HTTP 协议中,每次请求都是完全独立的。服务器处理完一个请求后,就"忘记"了刚才的一切。GET/POST/PUT/DELETE 这些方法,每一次调用都和上一次没有任何关系。

那登录状态怎么来的?靠 Header 里的 CookieAuthorization 令牌。每次请求客户端都主动带上身份凭证,服务器不存你是谁,只校验凭证是否有效。

用一个比喻来理解:

模式 比喻 特点
有状态 你去银行柜台,柜员认识你,知道你存了多少钱 服务器要记住你是谁,压力大
无状态 你去自助取款机,每次都要插卡输密码 服务器不记你是谁,每次自己证明身份

1.2 LLM 的无状态同样如此

当你调用 chat.completions.create() 时,DeepSeek/OpenAI 的服务器并不会替你存聊天记录 。它只看你这次传过来的 messages 数组里有什么,然后就生成回复。请求结束,关于你的一切它就"忘"了。

核心认知 :LLM 的"记忆力",本质上是你每次手动把全部对话历史打包发过去


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

三个字:高并发

想象一下,如果有 1000 万用户同时聊,每个用户服务器都要维护一份"对话状态",需要多少内存?而且用户的会话可能持续几分钟到几小时,这段时间状态都要占着资源。

无状态的设计带来三个关键优势:

复制代码
高并发  →  每个请求独立,服务器可以同时处理海量请求
高可用  →  任何一台服务器挂了,请求可以路由到另一台,因为不依赖特定机器
高扩展  →  流量上来了直接加服务器,水平扩展毫无压力(所有服务器对等)

知识点:水平扩展(Horizontal Scaling)

水平扩展 = 加更多机器,而不是给一台机器升配置。因为每个请求都是独立的、不依赖特定服务器,所以请求可以被负载均衡器分发到集群中任何一台机器上。这在有状态架构下很难做到 ------ 你必须保证同一个用户的请求每次都打到同一台机器(这叫"会话保持",是分布式系统的一大痛点)。


三、chatHistory 的困境

既然每次要带全部历史,那就带来了一个直接问题:对话越长,messages 越大,token 开销越大

来看看一次对话中 messages 的增长:

makefile 复制代码
第1轮: [system, user1]                         → 2 条消息
第2轮: [system, user1, assistant1, user2]       → 4 条消息
第3轮: [system, user1, assistant1, user2, assistant2, user3] → 6 条消息
...
第50轮: 101 条消息,可能几万 token

每次请求都要把整个历史重新发送一遍,其中 90% 的内容模型已经"看过"了,完全是重复传输和重复计算。

3.1 LRU 缓存策略

为了解决这个问题,一个朴素的做法是 LRU(Least Recently Used,最近最少使用)缓存

知识点:LRU 缓存

LRU 的核心思想:当容量满了,淘汰最久没被使用的那个。就像一个只能放 5 本书的书架,你每次拿一本看,看完放回去;当要放第 6 本时,把最久没碰的那本扔掉。

在 LLM 对话场景里:只保留最近的 N 轮对话,把久远的历史"遗忘"掉。这样 tokens 开销可控,而且最近的上下文通常也是最重要的。

但 LRU 也有问题:如果对话还没结束、任务还没完成,你就把早期关键信息丢了呢?


四、从 Prompt 工程到 Loop 工程

作者笔记里列出了一个很有意思的演进路线:

scss 复制代码
Prompt Engineering  →  Context Engineering  →  Loop Engineering
   (提示词工程)           (上下文工程)              (循环工程)

4.1 Prompt Engineering --- 抽卡时代

写一段精巧的 prompt,期待模型给出好结果。但就像抽卡游戏一样 ------ prompt 设计好,只是提升了"抽到金卡"的概率,并不是 100% 可控。

4.2 Context Engineering --- 给模型装上"外挂"

模型不懂的、模型没有的知识,我们通过上下文喂给它:

  • RAG(检索增强生成):从知识库里检索相关文档,拼到 prompt 里
  • MCP(Model Context Protocol):标准化的工具/数据连接协议
  • Skill 系统:预定义的领域能力模块
  • claude.md / agent.md:项目级别的系统指令

本质上还是在无状态的基础上,每次精心构造 messages 数组

4.3 Loop Engineering --- 让 AI 自己驱动自己

Harness 循环工程:AI 不只是"一问一答",而是在一个循环中持续运行 ------ 观察 → 思考 → 行动 → 观察 → ... 直到任务完成。这已经超越了单次无状态调用的范畴。


五、总结

回看最初那段代码,核心逻辑只有一句:

javascript 复制代码
chatHistory.push({ role: 'user', content: '请问我的名字是什么?' });
const response = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: chatHistory,  // ← 关键:每次带上全部历史
});

LLM 的"无状态"总结就是三句话:

  1. 每次请求都是独立的,服务器不存储任何会话状态
  2. "记住"是假象,真相是客户端每次手动把全部对话历史带上
  3. 这是刻意的设计,换来的是高并发、高可用、水平扩展的能力

理解了这一点,你就能明白为什么 AI 应用的架构设计里,上下文管理(怎么选历史、怎么压缩、怎么检索)才是真正的核心难题,而不是"怎么让模型记住更多"。


延伸思考 :既然每次都要传全部历史,那 OpenAI 的"记忆"功能(ChatGPT 能跨会话记住你的偏好)又是怎么实现的?答案是 ------ 它并不是打破了无状态,而是服务端在收到请求时 ,从数据库里取出你的"记忆摘要",注入到 system prompt 里,然后再走正常的无状态推理流程。本质还是"手动带上"。


本文基于对 LLM API 调用机制的学习和实践整理,希望能帮你建立对 AI 应用架构的底层认知。

相关推荐
AskHarries4 小时前
把一个外部系统接成 MCP 工具
后端·程序员
threerocks5 小时前
AI编程的商业模式已经在互联网大厂跑通了
程序员·aigc·ai编程
用户526835677905 小时前
云原生落地:如何配置 Alertmanager 插件,将 Prometheus 告警直接打通至硬件声光语音终端?
程序员
用户852495071845 小时前
我跟 AI 说了名字它转头就忘,后来我手动给它加了个"记忆"
程序员
zzzzzz3105 小时前
当甲方说'logo放大的同时再缩小一点'时,我用 AI 把这个需求做出来了
javascript·css·程序员
Hilaku5 小时前
Node.js 还能再战十年?给你一个不换引擎的理由
前端·javascript·程序员
Hyyy17 小时前
token是什么?为什么大模型会有上下文长度的限制
程序员·llm·ai编程
程序员cxuan1 天前
幽默,一个 Github 名字叫“马尾辫”,但是他给你省了 80% 的 token
人工智能·后端·程序员
kartjim1 天前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·程序员·ai编程