LLM 无状态本质与上下文工程:从 Prompt 到 Context 的进化——为什么 AI 总是"失忆"?

LLM 无状态本质与上下文工程:从 Prompt 到 Context 的进化------为什么 AI 总是"失忆"?

本文深入解析 LLM 的无状态(Stateless)本质,揭示为什么每次调用 API 都是"全新的开始",并系统讲解从 Prompt Engineering 到 Context Engineering 再到 Loop Engineering 的进化路径,助你构建真正"有记忆"的 AI 应用。


前言

你有没有遇到过这样的困惑:

你告诉 AI "我叫张三",下一秒问 "我是谁",它却回答 "我不知道你的名字"。

这不是 AI 笨,而是 LLM 的本质是无状态的。每次调用 API,对服务器来说都是一次全新的、独立的请求------它不记得你们上一轮聊过什么。

本文将带你理解这一底层机制,并掌握让 AI"有记忆"的核心技术:上下文工程。


一、LLM 的无状态本质

1.1 调用 LLM 接口的本质是什么?

css 复制代码
你的程序 ──HTTP POST──→ LLM 服务器
    │                        │
    │  {                     │
    │    model: "gpt-4",     │
    │    messages: [...]     │  ← 每次请求都是独立的
    │  }                     │
    │                        │
    └────── JSON 响应 ───────┘

本质:HTTP 调用 + 算力消耗 + 生成结果。

LLM 服务器面对的不是"一个用户的长对话",而是无数个独立的 HTTP 请求 。为了支撑高并发、高可用,后端必须设计为 无状态(Stateless)

1.2 什么是无状态(Stateless)?

无状态意味着:

  • 每次请求都是独立的,不依赖于之前的请求
  • 服务器不存储客户端的任何状态
  • 服务器可以水平扩展,任何一台机器处理任何请求都没差别
css 复制代码
有状态服务:                    无状态服务:
┌─────────┐                   ┌─────────┐
│ 用户A   │──→ 服务器1        │ 用户A   │──→ 负载均衡 ──→ 任意服务器
│ (状态)  │    (记住用户A)    │         │
└─────────┘                   └─────────┘
                               ↑
┌─────────┐                   │
│ 用户B   │──→ 服务器2        │ 用户B   │──→ 负载均衡 ──→ 任意服务器
│ (状态)  │    (记住用户B)    │         │
└─────────┘                   └─────────┘

问题:用户A下次请求到了服务器2,服务器2不认识他
优势:任何请求到任何服务器,结果都一样

💡 为什么 LLM 必须无状态? 如果服务器要"记住"每个用户的对话历史,面对数百万并发用户,内存会瞬间爆炸。无状态设计让服务器可以无限水平扩展。

1.3 HTTP 本身就是无状态协议

sql 复制代码
HTTP 请求1:GET /api/user
  ← 服务器返回用户信息

HTTP 请求2:GET /api/orders
  ← 服务器不知道你是谁!

HTTP 协议天生不记住"你是谁"。那网站是怎么记住登录状态的?

答案 :通过 Header 中的 Cookie / Authorization / Session ID

ini 复制代码
HTTP 请求1:POST /login
  → 服务器返回 Set-Cookie: session_id=abc123

HTTP 请求2:GET /api/orders
  → Cookie: session_id=abc123  ← 客户端主动带上身份
  → 服务器查数据库:session_id=abc123 → 用户ID=42

但 LLM API 不同

yaml 复制代码
LLM API 请求:
  Authorization: Bearer sk-xxx  ← 只认证身份,不恢复对话
  Body: { messages: [...] }     ← 对话历史由客户端维护

⚠️ 关键区别 :普通网站的 Cookie 用于"恢复会话状态",而 LLM API 的 Authorization 只用于"身份认证"。对话历史必须由客户端在每次请求时手动带上


二、实战演示:AI 的"失忆"与"记忆"

2.1 演示一:无状态------AI 不记得你

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,
});

async function testStateless() {
  // 第一次请求:告诉 AI 我的名字
  console.log('第一次请求:告诉模型我叫零零发');
  const response1 = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: [
      { role: 'system', content: '你是一个严谨的助手' },
      { role: 'user', content: '请记住,我的名字叫零零发' }
    ]
  });
  console.log('模型回复:', response1.choices[0].message.content);
  // 输出:"好的,我记住了,您的名字是零零发。"

  // 第二次请求:直接问 AI 我叫什么
  console.log('\n第二次请求:直接问我是谁');
  const response2 = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: [
      { role: 'user', content: '请问我的名字是什么?' }
    ]
  });
  console.log('模型回复:', response2.choices[0].message.content);
  // 输出:"抱歉,我不知道您的名字。" ← 失忆了!
}

testStateless();

为什么失忆了?

css 复制代码
请求1 和 请求2 是两个完全独立的 HTTP 请求:

请求1:
  messages: [
    { role: 'user', content: '请记住,我的名字叫零零发' }
  ]

请求2:
  messages: [
    { role: 'user', content: '请问我的名字是什么?' }  ← 没有上一轮的信息!
  ]

2.2 演示二:有记忆------手动带上对话历史

javascript 复制代码
// 对话历史数组
const chatHistory = [
  { role: 'system', content: '你是一个严谨的助手' }
];

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

  const response1 = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: chatHistory  // 带上完整历史
  });

  // 将模型回复也加入历史
  chatHistory.push({
    role: 'assistant',
    content: response1.choices[0].message.content
  });

  console.log('模型回复:', response1.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));
}

为什么记得了?

css 复制代码
请求2 的 messages 包含了请求1 的全部内容:

messages: [
  { role: 'system', content: '你是一个严谨的助手' },
  { role: 'user', content: '请记住我叫字节' },           ← 第一轮用户
  { role: 'assistant', content: '好的,我记住了...' },    ← 第一轮模型
  { role: 'user', content: '请问我的名字是什么?' }       ← 第二轮用户
]

LLM 看到完整的上下文,自然能回答出"字节"。

三、从 Prompt Engineering 到 Context Engineering

3.1 三代工程化方法的演进

vbnet 复制代码
第一代:Prompt Engineering(提示词工程)
  ├── 精心打磨一条 Prompt
  ├── 抽卡式:质量不可控
  └── "prompt 质量只能提升抽到金卡的概率"

第二代:Context Engineering(上下文工程)
  ├── 维护对话历史(chatHistory)
  ├── 注入知识库(RAG)
  ├── 配置文件(claude.md / agent.md)
  └── 让 LLM "懂" 我们

第三代:Loop Engineering(循环工程)
  ├── 生成 → 校验 → 迭代
  ├── 自动化工作流
  └── Harness AI 工程

3.2 Prompt Engineering 的局限

markdown 复制代码
用户:写一个小红书文案

Prompt 1(普通):"写一个小红书美妆文案"
→ 结果一般

Prompt 2(优化):"你是一位资深小红书美妆博主,
                    写一篇关于口红的文案,
                    标题带数字,正文<300字,
                    结尾有行动号召"
→ 结果好一些

Prompt 3(极致):"你是一位拥有100万粉丝的..."
→ 结果更好一些,但...

问题:
  - 每次都要重新写 Prompt
  - 质量仍然不可控(抽卡)
  - 没有记忆,每次从零开始

💡 Prompt Engineering 的天花板:Prompt 质量只能提升"抽到金卡"的概率,不是特别可控。

3.3 Context Engineering:让 LLM "懂" 我们

Context Engineering 的核心:不是让 LLM 记住,而是每次请求时把该记得的都带上

css 复制代码
Context 的来源:
├── 对话历史(chatHistory)
│   └── 用户和模型的完整对话记录
├── 知识库(RAG)
│   └── 检索到的相关文档片段
├── 配置文件
│   ├── claude.md(Claude Code 的配置)
│   └── agent.md(Agent 的人设和规则)
├── 实时信息
│   └── 当前时间、用户信息、环境状态
└── 工具结果
    └── 上一次工具调用的返回数据

Context 的组装

javascript 复制代码
const messages = [
  // 1. System 提示:全局人设和规则
  { role: 'system', content: '你是小红书美妆专家...' },

  // 2. 知识库注入:RAG 检索结果
  { role: 'system', content: '相关知识:口红成分...' },

  // 3. 对话历史:用户和模型的交互
  { role: 'user', content: '我想买一支口红' },
  { role: 'assistant', content: '推荐您试试...' },

  // 4. 当前用户输入
  { role: 'user', content: '有没有更便宜的?' }
];

3.4 Loop Engineering:自动化迭代

markdown 复制代码
Context Engineering + 自动化循环 = Loop Engineering

设定目标 + 规则
    │
    ▼
AI 生成(基于 Context)
    │
    ▼
自动校验(基于规则)
    │
    ▼
不达标 → 更新 Context → 再生成
    │
    ▼
达标 → 输出结果

🎯 三代进化:Prompt(单点优化)→ Context(系统记忆)→ Loop(自动迭代)


四、chatHistory 的隐患与优化

4.1 chatHistory 的问题

javascript 复制代码
const chatHistory = [
  { role: 'system', content: '...' },      // 50 tokens
  { role: 'user', content: '你好' },        // 10 tokens
  { role: 'assistant', content: '...' },    // 200 tokens
  { role: 'user', content: '...' },         // 50 tokens
  { role: 'assistant', content: '...' },    // 200 tokens
  // ... 100 轮对话后 ...
];

// 总 tokens:可能超过 10000+
// 费用:按 token 计费,越来越贵
// 性能:上下文太长,模型处理变慢

三大问题

问题 影响 解决方案
Token 开销大 费用增加 精简历史、摘要总结
上下文窗口限制 超出模型最大长度 滑动窗口、分段处理
信息噪声 久远信息干扰当前判断 LRU 淘汰、重要性筛选

4.2 LRU 淘汰策略

markdown 复制代码
LRU(Least Recently Used):最近最少使用

对话历史:
  [系统提示, 用户1, 助手1, 用户2, 助手2, ..., 用户100, 助手100]
     ↑                                              ↑
   最早(可删除)                              最近(保留)

策略:
  - 保留最近的 N 轮对话
  - 删除久远的历史
  - 系统提示和关键信息始终保留
javascript 复制代码
// 简单的 LRU 实现
function trimHistory(history, maxRounds = 10) {
  // 保留 system 提示
  const systemMessages = history.filter(m => m.role === 'system');

  // 保留最近 maxRounds 轮对话
  const recentMessages = history
    .filter(m => m.role !== 'system')
    .slice(-maxRounds * 2);  // 每轮包含 user + assistant

  return [...systemMessages, ...recentMessages];
}

4.3 摘要总结策略

css 复制代码
原始历史(100轮):
  用户:你好
  助手:你好,有什么可以帮你的?
  用户:我想买口红
  助手:推荐您试试...
  ...(97轮)...
  用户:有没有更便宜的?

摘要后(3条):
  [系统提示]
  [摘要:用户想买口红,预算有限,已推荐3款]
  [用户:有没有更便宜的?]
javascript 复制代码
async function summarizeHistory(history) {
  const response = await client.chat.completions.create({
    model: 'deepseek-v4-flash',
    messages: [
      {
        role: 'user',
        content: `请总结以下对话的核心信息,100字以内:
                  ${JSON.stringify(history)}`
      }
    ]
  });

  return response.choices[0].message.content;
}

五、知识图谱

sql 复制代码
LLM 无状态与上下文工程
├── LLM 无状态本质
│   ├── HTTP 调用 + 算力生成
│   ├── 无状态设计原因(高并发/水平扩展)
│   ├── HTTP 无状态协议
│   └── Cookie/Authorization 的区别
├── 实战演示
│   ├── 无状态:AI 失忆(两次独立请求)
│   └── 有记忆:chatHistory 维护上下文
├── 三代工程化进化
│   ├── Prompt Engineering(提示词工程)
│   │   └── 抽卡式,质量不可控
│   ├── Context Engineering(上下文工程)
│   │   ├── chatHistory 对话历史
│   │   ├── RAG 知识库
│   │   ├── claude.md / agent.md 配置
│   │   └── 实时信息注入
│   └── Loop Engineering(循环工程)
│       └── 生成 → 校验 → 自动迭代
├── chatHistory 优化
│   ├── Token 开销问题
│   ├── LRU 淘汰策略
│   └── 摘要总结策略
└── 核心洞察
    └── "LLM 不记忆,我们帮它记住"

六、总结

本文深入解析了 LLM 的无状态本质与上下文工程:

  1. LLM 是无状态的:每次 API 调用都是独立的 HTTP 请求,服务器不存储任何对话状态。
  2. HTTP 协议本身无状态:网站通过 Cookie/Session 维持状态,但 LLM API 的 Authorization 只用于认证,不恢复对话。
  3. 让 LLM"记得"的唯一方法 :每次请求时手动带上完整的 messages 数组(对话历史)。
  4. 从 Prompt 到 Context 到 Loop:Prompt Engineering(单点优化)→ Context Engineering(系统记忆)→ Loop Engineering(自动迭代)。
  5. chatHistory 的隐患:Token 开销大、上下文窗口限制、信息噪声。需要通过 LRU 淘汰、摘要总结等策略优化。
  6. 核心洞察:LLM 不记忆,我们帮它记住。Context Engineering 的本质是"在每次请求时,把该记得的都带上"。

🚀 学习建议:理解无状态本质后,在设计 AI 应用时,始终问自己:"这次请求带上了足够的上下文吗?" 同时注意控制 Token 开销,避免历史无限膨胀。


参考资源


📌 标签:#LLM #无状态 #Stateless #ContextEngineering #PromptEngineering #chatHistory #Token优化 #AI架构

💬 互动:你在维护对话历史时遇到过哪些坑?Token 开销如何控制?欢迎在评论区分享!

相关推荐
智泊AI2 小时前
AI大模型到底是怎么训练出来的?完整预训练过程一次性讲明白!
llm
嘻嘻仙人9 小时前
Python 开发者的性能革命:为什么你应该从 pip 转向 uv?
llm·agent
universeplayer9 小时前
我给 AI Agent 装了个飞机黑匣子:录下每一次 LLM 调用,崩了能确定性回放
llm·agent
JieE2129 小时前
从"无状态"到"懂你":深入理解 LLM 对话的本质,以及 Prompt/Context/Loop 三层工程进化之路
人工智能·llm·ai编程
Lkstar10 小时前
Function Calling 原理深度拆解:让 LLM 调用外部工具的机制与工具设计原则
人工智能·llm
Hyyy1 天前
token是什么?为什么大模型会有上下文长度的限制
程序员·llm·ai编程
阿里云云原生1 天前
软件工程领域 LLM 驱动的自迭代知识引擎
llm
吴佳浩1 天前
Hermes Agent 连环 400 真凶找到了:一个 call_id 让人炸毛
人工智能·llm·agent
武子康2 天前
调查研究-186 LangChain 和 LangGraph 的区别:从快速构建 Agent 到生产级工作流编排
人工智能·langchain·llm