当前位置: 首页 > 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.jsqmd.com/news/1064636/

相关文章:

  • 基于ATtiny28的RC5红外遥控发射器设计与实现
  • 微信小程序渗透测试实战指南:从环境搭建到漏洞挖掘
  • 2026年现阶段,探寻湖北新型悬挑工字钢领域优质服务商的联系之道 - 品牌鉴赏官2026
  • 二次元发卡系统终极指南:打造专业虚拟商品交易平台
  • 毕业寄电动车回家 2026学生操作步骤 - 快递物流资讯
  • 如何在Windows 11/10上深度定制系统界面字体?No!! MeiryoUI技术解析与实战指南
  • MongoDB电商订单建模与Windows本地实战指南
  • 运营计划PPT工具哪家强?我帮你把市面上的都扒了一遍
  • 基于 Harmony 7.0 应用的相框DIY应用首页实现
  • Socket 和 WebSocket 的关系
  • 2026年iPhone17护眼保护膜选购 光学适配与防护性能全解析 悟赫德
  • 跟着 MDN 学无障碍 Day 8:WAI-ARIA 实战技能测试解析
  • 2026年软文发稿价格全解析:8大类平台费用对比与省钱攻略 - GEORANK
  • 如何使用Privado开源数据安全扫描工具保护你的应用隐私
  • 如何快速搭建现代化后台管理系统?ThinkAdmin完整指南告诉你!
  • LLM 微调实战:从 LoRA 到 QLoRA 的参数高效微调原理与工程落地
  • Linux网络配置与文件下载实验报告
  • 【置顶必读】博主自我介绍,源码领取看这里
  • 退货寄快递哪家便宜?用寄半折比价,运费低至5折起 - 快递物流资讯
  • DSP56724/56725 DMA与时钟配置实战:音频处理系统性能优化指南
  • HC(S)08嵌入式开发中__near与__far关键字的内存管理实战
  • 2026年河南电池级柠檬酸优质供应商盘点:崟生化工等企业深度解析 - 品牌鉴赏官2026
  • 让大模型真正“懂”企业知识库
  • 2026年软文推广价格全攻略:8大渠道成本对比与ROI分析 - GEORANK
  • 飞思卡尔DSP56724/56725 EMC寄存器配置实战:从原理到音频处理应用
  • 2026年 东莞夹板厂家推荐榜单:ENF孕婴夹板、防虫抗蚁夹板与阻燃防火夹板优选品牌深度解析 - 品牌发掘
  • Sunshine自托管游戏串流:打造低延迟跨平台游戏共享解决方案
  • 天津遗产纠纷律师联系方式推荐 深耕本地司法实践专业能力扎实 - 外贸老黄
  • Linux sch_fq公平队列FQ流分类与credit机制
  • 3个技巧快速掌握ComfyUI中文工作流:从AI绘图新手到专业创作者的转变