# LLM 记忆系统设计:从上下文窗口到可治理的长期记忆
很多聊天机器人一开始看起来都很聪明,但只要多聊几轮,就会暴露一个核心问题:它不真正记得用户。
用户刚说过的偏好,下一轮又要重复;上周已经确认过的项目背景,今天又从零解释;模型偶尔还能引用一句历史对话,但更多时候是把相关、不相关、过期、冲突的信息一起塞进 Prompt,最后回答变得又慢又不稳定。
LLM 本身没有应用意义上的“记忆”。它每次调用看到的,只有本次请求里的上下文。所谓记忆系统,本质上是应用层围绕模型设计的一套上下文管理能力:该保存什么、保存到哪里、什么时候召回、怎么压缩、怎么更新、怎么删除、怎么避免错误记忆污染后续回答。
如果只是把最近几轮聊天记录拼进 Prompt,那只能算最原始的短期上下文。生产级记忆系统要解决的是更复杂的问题:会话内连续性、跨会话个性化、长期事实积累、任务状态恢复、用户偏好管理、隐私合规和可观测性。
# 记忆不是把所有历史都塞进 Prompt
做 LLM 记忆最常见的误区,是把聊天历史当成一个不断增长的数组,每次调用模型时全部塞进去。
这样做一开始能跑,但很快会遇到几个问题:
- token 成本线性增长。
- 上下文窗口被低价值内容占满。
- 历史里的错误信息会持续污染回答。
- 用户过期偏好和新偏好互相冲突。
- 敏感内容被反复带入模型调用。
- 检索和排障时很难知道哪些内容真正影响了回答。
更合理的做法是把记忆拆成不同层次,每一层有不同生命周期和使用方式。
用户请求
-> 读取会话状态
-> 召回相关长期记忆
-> 组装当前上下文
-> 调用模型或 Agent
-> 抽取可保存的新记忆
-> 校验、合并、写入存储
2
3
4
5
6
7
记忆系统不是一个“历史消息列表”,而是一条读写闭环。
# 记忆的三个问题
设计记忆前,先问三个问题。
第一,模型当前这一步需要知道什么?
这决定了读路径。不是所有记忆都要给模型看,只召回和当前任务相关的内容。
第二,这次交互产生了什么值得以后使用的信息?
这决定了写路径。不是所有聊天内容都值得保存,闲聊、一次性上下文、临时草稿、错误推断都不应该自动沉淀。
第三,保存的信息未来如何被更新和删除?
这决定了治理路径。长期记忆如果没有更新、冲突检测和删除机制,很快会变成过期信息的仓库。
这三个问题比具体使用 Redis、Postgres、向量库还是 LangGraph 更重要。技术选型是实现手段,记忆边界才是系统设计的核心。
# 短期记忆:会话内连续性
短期记忆解决的是当前会话里的连续性。
比如用户问:
我现在用 Flask 做后端。
数据库迁移怎么上线?
那回滚呢?
2
3
第三句话里的“那”依赖前面的上下文。如果没有短期记忆,模型无法知道用户还在讨论数据库迁移。
短期记忆通常是 thread / session 级别的状态,生命周期和一次会话绑定。它常见包含:
- 最近 N 轮消息。
- 当前任务状态。
- 当前工具调用结果。
- 临时变量。
- 当前用户意图。
- 当前未完成动作。
短期记忆的典型实现方式有三种。
# 滑动窗口
滑动窗口只保留最近 N 轮对话。
优点是简单、稳定、低成本:
def trim_messages(messages: list[dict], max_turns: int = 8) -> list[dict]:
return messages[-max_turns * 2:]
2
适合:
- 普通客服聊天。
- 短会话问答。
- 用户问题上下文不太长。
- 成本敏感场景。
缺点也明显:一旦关键信息出现在更早的位置,就可能被裁掉。
# 摘要压缩
摘要压缩会把较早历史压缩成一段摘要,再保留最近几轮原文。
长期会话摘要 + 最近 N 轮消息 -> 当前 Prompt
适合:
- 会话较长。
- 用户持续围绕同一任务推进。
- 早期背景仍然重要。
- 不需要逐字保留全部历史。
摘要压缩的问题是信息会损失,而且摘要本身可能带有模型误解。所以摘要要可追踪,最好保存摘要版本、生成时间、覆盖的消息范围和生成模型。
# 状态对象
有些任务不适合靠自然语言历史维持状态,而应该维护结构化状态。
例如报销助手:
from pydantic import BaseModel
class ExpenseDraft(BaseModel):
applicant: str | None = None
amount: float | None = None
currency: str = "CNY"
category: str | None = None
invoice_uploaded: bool = False
missing_fields: list[str] = []
2
3
4
5
6
7
8
9
10
每轮对话更新的是 ExpenseDraft,模型只需要看到当前状态和缺失字段,不需要反复阅读完整聊天历史。
生产里,状态对象比自然语言历史更可靠。凡是涉及表单、流程、审批、下单、配置变更的场景,都应该优先考虑结构化状态。
# 长期记忆:跨会话持续有效
长期记忆解决的是跨会话连续性。
比如用户上周说:
以后回答代码问题时,默认给我 Python 示例。
今天用户再问:
怎么实现一个重试装饰器?
系统应该能召回这个偏好,而不是要求用户重新说明。
长期记忆的生命周期超过单次会话,通常按用户、租户、项目或组织隔离。常见内容包括:
- 用户稳定偏好。
- 项目背景。
- 常用技术栈。
- 已确认事实。
- 长期任务目标。
- 用户反馈。
- 过去问题的处理结论。
长期记忆的关键不是“多存”,而是“有选择地存”。越是长期保存,越要谨慎。
# 三类长期记忆
长期记忆可以按用途拆成三类:semantic、episodic、procedural。
# Semantic memory:事实和偏好
Semantic memory 保存稳定事实和偏好。
例如:
{
"user_id": "u_1001",
"type": "preference",
"key": "code_language",
"value": "python",
"confidence": 0.92,
"source": "user_explicit",
"updated_at": "2026-08-06T10:00:00Z"
}
2
3
4
5
6
7
8
9
适合保存:
- 用户偏好。
- 项目技术栈。
- 常用输出格式。
- 已确认业务规则。
- 稳定身份信息。
Semantic memory 应该尽量结构化。不要把一句长文本直接当成事实长期保存,否则后面很难更新、搜索和冲突检测。
# Episodic memory:过去发生过什么
Episodic memory 保存事件和经历。
例如:
{
"user_id": "u_1001",
"event": "debugged_database_migration_failure",
"summary": "用户在 Flask 项目中遇到 Alembic migration head 不一致,最终通过 merge heads 解决。",
"occurred_at": "2026-08-01T15:30:00Z",
"tags": ["flask", "alembic", "migration"]
}
2
3
4
5
6
7
适合保存:
- 某次问题处理过程。
- 用户做过的选择。
- 项目阶段性结论。
- 关键决策记录。
- 已完成任务。
Episodic memory 不一定每次都要召回。它更适合在用户问“上次那个问题怎么处理的”“之前我们定的方案是什么”时,通过语义检索找回来。
# Procedural memory:怎么做事
Procedural memory 保存流程和习惯。
例如:
{
"scope": "team_backend",
"procedure": "database_migration_release",
"steps": [
"先在 staging 执行迁移",
"检查迁移 SQL 是否包含锁表风险",
"先发布兼容旧 schema 的代码",
"再执行向后兼容迁移",
"最后清理旧字段"
],
"updated_at": "2026-08-06T10:00:00Z"
}
2
3
4
5
6
7
8
9
10
11
12
适合保存:
- 团队工作流。
- 固定排障步骤。
- 代码生成规范。
- 审批流程。
- 项目约定。
Procedural memory 很像团队规则和操作手册。它对 Agent 很重要,因为 Agent 不只是回答问题,还会执行流程。
# 记忆写入:不要自动相信模型
记忆写入是最容易出问题的地方。
用户说:
我今天临时想用 Go 写一下这个脚本。
这不代表用户长期偏好从 Python 变成 Go。如果系统把它写成长期偏好,后面就会持续误导模型。
写入记忆前至少要判断:
- 这是事实、偏好、事件,还是临时上下文?
- 信息是否来自用户明确表达?
- 是否只是模型推断?
- 是否已经存在相同记忆?
- 是否和旧记忆冲突?
- 是否包含敏感信息?
- 是否需要用户确认?
- 是否有过期时间?
一个简单的写入流程:
对话内容
-> 候选记忆抽取
-> 类型分类
-> 敏感信息过滤
-> 冲突检测
-> 置信度判断
-> 写入或等待确认
2
3
4
5
6
7
代码上可以先定义候选记忆结构:
from enum import Enum
from pydantic import BaseModel, Field
class MemoryType(str, Enum):
semantic = "semantic"
episodic = "episodic"
procedural = "procedural"
class MemoryCandidate(BaseModel):
memory_type: MemoryType
content: str
reason: str
confidence: float = Field(ge=0, le=1)
source: str
needs_confirmation: bool = False
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
生产里不要让模型直接写数据库。模型可以生成候选记忆,但应用层要负责校验、去重、冲突检测和权限控制。
# 记忆读取:只召回当前需要的
记忆读取比写入更常发生,也更影响回答质量。
一个常见读路径:
用户问题
-> 判断是否需要记忆
-> 生成检索 query
-> 从短期状态读取最近上下文
-> 从长期记忆召回相关条目
-> 排序、过滤、压缩
-> 注入 Prompt
2
3
4
5
6
7
长期记忆不应该无条件注入。尤其是个性化偏好,如果每次都塞进去,会让模型过度迎合历史,甚至影响用户想要探索新方向。
可以按类型决定读取策略:
| 记忆类型 | 读取方式 |
|---|---|
| 最近对话 | 滑动窗口或摘要 |
| 用户偏好 | 按 key 直接读取 |
| 项目背景 | 按 project_id 读取 |
| 过去事件 | 语义检索 |
| 团队流程 | 按任务类型读取 |
| 工具执行状态 | 从结构化 state 读取 |
记忆召回后,还要做上下文预算控制。不要把十几条长期记忆原样塞进 Prompt。可以只注入最相关的 3 到 5 条,并明确告诉模型这些是“用户偏好”还是“历史事件”,避免模型把旧事件当成当前事实。
# 记忆存储怎么选
记忆存储没有唯一答案,取决于数据形态。
| 存储 | 适合 |
|---|---|
| Redis | 短期状态、会话缓存、低延迟读取 |
| Postgres / MySQL | 结构化长期记忆、偏好、流程、审计 |
| 向量库 | 事件回忆、语义检索、相似历史问题 |
| 对象存储 | 原始对话归档、大文本、附件 |
| 图数据库 | 实体关系、人物关系、复杂依赖 |
很多生产系统会混合使用:
- Redis 保存短期会话状态。
- Postgres 保存结构化偏好和记忆元数据。
- 向量库保存可语义召回的历史摘要。
- 对象存储保存原始消息归档。
不要为了“记忆”直接上向量库。向量库擅长相似检索,但不擅长精确更新、冲突检测、权限过滤和结构化查询。用户偏好这种数据,更适合结构化表。
# 一个记忆表设计
长期记忆可以先从一张结构化表开始。
CREATE TABLE agent_memories (
id BIGSERIAL PRIMARY KEY,
tenant_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
namespace VARCHAR(128) NOT NULL,
memory_type VARCHAR(32) NOT NULL,
memory_key VARCHAR(128),
content TEXT NOT NULL,
confidence NUMERIC(4, 3) NOT NULL DEFAULT 1.0,
source VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'active',
expires_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_agent_memories_user_namespace
ON agent_memories (tenant_id, user_id, namespace, status);
CREATE INDEX idx_agent_memories_key
ON agent_memories (tenant_id, user_id, memory_key, status);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
几个字段很关键:
tenant_id:多租户隔离。user_id:用户级记忆边界。namespace:按应用、项目或任务隔离。memory_type:区分事实、事件、流程。memory_key:偏好类记忆可以按 key 更新。confidence:记录置信度。source:区分用户明确表达、系统推断、人工录入。status:支持禁用、删除、等待确认。expires_at:支持过期记忆。
长期记忆必须能更新和删除。只追加不更新,早晚会积累冲突。
# 记忆注入 Prompt 的方式
记忆召回后,不要直接拼一大段“历史如下”。要让模型知道每类信息的可信度和边界。
示例:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
(
"system",
"""
你是一个企业 AI 助手。
请优先遵守当前用户请求。
下面的记忆用于辅助理解用户偏好,不代表当前任务的全部事实。
如果记忆和用户当前输入冲突,以用户当前输入为准。
用户偏好:
{user_preferences}
相关历史事件:
{episodic_memories}
""",
),
("human", "{question}"),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里有几个原则:
- 明确记忆类型。
- 明确当前输入优先级。
- 明确记忆可能过期。
- 不把外部记忆当 system 规则。
- 对敏感记忆谨慎注入。
长期记忆和 RAG 文档一样,都应该被视为外部上下文,而不是最高优先级指令。否则旧记忆可能覆盖当前用户真实意图。
# 冲突检测
长期记忆一定会遇到冲突。
例如旧记忆:
{
"key": "preferred_language",
"value": "Python"
}
2
3
4
新输入:
以后我的代码示例都用 TypeScript。
这不是新增一条偏好,而是更新旧偏好。
冲突处理可以分几类:
- 同 key 偏好:更新旧值。
- 同事件重复:合并或忽略。
- 事实冲突:降低置信度或请求确认。
- 临时偏好:不覆盖长期偏好。
- 高敏信息:不自动保存。
写入前可以先查询同 namespace 下同 key 记忆:
def upsert_preference(memory_store, user_id: str, key: str, value: str):
existing = memory_store.get_by_key(
user_id=user_id,
namespace="assistant_preferences",
key=key,
)
if existing:
memory_store.update(
memory_id=existing.id,
content=value,
source="user_explicit",
)
return
memory_store.create(
user_id=user_id,
namespace="assistant_preferences",
memory_type="semantic",
memory_key=key,
content=value,
source="user_explicit",
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
不要把冲突记忆都留着让模型自己判断。模型可以辅助判断,但系统要有明确的数据治理策略。
# 记忆的生命周期
记忆不是写进去就永远有效。
每条记忆都应该考虑生命周期:
- 什么时候创建。
- 什么时候更新。
- 什么时候过期。
- 谁可以查看。
- 谁可以删除。
- 是否需要用户确认。
- 是否参与模型上下文。
- 是否需要进入审计日志。
比如:
- 用户偏好可以长期保留。
- 某次临时任务背景可以会话结束后过期。
- 工单处理事件可以保留 90 天。
- 敏感信息默认不保存。
- 用户明确要求删除时必须删除或禁用。
生产里最好提供用户可见的记忆管理能力。至少要支持查看、关闭、删除和重新生成相关记忆。用户不知道系统记了什么,就很难建立信任。
# 记忆与隐私
记忆系统天然会存储用户信息,所以隐私是底线。
必须考虑:
- 是否有明确保存目的。
- 是否有用户授权。
- 是否支持删除。
- 是否做租户隔离。
- 是否对敏感字段加密。
- 是否限制员工查看。
- 是否记录访问审计。
- 是否避免把密钥和凭证写入记忆。
尤其不要保存:
- 密码。
- API Key。
- token。
- 身份证、银行卡等高敏信息。
- 未经确认的健康、财务、法律推断。
- 用户只是临时说过的一次性信息。
记忆越强,风险也越高。生产系统要把“少记、准记、可删”作为默认原则。
# 记忆与成本
记忆系统会影响三类成本。
第一是 token 成本。召回太多记忆,会让每次请求更贵。
第二是存储和检索成本。长期对话、向量索引、原始消息归档都会增长。
第三是质量成本。错误记忆进入上下文,会让模型持续输出错误答案。
所以需要几个指标:
- 每次请求注入多少条记忆。
- 记忆 token 占比。
- 记忆召回命中率。
- 用户纠正记忆的次数。
- 记忆导致错误回答的比例。
- 记忆写入通过率。
- 记忆冲突数量。
- 过期记忆清理数量。
没有这些指标,记忆系统很容易变成不可控的黑盒。
# 记忆模块架构
一个比较稳的记忆模块可以拆成五个组件。
MemoryExtractor
从对话里抽取候选记忆
MemoryValidator
做敏感信息过滤、置信度判断、冲突检测
MemoryStore
负责结构化存储、向量存储、更新、删除
MemoryRetriever
根据当前请求召回相关记忆
MemoryInjector
把召回结果按类型和优先级注入 Prompt
2
3
4
5
6
7
8
9
10
11
12
13
14
代码结构可以这样放:
app/
ai/
memory/
extractor.py
validator.py
store.py
retriever.py
injector.py
schemas.py
agents/
assistant.py
prompts/
assistant.py
2
3
4
5
6
7
8
9
10
11
12
13
记忆模块最好不要散落在 Agent 代码里。Agent 只应该调用清晰的记忆接口,而不是自己决定怎么写库、怎么去重、怎么删。
# 一个简化的读写流程
读路径:
def build_context_for_request(
*,
user_id: str,
session_id: str,
question: str,
memory_retriever,
session_store,
) -> dict:
recent_messages = session_store.get_recent_messages(
session_id=session_id,
limit=8,
)
memories = memory_retriever.retrieve(
user_id=user_id,
query=question,
limit=5,
)
return {
"recent_messages": recent_messages,
"memories": memories,
"question": question,
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
写路径:
def update_memory_after_response(
*,
user_id: str,
conversation_turn: dict,
extractor,
validator,
store,
) -> None:
candidates = extractor.extract(conversation_turn)
for candidate in candidates:
decision = validator.validate(
user_id=user_id,
candidate=candidate,
)
if decision.action == "write":
store.upsert(decision.memory)
elif decision.action == "confirm":
store.save_pending(decision.memory)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这两个流程要分开。不要在生成答案前随手写记忆,也不要让记忆写入失败影响用户本次回答。通常更推荐回答完成后异步处理候选记忆。
# 常见记忆模式
# Buffer memory
保存最近 N 轮消息,简单直接。
适合短会话,不适合长期助手。
# Summary memory
把早期对话压缩成摘要,保留最近原文。
适合长会话,但要注意摘要偏差和版本管理。
# Vector memory
把历史事件或摘要写入向量库,按语义召回。
适合找相似历史,但不适合精确偏好和权限规则。
# Entity memory
围绕实体保存事实,例如用户、项目、系统、订单。
适合企业场景,尤其是多项目、多对象、多关系的助手。
# Preference memory
保存用户稳定偏好,例如语言、格式、技术栈、交互风格。
适合个性化,但要支持用户查看和修改。
# Procedural memory
保存固定流程,例如发布步骤、排障流程、团队规范。
适合垂类 Agent,因为 Agent 不只是聊天,还要按组织流程做事。
# 生产 Checklist
上线前至少检查这些点:
- 是否区分短期记忆和长期记忆。
- 是否有明确 namespace。
- 是否按用户、租户、项目隔离。
- 是否有记忆写入白名单。
- 是否过滤敏感信息。
- 是否支持更新和删除。
- 是否有过期策略。
- 是否有冲突检测。
- 是否能追踪某次回答使用了哪些记忆。
- 是否限制每次注入的记忆数量。
- 是否记录记忆命中率和错误率。
- 是否提供用户可见的记忆管理能力。
这些不是锦上添花。没有这些治理能力,记忆系统越强,长期风险越大。
# 小结
LLM 记忆不是把历史对话塞进 Prompt,而是应用层的一套上下文读写系统。
短期记忆负责会话内连续性,长期记忆负责跨会话的偏好、事实、事件和流程。真正可维护的记忆系统,需要有抽取、校验、存储、召回、注入、更新、删除和审计。
生产里最重要的原则是:少记、准记、可控、可删。只有这样,记忆才会让 AI 助手变得更可靠,而不是把旧错误、隐私风险和 token 成本一起带进每一次回答。