赋予 AI 灵魂:如何在 Java AI 生态实现一个会“自我反思”的长期记忆系统

在 Agent 领域,有一句名言:"没有记忆的 Agent 只是一个高级的函数,拥有记忆的 Agent 才有灵魂。"

然而,在 Java 生态中实现一个真正具备"自我进化"能力的记忆系统并不容易。本文将深度解析如何基于 Solon AI 框架(及 Solon AI Skill 接口),参考 MemSkill 论文核心思想,实现一个具备提取、整合、修剪与检索能力的长期记忆组件。

一、 Agent 的记忆长效难题

目前大多数 AI 应用的记忆实现非常简单:直接将最近的对话记录塞进 Context 窗口。这种做法面临三大难题:

  1. Context 溢出 :LLM 的上下文窗口是昂贵且有限的,流水账式的记忆很快就会触顶。
  2. 认知噪声: 历史记录中充当"废话"的信息会干扰 AI 对当前任务的判断。
  3. 认知冲突: 当用户状态发生改变(例如:"我以前喜欢 Java,现在更喜欢 Go"),简单的检索(RAG)往往会搜出过时的信息,导致 AI 逻辑混乱。

MemSkill 的核心理念 : Agent 不应只是被动地存取数据,而应像人一样,能够主动地 归纳 事实、修正 错误,并将碎片化信息 升维 为心智模型(Mental Model)。

二、 思考与设计:心智模型自演进

我们要设计的 MemSkill 遵循四个核心动作:

  • Extract(提取):从对话中提取关键事实,并赋予重要度。
  • Consolidate(整合):将多个低级事实压缩为一个高层级的用户偏好摘要。
  • Prune(修剪):识别并删除过时或错误的记忆。
  • Search(检索):通过语义搜索找回最相关的上下文。
架构设计

为了保证工程落地,我们采用了 二级存储架构

  • Redis (KV 存储):利用 TTL 机制,根据重要度(Importance)实现记忆的物理淘汰。
  • Vector/Lucene (语义索引):提供多租户隔离的语义检索能力。

三、 代码实现:构建 MemSkill 核心

1. 记忆搜索供应商(MemSearchProvider)

首先,我们定义一个抽象层,既可以对接分布式的矢量数据库,也可以对接本地的 Lucene。

java 复制代码
public interface MemSearchProvider {
    /** 语义/模糊搜索 */
    List<MemSearchResult> search(String userId, String query, int limit);
    /** 获取高价值热记忆(用于画像注入) */
    List<MemSearchResult> getHotMemories(String userId, int limit);
    /** 同步索引 */
    void updateIndex(String userId, String key, String fact, int importance, String time);
    /** 移除索引 */
    void removeIndex(String userId, String key);
}
2. 核心技能类:MemSkill

这是 Agent 的"心智处理器"。它不仅提供工具给 AI 调用,还通过 getInstruction 在对话开启前动态注入用户画像。

java 复制代码
public class MemSkill extends AbsSkill {
    private final RedisClient redis;
    private final String userId;
    private final MemSearchProvider searchProvider;

    @Override
    public String getInstruction(Prompt prompt) {
        // 动态加载前 5 条核心认知碎片
        List<MemSearchResult> hot = searchProvider.getHotMemories(userId, 5);
        String mentalModel = formatModel(hot);

        return "### 长期记忆自演进指南\n" +
               "1. **核心心智模型**:这是你对用户的既有认知:\n" + mentalModel +
               "\n2. **认知演进准则**:\n" +
               "   - **时序优先**:冲突时以最近时间戳为准。\n" +
               "   - **主动修正**:现状与模型冲突时,须通过 mem_extract 或 mem_prune 更新。";
    }

    @ToolMapping(name = "mem_extract", description = "提取事实或偏好存入心智模型")
    public String extract(@Param("key") String key, @Param("fact") String fact, @Param("importance") int importance) {
        // 1. 获取旧记忆进行反思
        String oldJson = redis.getBucket().get(getFinalKey(key));
        
        // 2. 动态计算 TTL:核心总结(>=10)永久存储,普通事实 7-30 天
        int ttl = calculateTTL(importance);
        
        // 3. 同步到 Redis 和 搜索供应商
        saveMemory(key, fact, importance, ttl);
        
        return "【操作成功】心智模型已更新。若发现认知差异,请体现出你的认知进化。";
    }
}
3. 本地化落地:Lucene 适配

对于许多 Java 开发者,部署一套向量库成本太高。我们实现了一个基于 Lucene 的 MemSearchProvider,支持在本地磁盘或内存中实现"多租户隔离"的语义匹配。

java 复制代码
public class MemSearchProviderLuceneImpl implements MemSearchProvider, AutoCloseable {
    private final IndexWriter writer;

    @Override
    public List<MemSearchResult> search(String userId, String query, int limit) {
        // 通过 BooleanQuery 实现 (user_id = X AND content LIKE query)
        BooleanQuery.Builder mainQuery = new BooleanQuery.Builder();
        mainQuery.add(new TermQuery(new Term("user_id", userId)), BooleanClause.Occur.MUST);
        // ... 添加 QueryParser 解析的 query
        
        // 按相关度(或重要度)排序返回
        TopDocs topDocs = searcher.search(mainQuery.build(), limit);
        // ... 转换为 MemSearchResult
    }
}

四、 这种实现的精妙之处

  1. 认知对比(Reflexion) : 在 mem_extract 时,系统会把旧的记忆片段反馈给 AI。这会触发 Agent 的"惊觉"机制,使其能够说出:"我记得你之前提到过 A,但现在你似乎更倾向于 B,我已经为你更新了认知。"
  2. 重要度驱动的生命周期 : 不是所有记忆都是平等的。通过 importance 控制 TTL,实现了从"瞬时记忆"到"长期经验"的自然沉淀。
  3. Designer 角色注入 : 通过 getInstruction,我们将心智模型变成了 Agent 的"前置意识",这比单纯的 Tool Call 检索更高效、更拟人。

五、 结语

在 Solon AI 生态中,solon-ai-skill-memory 的实现证明了:长期记忆不只是数据的堆砌,而是认知的管理。 通过这套基于 Lucene/Redis 的自演进方案,Java 开发者可以非常轻松地为自己的 Agent 构建出一套过目不忘、且能自我反思的心智模型。

附:完整的实现参考

java 复制代码
import org.noear.redisx.RedisClient;
import org.noear.snack4.ONode;
import org.noear.solon.Utils;
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.skill.AbsSkill;
import org.noear.solon.ai.chat.prompt.Prompt;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.util.Assert;
import org.noear.solon.lang.Preview;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * MemSkill:基于自演进心智模型的长期记忆技能
 *
 * 遵从 MemSkill 论文核心:Extract (提取), Consolidate (整合), Prune (修剪), Search (检索)
 * 通过 Designer 指令引导 Agent 实现认知的自我更新。
 */
public class MemSkill extends AbsSkill {
    private static final Logger LOG = LoggerFactory.getLogger(MemSkill.class);
    private static final String BASE_PREFIX = "ai:memskill:";

    private final RedisClient redis;
    private final String userId;
    private final MemSearchProvider searchProvider;

    public MemSkill(RedisClient redis, String userId, MemSearchProvider searchProvider) {
        this.redis = redis;
        this.userId = userId;
        this.searchProvider = searchProvider;
    }

    @Override
    public String name() { return "mem_skill"; }

    @Override
    public String description() {
        return "长期记忆专家:负责用户心智模型的提取、演进、冲突消解与深度检索。";
    }

    /**
     * 充当 Designer 引导逻辑,动态加载心智模型
     */
    @Override
    public String getInstruction(Prompt prompt) {
        String mentalModel = "";
        if (searchProvider != null) {
            // 获取核心认知碎片(通常是重要度高或最近更新的)
            List<MemSearchResult> hot = searchProvider.getHotMemories(userId, 5);
            if (!hot.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                for (MemSearchResult r : hot) {
                    sb.append(String.format("- %s: %s (Time: %s)\n", r.key, r.content, r.time));
                }
                mentalModel = sb.toString();
            }
        }

        return "### 长期记忆自演进指南 (当前系统时间: " + getNow() + ")\n" +
                "你具备自主管理和演进用户心智模型的能力,请遵循以下原则:\n" +
                "1. **核心心智模型**:这是你对当前用户的既有认知,请基于此进行对话:\n" +
                (Assert.isEmpty(mentalModel) ? "- (心智模型构建中,请多提问以了解用户)" : mentalModel) +
                "\n\n2. **认知演进准则**:\n" +
                "   - **时序优先**:若认知记录存在冲突,以时间戳最近的为准。\n" +
                "   - **主动修正**:若用户现状与上述模型冲突,必须通过 `mem_extract` 更新或 `mem_prune` 修剪。\n" +
                "   - **归纳升维**:当认知库出现冗余时,主动使用 `mem_consolidate` 将低层事实归纳为高层偏好。";
    }

    private String getFinalKey(String key) { return BASE_PREFIX + userId + ":" + key; }

    private String getNow() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); }

    /**
     * EXTRACT & UPDATE: 提取与覆盖
     * 解决了记忆冲突与反思逻辑
     */
    @ToolMapping(name = "mem_extract",
            description = "将事实、偏好或进度存入用户心智模型。若存在同名 Key,系统将返回旧记录以供你对比反思。")
    public String extract(@Param("key") String key,
                          @Param("fact") String fact,
                          @Param("importance") int importance) {
        try {
            String finalKey = getFinalKey(key);
            String oldJson = redis.getBucket().get(finalKey);
            String now = getNow();

            StringBuilder feedback = new StringBuilder("【操作成功】心智模型已更新。");
            if (Utils.isNotEmpty(oldJson)) {
                ONode old = ONode.ofJson(oldJson);
                feedback.append("\n[认知对比] 发现历史记录:")
                        .append("\n- 旧内容: ").append(old.get("content").getString())
                        .append("\n- 旧时间: ").append(old.get("time").getString())
                        .append("\n请对比新旧信息差异。若发生改变,请在后续对话中体现出你的认知进化。");
            }

            Map<String, Object> data = new HashMap<>();
            data.put("content", fact);
            data.put("time", now);
            data.put("importance", importance);

            // 动态 TTL:重要信息(>=5)保留 30 天,核心总结(>=10)永久或极长,普通信息 7 天
            int ttl;
            if (importance >= 10) ttl = -1; // 核心经验永不过期
            else if (importance >= 5) ttl = 2592000;
            else ttl = 604800;

            redis.getBucket().store(finalKey, ONode.serialize(data), ttl);

            if (searchProvider != null) {
                searchProvider.updateIndex(userId, key, fact, importance, now);
            }

            return feedback.toString();
        } catch (Exception e) {
            LOG.error("MemSkill extract error", e);
            return "存储异常。";
        }
    }

    /**
     * SEARCH: 语义搜索
     */
    @ToolMapping(name = "mem_search",
            description = "语义检索:通过自然语言描述在心智模型中寻找相关的记忆碎片,辅助找回背景信息。")
    public String search(@Param("query") String query) {
        if (searchProvider == null) return "搜索适配器未配置。";

        List<MemSearchResult> results = searchProvider.search(userId, query, 3);
        if (results.isEmpty()) return "未发现相关认知片段。";

        StringBuilder sb = new StringBuilder("匹配到以下认知参考(建议优先参考时间戳较近的记录):\n");
        for (MemSearchResult res : results) {
            sb.append(String.format("- [%s] (Key: %s): %s\n",
                    Utils.isNotEmpty(res.time) ? res.time : "未知时间", res.key, res.content));
        }
        return sb.toString();
    }

    /**
     * RECALL: 精确召回
     */
    @ToolMapping(name = "mem_recall", description = "精确召回:通过 Key 获取该认知条目的完整细节。")
    public String recall(@Param("key") String key) {
        try {
            String val = redis.getBucket().get(getFinalKey(key));
            if (Utils.isEmpty(val)) return "未找到认知条目 [" + key + "]。";
            ONode node = ONode.ofJson(val);
            return String.format("【认知详情】内容:%s | 记录时间:%s | 重要度:%s",
                    node.get("content").getString(), node.get("time").getString(), node.get("importance").getString());
        } catch (Exception e) { return "读取异常。"; }
    }

    /**
     * CONSOLIDATE: 知识整合
     * 对齐 MemSkill 的"压缩"思想,将事实进化为经验
     */
    @ToolMapping(name = "mem_consolidate",
            description = "认知整合:将多个碎片的 Key 合并为一个高层级的总结记录。这能显著提升心智模型的质量。")
    public String consolidate(@Param("old_keys") List<String> oldKeys,
                              @Param("new_key") String newKey,
                              @Param("summary") String summary) {
        // 核心总结赋予最高重要度 10,确保持久存储
        extract(newKey, "[认知总结] " + summary, 10);

        for (String k : oldKeys) {
            redis.getBucket().remove(getFinalKey(k));
            if (searchProvider != null) searchProvider.removeIndex(userId, k);
        }
        LOG.info("MemSkill: {} 进行了心智模型演进 -> {}", userId, newKey);
        return "整合完成。碎片信息已归纳,心智模型已升维。";
    }

    /**
     * PRUNE: 记忆修剪
     */
    @ToolMapping(name = "mem_prune", description = "认知修正:删除错误、重复或过时的认知。")
    public String prune(@Param("key") String key) {
        redis.getBucket().remove(getFinalKey(key));
        if (searchProvider != null) searchProvider.removeIndex(userId, key);
        return "已从模型中清理 Key: " + key;
    }
}
相关推荐
忙碌5441 小时前
2026年大语言模型微调实战:从零到一构建专属AI助手
人工智能·深度学习
向哆哆1 小时前
厨房食品卫生安全检测数据集:智能餐饮与食品安全保障的视觉卫士
人工智能·安全·目标跟踪
大模型任我行1 小时前
谷歌:预训练到微调的知识迁移规律
人工智能·语言模型·自然语言处理·论文笔记
悠闲蜗牛�1 小时前
高并发大模型推理优化实战:从模型压缩到服务化部署
人工智能
菜鸟小芯1 小时前
【GLM-5 陪练式前端新手入门】第四篇:卡片布局 —— 让个人主页内容更有层次
前端·人工智能
wangbing11251 小时前
开发指南142-类和字符串转换
java·开发语言
AI周红伟1 小时前
大模型部署入门教程,消费级显卡跑通Qwen3.5-Plus,最低配置部署教程,不能在简单了
大数据·人工智能·大模型·智能体
阿_旭2 小时前
【视觉AI赋能智慧农业】三大应用场景、简化农作流程、核心价值全解析
人工智能·智慧农业
沃达德软件2 小时前
视频监控数据分析服务
图像处理·人工智能·深度学习·目标检测·计算机视觉·数据挖掘·数据分析