LLM 的无状态:从 HTTP 协议到对话上下文工程

前言:调用 LLM 接口的本质是一次 HTTP 请求------计算资源接收输入、生成输出、返回结果。但为什么每次请求都要手动带上全部对话历史?"无状态"究竟意味着什么?本文从 HTTP 无状态协议出发,用实战代码验证 LLM 的无状态本质,再延伸到 Prompt → Context → Loop 三层上下文工程,帮你建立对 LLM 底层运行规则的完整认知。


目录

  1. [LLM 接口的本质:HTTP 调用与算力生成](#LLM 接口的本质:HTTP 调用与算力生成 "#%E4%B8%80llm-%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%9C%AC%E8%B4%A8http-%E8%B0%83%E7%94%A8%E4%B8%8E%E7%AE%97%E5%8A%9B%E7%94%9F%E6%88%90")
  2. 无状态是什么?
  3. [实战:用代码验证 LLM 的无状态](#实战:用代码验证 LLM 的无状态 "#%E4%B8%89%E5%AE%9E%E6%88%98%E7%94%A8%E4%BB%A3%E7%A0%81%E9%AA%8C%E8%AF%81-llm-%E7%9A%84%E6%97%A0%E7%8A%B6%E6%80%81")
  4. [从 Prompt 工程到 Loop 工程的三层进化](#从 Prompt 工程到 Loop 工程的三层进化 "#%E5%9B%9B%E4%BB%8E-prompt-%E5%B7%A5%E7%A8%8B%E5%88%B0-loop-%E5%B7%A5%E7%A8%8B%E7%9A%84%E4%B8%89%E5%B1%82%E8%BF%9B%E5%8C%96")
  5. [chatHistory 的代价与优化方向](#chatHistory 的代价与优化方向 "#%E4%BA%94chathistory-%E7%9A%84%E4%BB%A3%E4%BB%B7%E4%B8%8E%E4%BC%98%E5%8C%96%E6%96%B9%E5%90%91")
  6. 总结

一、LLM 接口的本质:HTTP 调用与算力生成

无论是 ChatGPT、Claude 还是 DeepSeek,当你调用它们的 API 时,本质只有一件事:发起一次 HTTP 请求,服务端接收你的输入、用 GPU 算力生成结果,然后返回给你。

vbscript 复制代码
用户代码 → HTTP POST → LLM 服务器(GPU 计算) → HTTP Response → 用户代码

这和调用任何其他 Web API 没有本质区别------请求到达服务端,服务端处理,返回结果。只不过 LLM 的处理过程是一次性的推理计算:你丢给它一段文本,它生成一段回复。

既然是 HTTP 调用,那高并发、高可用的后端架构需求就摆在了面前。而支撑高并发和高可用的关键技术前提,正是 "无状态"


二、无状态是什么?

HTTP 本身就是无状态协议

HTTP 协议从设计之初就是**无状态(Stateless)**的。一次标准的 RESTful 交互:

sql 复制代码
GET /api/user/1  →  服务端返回用户 1 的数据
POST /api/order  →  服务端创建订单

每一次请求都是独立的、自包含的 。服务端处理完请求后不会"记住"你是谁,除非你通过 header 里的 CookieAuthorization 字段主动带上身份凭证。所有人都公平------谁带了凭证,谁就是那个用户。

有状态 vs 无状态

有状态 无状态
服务端行为 记住每个客户端的上下文 每次请求视为全新的
客户端要求 无需重复提供上下文 每次请求自备完整信息
水平扩展 困难------状态绑在特定服务器 容易------任意服务器都能处理
典型场景 传统桌面应用、游戏服务器 RESTful API、LLM 接口

有状态意味着服务端需要为每个连接的客户端维护一份"你是谁的记忆"。当并发量上来时,服务端的压力急剧增加:不仅要算,还要记。而无状态架构下,服务器不需要存储任何客户端状态------每个请求都是独立的,可以在任何一台服务器上处理,水平扩展只是加机器的事。

LLM 也是无状态的

LLM 服务端遵循的正是这套无状态原则。每次你发送一个 API 请求,服务端看到的只是一段 messages 数组------它不知道你们上一轮聊了什么,也不会在服务端保存你们的对话历史。对 LLM 来说,每一次请求都是"初次见面"。

这就引出了一个核心问题:怎么让 LLM "懂"我们?


三、实战:用代码验证 LLM 的无状态

答案很直接------每次手动带上全部对话历史。

下面这段代码使用了 OpenAI 兼容的 SDK 来调用 LLM。它用数组 chatHistory 在客户端手动维护对话历史,每次发请求时把整个数组塞进 messages 字段:

javascript 复制代码
import OpenAI from 'openai';
import { config } from 'dotenv';
config();

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

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

async function testStateless() {
  console.log('第一次请求,告诉模型一个信息');

  // 为了让 LLM "懂"我们,每次请求都带上完整 history
  chatHistory.push({
    role: 'user',
    content: '请记住我叫字节D'
  });

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

  // 把模型的回复也存入 history
  chatHistory.push({
    role: 'assistant',
    content: response.choices[0].message.content
  });

  console.log('模型回复:', response.choices[0].message.content);

  console.log('第二次请求,直接问我是谁');
  chatHistory.push({
    role: 'user',
    content: '请问我的名字是什么?'
  });

  const response2 = await client.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);
}

testStateless()
  .catch(err => {
    console.log(err);
  });

代码拆解:三步理解无状态交互

第一次请求 :在 chatHistory 中追加用户消息 "请记住我叫字节D",然后把整个 chatHistory 发送给 LLM。服务端收到 messages 数组后完成推理,返回回复。处理结束后,服务端就彻底忘记刚才发生了什么------它不会存下"字节D"这个名字。

存储模型回复 :收到模型的回复后,同样 pushchatHistory。这非常关键------大模型的输出也是对话历史的一部分,如果只存用户消息而不存模型回复,上下文就不完整。

第二次请求 :追加 "请问我的名字是什么?"chatHistory。此时 chatHistory 已经包含了 system prompt + 第一轮用户消息 + 第一轮模型回复 + 第二轮用户消息。整个数组再次原封不动地发往服务端。LLM 查看了全部历史,从第一条用户消息中找到"字节D"这个名字,给出了正确的回答。

sql 复制代码
请求 1: [system, user:"我叫字节D"] → 服务端推理 → 回复
        (服务端已遗忘)
请求 2: [system, user:"我叫字节D", assistant:"...", user:"我是谁?"] → 服务端推理 → 回复

关键认知 :LLM 从来不会真的"记住"任何东西。它之所以看起来有记忆,是因为我们每次都不厌其烦地把整个聊天记录塞进请求体中。服务端并发处理成千上万个请求,任意一台机器都能接住你的下一次请求------因为状态不在服务端,而在客户端手里的 chatHistory 里。


四、从 Prompt 工程到 Loop 工程的三层进化

理解了 LLM 的无状态本质后,一个自然的问题是:我们给 LLM 提供的"上下文"到底可以包含什么?这个问题的答案经历了三个阶段的演进:

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

最初的玩法是聊天对话------精心设计 system prompt,调整措辞、角色设定、输出格式要求。上下文主要来自历史对话知识库文档claude.md / agent.md 这类规则文件。

但这个阶段的瓶颈也很明显:Prompt 的质量提升像抽卡------好的设计能提高"抽到金卡"的概率,但本质上并不可精确控制。模型可能这次听懂了,下次又忘了。

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

当 Prompt Engineering 遇到天花板,大家开始把注意力转向上下文本身的质量和结构:

  • RAG(检索增强生成):LLM 没有的知识,我们通过检索外部知识库的方式"喂"给它。
  • MCP(模型上下文协议):让 LLM 能调用外部工具、访问实时数据,上下文的边界从纯文本扩展到了动态能力。
  • Skill 机制:将特定任务的能力封装为可复用的上下文单元,按需加载。

上下文工程的核心理念是:不是让模型更聪明,而是给它更好的输入。

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

Context Engineering 再进一步,就是 Loop Engineering------Harness AI 工程。它的核心思路是:让 AI 不只是"一问一答",而是进入一个持续的、可自我修正的循环

erlang 复制代码
执行 → 观察结果 → 分析反馈 → 调整策略 → 再次执行 → ...

这个循环把 LLM 从被动的"回答机器"变成了主动的"执行者"。Claude Code 的 /loop 命令就是这种理念的典型实践------每隔一段时间自动执行一个任务,观察结果,再做下一步决策。

阶段 核心关注点 上下文来源 典型手段
Prompt Engineering 提示词质量 历史对话、规则文档 角色设定、格式约束
Context Engineering 上下文结构 知识库、外部工具 RAG、MCP、Skill
Loop Engineering 持续闭环 执行结果、反馈信号 Harness AI、自主循环

三层工程并非互相替代,而是递进叠加------好的 Loop 依赖优质的 Context,好的 Context 依赖精准的 Prompt。


五、chatHistory 的代价与优化方向

回到最实际的代码层面:手动维护 chatHistory 虽然解决了无状态问题,但它也带来了新的挑战。

问题一:消息体不断膨胀

每聊一轮,chatHistory 就多两条消息(一条 user + 一条 assistant)。对话越长,数组越大------你聊了 50 轮,就要把 100+ 条消息全部打包发送。每次请求的 messages 体积越来越大,网络传输和 JSON 序列化的开销也随之增长。

问题二:Token 开销指数级增长

更大的问题是 Token 消耗。LLM API 的计费是基于输入 Token 数量的,而 messages 里的每一个字都会计入 Token 开销。如果你在长对话中反复把完整历史发过去,输入 Token 的开销会随着对话轮次线性增长,而其中大部分开销都花在了 LLM "重温"那些已经讨论过的旧内容上。

yaml 复制代码
对话轮次      1     5     10    20    50
输入 Token   100   500   1000  2000  5000(示意数据)

每轮请求的输入都在变多,但用户感知到的"智能"并没有等比提升------那些 20 轮之前的对话内容,很可能对当前任务已经没有任何帮助了。

问题三:LRU 策略------删除旧对话可行吗?

直觉上的优化方式是"删掉旧的、保留新的",类似操作系统的 LRU(Least Recently Used) 淘汰策略。

但对话不是缓存。你正在进行的任务可能引用到 15 轮之前的一个关键信息,贸然删除旧消息会破坏上下文的完整性。而如果不删,Token 开销持续攀升。

所以目前实践中的平衡点是:保留最近的相关对话,对久远的、与当前任务无关的内容做适当的裁剪。 这不是一个已完美解决的问题------"什么该保留、什么该删除"本身就是 Context Engineering 的核心课题之一。未来更智能的上下文管理机制(如自动摘要、层级压缩、向量检索等)会是 LLM 应用的关键竞争力。

每一条 chatHistory 里的消息都有成本。知道你维护的上下文正在花掉多少 Token,是每个 LLM 应用开发者必须具备的意识。


六、总结

  1. LLM 接口是 HTTP 调用 :本质是客户端发送 messages 数组,服务端用 GPU 算力生成回复,和任何 Web API 没有区别。
  2. LLM 是无状态的:服务端不保存任何对话上下文,每次请求都是独立的。高并发、水平扩展正是建立在无状态架构之上。
  3. 让 LLM "懂"我们 :客户端手动维护 chatHistory 数组,每次请求携带完整历史。模型的回复也必须存入 history,否则上下文不完整。
  4. 三层工程递进:Prompt Engineering(提示词质量)→ Context Engineering(上下文结构与工具集成)→ Loop Engineering(自主执行闭环),每一层都建立在前一层的基础之上。
  5. chatHistory 有代价:消息体持续膨胀 → Token 开销线性增长 → LRU 裁剪是权宜之计,更智能的上下文管理是未来的方向。

一条核心直觉:LLM 不是你的微信好友------它没有记忆,不会"记住"你们昨天聊过什么。每一次对话,都是你拿着一本完整的聊天记录,重新递到它面前说:"来,我们继续。"


------ 理解无状态,是理解 LLM 工作机制的第一块基石。

相关推荐
杨利杰YJlio1 小时前
Codex桌面客户端上手:项目、插件与自动化实战
前端·后端
胡志辉1 小时前
从 prototype 到 V8,看懂 JavaScript 原型链
前端·javascript
ricardo19731 小时前
React 渲染优化:memo / useMemo / useCallback 的正确姿势与并发模式实战
前端·面试
ClouGence1 小时前
零代码自动化测试:手把手教你录出一条能反复用的测试用例
前端·测试
skiyee1 小时前
🔥UniApp 仅需 5 行代码!实现所有页面中控制应用主题变化
前端·微信小程序
LaiYoung_1 小时前
🎁 送你一套超好用超实用的 FE AI-Coding Skills
前端·人工智能·开源
幼儿园技术家2 小时前
实现 GEO 监控:从多引擎探测到优化闭环
前端·后端
甲维斯2 小时前
GLM5.2+ZCode复刻坦克大战,自测50万帧!
前端·ai编程·游戏开发
Csvn2 小时前
useRef 的 5 个冷门但救命的高级用法
前端