欢迎光临
我们一直在努力

Agent记忆模块系列:01架构设计

Dream-SaaS 记忆模块架构复盘,ADR 全记录


0. 背景:Agent 最后缺的那块板——记忆

Dream-SaaS 多 Agent 平台开发中遇到一个很现实的问题:

代码审查 Agent 第一次审完代码,用户说"以后安全相关的多关注",第二次对话 Agent 忘了。同样的需求说了三遍。

Graph 编排解决了,RAG 解决了文档检索,但记住用户是谁、记住用户偏好这块——没有现成方案。自己写,核心三条诉求:

  • Spring 生态原生接入,不要跨语言
  • 按业务分层设计记忆单元,不能套固定模板
  • HITL 机制,LLM 提取的记忆要经过确认,不能直接写进去

  • 1. 记忆的三个层次:不是越多越好

    Agent 的记忆需求其实分三层,这三层解决的问题完全不同:

    ┌─────────────────────────────────────────────────────────┐
    │ Agent 的记忆需求 │
    ├────────────────┬──────────────────┬────────────────────┤
    │ 对话窗口 │ 长期结构化记忆 │ 语义知识记忆 │
    │ ChatMemory │ Structured KV │ Vector Embedding │
    ├────────────────┼──────────────────┼────────────────────┤
    │ 最近 N 轮对话 │ 规则/事实/状态 KV │ embedding 语义检索 │
    │ 窗口滑动过期 │ 精确读写,按 ID │ 模糊匹配,topK 返回 │
    ├────────────────┴──────────────────┴────────────────────┤
    │ Dream-SaaS 记忆模块覆盖:L1 + L2 │
    │ Spring AI ChatMemory 覆盖:L3(对话窗口) │
    └─────────────────────────────────────────────────────────┘

    L1 结构化记忆(StructuredMemoryService) — 回答"用户是谁、规则是什么"

    • rules:行为准则。比如"回复用中文"、"代码审查优先看安全性"
    • facts:客观事实。比如"用户是 Java 开发者"、"正在做 Dream-SaaS 项目"
    • status:当前状态 KV。比如 current_project=dream-saas、mood=tired

    L2 向量语义记忆(VectorMemoryService) — 回答"之前讨论过什么相关的"

    • 语义检索:"之前讨论过 K8s 部署的内容吗?"
    • 跨对话积累:用户和 Agent 聊过多次 Spring AI,不需要每次重新说明上下文

    L3 对话窗口(Spring AI ChatMemory) — 回答"最近几轮聊了什么"

    • 这是 Spring AI 自带的,不是我们做的
    • 纯消息历史,滑动窗口,超出窗口就"忘了"

    2. 核心设计:双层接口 + 双套实现

    dream-ai-agent-memory/
    ├── dream-ai-agent-memory-api ← 接口层,对业务透明
    │ ├── StructuredMemoryService ← L1 接口
    │ ├── VectorMemoryService ← L2 接口
    │ ├── MemorySnapshot ← 返回模型(不可变快照)
    │ └── MemoryPendingItem ← HITL 待确认条目
    ├── dream-ai-agent-memory-pgvector ← 生产实现(推荐)
    │ ├── PgStructuredMemoryService ← L1 PG 持久化
    │ ├── PgVectorMemoryService ← L2 pgvector 向量检索
    │ ├── MemoryExtractService ← LLM 自动提取 + HITL
    │ └── metrics/MemoryMetrics ← Micrometer 监控
    ├── dream-ai-agent-memory-redis ← 降级实现(热缓存场景)
    │ ├── RedisStructuredMemoryService ← L1 Redis(TTL 自动过期)
    │ └── RedisVectorMemoryService ← L2 降级(无真正向量)
    └── dream-ai-agent-memory-mcp ← MCP 工具暴露层
    └── MemoryMcpContributor ← 暴露为 MCP 工具(read/search/store)

    为什么分层

    接口与实现分离,业务代码永远只依赖 StructuredMemoryService / VectorMemoryService 两个接口。切换存储引擎只需要改一行配置:

    # 切 PGvector
    ai.memory.pgvector.enabled: true

    # 切 Redis 降级
    ai.memory.pgvector.enabled: false
    ai.memory.redis.enabled: true

    @ConditionalOnProperty 自动装配,零代码改动。


    3. L1 StructuredMemoryService:三种单元的 CRUD

    记忆模型:MemorySnapshot

    get(ownerId) 返回一个不可变快照:

    MemorySnapshot snapshot = structuredMemory.get("author");
    // snapshot.rules → List<MemoryRule>
    // snapshot.facts → List<MemoryFact>
    // snapshot.status → Map<String, String>
    // snapshot.pending → List<MemoryPendingItem> // 待确认

    注入 Agent 的方式很简单:

    String systemPrompt = """
    你是一个 AI 搭子,用户信息如下:
    规则:%s
    事实:%s
    当前状态:%s
    """.formatted(
    snapshot.ruleContents(),
    snapshot.factContents(),
    snapshot.status()
    );

    三种单元的定位差异

    单元语义典型场景修改频率
    rules 用户明确要求的行为准则 "回复时先说结论再说原因"
    facts 关于用户/环境的客观事实 "用户是 Java 开发者"
    status 当前工作的上下文状态 project=dream-saas

    HITL 待确认机制

    LLM 自动提取记忆时,不是直接写入,而是走 HITL(Human-in-the-Loop)三色阈值:

    置信度 ≥ 0.82 → 直接写入(高置信度)
    置信度 ≥ 0.55 → 入队待用户确认(中等置信度)
    置信度 < 0.55 → 跳过(低置信度不写入)

    为什么这样设计?防幻觉。LLM 有时会"创造"用户没说过的事实,不经过确认就写入会影响 Agent 长期行为。


    4. L2 VectorMemoryService:混合检索的三信号融合

    为什么需要混合检索

    纯向量检索有个问题:语义相关但用词不同可能漏掉(比如用户说"部署"而记忆中写的是"上线"),纯关键词检索则无法处理同义词扩展。解决方案:三个信号加权融合

    hybrid_score = 0.6 × vector_score + 0.3 × keyword_score + 0.1 × time_score

    信号来源权重解决什么问题
    vector_score pgvector cosine 相似度 0.6 语义理解("K8s"≈"Kubernetes")
    keyword_score ILIKE 关键词匹配率 0.3 精确召回("部署"必须匹配"部署")
    time_score 1/(1+days/30) 指数衰减 0.1 新记忆优先(防止古老记忆反复出现)

    权重为什么这么定?0.6/0.3/0.1 是经验值,语义权重最高但不能独占(否则关键词退化严重),时间权重最低(只是微调,不喧宾夺主)。

    去重策略

    store 时检测相似记忆,cosine > 0.85 即认为是"重复":

    store(content)
    → 生成 embedding
    → cosine > 0.85 → 合并(更新 content + 合并 metadata,保留原有 embedding)
    → cosine ≤ 0.85 → 新增

    合并时 merged_from 和 merged_at 记录到 metadata,可追溯。


    5. 核心设计决策

    ADR-001:分层架构(接口与实现分离)

    决策:接口层独立于实现层,业务代码只依赖接口。

    理由:L1/L2 接口不变,实现可以在 Redis / PGvector / MCP 之间切换,不影响上层 Agent 代码。

    放弃:无。要做多 Agent 共享记忆,存储灵活性是必须的。


    ADR-002:选择 PGvector 而非 Milvus

    决策:向量存储选 PostgreSQL + pgvector,不单独部署 Milvus。

    理由:

    • Dream-SaaS 已有 PostgreSQL,复用现有基础设施
    • pgvector 在 PG 15+ 性能已足够(1000条记忆检索 < 50ms)
    • 无额外运维成本

    放弃:Milvus 的分布式向量检索能力。对于几千条记忆的场景,pgvector 完全够用。


    ADR-003:混合检索三信号融合

    决策:hybrid_score = 0.6×vector + 0.3×keyword + 0.1×time

    理由:纯向量在专有名词/产品名场景召回率差,纯关键词无法处理同义词扩展。三信号互补。

    放弃:纯向量检索(简单但不够精准)。


    ADR-004:HITL 置信度分层

    决策:≥0.82 自动写入,≥0.55 待确认,<0.55 跳过。

    理由:防止 LLM 幻觉导致错误记忆永久化。用户确认为记忆的参与感,也能提升用户对 Agent 的信任。

    放弃:直接写入(省事但风险高)。纯人工确认(体验差)。


    ADR-005:Embedding 可选注入

    决策:EmbeddingModel 有就注入,没有就走 ILIKE 降级。

    理由:降低接入门槛。开发/测试环境不一定有可用模型,降级到 ILIKE 至少能跑通。

    放弃:强制要求 EmbeddingModel(接入成本高)。


    6. 篇2 预告

    下篇讲双层实现的源码拆解:PgStructuredMemoryService 三种单元的 PG 存储、PgVectorMemoryService 混合检索三信号融合完整调用链、Redis 降级方案的读写模式,以及测试兼容的坑。


    赞(0)
    未经允许不得转载:171主机测评 » Agent记忆模块系列:01架构设计
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址