Agent记忆模块系列:02实现详解

0 阅读5分钟

上篇聊了架构设计,这篇直接上代码——用Spring AI怎么搭记忆模块,核心增删改查怎么写,记忆从哪来。


一、模块结构

memory-api(接口层)
    ↑  只依赖接口,不关心实现
    │
memory-pgvector  ←  memory-redis  ←  memory-mcp
(生产推荐)         (缓存方案)        (给外部Agent用)
模块说明
memory-api只定义接口,不写实现。业务代码依赖它,打成jar包后各实现模块独立加载
memory-pgvector用PostgreSQL存储,Service写业务逻辑,Mapper写SQL
memory-redis适合开发测试快速验证,或者做缓存层
memory-mcp把记忆能力暴露给外部Agent,通过@Tool注解注册

二、表结构设计

三张核心表:

-- 结构化记忆:rules / facts / status
CREATE TABLE memory.structured_memory (
    id          BIGSERIAL PRIMARY KEY,
    owner_id    VARCHAR(255) NOT NULL,
    memory_type VARCHAR(50)  NOT NULL,  -- rule / fact / status
    content     TEXT         NOT NULL,
    status_key  VARCHAR(255),           -- status类型专用
    metadata    TEXT,                   -- JSON格式扩展字段
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_structured_owner_type ON memory.structured_memory (owner_id, memory_type);
CREATE INDEX idx_structured_owner_status ON memory.structured_memory (owner_id, memory_type, status_key);

-- 向量记忆:语义检索
CREATE TABLE memory.vector_memory (
    id          BIGSERIAL PRIMARY KEY,
    namespace   VARCHAR(255) NOT NULL,
    content     TEXT         NOT NULL,
    embedding   TEXT,                    -- 向量存为TEXT,pgvector扩展支持向量运算
    metadata    TEXT,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_vector_namespace ON memory.vector_memory (namespace);

-- 待审核队列:HITL
CREATE TABLE memory.pending_memory (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    owner_id        VARCHAR(255) NOT NULL,
    kind            VARCHAR(50),
    payload         TEXT,                    -- JSON格式
    confidence      DOUBLE PRECISION,
    source_snippet  TEXT,                    -- 来源对话片段
    status          VARCHAR(50) DEFAULT 'pending',
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_pending_owner_status ON memory.pending_memory (owner_id, status);

关键设计:一张表存三种类型(rule/fact/status),通过 memory_type 字段区分,比拆成三张表更灵活。


三、StructuredMemoryService:结构化记忆增删改查

3.1 添加记忆

// 添加规则
structuredMemoryService.addRule(userId, "用户不喜欢被催单");

// 添加事实
structuredMemoryService.addFact(userId, "用户上周买了奶粉");

// 更新状态(key-value形式)
structuredMemoryService.updateStatus(userId, "step", "投诉处理中");

3.2 查询记忆

// 获取完整记忆快照
MemorySnapshot snapshot = structuredMemoryService.get(userId);
List<String> rules = snapshot.ruleContents();    // ["用户不喜欢被催单"]
List<String> facts = snapshot.factContents();    // ["用户上周买了奶粉"]
Map<String, String> status = snapshot.status(); // {step: "投诉处理中"}

3.3 删除记忆

// 按ID删除规则或事实
structuredMemoryService.deleteRule(userId, ruleId);
structuredMemoryService.deleteFact(userId, factId);

// 按key删除状态
structuredMemoryService.deleteStatus(userId, "step");

3.4 批量替换

// 全量替换(先删再插,同一事务)
List<String> newRules = List.of("新规则1", "新规则2");
Map<String, String> newStatus = Map.of("step", "新状态");
structuredMemoryService.replace(userId, newRules, null, newStatus);

四、记忆从哪来:用LLM从对话中提取

这是记忆模块的核心问题——不是手动调用API写入,而是让AI从对话中自动提取

4.1 提取Prompt设计

String systemPrompt = """
你是一个记忆提炼器,从对话中提取值得长期记忆的信息。

【提取类型】
- facts:用户明确表述的事实,0-2条
- rules:用户明确要求的行为规则,0-1条
- status_updates:当前状态更新

【输出格式】
{
    "facts": ["用户上周买了奶粉"],
    "rules": ["用户不喜欢被催单"],
    "status_updates": [{"key": "step", "value": "投诉处理中"}],
    "confidence": 0.85
}

【注意事项】
- 只提取对话中明确提及的信息,不要编造
- confidence:0.0-1.0,越高越确定
- 无新信息则各字段为空
""";

// 调用
ChatResponse response = chatModel.call(new PromptTemplate(systemPrompt)
    .render(Map.of("conversation", conversationText)));

// 解析结果
Map<String, Object> result = objectMapper.readValue(
    response.getResult().getText(), Map.class);
Double confidence = (Double) result.get("confidence");

4.2 置信度分流

double confidence = (Double) result.get("confidence");

if (confidence >= 0.82) {
    // 高置信度:自动写入
    for (String fact : (List<String>) result.get("facts")) {
        structuredMemoryService.addFact(userId, fact);
    }
} else if (confidence >= 0.55) {
    // 中置信度:待人工确认
    structuredMemoryService.enqueuePending(userId, "fact",
        Map.of("value", fact), confidence, snippet);
}
// < 0.55:直接跳过

4.3 置信度阈值说明

置信度阈值处理方式
≥ 0.82自动写入,无需人工干预
0.55 ~ 0.82入待审核队列,人工确认后写入
< 0.55跳过,避免噪音

五、HITL:人机协作的质量兜底

语义检索依赖向量相似度,但模型判断"这句话该不该被记住"的能力有限。HITL就是在这层补一个人工确认环节。

5.1 适用场景

  • 高风险信息:涉及金额、承诺、投诉等
  • 低置信度内容:模型拿不准的信息
  • 关键规则:用户明确要求的行为偏好

5.2 代码实现

// 入队
public void enqueuePending(String ownerId, String kind,
                           Map<String, Object> payload,
                           double confidence, String sourceSnippet) {
    PendingMemoryEntity pending = PendingMemoryEntity.builder()
        .ownerId(ownerId)
        .kind(kind)
        .payload(JsonUtils.toJson(payload))
        .confidence(confidence)
        .sourceSnippet(sourceSnippet)
        .status("pending")
        .build();
    pendingMemoryMapper.insert(pending);
}

// 审核通过后写入
public void approvePending(UUID pendingId) {
    PendingMemoryEntity pending = pendingMemoryMapper.selectById(pendingId);
    Map<String, Object> payload = JsonUtils.fromJson(pending.getPayload());
    
    switch (pending.getKind()) {
        case "fact" -> structuredMemoryService.addFact(
            pending.getOwnerId(), (String) payload.get("value"));
        case "rule" -> structuredMemoryService.addRule(
            pending.getOwnerId(), (String) payload.get("value"));
    }
    
    pendingMemoryMapper.updateStatus(pendingId, "approved");
}

5.3 与向量检索的配合

内容类型向量状态说明
高置信度已激活直接写入结构化记忆,同步生成向量
待审核pending向量生成但状态为pending,审核通过后激活
审核拒绝已拒绝向量保留但不暴露给检索结果

六、VectorMemoryService:语义搜索

6.1 存储与搜索

// 存储记忆,自动生成向量并入库
vectorMemoryService.addMemory(userId, "用户上周买了奶粉");

// 语义搜索:用户说"买了啥"能找到"用户上周买了奶粉"
List<VectorMemoryRecord> results = vectorMemoryService.search("买了啥", userId, 5);

6.2 混合检索

// 语义 + 关键词 + 时间排序,生产环境标配
hybridSearch(query, userId, startTime, endTime);

混合检索权重建议:向量0.6 + 关键词0.3 + 时间0.1。


七、Redis 实现

Redis 版本通过 @ConditionalOnMissingBean 在 PG 不可用时自动降级:

// 存储
redisTemplate.opsForHash().put(key, "rules", serializeRules(rules));
redisTemplate.expire(key, Duration.ofDays(7));  // 7天过期

// 查询
Object rules = redisTemplate.opsForHash().get(key, "rules");

八、MCP 工具暴露

memory-mcp 模块把记忆能力暴露给外部Agent:

工具名功能
memoryGetSnapshot获取完整记忆
memoryAddRule添加规则
memoryAddFact添加事实
memoryUpdateStatus更新状态
memoryVectorSearch语义检索
memoryVectorStore存储向量

九、总结

本文介绍了 Spring AI 记忆模块的核心实现:

  1. 模块分层:接口与实现分离,支持多存储后端
  2. 数据结构:结构化记忆(rule/fact/status)+ 向量记忆 + HITL队列
  3. 记忆提取:LLM自动提取 + 置信度分流 + 人工兜底
  4. 语义检索:向量相似度 + 混合检索