Skip to content

记忆存储与检索

1. 存储层如何服务 Agent

1.1 背景

记忆抽取后,需要存到可检索、可更新、可审计的系统里。很多原型直接把记忆写入向量库,但长期运行后会遇到权限隔离、重复记忆、事实冲突、过期、召回噪声和来源追溯问题。Agent 记忆存储要同时支持语义召回和结构化过滤。

一个可用的记忆系统通常包含三类索引:向量索引用于语义相似召回,结构化字段用于用户、项目、类型、权限和时间过滤,全文索引用于精确词匹配。单一向量检索很难覆盖所有需求。

1.2 数据模型

json
{
  "id": "mem_001",
  "namespace": "user:liys05/project:liyyro",
  "type": "semantic",
  "content": "liyyro 项目使用 VitePress 构建文档站点。",
  "embedding": [0.01, 0.02],
  "source": {"trace_id": "tr_001", "path": "package.json"},
  "confidence": 0.95,
  "created_at": "2026-06-24T10:00:00+08:00",
  "expires_at": null,
  "tags": ["docs", "vitepress"]
}

namespace 决定隔离边界,type 决定检索和使用方式,source 支持复核,confidenceexpires_at 支持排序和清理。

2. 检索流程

2.1 多阶段检索

先过滤再召回,可以减少跨用户和跨项目泄露。重排时不能只看向量相似度,还要考虑记忆类型、更新时间、置信度和来源可信度。

2.2 检索伪代码

python
def retrieve_memories(query, user_id, project_id, memory_type=None, limit=5):
    filters = {
        "namespace": f"user:{user_id}/project:{project_id}",
        "not_expired": True,
    }
    if memory_type:
        filters["type"] = memory_type

    candidates = vector_store.search(query, filters=filters, limit=30)
    ranked = sorted(
        candidates,
        key=lambda m: (m.score, m.confidence, freshness_score(m)),
        reverse=True,
    )

    return dedupe_and_clip(ranked, limit=limit)

检索函数必须接收用户和项目边界。若只传 query,长期记忆很容易出现权限串扰。

3. 存储方案对比

3.1 方案选择

方案优点局限适用场景
关系型数据库事务、权限、结构化过滤强语义召回需要扩展偏好、事实、审计
向量数据库相似召回方便冲突和权限需要额外设计语义记忆、情节记忆
文档数据库存储灵活查询和一致性要设计复杂元数据
混合检索结构化过滤 + 向量 + 全文实现复杂生产长期记忆

生产系统更常见的是混合方案:关系型数据库保存元数据、权限和来源,向量索引保存 embedding,全文索引支持精确召回。

3.2 记忆进入上下文

检索结果不能原样塞入模型。Runtime 应把记忆压缩为少量上下文片段,并标注类型和来源。

text
相关记忆:
1. [preference][source: tr_12] 用户偏好中文技术写作。
2. [semantic][source: package.json] 当前项目使用 VitePress 构建文档。
3. [reflection][source: eval_07] 修改文档后需要运行 docs:build。

这样的格式能让模型区分偏好、事实和经验。最终回答引用事实时,应优先引用原始来源。

4. 风险控制

4.1 常见失败

失败表现处理方式
召回无关相似文本挤占上下文加类型过滤和重排
重复记忆同一偏好写入多次写入时做近似去重
冲突事实新旧项目状态互相矛盾标记冲突并请求验证
权限串扰读到其他用户记忆namespace 强制过滤
过期污染旧信息影响新任务TTL、版本、最近验证时间

记忆检索要服务任务,不应追求召回越多越好。少量可信、相关、可追溯的记忆更适合进入模型上下文。

参考资料