四:Agent 长期记忆与用户画像——让智能体真正“认识“你的用户

用户画像记忆:个性化 Agent Harness 的构建 静态属性:脱敏后的长期不变属性,比如年龄段、职业类别、所在城市(仅保留到市一级)、长期禁忌(比如过敏史)等动态属性:会随时间变化的属性,比如近期偏好(最近在减肥)、行为习惯(每周三下午开周会)、知识水平(Python入门)、情绪倾向(容易焦虑)等场景属性:特定业务场景下的专属属性,比如电商场景的消费能力、教育场景的学习进度、金融场景的风险承受能力等# 标签模型tag_key: str = Field(description="标签键,比如food_allergy") 阅读详情

系列第四篇:Agent 长期记忆与用户画像——让智能体真正"认识"你的用户

前言

前三篇文章中,我们构建了单体 Agent、Multi-Agent 协作体系,并建立了完整的评估框架。但有一个问题始终困扰着落地团队:

“Agent 每次对话都像第一次见面,它不记得张工喜欢简洁的回复、不知道李经理只看数据不看分析、忘了上次那个区域的能耗问题是怎么解决的。”

短期记忆(消息窗口)只能维持一次会话的上下文。长期记忆 才是让 Agent 从"通用助手"进化为"专属顾问"的关键。本文将系统性地构建一套 Agent 长期记忆 + 用户画像系统。


一、记忆系统的完整分层架构

1.1 人类记忆的启发

在设计 Agent 记忆系统之前,我们先看看人类记忆是怎么工作的。认知心理学将人类记忆分为三个层次:

  • 工作记忆(Working Memory):容量小(约 7±2 项)、持续时间短(约 20 秒)。你正在读这句话时,脑中暂时保持的那几个词就是工作记忆。
  • 情景记忆(Episodic Memory):对具体事件的记忆。“上周三 C 栋数据中心能耗飙升,最后发现是空调系统故障”——这是一个情景记忆。
  • 语义记忆(Semantic Memory):抽象化的知识和规律。“数据中心的能耗通常在夏季会偏高”——这是从多次经验中总结出的规律性知识。

Agent 的记忆系统也应该遵循类似的分层设计:

┌────────────────────────────────────────────────────────────┐
│                    Agent 记忆分层架构                       │
│                                                            │
│  ┌────────────────────────────────────────────────────┐    │
│  │  Layer 1: 工作记忆 (Working Memory)                │    │
│  │  · 当前会话的消息窗口                               │    │
│  │  · 容量:最近 20~50 条消息                          │    │
│  │  · 生命周期:会话级别                               │    │
│  │  · 用途:多轮对话的上下文连贯性                      │    │
│  └────────────────────────────────────────────────────┘    │
│                           │ 会话结束时摘要沉淀             │
│                           ▼                                │
│  ┌────────────────────────────────────────────────────┐    │
│  │  Layer 2: 情景记忆 (Episodic Memory)               │    │
│  │  · 历史对话的结构化摘要                             │    │
│  │  · 容量:无限制(向量数据库存储)                    │    │
│  │  · 生命周期:永久(可设定衰减权重)                  │    │
│  │  · 用途:回忆"上次那个问题是怎么解决的"              │    │
│  └────────────────────────────────────────────────────┘    │
│                           │ 多次经验抽象提炼               │
│                           ▼                                │
│  ┌────────────────────────────────────────────────────┐    │
│  │  Layer 3: 语义记忆 (Semantic Memory / User Profile) │    │
│  │  · 用户偏好、行为习惯、领域知识                     │    │
│  │  · 容量:精炼的键值对 + 向量知识                    │    │
│  │  · 生命周期:长期(定期更新)                       │    │
│  │  · 用途:"张工喜欢简洁回答"、"李经理关注成本指标"   │    │
│  └────────────────────────────────────────────────────┘    │
│                                                            │
└────────────────────────────────────────────────────────────┘

1.2 三层记忆的协作机制

当用户发起一次对话时,三层记忆的协作过程如下:

用户提问:"上次C栋的问题解决了吗?"

Step 1: 工作记忆 — 检查当前会话是否有 C 栋相关上下文
         → 没有(这是新会话的第一轮)

Step 2: 情景记忆 — 在历史对话摘要中检索"C栋"相关内容
         → 命中: "2026-08-20 C栋能耗异常,排查发现空调系统故障,
                  已提交维修工单 #20260820-003"

Step 3: 语义记忆 — 检索当前用户的偏好画像
         → 用户: 张工,偏好: 简洁回复,关注: 维修进度

Step 4: Agent 生成回答(融合三层记忆的信息)
         → "C栋上次是空调系统故障,工单 #20260820-003。
            需要我查一下当前维修进度吗?"

关键点:三层记忆不是简单叠加,而是 按需检索、按需注入。不是每次对话都需要三层全查——工作记忆始终参与,情景记忆和语义记忆只在相关时才检索注入,避免无关信息干扰模型。


二、情景记忆:让 Agent 能"回忆往事"

2.1 对话摘要的自动生成

情景记忆的核心是 对话摘要——每次会话结束后,用一个专门的"摘要 Agent"将对话内容压缩为结构化记录。

为什么不直接存原始对话?因为:

  • 一次对话可能有 30~50 轮消息,Token 量很大
  • 大部分消息是"过程性"的(工具调用、中间推理),对未来的记忆价值很低
  • 摘要可以提取关键信息,压缩比通常能达到 10:1 以上
/**
 * 对话摘要服务
 * 
 * 触发时机:每次会话结束(用户关闭页面 / 超时不活跃 / 手动结束)
 * 
 * 设计原则:
 * 1. 摘要要保留"决策性信息",丢弃"过程性信息"
 * 2. 必须提取实体(人名、地点、设备、数值),便于后续检索
 * 3. 保留"结论"和"行动",弱化"推理过程"
 */
@Service
public class ConversationSummaryService {

    private final ChatLanguageModel summaryModel;

    public ConversationSummaryService() {
        // 摘要任务用小模型即可(如 gpt-4o-mini),节省成本
        this.summaryModel = OpenAiChatModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .modelName("gpt-4o-mini")
            .temperature(0.1)  // 摘要需要高保真,低温度
            .build();
    }

    /**
     * 将一次完整对话压缩为结构化摘要
     * 
     * @param sessionId  会话 ID
     * @param userId     用户 ID
     * @param messages   完整对话消息列表
     * @return 结构化摘要对象
     */
    public ConversationSummary summarize(String sessionId,
                                         String userId,
                                         List<ChatMessage> messages) {
        String conversationText = messages.stream()
            .map(msg -> String.format("[%s] %s",
                msg.type() == ChatMessageType.USER ? "用户" : "Agent",
                msg.text()))
            .collect(Collectors.joining("\n"));

        String prompt = String.format("""
            你是一个对话摘要专家。请将以下对话压缩为结构化摘要。
            
            ## 摘要要求
            1. topic: 一句话概括对话主题
            2. keyFacts: 提取关键事实(数据、结论、决策),保留数值和单位
            3. actions: 列出执行的行动和结果
            4. entities: 提取涉及的实体(人名、区域、设备、日期)
            5. unresolved: 未解决的问题或待跟进事项(如果有)
            6. outcome: 最终结果(成功/待跟进/失败)
            
            严格按 JSON 格式返回:
            {
              "topic": "...",
              "keyFacts": ["...", "..."],
              "actions": [{"action": "...", "result": "..."}],
              "entities": {
                "people": ["..."],
                "areas": ["..."],
                "devices": ["..."],
                "dates": ["..."]
              },
              "unresolved": ["..."],
              "outcome": "success|pending|failed"
            }
            
            ## 对话内容
            %s
            """, conversationText);

        String result = summaryModel.generate(
            UserMessage.from(prompt)).content().text().trim();

        // 解析 JSON 并构建摘要对象
        JsonNode node = JsonUtils.parse(result);

        return ConversationSummary.builder()
            .sessionId(sessionId)
            .userId(userId)
            .timestamp(Instant.now())
            .topic(node.get("topic").asText())
            .keyFacts(parseStringList(node.get("keyFacts")))
            .actions(parseActions(node.get("actions")))
            .entities(parseEntities(node.get("entities")))
            .unresolved(parseStringList(node.get("unresolved")))
            .outcome(node.get("outcome").asText())
            .originalMessageCount(messages.size())
            .build();
    }
}

2.2 摘要的结构化定义

/**
 * 对话摘要
 * 
 * 设计考量:
 * - 保留 sessionId 用于溯源(可以追溯到原始对话)
 * - timestamp 用于时间衰减(越老的记忆权重越低)
 * - entities 用于精确检索("所有和C栋相关的历史")
 * - outcome 用于快速过滤(只看未解决的问题)
 */
public class ConversationSummary {
    private String sessionId;
    private String userId;
    private Instant timestamp;

    // 核心内容
    private String topic;              // "C栋能耗异常排查"
    private List<String> keyFacts;     // ["C栋日耗8734kWh,超出均值84%"]
    private List<Action> actions;      // [{通知张工, 成功}, {提交工单, #003}]
    private Entities entities;         // {people:[张工], areas:[C栋], ...}
    private List<String> unresolved;   // ["等待维修结果"]
    private String outcome;            // "pending"

    // 元数据
    private int originalMessageCount;  // 原始消息数(用于衡量信息压缩比)

    /**
     * 生成用于向量化的文本表示
     * 
     * 为什么不用原始对话做 Embedding?
     * 因为摘要去除了噪声信息,Embedding 质量更高,
     * 检索时语义匹配更精准。
     */
    public String toEmbeddingText() {
        return String.format("""
            主题: %s
            关键事实: %s
            执行行动: %s
            涉及实体: %s
            未解决: %s
            """,
            topic,
            String.join("; ", keyFacts),
            actions.stream()
                .map(a -> a.action() + "→" + a.result())
                .collect(Collectors.joining("; ")),
            entities.toBriefString(),
            String.join("; ", unresolved)
        );
    }
}

2.3 情景记忆的存储与检索

/**
 * 情景记忆存储服务
 * 
 * 存储策略:双写
 * 1. 摘要的结构化字段存入关系数据库(支持精确查询)
 * 2. 摘要的文本表示向量化后存入向量数据库(支持语义检索)
 * 
 * 两种检索方式互补:
 * - 精确查询:"查找张工相关的所有对话" → SQL WHERE entities LIKE '%张工%'
 * - 语义检索:"上次那个设备故障是怎么处理的" → 向量相似度搜索
 */
@Service
public class EpisodicMemoryService {

    private final SummaryRepository summaryRepo;        // JPA / MyBatis
    private final EmbeddingStore<TextSegment> vectorStore;
    private final EmbeddingModel embeddingModel;

    public EpisodicMemoryService(SummaryRepository summaryRepo,
                                 EmbeddingModel embeddingModel) {
        this.summaryRepo = summaryRepo;
        this.embeddingModel = embeddingModel;
        // 情景记忆使用独立的向量集合,与 RAG 知识库隔离
        this.vectorStore = new InMemoryEmbeddingStore<>();
    }

    /**
     * 保存对话摘要(双写)
     */
    @Transactional
    public void save(ConversationSummary summary) {
        // 1. 结构化存储
        summaryRepo.save(summary);

        // 2. 向量化存储
        String embeddingText = summary.toEmbeddingText();
        Embedding embedding = embeddingModel.embed(embeddingText).content();
        TextSegment segment = TextSegment.from(embeddingText,
            Metadata.from("sessionId", summary.getSessionId())
                .add("userId", summary.getUserId())
                .add("timestamp", summary.getTimestamp().toString())
                .add("outcome", summary.getOutcome())
        );
        vectorStore.add(embedding, segment);

        log.info("情景记忆已保存: session={}, topic={}",
            summary.getSessionId(), summary.getTopic());
    }

    /**
     * 语义检索:根据当前问题检索历史对话
     * 
     * @param query      当前问题
     * @param userId     用户 ID(只检索该用户的历史)
     * @param maxResults 最大返回数
     * @return 相关的历史摘要列表(按相关性排序)
     */
    public List<ConversationSummary> search(String query,
                                            String userId,
                                            int maxResults) {
        // 1. 向量检索(语义匹配)
        Embedding queryVec = embeddingModel.embed(query).content();
        List<EmbeddingSearchResult<TextSegment>> results =
            vectorStore.findRelevant(queryVec, maxResults * 2, 0.6);

        // 2. 按用户过滤 + 时间衰减加权
        return results.stream()
            .filter(r -> userId.equals(r.embeddedObject()
                .metadata().getString("userId")))
            .map(r -> {
                ConversationSummary summary = summaryRepo
                    .findBySessionId(r.embeddedObject()
                        .metadata().getString("sessionId"))
                    .orElse(null);
                return summary;
            })
            .filter(Objects::nonNull)
            .sorted((a, b) -> {
                // 时间衰减:近期记忆优先
                double decayA = timeDecay(a.getTimestamp());
                double decayB = timeDecay(b.getTimestamp());
                return Double.compare(decayB, decayA);
            })
            .limit(maxResults)
            .toList();
    }

    /**
     * 精确查询:按实体检索(如"所有和C栋相关的历史")
     */
    public List<ConversationSummary> searchByEntity(String userId,
                                                     String entityType,
                                                     String entityValue) {
        return summaryRepo.findByUserIdAndEntity(
            userId, entityType, entityValue);
    }

    /**
     * 时间衰减函数
     * 
     * 原理:借鉴 Ebbinghaus 遗忘曲线
     * - 1 天前的记忆:权重 0.95(几乎不衰减)
     * - 7 天前:权重 0.80
     * - 30 天前:权重 0.50
     * - 90 天前:权重 0.20(大幅衰减但仍保留)
     * 
     * 衰减不是删除——老记忆仍然可以被高相关性检索召回,
     * 只是在综合排序中权重降低。
     */
    private double timeDecay(Instant timestamp) {
        long daysAgo = Duration.between(timestamp, Instant.now()).toDays();
        return Math.exp(-0.01 * daysAgo);
    }
}

三、语义记忆:用户画像系统

3.1 用户画像的本质

如果说情景记忆是"Agent 经历了什么",那语义记忆(用户画像)就是"Agent 对用户了解多少"。

用户画像不是简单的标签集合,而是从多次交互中 自动提炼 出的、关于用户的结构化认知。它包括:

维度示例用途
回复偏好“喜欢简洁回复,不要过多解释”控制回答的长度和风格
关注指标“主要关注成本(电费),而非技术细节”调整回答的侧重点
职责范围“负责 A/B 栋的运维管理”限定默认查询范围
技术水平“对能耗分析有专业背景”决定是否使用专业术语
历史关切“持续关注C栋数据中心能耗”主动关联相关信息
交互习惯“通常在早上9~10点查询日报”预测性推荐

3.2 用户画像的自动提取

用户画像不应该让用户手动填写——那样用户体验极差,而且信息会很快过时。正确的做法是:从对话历史中自动提取和更新。

/**
 * 用户画像自动提取服务
 * 
 * 工作原理:
 * 1. 每次会话结束后,用 LLM 分析对话中透露的用户偏好信号
 * 2. 与现有画像合并(增量更新,而非覆盖)
 * 3. 对冲突信息进行仲裁(以最新对话为准)
 * 
 * 提取信号举例:
 * - 用户说"说重点"→ 偏好简洁回复
 * - 用户问"这得花多少钱"→ 关注成本指标
 * - 用户说"我是负责B栋的"→ 职责范围: B栋
 * - 用户连续三次查询C栋→ 历史关切: C栋能耗
 */
@Service
public class UserProfileExtractor {

    private final ChatLanguageModel extractorModel;

    public UserProfileExtractor() {
        this.extractorModel = OpenAiChatModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .modelName("gpt-4o-mini")  // 提取任务用小模型
            .temperature(0.0)
            .build();
    }

    /**
     * 从对话中提取用户画像信号
     * 
     * @param messages 完整对话消息
     * @return 提取到的画像信号(可能为空)
     */
    public UserProfileSignals extractSignals(List<ChatMessage> messages) {
        String conversationText = messages.stream()
            .map(msg -> String.format("[%s] %s",
                msg.type() == ChatMessageType.USER ? "用户" : "Agent",
                msg.text()))
            .collect(Collectors.joining("\n"));

        String prompt = String.format("""
            你是一个用户画像分析专家。请从以下对话中提取用户偏好信号。
            
            ## 提取维度
            1. replyPreference: 用户偏好的回复风格
               信号示例:"说重点"="简洁"、"能详细说说吗"="详细"、无信号=null
            2. focusMetrics: 用户关注的指标类型
               可选值: cost(成本), efficiency(效率), safety(安全),
                      environment(环保), 无信号=null
            3. responsibilityScope: 用户的管理/职责范围
               如"我负责A栋"→ "A栋"
            4. expertiseLevel: 用户的专业水平
               可选值: beginner, intermediate, expert
            5. interests: 用户表现出持续关注的主题
            6. implicitNeeds: 对话中暗示但未明确说出的需求
            
            重要:只提取有明确证据支撑的信号,不要猜测。
            如果某个维度没有信号,填 null。
            
            严格按 JSON 返回:
            {
              "replyPreference": null 或 "简洁/详细/专业/通俗",
              "focusMetrics": null 或 ["cost", "safety"],
              "responsibilityScope": null 或 "区域描述",
              "expertiseLevel": null 或 "beginner/intermediate/expert",
              "interests": null 或 ["主题1", "主题2"],
              "implicitNeeds": null 或 ["需求1"]
            }
            
            ## 对话内容
            %s
            """, conversationText);

        String result = extractorModel.generate(
            UserMessage.from(prompt)).content().text().trim();

        return parseSignals(result);
    }
}

3.3 用户画像的增量更新

画像更新不是简单的"新覆盖旧",而是一个 增量合并 + 冲突仲裁 的过程:

/**
 * 用户画像管理服务
 * 
 * 更新策略:
 * - 新维度直接追加
 * - 同维度新值:与旧值对比,冲突时以最新为准
 * - 兴趣/关注点:采用计数器模式,多次出现权重增加
 * - 长期未验证的画像:随时间降低置信度
 */
@Service
public class UserProfileService {

    private final UserProfileRepository profileRepo;
    private final UserProfileExtractor extractor;

    /**
     * 会话结束后更新用户画像
     */
    @Transactional
    public void updateFromConversation(String userId,
                                       List<ChatMessage> messages) {
        // 1. 从对话中提取信号
        UserProfileSignals signals = extractor.extractSignals(messages);

        // 2. 如果没有任何信号,跳过
        if (signals.isEmpty()) {
            log.debug("本次对话未提取到用户画像信号: userId={}", userId);
            return;
        }

        // 3. 加载现有画像
        UserProfile profile = profileRepo.findByUserId(userId)
            .orElse(UserProfile.newProfile(userId));

        // 4. 增量合并
        mergeSignals(profile, signals);

        // 5. 持久化
        profileRepo.save(profile);

        log.info("用户画像已更新: userId={}, 更新维度数={}",
            userId, signals.nonNullCount());
    }

    /**
     * 增量合并逻辑
     * 
     * 核心原则:
     * - 直接属性(偏好、专业水平):新值覆盖旧值
     * - 集合属性(兴趣、关注指标):累加权重,不直接删除
     * - 置信度:每次验证 +0.1,长期未验证每月 -0.05
     */
    private void mergeSignals(UserProfile profile,
                              UserProfileSignals signals) {
        Instant now = Instant.now();

        // 回复偏好:直接覆盖(用户偏好可能随场景变化)
        if (signals.replyPreference() != null) {
            profile.setReplyPreference(signals.replyPreference());
            profile.setPreferenceConfidence(
                Math.min(1.0, profile.getPreferenceConfidence() + 0.1));
            profile.setPreferenceUpdatedAt(now);
        }

        // 关注指标:累加计数(出现越多次,权重越高)
        if (signals.focusMetrics() != null) {
            for (String metric : signals.focusMetrics()) {
                profile.incrementMetricCount(metric);
            }
        }

        // 职责范围:直接覆盖
        if (signals.responsibilityScope() != null) {
            profile.setResponsibilityScope(signals.responsibilityScope());
        }

        // 专业水平:只升不降(用户展示更高水平时更新)
        if (signals.expertiseLevel() != null) {
            int newLevel = expertiseRank(signals.expertiseLevel());
            int oldLevel = expertiseRank(profile.getExpertiseLevel());
            if (newLevel > oldLevel) {
                profile.setExpertiseLevel(signals.expertiseLevel());
            }
        }

        // 兴趣主题:计数器累加
        if (signals.interests() != null) {
            for (String interest : signals.interests()) {
                profile.incrementInterestWeight(interest);
            }
        }
    }

    private int expertiseRank(String level) {
        return switch (level) {
            case "beginner" -> 1;
            case "intermediate" -> 2;
            case "expert" -> 3;
            default -> 0;
        };
    }
}

3.4 画像数据的定义

/**
 * 用户画像实体
 * 
 * 存储设计:
 * - 结构化字段存主表(高频读取)
 * - 兴趣计数、指标计数存 JSON 字段(灵活扩展)
 * - 置信度字段用于时间衰减
 */
@Table(name = "agent_user_profile")
public class UserProfile {

    @Id
    private String userId;

    // ===== 核心画像 =====

    /** 回复偏好: "简洁" / "详细" / "专业" / "通俗" */
    private String replyPreference;
    private double preferenceConfidence;  // 0.0~1.0 置信度
    private Instant preferenceUpdatedAt;

    /** 专业水平: beginner / intermediate / expert */
    private String expertiseLevel;

    /** 职责范围: "A栋办公楼运维" */
    private String responsibilityScope;

    /** 关注指标及计数: {"cost": 12, "safety": 5} */
    @Column(columnDefinition = "json")
    private Map<String, Integer> focusMetricCounts;

    /** 兴趣主题及权重: {"数据中心能耗": 8, "空调系统": 3} */
    @Column(columnDefinition = "json")
    private Map<String, Integer> interestWeights;

    /** 画像最后活跃时间 */
    private Instant lastActiveAt;

    /**
     * 生成注入 Prompt 的画像描述文本
     * 
     * 这个方法决定了画像信息如何"喂给"大模型。
     * 描述要精炼,控制在 200 字以内,避免占用过多上下文窗口。
     */
    public String toPromptDescription() {
        StringBuilder sb = new StringBuilder("当前用户画像:\n");

        if (replyPreference != null) {
            sb.append(String.format("- 偏好%s风格的回复\n",
                replyPreference));
        }

        if (expertiseLevel != null) {
            sb.append(String.format("- 专业水平: %s\n",
                switch (expertiseLevel) {
                    case "beginner" -> "入门,请用通俗语言";
                    case "intermediate" -> "中等,可使用专业术语";
                    case "expert" -> "专家,可使用深度技术内容";
                    default -> "未知";
                }));
        }

        if (responsibilityScope != null) {
            sb.append(String.format("- 负责范围: %s\n",
                responsibilityScope));
        }

        // 只取 Top 3 关注指标
        if (focusMetricCounts != null && !focusMetricCounts.isEmpty()) {
            String topMetrics = focusMetricCounts.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue()
                    .reversed())
                .limit(3)
                .map(e -> metricLabel(e.getKey()))
                .collect(Collectors.joining("、"));
            sb.append(String.format("- 重点关注: %s\n", topMetrics));
        }

        // 只取 Top 3 兴趣主题
        if (interestWeights != null && !interestWeights.isEmpty()) {
            String topInterests = interestWeights.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue()
                    .reversed())
                .limit(3)
                .map(Map.Entry::getKey)
                .collect(Collectors.joining("、"));
            sb.append(String.format("- 持续关注: %s\n", topInterests));
        }

        return sb.toString();
    }
}

四、三层记忆的统一注入

4.1 记忆编排器:决定"什么时候注入什么"

/**
 * 统一记忆编排器
 * 
 * 职责:在每次对话前,协调三层记忆的检索与注入
 * 
 * 注入策略:
 * 1. 工作记忆:始终注入(由 ChatMemory 自动管理)
 * 2. 用户画像:始终注入(200字以内,开销极低)
 * 3. 情景记忆:按需检索,相关性 > 0.65 时才注入
 * 
 * Token 预算控制:
 * 记忆注入总量不超过 800 tokens,避免挤压对话空间
 */
@Service
public class MemoryOrchestrator {

    private final UserProfileService profileService;
    private final EpisodicMemoryService episodicService;
    private static final int MAX_EPISODIC_RESULTS = 3;
    private static final double MIN_RELEVANCE = 0.65;

    /**
     * 构建增强型 System Prompt
     * 
     * 将用户画像 + 相关历史记忆注入到系统提示词中
     */
    public String buildEnhancedSystemPrompt(String userId,
                                            String userQuery,
                                            String basePrompt) {
        StringBuilder enhanced = new StringBuilder(basePrompt);

        // ===== 注入用户画像(语义记忆)=====
        UserProfile profile = profileService.getProfile(userId);
        if (profile != null) {
            enhanced.append("\n\n");
            enhanced.append(profile.toPromptDescription());
        }

        // ===== 注入相关历史(情景记忆)=====
        List<ConversationSummary> memories = episodicService.search(
            userQuery, userId, MAX_EPISODIC_RESULTS);

        if (!memories.isEmpty()) {
            enhanced.append("\n\n相关历史对话记忆:\n");
            for (ConversationSummary memory : memories) {
                enhanced.append(String.format(
                    "- [%s] %s(结果: %s)\n",
                    memory.getTimestamp().toLocalDate(),
                    memory.getTopic(),
                    memory.getOutcome()
                ));
                // 只注入关键事实,不注入完整摘要
                if (!memory.getKeyFacts().isEmpty()) {
                    enhanced.append(String.format("  关键信息: %s\n",
                        String.join("; ", memory.getKeyFacts())));
                }
            }
        }

        return enhanced.toString();
    }

    /**
     * 构建动态 System Prompt 的 Agent 接口
     * 
     * 通过 @V 注解注入动态变量,实现每次对话的个性化提示词
     */
    public interface PersonalizedAgent {

        @SystemMessage("{{systemPrompt}}")
        String chat(@UserMessage String userMessage,
                    @V("systemPrompt") String systemPrompt);
    }
}

4.2 会话生命周期钩子

/**
 * 会话生命周期管理器
 * 
 * 在关键节点触发记忆操作:
 * - 会话开始:加载用户画像
 * - 每轮对话:更新工作记忆
 * - 会话结束:生成情景记忆摘要 + 更新用户画像
 */
@Component
public class SessionLifecycleManager {

    private final ConversationSummaryService summaryService;
    private final UserProfileService profileService;
    private final ChatMemoryStore memoryStore;

    /**
     * 会话结束回调
     * 
     * 这是一个异步操作,不阻塞用户响应
     */
    @Async
    @EventListener
    public void onSessionEnd(SessionEndEvent event) {
        String sessionId = event.getSessionId();
        String userId = event.getUserId();

        log.info("会话结束,开始记忆沉淀: session={}", sessionId);

        // 1. 获取完整对话历史
        List<ChatMessage> messages = memoryStore.getMessages(sessionId);

        if (messages.size() < 4) {
            // 对话太短(少于2轮),没有记忆价值
            log.debug("对话过短,跳过记忆沉淀: {} 条消息", messages.size());
            return;
        }

        // 2. 生成情景记忆(对话摘要)
        ConversationSummary summary =
            summaryService.summarize(sessionId, userId, messages);
        episodicMemoryService.save(summary);

        // 3. 更新用户画像(语义记忆)
        profileService.updateFromConversation(userId, messages);

        log.info("记忆沉淀完成: session={}, topic={}",
            sessionId, summary.getTopic());
    }
}

五、数据库设计

-- 用户画像主表
CREATE TABLE agent_user_profile (
    user_id              VARCHAR(64)  PRIMARY KEY,
    reply_preference     VARCHAR(32),
    preference_confidence DOUBLE DEFAULT 0.0,
    preference_updated_at TIMESTAMP,
    expertise_level      VARCHAR(32),
    responsibility_scope  VARCHAR(256),
    focus_metric_counts   JSON,         -- {"cost": 12, "safety": 5}
    interest_weights      JSON,         -- {"数据中心能耗": 8}
    last_active_at        TIMESTAMP,
    created_at            TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at            TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 对话摘要表(情景记忆)
CREATE TABLE agent_conversation_summary (
    session_id          VARCHAR(64)  PRIMARY KEY,
    user_id             VARCHAR(64)  NOT NULL,
    topic               VARCHAR(512) NOT NULL,
    key_facts           JSON,           -- ["事实1", "事实2"]
    actions             JSON,           -- [{"action":"...", "result":"..."}]
    entities            JSON,           -- {"people":[], "areas":[], ...}
    unresolved          JSON,           -- ["未解决问题"]
    outcome             VARCHAR(32),    -- success/pending/failed
    original_msg_count  INT,
    summary_text        TEXT,           -- 摘要全文(用于向量化)
    created_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_created_at (created_at)
);

控制障碍函数(Control Barrier Function,CBF) 一、定义 介绍控制障碍函数 阅读详情

相关推荐

linux 内核vxlan收发包流程

1.vxlan口收包处理流程 前面几章详细介绍了vxlan接口的使用,而且已经知道vxlan是 MAC IN UDP中的封装,因此,在解封装之前,一切按照原有流程走,在此复习一下内核收发包流程(驱动层的数据处理这次不再解析,直接从__netif_receive_skb_core开始) #物理网卡处理中断,触发softirq i40e_intr └─napi_schedule_irqoff #i40e softirq把包放到per cpu backlog,触发softirq i40e_napi_p

魏言华的博客 3826

从大模型到 Agent:具备记忆与规划能力的智能体是如何工作的

本文从工程系统与认知结构的视角,系统梳理了大模型 Agent 的核心概念与关键能力演进路径。文章首先明确了 Agent 相较于传统大模型在交互方式与系统形态上的本质差异,随后重点分析了 Agent 的记忆机制(短期记忆与长期记忆)以及面向复杂任务的规划能力。在此基础上,文章深入介绍了 CoT、ToT、GoT 等推理范式如何推动大模型从线性推理走向搜索与结构化决策,并重点阐述了 ReAct 框架如何通过“推理—行动—反思”的闭环,将模型能力与真实环境交互有效结合。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 5万+

pytorch转onnx转paddlepaddle

1.pt转onnx 可查看我之前的文章:https://blog.youkuaiyun.com/oyjwin/article/details/117778583?spm=1001.2014.3001.5501

oyjwin的博客 1461

AI Agent的记忆奥秘:短期VS长期记忆详解

假设你(用户)和你的聪明助理(AI Agent)还是需要协作完成一个市场分析报告项目。1、向量数据库向量数据库构建的长期记忆系统,就像升级为智能语义索引档案馆工作原理每份档案都有“内容指纹”:无论报告标题是什么,系统都会分析内容本质,生成语义标识相关档案自动聚类:“市场趋势分析”、“竞争对手研究”、“用户调研”会被放在相邻区域跨时空关联:2022年的“移动端增长”与2024年的“App用户分析”自动关联技术实现类比。

ytt0523_com的博客 1115

MIKE21桥墩模拟实战:抠网格与构筑物法对比与选型指南

本文详细对比了MIKE21软件中抠网格法与构筑物法在桥墩模拟中的应用,包括操作步骤、适用场景及优缺点分析。通过实际案例数据展示两种方法的精度与效率差异,并提供选型决策树和常见问题解决方案,帮助工程师根据项目需求选择最优模拟方法。

weixin_42656416的博客 322

基于555定时器的呼吸灯电路

呼吸灯是指LED灯在芯片的控制下完成由亮到暗的逐渐变化,感觉就像人在呼吸,其广泛使用在手机上,起到一个通知提醒的作用。呼吸灯在许多灯光装饰上也有大量使用,如音响、汽车广告牌等,在生活中较为常见。...

qq1074237849的博客 9492

小智伴的记忆逻辑实施:认识Agent的短期记忆和长期记忆

摘要: 本文探讨了大模型中的记忆机制,重点区分了短期记忆和长期记忆的功能及实现方式。短期记忆用于保持当前会话的上下文连贯性,通过修剪、总结或自定义策略优化;长期记忆则通过向量数据库等存储跨会话经验,适用于用户画像等场景。记忆机制的选择需结合场景复杂度、交互频率和知识相关性,分为即时任务型、连续交互型和知识驱动型三类。以“小智伴”系统为例,其采用短期+长期记忆结合,并计划引入反射循环机制实现自我优化。文章强调,构建符合业务需求的记忆体系是提升智能体效能的关键,并推荐了大模型学习资源。 (149字)

AI_today的博客 1292

智慧楼宇解决方案-全套大合集

智慧楼宇解决方案,楼宇能耗-环境-设备统一管控解决方案。

byte58的博客 2590

AgentScope Runtime记忆实战:ModelStudio Memory构建长期记忆与用户画像的完整指南

🧠 **AgentScope Runtime** 是面向生产环境的 Agent 应用运行框架,而它的 **ModelStudio Memory 记忆组件**能让你的 AI 应用拥有"长期记忆":自动保存对话、语义检索历史、并从对话中自动提取用户画像。本文带你快速上手 AgentScope Runtime 记忆能力,用 ModelStudio Memory 让 AI 真正"记住"用户。 ## 为

gitblog_00045的博客 150

22、光无线通信调制技术详解

本文详细介绍了光无线通信中的多种调制技术,包括开关键控(OOK)、脉冲位置调制(PPM)、数字脉冲间隔调制(DPIM)、副载波强度调制(SCM)、相移键控(PSK)、正交幅度调制(QAM)以及多载波调制(MCM)如OFDM和MSIM。文章分析了各类调制技术在带宽需求、功率效率、传输容量、传输速率和PAPR等方面的性能差异,并通过Matlab仿真展示了其在AWGN信道下的误码率表现。同时,对比了不同技术的适用场景,提出了调制选择流程,并结合室内光通信与自由空间光通信的应用案例,展望了未来调制技术向高频谱效率、

net55的博客 116

Loki 分布式日志中心服务

分布式日志中心 Loki + Promtail + Grafana

原名又逢乱世。不要放下学习的脚步,毕竟比天天打牌、钓鱼、打游戏更能从内在充实自己。 2353

agno中的智能体记忆系统:短期与长期记忆的设计与实现

在多智能体系统(Multi-Agent System, MAS)中,记忆系统是实现智能体持续学习和个性化交互的核心组件。agno作为高性能多智能体运行时框架,其记忆系统通过分层设计实现了短期会话记忆与长期用户画像的协同管理,支持智能体在动态环境中保持上下文连贯性并积累持久知识。本文将深入解析agno记忆系统的架构设计、核心功能及工程实践,帮助开发者构建具备类人记忆能力的智能体应用。 ## 记忆系...

gitblog_00328的博客 799

Explorer.exe系统错误,电脑重启后刚开始一段时间正常,过一会桌面任务栏就卡死,可以打开任务管理器

联想E14Win10专业版。

本博客,博文仅代表个人操作经验,不能完全解决你的问题,仅供参考,佛系回复。 8050

给Agent接长期记忆:用户画像存取实操

我一开始每轮都无脑更新画像,后果是用户随口说句"今天天气真好",模型也认真琢磨要不要更新字段,偶尔还真给我把 last_topic 写成"天气"。让模型 merge 旧摘要和新信息,它偶尔会把旧的关键事实悄悄丢掉——比如原来写着"拒绝股票型",改写完变成只剩最近一句"想了解黄金"。我现在的土办法是,把画像表里的硬字段当锚点,摘要只补软性偏好,硬事实绝不依赖摘要。字段是预先定义死的,模型只负责"填空",不让它自由发挥造字段——不然今天叫 risk_level 明天叫 riskLevel,存取直接对不上。

sclw09的博客 312

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

企业AI Agent记忆系统怎么设计?从短期上下文、长期记忆到用户画像与项目记忆

企业智能体平台需构建完善的Agent记忆系统,实现从短期对话到长期项目管理的多维度记忆能力。短期记忆维持对话连贯性,任务状态记忆保障复杂流程可靠性,而长期记忆(用户画像、项目历史等)则需结构化存储并支持动态更新。记忆系统应区别于知识库,采用分层存储机制(结构化数据库+向量检索),严格遵循权限管理和数据生命周期规则。关键在于平衡记忆价值与隐私安全,通过"记忆候选-确认"机制确保信息准确性,避免数据污染。企业应用应优先选择销售助手、项目管理等高价值场景,最终实现"可治理的记忆&qu

weixin_60702646的博客 328

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

给Agent加长期记忆:用户画像跨会话沉淀

而不是无脑堆原文。

jyqkj的博客 177

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

【必收藏】AI Agent记忆系统详解:从短期记忆到长期记忆的完整架构

对于 AI Agent 而言,记忆至关重要,因为它使它们能够记住之前的互动、从反馈中学习,并适应用户的偏好。

优快云_224022的博客 1047

Agent 的记忆之术:从金鱼脑到长期记忆,AI 智能体记忆机制的设计哲学

Agent 记忆机制是从金鱼脑到长期记忆的进化之路——工作记忆是当下,短期记忆是近期,长期记忆是知识,反思记忆是智慧。落地建议:工作记忆容量 4-8K tokens,超出时摘要压缩转移;短期记忆按重要性淘汰,保留关键事件;长期记忆用向量数据库存储,支持语义检索;反思记忆定期从经验中提炼策略,设置最低样本量阈值。记忆不是越多越好,而是越精准越好——就像 Tensor 的记忆,它不记得三天前吃的什么猫粮,但永远记得零食柜的位置。好的记忆系统,应该记住该记住的,忘掉该忘掉的。

weixin_43272162的博客 5005

【智能体】AI Agent 记忆系统:从短期到长期的技术架构与实践指南

对 AI Agent 而言,记忆是其实现历史交互回溯、反馈学习与用户偏好适配的核心能力,主要分为两个层面:会话级记忆(短期记忆):特指用户与 AI Agent 在单次会话中的多轮交互数据,包括用户查询、Agent 响应、工具调用及执行结果等,直接服务于当前会话的实时交互。跨会话记忆(长期记忆):从多次会话中抽取提炼的通用信息,涵盖用户偏好、核心事实、领域经验等,能够跨会话辅助 Agent 进行个性化推理。

qq_41466440的博客 1221

Agent Memory 到底是什么?为什么长期记忆正在成为 AI 的第二大脑

《AI Agent记忆系统的演进与未来》摘要:随着AI从聊天机器人向智能体(Agent)演进,记忆系统(Memory)成为关键技术瓶颈。本文剖析了Agent记忆系统的核心设计:区分工作记忆(Context)与长期记忆(Memory),采用人类启发的三级分层架构(工作记忆/短期记忆/长期记忆),并揭示其存储内容远超聊天记录,涵盖用户画像、项目知识和行为经验。作者指出全量记忆注入Prompt的不可行性,提出知识网络(Memory Graph)比传统向量数据库更符合联想记忆特性。文章预见记忆系统将发展为包含存储、

记录从移动端、鸿蒙到物联网与AI的完整实战与思考。专注于分享可复用的工程方案与产品思维,尤其深入鸿蒙生态与全栈开发。以十年工程与管理沉淀,为你提供清晰的技术纵深路径。坚信技术的价值在于“贯通”与“落地”。 1042

挺意外的,Agent长期记忆潜力被AMemGym挖出来了

AMemGym的意义,不仅仅是提出了一个新的评测工具。它是对AI助手记忆能力评估和优化方式的一次范式转换:从静态的、事后的、脱离交互的评估,转向动态的、在线的、贴合交互的评估与训练。长期记忆,是AI迈向真正个性化、持续化服务的基石。AMemGym挖出的,或许正是这块基石的第一个可靠蓝图。接下来的故事,值得期待。Agent · 目录从0到1!想入门大模型(LLM)却不知道从哪开始?我根据最新的技术栈和我自己的经历&理解,帮大家整理了一份LLM学习路线图,涵盖从理论基础到落地应用的全流程!

Python_0011的博客 835

AI Agent记忆革命:TencentDB Agent Memory如何让智能体跨会话复用经验?

TencentDB Agent Memory是一款团队级AI智能体记忆中枢,能够将对话、文档和代码转化为四种可复用的记忆资产(聊天记忆、技能、LLM知识库、代码图谱),实现跨智能体和框架的记忆治理、共享与装备。它解决了传统AI智能体记忆碎片化、不可复用的痛点,让智能体真正具备长期学习和经验积累的能力。 ## 为什么AI智能体需要分层记忆系统? 当前AI智能体的记忆系统普遍存在两大问题:要么是简

gitblog_01035的博客 1019

agent-memory快速上手:5行代码为AI Agent构建长期记忆的完整新手指南

**agent-memory**(Jiuwen Memory)是一款面向 AI 智能体的**长期记忆模块**,为运行在 openJiuwen 框架上的智能体提供记忆提取、存储、检索与迁移能力。它不只是向量检索,而是融合「多形式索引 + 记忆自演进 + 混合检索 + 多集成方式」的通用记忆底座——无论你写的是聊天助手、编码 Agent 还是自主 Agent,都能通过 SDK / HTTP API /

gitblog_00285的博客 990

一文读懂AI Agent记忆系统:短期与长期记忆架构全解析(珍藏指南)

对于 AI Agent 而言,记忆至关重要,因为它使它们能够记住之前的互动、从反馈中学习,并适应用户的偏好。

youmaob的博客 1086

AI Agent 面试题 402:如何实现Agent的个性化记忆(用户画像记忆)?

长期记忆(向量存储) 是 AI Agent 技术体系中的重要组成部分。简单来说,它涉及到 Agent 如何在 记忆与状态管理 层面实现智能化的行为和决策。在实际应用中,长期记忆(向量存储) 的核心目标是让 Agent 能够更加高效、准确地完成特定任务。这需要我们深入理解其底层原理和实现机制。从学术角度来看,长期记忆(向量存储) 的研究可以追溯到人工智能的早期阶段。早在 1950 年代,Alan Turing 就提出了关于机器智能的基本设想。

weixin_42618497的博客 148

AI Agent 为什么总「失忆」?长期记忆库:让智能体跨会话记住每一位客户

<think>好的,用户需要根据一篇文章生成不超过150字的摘要。文章内容已经仔细读完,核心是讲AI智能体的长期记忆库功能,包含定位、机制、接入方式、场景和设计要点。 提取关键信息:长期记忆库解决跨会话记忆问题,核心是写入和检索两个机制,支持一键开启或精细编排两种接入方式,落地场景有用户画像、长期跟进和个性化推荐,设计亮点是按用户隔离、数据角色机制。 摘要需要覆盖:问题(AI总失忆)、方案(长期记忆库)、核心机制(写入+检索)、接入方式(两种)、场景(三类)、设计亮点(用户隔离和数据角色)。这几点都必须在1

Zseny_的博客 200
上一篇: 三:Agent 自动评估体系与 A2A 协议实战:让你的 AI 智能体从“能用“走向“可靠“
下一篇: 五:Agent 可观测性与成本优化——从“能跑“到“好管“
yychen_java
博客等级 码龄8年 1451粉丝 77原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值