# LLM 记忆系统设计:从上下文窗口到可治理的长期记忆

很多聊天机器人一开始看起来都很聪明,但只要多聊几轮,就会暴露一个核心问题:它不真正记得用户。

用户刚说过的偏好,下一轮又要重复;上周已经确认过的项目背景,今天又从零解释;模型偶尔还能引用一句历史对话,但更多时候是把相关、不相关、过期、冲突的信息一起塞进 Prompt,最后回答变得又慢又不稳定。

LLM 本身没有应用意义上的“记忆”。它每次调用看到的,只有本次请求里的上下文。所谓记忆系统,本质上是应用层围绕模型设计的一套上下文管理能力:该保存什么、保存到哪里、什么时候召回、怎么压缩、怎么更新、怎么删除、怎么避免错误记忆污染后续回答。

如果只是把最近几轮聊天记录拼进 Prompt,那只能算最原始的短期上下文。生产级记忆系统要解决的是更复杂的问题:会话内连续性、跨会话个性化、长期事实积累、任务状态恢复、用户偏好管理、隐私合规和可观测性。

# 记忆不是把所有历史都塞进 Prompt

做 LLM 记忆最常见的误区,是把聊天历史当成一个不断增长的数组,每次调用模型时全部塞进去。

这样做一开始能跑,但很快会遇到几个问题:

  • token 成本线性增长。
  • 上下文窗口被低价值内容占满。
  • 历史里的错误信息会持续污染回答。
  • 用户过期偏好和新偏好互相冲突。
  • 敏感内容被反复带入模型调用。
  • 检索和排障时很难知道哪些内容真正影响了回答。

更合理的做法是把记忆拆成不同层次,每一层有不同生命周期和使用方式。

用户请求
  -> 读取会话状态
  -> 召回相关长期记忆
  -> 组装当前上下文
  -> 调用模型或 Agent
  -> 抽取可保存的新记忆
  -> 校验、合并、写入存储
1
2
3
4
5
6
7

记忆系统不是一个“历史消息列表”,而是一条读写闭环。

# 记忆的三个问题

设计记忆前,先问三个问题。

第一,模型当前这一步需要知道什么?

这决定了读路径。不是所有记忆都要给模型看,只召回和当前任务相关的内容。

第二,这次交互产生了什么值得以后使用的信息?

这决定了写路径。不是所有聊天内容都值得保存,闲聊、一次性上下文、临时草稿、错误推断都不应该自动沉淀。

第三,保存的信息未来如何被更新和删除?

这决定了治理路径。长期记忆如果没有更新、冲突检测和删除机制,很快会变成过期信息的仓库。

这三个问题比具体使用 Redis、Postgres、向量库还是 LangGraph 更重要。技术选型是实现手段,记忆边界才是系统设计的核心。

# 短期记忆:会话内连续性

短期记忆解决的是当前会话里的连续性。

比如用户问:

我现在用 Flask 做后端。
数据库迁移怎么上线?
那回滚呢?
1
2
3

第三句话里的“那”依赖前面的上下文。如果没有短期记忆,模型无法知道用户还在讨论数据库迁移。

短期记忆通常是 thread / session 级别的状态,生命周期和一次会话绑定。它常见包含:

  • 最近 N 轮消息。
  • 当前任务状态。
  • 当前工具调用结果。
  • 临时变量。
  • 当前用户意图。
  • 当前未完成动作。

短期记忆的典型实现方式有三种。

# 滑动窗口

滑动窗口只保留最近 N 轮对话。

优点是简单、稳定、低成本:

def trim_messages(messages: list[dict], max_turns: int = 8) -> list[dict]:
    return messages[-max_turns * 2:]
1
2

适合:

  • 普通客服聊天。
  • 短会话问答。
  • 用户问题上下文不太长。
  • 成本敏感场景。

缺点也明显:一旦关键信息出现在更早的位置,就可能被裁掉。

# 摘要压缩

摘要压缩会把较早历史压缩成一段摘要,再保留最近几轮原文。

长期会话摘要 + 最近 N 轮消息 -> 当前 Prompt
1

适合:

  • 会话较长。
  • 用户持续围绕同一任务推进。
  • 早期背景仍然重要。
  • 不需要逐字保留全部历史。

摘要压缩的问题是信息会损失,而且摘要本身可能带有模型误解。所以摘要要可追踪,最好保存摘要版本、生成时间、覆盖的消息范围和生成模型。

# 状态对象

有些任务不适合靠自然语言历史维持状态,而应该维护结构化状态。

例如报销助手:

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] = []
1
2
3
4
5
6
7
8
9
10

每轮对话更新的是 ExpenseDraft,模型只需要看到当前状态和缺失字段,不需要反复阅读完整聊天历史。

生产里,状态对象比自然语言历史更可靠。凡是涉及表单、流程、审批、下单、配置变更的场景,都应该优先考虑结构化状态。

# 长期记忆:跨会话持续有效

长期记忆解决的是跨会话连续性。

比如用户上周说:

以后回答代码问题时,默认给我 Python 示例。
1

今天用户再问:

怎么实现一个重试装饰器?
1

系统应该能召回这个偏好,而不是要求用户重新说明。

长期记忆的生命周期超过单次会话,通常按用户、租户、项目或组织隔离。常见内容包括:

  • 用户稳定偏好。
  • 项目背景。
  • 常用技术栈。
  • 已确认事实。
  • 长期任务目标。
  • 用户反馈。
  • 过去问题的处理结论。

长期记忆的关键不是“多存”,而是“有选择地存”。越是长期保存,越要谨慎。

# 三类长期记忆

长期记忆可以按用途拆成三类: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"
}
1
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"]
}
1
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"
}
1
2
3
4
5
6
7
8
9
10
11
12

适合保存:

  • 团队工作流。
  • 固定排障步骤。
  • 代码生成规范。
  • 审批流程。
  • 项目约定。

Procedural memory 很像团队规则和操作手册。它对 Agent 很重要,因为 Agent 不只是回答问题,还会执行流程。

# 记忆写入:不要自动相信模型

记忆写入是最容易出问题的地方。

用户说:

我今天临时想用 Go 写一下这个脚本。
1

这不代表用户长期偏好从 Python 变成 Go。如果系统把它写成长期偏好,后面就会持续误导模型。

写入记忆前至少要判断:

  • 这是事实、偏好、事件,还是临时上下文?
  • 信息是否来自用户明确表达?
  • 是否只是模型推断?
  • 是否已经存在相同记忆?
  • 是否和旧记忆冲突?
  • 是否包含敏感信息?
  • 是否需要用户确认?
  • 是否有过期时间?

一个简单的写入流程:

对话内容
  -> 候选记忆抽取
  -> 类型分类
  -> 敏感信息过滤
  -> 冲突检测
  -> 置信度判断
  -> 写入或等待确认
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

生产里不要让模型直接写数据库。模型可以生成候选记忆,但应用层要负责校验、去重、冲突检测和权限控制。

# 记忆读取:只召回当前需要的

记忆读取比写入更常发生,也更影响回答质量。

一个常见读路径:

用户问题
  -> 判断是否需要记忆
  -> 生成检索 query
  -> 从短期状态读取最近上下文
  -> 从长期记忆召回相关条目
  -> 排序、过滤、压缩
  -> 注入 Prompt
1
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);
1
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}"),
])
1
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"
}
1
2
3
4

新输入:

以后我的代码示例都用 TypeScript。
1

这不是新增一条偏好,而是更新旧偏好。

冲突处理可以分几类:

  • 同 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",
    )
1
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
1
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
1
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,
    }
1
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)
1
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 成本一起带进每一次回答。