当前位置: 首页 > news >正文

理解 LLM 的无状态架构:从原理到实践

TL;DR— LLM API 本质是无状态 HTTP 调用。每次请求都是独立的,模型不记得你上一轮说了什么。"记忆"是我们在客户端手动拼接chatHistory伪造出来的。本文从架构原理出发,结合可运行 Demo,层层递进地讲清楚这件事。


📡 一、调用 LLM 接口的本质是什么?

当我们写下一行client.chat.completions.create(...)时,底层到底发生了什么?

┌──────────┐ HTTP POST ┌──────────────┐ GPU 推理 ┌──────────┐ │ 客户端 │ ──────────────> │ LLM API 服务 │ ────────────> │ 模型算力 │ │ (你的代码) │ <────────────── │ (DeepSeek / │ <──────────── │ (GPU 集群) │ └──────────┘ JSON Response │ OpenAI 等) │ └──────────┘ └──────────────┘

本质就是三次握手后的 HTTP 调用 + 算力生成。把一堆文本(messages)POST 过去,服务器跑一遍 GPU 推理,然后把生成的文本返回给你。和调用一个 RESTful API 没有本质区别。

这引出了一个关键架构命题:

🔑为了支持高并发、高可用,后端必须是 Stateless(无状态)的。


🌐 二、什么是"无状态"?

2.1 HTTP 天然就是无状态的

HTTP 协议本身就是一个无状态协议。每一次 GET / POST 请求都是独立的,服务器处理完就忘掉。

GET /api/user/123 → 服务器返回用户数据 → 服务器:完事,忘了 POST /api/chat → 服务器返回模型回复 → 服务器:完事,忘了

那"登录态"是怎么来的?——靠的是Header 中的 Cookie / Authorization Token,由客户端每次主动携带,而不是服务器"记住"了谁。

2.2 无状态 vs 有状态

维度🟢 无状态 (Stateless)🔴 有状态 (Stateful)
每次请求独立,不依赖之前请求依赖服务器保存的上下文
服务器负担低,不需要存客户端信息高,需要维护会话状态
水平扩展✅ 任意一台服务器都能处理❌ 需要会话亲和性(sticky session)
容错性✅ 一台挂了换一台即可❌ 状态丢失则会话中断
类比自动售货机:投币→出货→遗忘餐厅服务员:记住每桌点了什么

2.3 试想 LLM 服务器"有状态"会怎样?

用户 A: "我叫小明" → 服务器 1 记住了 用户 A: "我叫什么?" → 负载均衡到了服务器 2 → 服务器 2:???

如果每台服务器都要维护上百万用户的对话状态,内存直接爆炸,更别提扩缩容、故障转移的噩梦。所以LLM API 必须是 Stateless 的——服务器不保存任何对话上下文。


🧩 三、那模型是怎么"记住"对话的?

答案是:它根本没记。每次把全部历史对话拼好,重新发给它。

3.1 运行底层规则

用户输入

拼接全部历史消息

HTTP POST 到 LLM

模型生成回复

将回复追加到历史

三个核心原则:

  1. 🚫LLM 是无状态的——它不记得上一轮说了什么
  2. 🤝想让它"懂"你——每次手动带上全部对话历史
  3. ⚖️服务器端并发友好——请求在任何一台机器上运行都没差别

3.2 看代码 👇

下面是一段真实的 Demo,演示了"让模型记住名字"这件事是怎么靠手拼chatHistory做到的:

importOpenAIfrom'openai';import{config}from'dotenv';config();constclient=newOpenAI({apiKey:process.env.DEEPSEEK_API_KEY,baseURL:process.env.DEEPSEEK_API_BASE_URL,});// 🔑 核心:历史对话数组,这是我们手动维护的"记忆"constchatHistory=[{role:'system',content:'你是一个严谨的助手'}];asyncfunctiontestStateless(){// ──── 第一轮:告诉模型名字 ────console.log('📝 第一次请求,告诉模型一个信息');chatHistory.push({role:'user',content:'请记住,我的名字叫零零发'});constresponse=awaitclient.chat.completions.create({model:'deepseek-v4-flash',messages:chatHistory// 👈 把全部历史传过去});// ⚠️ 关键:模型的回复也要加入历史!chatHistory.push({role:'assistant',content:response.choices[0].message.content});console.log('🤖 模型回复:',response.choices[0].message.content);// ──── 第二轮:问名字 ────console.log('🔍 第二次请求,直接问我是谁?');chatHistory.push({role:'user',content:'请问我的名字是什么?'});constresponse2=awaitclient.chat.completions.create({model:'deepseek-v4-flash',messages:chatHistory// 👈 再次把全部历史传过去});chatHistory.push({role:'assistant',content:response2.choices[0].message.content});console.log('🤖 模型回复:',response2.choices[0].message.content);// 打印最终的历史数组console.log('📦 最终 chatHistory:',JSON.stringify(chatHistory,null,2));}testStateless().catch(err=>{console.log('❌',err);});

3.3 🔴 有状态 vs 🟢 无状态:代码差异一针见血

源码中有一段被注释掉的代码,恰好展示了"有状态幻想"和"无状态现实"的对比:

constresponse=awaitclient.chat.completions.create({model:'deepseek-v4-flash',// ❌ 有状态的幻想写法(被注释掉了):// messages: [// { role: 'system', content: '你是一个严谨的助手' },// { role: 'user', content: '请记住,我的名字叫零零发' }// ]// ✅ 无状态的正确写法:messages:chatHistory// 把整个历史数组传过去});

再对比第二轮:

constresponse2=awaitclient.chat.completions.create({model:'deepseek-v4-flash',// ❌ 有状态的幻想写法(被注释掉了):// messages: [// { role: 'user', content: '请问我的名字是什么?' }// ]// ✅ 无状态的正确写法:messages:chatHistory// 再次把整个历史数组传过去});

一张表看清区别:

🔴 有状态幻想🟢 无状态现实
第一轮发的 messages[system, user-1][system, user-1]← 一样
第二轮发的 messages[user-2]← 只有当前[system, user-1, assistant-1, user-2]完整历史
模型知道之前聊了什么吗?❌ 不知道。它只看到"请问我的名字是什么?"✅ 知道。它看到了完整的对话链
服务器的负担😱 需要为每个用户存历史,无法扩展😊 零负担,收到什么处理什么
为什么这是幻想HTTP 是无状态的,服务器不会帮你记住客户端自己维护chatHistory,每次全量携带

🎯核心差异一句话:有状态 = 期望服务器帮你记。无状态 = 你自己记好,每次全量带上。

chatHistory就是这个"记忆载体"——一个客户端维护的数组,每次请求都完整发送。代码中被注释掉的部分,正是新手最容易踩的坑:以为上一轮消息服务器已经知道了,这轮只发新消息就行——实际上那样做模型完全不知道你在说什么。

3.4 另一个关键事实

💡模型回复不加入chatHistory= 模型不知道自己刚才说了什么。

注意代码中每次client.chat.completions.create()后,都紧跟着一句:

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

如果漏掉这一步,下一轮对话中模型就看不到自己上一轮的回复,上下文就断了。这不是模型"记不住"——而是我们根本没把那条消息放进下一轮的messages里。


⚠️ 四、chatHistory模式的问题

手拼历史对话虽然能工作,但随着对话增长,问题逐渐暴露:

4.1 消息膨胀 → Token 开销指数增长

第 1 轮: 2 条消息 (system + user) 第 2 轮: 4 条消息 第 3 轮: 6 条消息 ... 第 N 轮: 2N 条消息

每轮对话的 Token 消耗 =前面所有轮次的总和。聊得越久,单次请求越贵、越慢。

4.2 容量天花板

模型都有上下文窗口限制(Context Window)。当chatHistory超过这个窗口,必须裁切。但简单粗暴地删掉最早的消息,模型就丢失了"早期记忆"。

4.3 LRU 缓存策略:一种折中

┌─────────────────────────────────────────────┐ │ chatHistory 数组 │ ├──────────┬──────────┬──────────┬───────────┤ │ 第1轮对话 │ 第2轮对话 │ 第3轮对话 │ ...第N轮 │ │ (丢弃) │ (保留) │ (保留) │ (保留) │ └──────────┴──────────┴──────────┴───────────┘ ↑ Token 容量上限

类似 LRU(Least Recently Used)缓存:保留最近聊的,丢弃久远的。但这对长线任务是个问题——任务还没完成,早期关键信息就已经被淘汰了。


🚀 五、演进:从 Prompt 到 Context 到 Loop

LLM 工程能力的升级路径,本质是在无状态地基上层层搭建"有状态"的抽象:

阶段名称核心思路典型手段
🥉 L1Prompt Engineering写高质量 Prompt,把上下文塞进 messagesSystem Prompt、Few-shot、历史对话拼接
🥈 L2Context Engineering动态检索 + 工具调用,扩展"上下文"边界RAG 知识库、MCP 工具、Skill 调用
🥇 L3Loop Engineering循环编排,把 LLM 嵌入工程流水线Agent Harness、自主循环、多步推理

L1 — Prompt Engineering 🎨

当前最普遍的实践。通过精心设计systemprompt + 手动维护chatHistory+ 知识库文件(如CLAUDE.mdAGENTS.md)塞进上下文。

  • 优点:简单直接
  • 痛点:像抽卡——Prompt 质量能提高抽到"金卡"的概率,但不是特别可控

L2 — Context Engineering 🔧

LLM 的知识有截止日期,也不知道你的私有数据。所以需要:

  • RAG:检索增强生成,从外部知识库拉相关资料注入上下文
  • MCP / Skill:让 LLM 调用外部工具,获取实时数据、操作外部系统

L3 — Loop Engineering ⚙️

当前的前沿方向。把 LLM 嵌入一个**循环编排引擎(Harness)**中:

┌─────────────────────────────────────────────┐ │ Harness (编排引擎) │ │ │ │ ┌──────┐ ┌──────────┐ ┌─────────┐ │ │ │ 思考 │ → │ 执行动作 │ → │ 观察结果 │ │ │ └──────┘ └──────────┘ └─────────┘ │ │ ↑ ↓ │ │ └──────── 循环迭代 ──────────┘ │ └─────────────────────────────────────────────┘

每次调 LLM 仍然是无状态的,但Harness在客户端维护状态、决策循环、工具结果、多轮推理——用工程手段在无状态地基上盖出了有状态的 AI Agent。


🏁 六、总结

无状态 LLM 架构全景 HTTP 协议 (Stateless) ═══════════════════════════════════════ │ │ │ 每次请求 = 独立的 POST │ │ 服务器不保存任何对话上下文 │ │ 可水平扩展,任意服务器都能处理 │ ═══════════════════════════════════════ │ │ 之上构建 ▼ ═══════════════════════════════════════ │ chatHistory 数组 │ │ 客户端手动维护"记忆" │ │ 每轮拼接全部消息再发出去 │ ═══════════════════════════════════════ │ │ 之上再构建 ▼ ═══════════════════════════════════════ │ Context / Loop Engineering │ │ RAG + 工具调用 + 循环编排 │ │ 在无状态地基上盖出有状态 Agent │ ═══════════════════════════════════════

核心认知一句话:

🎯LLM 没有记忆——你每次带上的messages数组,就是它的全部世界。

理解了这个,你就理解了为什么chatHistory这么重要、为什么 Token 消耗随对话增长、以及为什么所有 AI 工程最终都在围绕"上下文管理"做文章。


http://www.gsyq.cn/news/1576576.html

相关文章:

  • 2026黄冈漏水检测维修本地口碑防水商家榜单:厨卫/阳台/屋面/地下室渗漏水维修,持证施工+明码实价,防水补漏公司TOP5推荐 - 即刻修防水
  • 二次元发卡系统终极指南:打造专业虚拟商品交易平台
  • MongoDB电商订单建模与Windows本地实战指南
  • 跟着 MDN 学无障碍 Day 8:WAI-ARIA 实战技能测试解析
  • 2026年河南电池级柠檬酸优质供应商盘点:崟生化工等企业深度解析 - 品牌鉴赏官2026
  • 【置顶必读】博主自我介绍,源码领取看这里
  • HC(S)08嵌入式开发中__near与__far关键字的内存管理实战
  • 飞思卡尔DSP56724/56725 EMC寄存器配置实战:从原理到音频处理应用
  • 3个技巧快速掌握ComfyUI中文工作流:从AI绘图新手到专业创作者的转变
  • 树莓派打造便携式Kali Linux渗透测试工作站:硬件选型、系统优化与实战指南
  • 基于Stein变分梯度下降的分布估计算法:组合优化新范式
  • 2026年当下四川靠谱的LED显示屏安装服务商深度解析与选择指南 - 品牌鉴赏官2026
  • 2026辽阳防水补漏避坑指南:卫生间/厨房/阳台/屋顶/地下室漏水检测维修全攻略,正规施工+透明报价+口碑榜靠谱服务商推荐 - 安佳防水
  • 2026年GEO优化服务商TOP8权威评测:AI搜索时代的品牌增长路径 - GEORANK
  • 2026年北京西装定制:五大品牌深度测评—婚礼与成人礼场景 - 博客湾
  • OpenVAS漏洞扫描结果精准评估:从海量告警到可行动风险矩阵
  • AI搜索排名优化哪家强?2026年TOP8GEO服务商实力对比 - GEORANK
  • 2026马鞍山漏水检测维修本地口碑防水商家榜单:厨卫/阳台/屋面/地下室渗漏水维修,持证施工+明码实价,防水补漏公司TOP5推荐 - 即刻修防水
  • AI搜索优化服务商TOP8推荐:2026年企业AI流量增长必看指南 - GEORANK
  • AVR单片机EMC设计实战:从硬件滤波到软件抗干扰的完整指南
  • Ubuntu 20.04 生产级 Zabbix 部署:内核调优、MySQL 8.0 安全配置与 Nginx 加固
  • Deepseek-MoE同步税:MoE架构在GPU部署中的通信与调度开销解析
  • Frida-il2cpp-bridge实战:Unity游戏逆向分析与动态插桩技术详解
  • 第21章:结构化输出与JSON稳定性治理
  • 2026高效过滤器哪家最好用?专业性能对比参考 - 品牌排行榜
  • 2026年6月深度解析:义乌诚信中小件健身器材工厂的崛起之路 - 品牌鉴赏官2026
  • 网购退货寄件步骤:教你轻松省钱寄回 - 快递物流资讯
  • 天津继承诉讼律师联系方式推荐 家理天津分所姜春梅律师团队 - 外贸老黄
  • 如何快速掌握Zotero文献管理:Better BibTeX插件完整使用指南
  • 如何零基础使用Mermaid Live Editor:免费在线图表制作终极指南