每次跟大模型聊天,都是一次「失忆」的 HTTP 请求

一个被大多数人忽略的事实

先看一段代码:

javascript 复制代码
const response = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: [
    { role: 'system', content: '你是一个严谨的助手' },
    { role: 'user', content: '请记住,我的名字是9527' }
  ]
})

你觉得这段代码做了什么?很多人会说:「我跟大模型说了一句话,它回复了我,然后记住了我的名字。」

实际上它什么都没记住。

如果你紧接着再发一个请求:「请告诉我我的名字是什么?」,而不把刚才的对话历史带上------模型会一脸茫然地告诉你:我不知道。

这不是模型的问题,也不是 API 的问题。这是 HTTP 协议的DNA决定的。

扒开 SDK 的外衣,里面就是一发 POST

无论你用 OpenAI SDK、LangChain、还是直接 curl,调用大模型接口的本质没有变过:

一次 HTTP POST 请求,把参数打包成 JSON 塞进请求体,服务器算完把结果扔回来,连接断开,服务器瞬间失忆。

SDK 不过是帮你把 HTTP 细节封装了一下,让 fetch 调用看起来像一个本地函数。但这层糖衣裹得越好,越容易让人产生错觉------以为服务器在跟你「保持联系」。

真相是:没有任何一个主流 LLM 服务端会帮你存对话历史。每次请求都是一张白纸,你画什么它就看见什么。

无状态:不是缺陷,是刻意为之

为什么服务器不肯帮我们记住聊天记录?是我们付费不够多吗?

不是的。这叫无状态架构(Stateless),是互联网基础设施的默认设定。

HTTP 协议从出生那天起就是无状态的。你在京东上浏览商品、加购物车,每次刷新页面,服务端不会凭空「认出」你是谁------它靠的是 Cookie 里塞的 session token,是客户端每次主动把身份信息带上去。

这么设计只有一个原因:为了扛住海量用户。

想象一下,如果服务器要维护每个用户的会话状态------你叫什么名字、刚才聊了什么、偏好哪种回答风格------数亿用户同时在线的场景下,服务器的内存会瞬间被吃干抹净。更别提某台服务器挂了以后,那台机器上的所有「记忆」都永久丢失。

有状态系统就像一个小餐馆,老板能记住每个熟客的喜好------但店面最多接待几十人。无状态系统是连锁快餐,没人认识你、没人记住你,但每天可以服务几十万人。

LLM 选择无状态,从来不是为了省事,是为天花板做的取舍。

那模型为什么看起来「记得」我?

因为客户端替你做了记忆这件事

看看这段代码的第二部分:

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

// 第一次对话
chatHistory.push({ role: 'user', content: '请记住,我的名字是9527' })
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     // ← 再次把整个历史数组传过去
})

整个过程就像这样:

sql 复制代码
请求1:  [system, user: "我叫9527"]              → 服务器: "好的记住了"
请求2:  [system, user: "我叫9527", assistant, user: "我叫啥?"] → 服务器: "你叫9527"

每次请求的 messages 数组都是一个自包含的完整世界。 服务器不需要知道你是谁、刚才发生了什么------它只对眼前这个数组负责,读完就忘。

这个设计漂亮的地方在于:你真的可以在第二次请求时把第一次的对话丢掉,服务器完全不会察觉,也不会抱怨。从头到尾,状态只存在于客户端的内存里。

无状态带来的三个分布式红利

既然所有状态都在客户端,那架构层面就获得了三张王牌:

1. 水平扩展------加机器就能扛

每个请求完全独立,哪台服务器处理都行。流量上来了,直接加机器,前面挂个负载均衡器就搞定。没有「这个用户的会话在机器A上,别的机器处理不了」这种烦恼。

2. 高可用------挂了就当无事发生

假设某台服务器在返回结果之前宕机了。客户端拿到超时后,直接把同样的 messages 数组重试一次------另一台空闲的服务器接过来,继续算,用户无感知。

在有状态系统里,这台机器挂了就意味着上面的会话全部丢失,用户得从头开始。无状态系统里,挂了一台机器只是一个短暂的网络波动。

3. 高扩展性------节点随便增减

缩容时从负载均衡器摘掉几个节点,没有任何迁移成本。因为服务器上本来也不存用户状态,摘掉就摘掉了,不需要「搬家」。

这三点都不是锦上添花,而是 LLM 这种量级业务的基础生存条件。

chatHistory 的暗面:你用 token 换记忆

但是客户端维护历史也不是没有代价的。

每一轮对话,messages 数组都在膨胀。10 轮、50 轮、100 轮下来,每次请求都带着整个对话史------token 开销是线性往上走的,而且是跟着你跑的。同样的对话到第 50 轮,每次请求要比第一轮多花 50 倍的 token。

当然,这只在你把完整的 messages 打进每次请求 时才成立。如果你的实现是增量式------每轮只把当轮的 user 和 assistant 追加进去,而服务端又不保留上下文------那状态就会丢失。所以本质上,要保证模型「记得」,每次都必须把完整历史搬过去,这是 token 膨胀的根本原因。

于是就有了 LRU 这类策略:设定一个容量上限(比如 10k token 或最近 20 条消息),超出就删掉最久远的内容。

但这又引出了另一个问题:任务还没完成,关键上下文可能已经被淘汰了。开头你告诉模型「你是一个严谨的助手」,聊到第 50 轮它突然开始胡言乱语------因为 system prompt 早就被挤出容量窗口了。

这是个取舍问题:轮次数 vs 长期连贯性。目前没有任何完美的解法。

从 Prompt 工程到上下文工程,再到循环工程

这个「记忆只能靠客户端传」的本质,催生了三个层次的演化:

第一层:Prompt Engineering(提示词工程)

写好 system prompt、调整 instructions、优化少样本示例------这在某种程度上就是在管好你每次传给模型的那个 messages 数组的内容质量。但它更像抽卡:好的 prompt 提升你抽到金卡的概率,但做不到完全可控。

第二层:Context Engineering(上下文工程)

既然 messages 就是一切,那能不能把知识库、项目文档(比如 claude.md)、外部检索结果(RAG)也拼进去?每一步都在往 messages 里塞更多结构化信息,让模型在一次白纸请求里「看见」尽可能多的、有用的上下文。上下文工程的核心不是模型能力,是信息组织能力。

第三层:Loop Engineering(循环工程)

AI 编程工具(Harness 类型)把上下文工程推进到了循环层面。不再是人------模型------人------模型的单线交互,而是模型工作→产出结果→结果进入下一轮上下文→继续工作。人从「每次打字的人」变成了「观察者」或「决策者」。状态的维护方式没变------还是靠客户端每次把完整的上下文传过去,但上下文的编排效率成了核心竞争力。

三层演进看着像技术升级,但底层那行 HTTP POST 从来没变过。

回到代码,再感受一次

javascript 复制代码
// 让我记住你的名字
chatHistory.push({ role: 'user', content: '请记住,我的名字是9527' })
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   // 完整历史再次传入
})

三件事:

  1. 每次请求都是独立的 HTTP POST
  2. 服务器响应完就失忆,不存任何状态
  3. 上下文靠客户端每次把完整 messages 传进去------自包含、自给自足

这就是 LLM 无状态的全部真相。理解了它,你就理解了 LLM 应用架构的底层逻辑。

相关推荐
sarasuki1 小时前
彻底搞懂JS闭包:从作用域链、形成条件到优缺点
javascript
糖拌西瓜皮1 小时前
TypeScript 进阶:泛型、条件类型、类型守卫与装饰器
javascript·node.js
swipe14 小时前
从 0 到 1 实现大文件上传:分片、秒传、断点续传、暂停、重试与服务端合并
前端·javascript·面试
kyriewen16 小时前
AI 生成的代码能跑就行?这 5 个坑迟早炸
前端·javascript·ai编程
kisshyshy16 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
胡志辉16 小时前
从v8源码和react深入浅出理解 JavaScript 作用域链与闭包
前端·javascript
Bolt17 小时前
TypeScript 7.0 来了:当 tsc 用 Go 重写之后
javascript·typescript·go
阳火锅19 小时前
😭测试小姐姐终于不骂我了!这个提BUG神器太香了...
前端·javascript·面试
林希_Rachel_傻希希21 小时前
js里面的proxy理解。以及vue3响应式数据设计底层
前端·javascript·面试