前言:调用 LLM 接口的本质是一次 HTTP 请求------计算资源接收输入、生成输出、返回结果。但为什么每次请求都要手动带上全部对话历史?"无状态"究竟意味着什么?本文从 HTTP 无状态协议出发,用实战代码验证 LLM 的无状态本质,再延伸到 Prompt → Context → Loop 三层上下文工程,帮你建立对 LLM 底层运行规则的完整认知。
目录
- [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")
- 无状态是什么?
- [实战:用代码验证 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")
- [从 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")
- [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")
- 总结
一、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 里的 Cookie 或 Authorization 字段主动带上身份凭证。所有人都公平------谁带了凭证,谁就是那个用户。
有状态 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"这个名字。
存储模型回复 :收到模型的回复后,同样 push 进 chatHistory。这非常关键------大模型的输出也是对话历史的一部分,如果只存用户消息而不存模型回复,上下文就不完整。
第二次请求 :追加 "请问我的名字是什么?" 到 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 应用开发者必须具备的意识。
六、总结
- LLM 接口是 HTTP 调用 :本质是客户端发送
messages数组,服务端用 GPU 算力生成回复,和任何 Web API 没有区别。 - LLM 是无状态的:服务端不保存任何对话上下文,每次请求都是独立的。高并发、水平扩展正是建立在无状态架构之上。
- 让 LLM "懂"我们 :客户端手动维护
chatHistory数组,每次请求携带完整历史。模型的回复也必须存入 history,否则上下文不完整。 - 三层工程递进:Prompt Engineering(提示词质量)→ Context Engineering(上下文结构与工具集成)→ Loop Engineering(自主执行闭环),每一层都建立在前一层的基础之上。
- chatHistory 有代价:消息体持续膨胀 → Token 开销线性增长 → LRU 裁剪是权宜之计,更智能的上下文管理是未来的方向。
一条核心直觉:LLM 不是你的微信好友------它没有记忆,不会"记住"你们昨天聊过什么。每一次对话,都是你拿着一本完整的聊天记录,重新递到它面前说:"来,我们继续。"
------ 理解无状态,是理解 LLM 工作机制的第一块基石。