第 15 章:记忆与上下文:分层的信度管理
15.1 工程问题的本质:智能体记得什么、信多少
智能体的回答质量不仅取决于"这次输入了什么",更取决于"它记得什么、记得多新、记得多准"。会话内的上下文(短期记忆)决定连贯性,跨会话的知识(长期记忆)决定专业性,而记忆的信度决定输出是否可信——记错的长期记忆比没有记忆更危险。
设计原理: 记忆管理的本质是信度分层:不同来源、不同时效的信息,在生成上下文中的可信度不同。短期记忆(会话内)可信度高但时效短;训练数据(已审批样本)可信度受治理约束;知识库(RAG 语料)可信度取决于采集与标注。系统应当知道每一条进入上下文的记忆来自哪里、何时产生、可信几何——这是"记忆分层"比"记忆堆叠"先进的地方。
15.2 短期记忆与上下文窗口:有限的分配
短期记忆承载当前会话的上下文,而模型的上下文窗口有限。窗口管理策略:最相关的信息保全文,久远的信息做摘要,控制每次调用的输入长度与成本。
原理: 上下文窗口的分配是一个信度与保真度的权衡:全文保真度高但占窗口,摘要省窗口但丢细节。策略是"旧的做摘要、新的保全文"——按新旧递减保真度,因为越近的信息通常越相关。框架层控制注入量(最近 2 轮全文),业务层提供裁剪/摘要后的完整历史——窗口分配是分层责任,框架保证"不会超窗",业务决定"留下什么"。
15.3 长期记忆:训练数据与知识库的分工
长期记忆分两类:训练数据(影响行为——意图识别、few-shot、规则)与知识库(影响内容——问答的语料来源)。二者解耦:审批留言等过程性数据不进入知识库。
原理: 行为记忆与内容记忆的职责不同,污染的成本不同。过程性数据(审批留言、会话噪音)进入知识库会污染检索结果——用户问"报销怎么走",检索到一段审批过程中的闲聊,答案质量即下降。记忆的写入必须有准入:行为记忆走审批治理(第 14 章),内容记忆走知识库准入——不是所有对话都能成为知识[41]。
15.4 知识检索(RAG):内容记忆的检索增强
RAG 将知识库检索结果注入生成上下文[33],缓解幻觉、突破训练数据边界。在框架中,查询流程的 kb 步骤与 db / web / llm 步骤自由组合,检索结果与事实查询结果经变量引用注入生成步骤。
原理: RAG 与事实查询的信度不同:数据库查询结果是确定的业务事实,知识库检索结果是语义相关的资料。将两者分步骤呈现(见 8.4 节"事实与观点的分离"),用户才能区分"系统查到的"与"系统查到的资料里推断的"。记忆注入生成,但不能冒充事实。
15.5 记忆的时效性与窗口分配原理
15.5.1 遗忘曲线与摘要时机
窗口管理"旧的做摘要、新的保全文",但"多旧算旧"需要原理支撑。摘要时机(历史超 20 条触发、窗口超限裁剪)对应的是一类信息价值衰减假设:对话信息的即时价值随轮次间隔递减。这一假设与记忆研究的"遗忘曲线"同构——越久远的信息,保留其完整形态的边际价值越低。
原理: 窗口分配的目标函数是"信息价值 / token 成本"最大化。全文保留近期(价值高、占窗口),摘要化久远(价值衰减、省窗口),丢弃超出窗口的(价值趋零)。工程要点是阈值可配置:会话保留条数、token 上限、摘要触发点均参数化——遗忘的速度由业务场景决定(技术支持会话比闲聊需要更长的全文窗口)。
15.5.2 token 估算的工程权衡
窗口管理依赖 token 估算。估算方法的取舍,见表 15-1:
表 15-1 token 估算方法对比
| 估算方法 | 精度 | 成本 | 适用 |
|---|---|---|---|
| 字符长度比例 | 低(中英混合偏差大) | 零 | 快速粗裁 |
| CJK 按字 / 拉丁按词的启发式 | 中 | 零 | 框架默认(CJK 1 字 ≈ 1 token) |
| 调用分词器 | 高(与模型一致) | 有 | 精度敏感场景 |
原理: 估算精度与成本成正比,而窗口管理的目标不是"精确等于上限",而是"显著不超限"。启发式估算的误差在 ±20% 内即可安全裁剪(留出缓冲);只有预算极紧或超长文本场景才值得调用分词器。工程取舍:用零成本的近似换取够用的安全边界。
15.5.3 记忆的时效标注
信度分层的落点是时效标注:进入上下文的每条记忆都应能回答"何时产生、来自哪里"。会话内消息天然带时间戳;训练样本带审批时间;知识库条目带录入时间。时效标注的价值:系统(及审计者)能判断"这条记忆是否已过期"——库存数据昨天的结论,今天不应作为事实引用。
15.6 失败模式与权衡
表 15-2 记忆与上下文的失败模式与权衡
| 失败形态 | 根因 | 设计应对 |
|---|---|---|
| 上下文超窗截断关键信息 | 历史注入无上限 | token 估算 + 窗口裁剪 |
| 记忆污染知识库 | 过程性数据入库 | 记忆写入准入 |
| 检索结果冒充事实 | 资料与事实混在一起呈现 | db/kb 分步骤呈现 |
| 长期记忆失效 | 训练样本未审批 | 样本审批治理 |
权衡: 窗口裁剪以"久远信息降保真"为代价,换取不超窗与成本可控;记忆准入以"学习更慢"为代价,换取知识库质量。两个权衡的调节权都在配置层——窗口参数、知识库准入规则均可配置化。
15.7 实现剖析:实体索引的信度升降与学习闭环
本节从 runtime/entity_index.py(508 行)摘取两段,印证 15.3 节的"长期记忆"与 15.5 节的"记忆时效性"。
在展开代码之前,先把 15.2~15.5 节的分层记忆落成表族关系,可看清"短期 / 摘要 / 长期"三层各自的载体,以及长期记忆"提案—审批—转正"的写入路径,如图 15-1 所示:
图 15-1 分层记忆的表族关系与信度写入路径
erDiagram
CONVERSATIONS ||--o{ CONVERSATION_MESSAGES : "包含轮次"
CONVERSATIONS ||--o| CONVERSATION_SUMMARIES : "超阈值后摘要化"
CONVERSATIONS ||--o{ CONVERSATION_CORRECTIONS : "产生纠正"
CONVERSATION_CORRECTIONS ||--o{ LEARNED_ENTITY_CANDIDATES : "扫描生成候选"
LEARNED_ENTITY_CANDIDATES ||--o| SEMANTIC_ENTITY_INDEX : "人工审批后转正"
CONVERSATIONS {
string session_id PK
string user_id
datetime updated_at
}
CONVERSATION_MESSAGES {
bigint id PK
string session_id FK
string role
text content
datetime created_at
}
CONVERSATION_SUMMARIES {
string session_id PK
text summary
int covered_turns
}
CONVERSATION_CORRECTIONS {
bigint id PK
string session_id FK
string term
string entity_type
}
LEARNED_ENTITY_CANDIDATES {
bigint id PK
string entity_type
string term
string status
text evidence
}
SEMANTIC_ENTITY_INDEX {
bigint id PK
string entity_type
string term
float weight
string status
datetime updated_at
}
读图要点: 三层记忆对应三组表——CONVERSATION_MESSAGES(短期·全文保留)、CONVERSATION_SUMMARIES(摘要·超阈值后生成)、SEMANTIC_ENTITY_INDEX(长期·跨会话复用)。
关键路径在最下方两段关系:纠正信号不直接写正式索引,而是先落 LEARNED_ENTITY_CANDIDATES(status='pending'),经人工审批后才成为 SEMANTIC_ENTITY_INDEX 中的 active 行——与 14.2 节"记录与生效分离"同构。
SEMANTIC_ENTITY_INDEX.weight 即 15.7.1 节可升可降的信度数值,其上下界由 cap / floor 约束。
15.7.1 信度是"可升可降"的数值,而非一次性判定
def increase_entity_weight(self, terms, entity_type):
"""画像反馈:命中实体权重上调(weight = LEAST(weight*1.1, 5.0))"""
db = self._resolve_db()
if db is None or not terms:
return
for term in terms:
db.execute(
"UPDATE semantic_entity_index "
"SET weight = LEAST(COALESCE(weight, 1.0) * :factor, :cap), "
" updated_at = NOW() "
"WHERE entity_type = :etype AND lower(term) = lower(:term)",
{"factor": 1.1, "cap": 5.0, "etype": entity_type, "term": term})
def decay_weights(self, max_idle_days=90, factor=0.9, floor=0.1):
"""权重衰减:updated_at 超过 max_idle_days 的实体 weight *= factor"""
db = self._resolve_db()
if db is None:
return 0
res = db.execute(
"UPDATE semantic_entity_index "
"SET weight = GREATEST(COALESCE(weight, 1.0) * :factor, :floor), "
" updated_at = NOW() "
"WHERE status = 'active' "
" AND updated_at < NOW() - make_interval(days => :days) "
" AND COALESCE(weight, 1.0) > :floor",
{"factor": factor, "floor": floor, "days": int(max_idle_days)})
return int(getattr(res, "rowcount", 0) or 0)
这段代码印证 15.5 节:记忆的"信度"被实现为一个带上下界的数值。命中即 weight = 1.1(上限 5.0),长期不用则 weight = 0.9(下限 0.1)——两个方向都设了边界,因此权重不会无限膨胀,也不会归零丢失。上调走 LEAST 封顶、下调走 GREATEST 兜底,且只在 status='active' 上衰减。
周期任务调用 decay_weights 使"久未使用的实体自然退居次要",这比"手工清理"更符合记忆的自然衰减规律。信度是可计算、可调参(factor/cap/floor)的事实,而非模糊判断。
15.7.2 学习闭环:候选提案不直接写正式索引
def propose_from_corrections(self, limit=50):
"""学习闭环:扫描 conversation_corrections(L1 纠正信号)生产
learned_entity_candidates(status='pending',携带 evidence 举证),
供人工审批后转正——不直接写正式索引。同名 (entity_type, term) 且
status=pending 已存在则跳过(无游标、幂等)。"""
db = self._resolve_db()
if db is None:
return []
# ... 扫描纠正信号 → 生成候选(pending)→ 人工 approve_candidate 转正
这段代码印证 15.3 节:长期记忆的写入同样是"提案—审批—转正"三步,而非自动写入。用户的每次纠正(conversation_corrections)只生成 status='pending' 的候选并附带证据(evidence),须经人工 approve_candidate 才进入正式索引(第 14 章"可训练边界")。
幂等设计(同 (entity_type, term) 的 pending 已存在则跳过、无游标)保证重复扫描不会产生重复候选。这条闭环使长期记忆既能从使用中生长,又不会因噪声纠正而污染——与 15.3 节"训练数据与知识库分工"一致:能自动积累的是候选,能生效的是经治理的知识。
15.8 本章小结
- 记忆管理的本质是信度分层:记得什么、记得多新、信多少;
- 上下文窗口分配是信度与保真度的权衡,旧的做摘要、新的保全文;
- 行为记忆与内容记忆分工,过程性数据不进知识库;
- RAG 注入生成但不能冒充事实,事实与观点分步呈现。