让 AI 记住你是谁:从 HTTP 无状态到对话历史
每次调用大模型,它都不记得你上一句说了什么------这不是 bug,是特性。
一、调用大模型接口的本质是什么?
先看一段代码:
js
const response = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: [
{ role: "user", content: "我叫零零发" }
]
})
看起来挺简单?但如果你紧接着再问一句:
js
const response2 = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: [
{ role: "user", content: "我叫什么名字?" }
]
})
猜猜大模型会怎么回答?
抱歉,我无法知道您的名字。
它忘了。刚说完就忘。
这不是 AI 智商不够,而是底层协议决定的------HTTP 是无状态的。
二、什么是无状态?
调用大模型接口的本质,就是发一个 HTTP 请求,收一个 HTTP 响应。
HTTP 协议的设计哲学是:每次请求都是独立的,服务器不记得你之前来过。
你第一次请求:我叫零零发
服务器:好的,零零发(处理完就忘)
你第二次请求:我叫什么名字?
服务器:???第一次见你啊(完全没印象)
听起来不近人情,但这是后端架构的核心设计:
| 有状态 | 无状态 | |
|---|---|---|
| 服务器 | 要记住你的信息 | 处理完就忘 |
| 并发 | 只能固定在某一台服务器 | 任意服务器都能处理 |
| 容灾 | 服务器挂了状态丢失 | 随便换台机器继续干 |
对于大模型来说,无状态是必须的。每天几亿次请求,如果服务器要记住每个人的全部对话历史,内存早爆了。
三、怎么让 AI 记住你?
既然大模型不主动记,那我们就手动维护对话历史,每次请求把全部对话都带上。
思路很简单:用一个数组存着所有消息,每次请求把整个数组传进去。
js
// 对话历史
const chatHistory = [
{ role: "system", content: "你是一个严谨的助手" }
]
async function main() {
// 第一次请求
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)
// 输出:好的,零零发,我已经记住了你的名字。
// 第二次请求
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)
// 输出:您的名字是零零发。
}
运行结果:
第一次请求:请记住,我的名字叫零零发
模型回复:好的,零零发,我已经记住了你的名字。
第二次请求:请问我的名字是什么?
模型回复:您的名字是零零发。
这次它记住了。因为第二次请求时,messages 里包含了完整的对话记录:
css
[ { role: "system", content: "你是一个严谨的助手" }, { role: "user", content: "请记住,我的名字叫零零发" }, { role: "assistant", content: "好的,零零发..." }, { role: "user", content: "请问我的名字是什么?" }]
四、对话历史越长越烧钱
手动维护历史解决了"记住"的问题,但带来了新问题------messages 越来越大。
每轮对话,你不仅要发新的问题,还要把之前所有的对话都发一遍。对话越长,token 消耗越大。
第 1 轮: 3 条消息
第 10 轮: 21 条消息
第 100 轮:201 条消息
每一条消息都在烧 token,也就是在烧钱。
怎么解决?
常见策略就是 LRU 淘汰------保留最近在聊的,删掉久远的。
js
const MAX_HISTORY = 20 // 只保留最近 20 条
function addMessage(history, msg) {
history.push(msg)
if (history.length > MAX_HISTORY) {
// 保留 system 提示词,删掉最早的用户/助手消息
return [history[0], ...history.slice(-MAX_HISTORY + 1)]
}
return history
}
除了 LRU,还有几个方向:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 滑动窗口 | 只保留最近 N 轮对话 | 通用聊天 |
| 摘要压缩 | 把历史对话总结成一段话 | 长对话任务 |
| RAG 检索 | 从知识库里召回相关片段 | 问答系统 |
| 分层记忆 | 短期记忆(最近)+ 长期记忆(摘要) | 复杂 Agent |
五、总结
| 概念 | 一句话 |
|---|---|
| 无状态 | 每次请求独立,服务器不记你 |
| HTTP 的本质 | 请求 - 响应,处理完就忘 |
| 手动维护历史 | 用数组存全部对话,每次带上 |
| Token 膨胀 | 历史越长,花钱越多 |
| LRU 淘汰 | 删久远的,留最近的 |
互动提问:你在用 AI API 时有没有遇到过"明明刚才说过,它却不记得"的情况?你是怎么解决的?评论区聊聊。