从"无状态"到"懂你":深入理解 LLM 对话的本质,以及 Prompt/Context/Loop 三层工程进化之路

每次和大模型聊天,它为什么能"记住"你上一句说了什么?你真的理解背后的"无状态"原理吗?


一、引言:一次"失忆"实验

先来看一段简单的代码:

javascript 复制代码
import OpenAI from 'openai';

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

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

async function testStateless() {
  // 第一次请求:告诉模型我的名字
  chatHistory.push({ role: 'user', content: '请记住我叫字节戴' });

  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  // 👈 还是把整个历史发过去
  });

  console.log(response2.choices[0].message.content);
  // 输出:"你的名字是字节戴"
}

testStateless();

如果第二次请求我们只发 [{ role: 'user', content: '请问我的名字是什么?' }],不带上历史消息------模型会怎么回答?

它会一脸茫然。因为它根本"不认识"你。

这就是今天要聊的核心话题 ------ LLM 的无状态本质,以及从 Prompt Engineering 到 Context Engineering 再到 Loop Engineering 的三层进化。


二、LLM 调用本质:一次 HTTP 请求而已

抛开所有花哨的 UI 和封装,调用大模型 API 的本质是什么?

复制代码
HTTP 请求 → 显卡计算 → 返回生成结果

仅此而已。你通过 HTTPS 发一段 JSON 过去,服务器上的 GPU 跑一遍推理,然后把生成的 token 序列还给你。

这意味着三件事:

  1. 每次请求都是独立的 ------ 服务器不记得你上一秒说过什么
  2. 服务器不存储客户端状态 ------ 不存在"会话"(Session)这个概念
  3. 可以水平扩展 ------ 你的两次请求可以落到不同机器上,结果无差异

这就是 Stateless(无状态)

和 HTTP 协议本身一样:HTTP 是无状态协议,每一次 GET/POST 都是独立的。Web 应用通过 Cookie / Authorization Header 来"伪造"有状态,而大模型应用则通过每次手动带上全部对话历史来实现"记忆"。

sql 复制代码
HTTP 无状态协议
  ├── GET / POST / PUT / DELETE ... 每次独立
  └── + Header (Cookie? Authorization?) → "伪造"有状态

LLM 无状态调用
  ├── 每次 POST /v1/chat/completions 都是独立的
  └── + 手动拼接完整 messages[] → "伪造"有记忆

核心洞察:大模型本身没有"记忆",是你每次把记忆掏出来给它看一遍。


三、"有状态"的诱惑与代价

有人会问:为什么不直接让服务端维护会话状态?像传统 Web 应用的 Session 那样?

答案是:压力太大了。

想象一下,如果服务端要为每个用户维护上下文:

维度 无状态(Stateless) 有状态(Stateful)
服务器压力 低,不存状态 高,每个会话占用内存/显存
水平扩展 ✅ 任意机器处理任意请求 ❌ 需要会话亲和性(sticky session)
并发能力 ✅ 天然支持高并发 ❌ 状态同步是瓶颈
容错性 ✅ 一台挂了另一台顶上 ❌ 状态丢失不可恢复
公平性 ✅ 所有请求一视同仁 ❌ 老用户占用资源更多

大模型推理本身就是计算密集型 + 显存饥渴的任务。如果还要维护几百万用户的对话状态,KV Cache 会迅速撑爆显存。

所以,无状态不是选择,是必然。


四、对话历史的代价:Token 膨胀危机

既然要"手动带历史",那就必须面对一个现实问题:

复制代码
messages 数组越来越大 → Token 开销线性增长 → 钱越烧越多

举个例子,假设你和模型聊了 100 轮:

erlang 复制代码
第 1 轮:200 tokens
第 2 轮:400 tokens
第 3 轮:600 tokens
...
第 100 轮:20,000 tokens  ← 你每一轮都在为历史买单

这还不算模型回复的 tokens。用 DeepSeek 的价格算,如果单次对话持续 100 轮,单是输入 token 的成本就能翻几十倍。

更致命的是上下文窗口限制。 即使现在主流模型支持 128K、1M token 的上下文,也总有装满的那一天。

LRU(最近最少使用)策略

一个自然的优化思路是 LRU:

javascript 复制代码
// 伪代码
function pruneHistory(messages, maxTokens) {
  let totalTokens = countTokens(messages);
  while (totalTokens > maxTokens && messages.length > 2) {
    // 保留 system prompt 和最近的对话
    const removed = messages.splice(1, 2); // 移除最早的一对 user+assistant
    totalTokens -= countTokens(removed);
  }
  return messages;
}

最近在聊的留下,久远的适当删除。 这个思路简单实用,但也丢了潜在的上下文------比如你在第 1 轮告诉了模型你的名字,第 50 轮问它时,它已经"忘"了。

这就是为什么实际工程中需要更精细的策略:摘要压缩、向量检索(RAG)、分层记忆。


五、三层进化:从 Prompt 到 Loop

聊完原理,我们升级视角,从工程哲学的层面看整个 AI 应用开发的演化路径。

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

这是大多数人入门的方式。

复制代码
你 → 写 Prompt → 模型 → 回复

你在聊天框里反复调整措辞,希望能稳定产出高质量回答。你维护历史对话,你整理知识库文件(claude.mdagent.md),你精心设计 system prompt。

但本质上,Prompt Engineering 像是在抽卡

好的 Prompt 设计只能提升你"抽到金卡"的概率,做不到 100% 可控。

同样的 Prompt,模型有时输出惊艳,有时胡言乱语。这是 LLM 的概率本质决定的。

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

当你发现"光靠 Prompt 不够"时,你开始进入上下文工程的领域。

核心思路:模型不懂的东西,是因为你还没喂给它。

手段 解决的问题
RAG(检索增强生成) "模型不知道" → 实时检索外部知识
MCP(协议/工具调用) "模型做不到" → 赋予它调接口的能力
Skills / Agent "模型不会规划" → 给它一套工作流

Context Engineering 不再纠结于"怎么问",而是关注**"给什么"**。

你不再期望模型记住一切------你构建一套系统,在需要时精准注入上下文。

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

这是最前沿的一层,也是 AI Agent 能真正"干活"的核心。

传统的人机交互:

复制代码
用户输入 → 模型输出 → 结束

Loop Engineering 的模式:

markdown 复制代码
用户输入 → 模型规划 → 执行动作 → 观察结果 → 反馈调整 → 再次执行 → ... → 达到目标
              ↑__________________________________________________↓

这就是 Claude Code、Cursor、Devin 这类产品背后的核心范式 ------ Harness(马具)思维

你的角色从"写 Prompt 的人"变成了"套马的人":

  • 你给 AI 设定目标和约束
  • AI 自己在 Loop 中迭代
  • 你只在关键节点介入
markdown 复制代码
┌─────────────────────────────────────────┐
│        Loop Engineering (Harness)        │
│                                          │
│  用户 ──→ 定义目标 ──→ AI Loop ──→ 结果   │
│               │            │             │
│               │   ┌────────▼──────────┐  │
│               │   │  观察 → 思考 → 执行  │  │
│               │   │    ↑___________↓   │  │
│               │   └───────────────────┘  │
│               │            │             │
│               └──── 人在环路外 ──────────┘  │
└─────────────────────────────────────────┘

六、ChatHistory 的工程化挑战

回到最接地气的部分。不管你用哪一层工程,ChatHistory(对话历史)的管理是绕不开的。结合前面的分析:

挑战 说明 应对
完整性 不维护完整历史,模型的理解就会断裂 持久化存储,结构化记录
Token 膨胀 消息越来越多,成本线性增长 LRU 淘汰 + 摘要压缩 + RAG 检索
任务未完成就被截断 聊着聊着上下文满了,任务还没做完 任务拆解 + 检查点保存
容量规划 什么时候该删、该压缩、该检索? 基于 token 预算的主动管理

一个好的 ChatHistory 管理策略,几乎决定了一个 AI 应用能不能"长期跑下去"。


七、总结:三个核心认知

梳理完这些,我希望你带走三个核心认知:

认知一:LLM 就是无状态的

不用心存幻想。它每一次推理都从零开始。你感受到的"记忆",是你自己维护的。

认知二:无状态 = 高并发 + 高可用 + 水平扩展

这不是缺陷,这是架构选择。正是因为无状态,AI 服务才能支撑千万级用户同时使用。

认知三:工程能力分层演进

vbnet 复制代码
Prompt Engineering     →  会问问题
Context Engineering    →  会给信息
Loop Engineering       →  会造系统

你在哪一层,决定了你的 AI 应用有多大的想象空间。


八、附:完整 Demo 代码

最后附上完整的演示代码,它虽然短,但完美诠释了"无状态"的核心理念:

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('=== 第一次请求:告诉模型我的名字 ===');
  chatHistory.push({
    role: 'user',
    content: '请记住我叫字节戴'
  });

  const response = await client.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('\n=== 第二次请求:问模型我是谁 ===');
  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('\n=== 完整对话历史 ===');
  console.log(JSON.stringify(chatHistory, null, 2));
}

testStateless().catch(err => console.error(err));

运行它,然后把第二次请求的 messages 改成只带当前问题------你会立刻"看见"什么叫无状态。


延伸思考:如果 LLM 是有状态的,Claude Code 在每次迭代时就不需要重新读取文件、不需要每次把上下文重传一遍。但也正因为它是无状态的,才有了 RAG、MCP、Agent 这一整个生态。无状态不是问题,无状态是土壤。


相关推荐
稚雪九月1 小时前
永久记忆,丰富情感,Atrium AI框架:给AI一颗真正的心
人工智能
颜进强1 小时前
AI性能参数-截断、延迟与流式输出
前端·后端·ai编程
小鼻子的猫1 小时前
万字长文讲透 AI Agent 架构设计:从 ReAct 到多 Agent 协作,附完整 Python 代码
人工智能
Hector_zh1 小时前
实战·第八篇:当模型陷入死循环——FACA破解JSON生成的架构陷阱
人工智能·agent·vibecoding
魏祖潇1 小时前
AI 能记住了,但能自己干活吗?——看懂执行系统,你就知道它怎么完成复杂任务
人工智能·ai编程
Lkstar1 小时前
Function Calling 原理深度拆解:让 LLM 调用外部工具的机制与工具设计原则
人工智能·llm
IT_陈寒2 小时前
Vue的响应式真把我坑惨了,原来问题出在这
前端·人工智能·后端
武子康2 小时前
调查研究-190 Continue.dev 被 Cursor 收购:AI 编程工具正从“插件竞争“迈入“平台整合“阶段
人工智能·ai编程·cursor
武子康2 小时前
调查研究-189 Kronos 调研:金融 K 线基础模型,是真突破,还是量化圈的新玩具?
人工智能·深度学习·openai