AI的"记忆"是有代价的------Token管理艺术
内存不崩了,但接口开始报错
400 context_length_exceeded。这一篇深入大模型上下文窗口原理,对比滑动窗口、摘要压缩与结构化存储三种方案。
01. 场景还原:AI突然"失忆"
用户反馈:"AI刚开始还挺聪明,聊到第50轮的时候,我提到20分钟前让它记住的一个账号,它居然问我'什么账号?'"
检查后台日志,发现调用OpenAI API时返回了错误:
json
{
"error": {
"message": "This model's maximum context length is 16385 tokens.
You requested 21000 tokens.",
"type": "invalid_request_error"
}
}
根本原因 :Transformer架构的注意力机制计算复杂度为O(n²),所以每个模型都有硬性的上下文窗口限制。我们前端传了太多历史消息,直接把Prompt撑爆了。
02. 方案对比与决策矩阵
| 策略 | 原理 | 信息保留度 | 额外成本 | 适用场景 |
|---|---|---|---|---|
| 滑动窗口 | 只保留最近N轮,丢弃更早的 | ⭐⭐ | 零成本 | 短平快问答、客服 |
| 摘要压缩 | 用AI将历史浓缩为摘要 | ⭐⭐⭐⭐ | 额外LLM调用 | 长文档分析、复杂推理 |
| 结构化记忆 | 提取实体/意图存入向量库 | ⭐⭐⭐⭐⭐ | 需向量数据库 | 个性化助手、Agent |
03. 方案一:滑动窗口(低复杂度)
前端维护一个滑动窗口,发送请求时只取最后MAX_ROUNDS轮:
typescript
const MAX_ROUNDS = 10; // 保留最近10轮(20条消息)
function buildPrompt(allMessages: Message[]): Message[] {
if (allMessages.length <= MAX_ROUNDS * 2) {
return allMessages;
}
// 裁剪
const trimmed = allMessages.slice(-MAX_ROUNDS * 2);
// 【致命缺陷】用户问"我们之前说的那个方案是什么?"------AI根本不知道"之前"是什么
return trimmed;
}
致命伤:如果用户在第50轮问"最开始你建议的方案是什么?",AI早就把第1轮的方案丢了。
04. 方案二:摘要压缩(中高复杂度)------ 推荐实战
核心流程:
- 触发阈值:当对话轮次超过15轮,或估算Token超过总窗口的70%时触发。
- 压缩动作:调用LLM(可用更便宜的模型,如GPT-3.5-turbo)生成历史摘要。
- 上下文重构 :后续请求 =
[System: 历史摘要]+最近5轮完整对话。
Prompt工程模板:
typescript
const SUMMARY_PROMPT = `
你是一个对话摘要助手。请将以下对话历史压缩成一段200字以内的摘要。
严格要求:
1. 保留用户的核心目标/问题。
2. 保留AI给出的关键结论和行动建议。
3. 保留所有重要的实体(日期、金额、人名、账号、代码逻辑)。
4. 丢弃寒暄、重复确认等冗余信息。
对话历史:
{history}
摘要:
`;
// 在代码中触发
async function compressHistory(messages: Message[]): Promise<string> {
// 只压缩非系统消息的前半部分(较老的历史)
const toCompress = messages.slice(0, -6);
const recent = messages.slice(-6);
const summary = await callLLM({
messages: [
{ role: 'system', content: SUMMARY_PROMPT.replace('{history}',
toCompress.map(m => `${m.role}: ${m.content}`).join('\n'))
}
],
max_tokens: 300,
temperature: 0.3,
});
// 新架构:摘要 + 最近消息
return {
summary,
recent,
totalTokens: estimateTokens(summary) + estimateTokens(recent),
};
}
成本考量:每压缩一次,多消耗约15%的LLM计算资源(但远低于传输全量历史的Token成本)。设置压缩间隔,不要每轮都压。
05. 方案三:结构化记忆(混合记忆架构)
对于助手类应用,我们需要长期记忆。可以引入向量数据库(如Pinecone、Milvus)或简单的关系型存储:
typescript
// 提取结构化信息
interface MemoryEntity {
type: 'user_preference' | 'fact' | 'task';
key: string;
value: string;
timestamp: number;
}
// 每次对话后,调用结构化提取函数
async function extractMemory(messages: Message[]): Promise<MemoryEntity[]> {
const extractionPrompt = `
从最近的对话中提取结构化记忆,以JSON数组返回:
[
{ "type": "fact", "key": "user_name", "value": "张三" },
{ "type": "user_preference", "key": "coding_language", "value": "Python" }
]
对话内容:${messages.slice(-4).map(m => m.content).join('\n')}
`;
// 调用LLM提取...
}
最终发送给大模型的上下文变为:System: 摘要 + 结构化记忆 + 最近3轮对话。
06. 关键:用户体验透明化
必须在前端界面提示用户"上下文截断"状态,否则用户会觉得AI变傻了。
tsx
<div className="context-hint">
<InfoIcon />
<span>当前对话已超过{MAX_ROUNDS}轮,AI仅保留最近{MAX_ROUNDS}轮的完整记忆。
<a onClick={showFullHistory}>查看完整历史</a>
</span>
</div>
📖 下一篇预告(第04篇)
刷新不丢、断线能续------持久化与恢复机制
场景预告:用户在地铁里信号断断续续,AI说了半句话就卡住了;用户手滑F5,聊了半小时的对话全部消失。。
核心看点:IndexedDB+Dexie.js封装 / SSE原生Last-Event-ID断线续传 / 指数退避重连算法 / 归一化状态(entities+order)+ Valtio精准重渲染
👋 下一篇更精彩 ,关注我抢先看!
你的「点赞 👍 + 收藏 ⭐」是对技术创作者最好的鼓励!