zhou zhou
9 小时以前 80a6d9236ade191a5de0975abe4de5a6e7e63915
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
@@ -30,6 +30,11 @@
    private final AiChatSessionMapper aiChatSessionMapper;
    private final AiChatMessageMapper aiChatMessageMapper;
    /**
     * 读取会话记忆快照。
     * 返回结果同时包含完整落库历史、短期记忆窗口以及摘要/事实记忆,
     * 便于调用方按不同用途选择数据粒度。
     */
    @Override
    public AiChatMemoryDto getMemory(Long userId, Long tenantId, String promptCode, Long sessionId) {
        ensureIdentity(userId, tenantId);
@@ -59,6 +64,10 @@
                .build();
    }
    /**
     * 查询当前用户在某个 Prompt 下的会话列表。
     * 列表只返回用于侧边栏展示的摘要信息,不返回完整对话内容。
     */
    @Override
    public List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode, String keyword) {
        ensureIdentity(userId, tenantId);
@@ -83,6 +92,10 @@
        return result;
    }
    /**
     * 解析本轮请求应该落到哪个会话。
     * 如果前端带了 sessionId 则做归属校验并复用;否则自动创建新会话。
     */
    @Override
    public AiChatSession resolveSession(Long userId, Long tenantId, String promptCode, Long sessionId, String titleSeed) {
        ensureIdentity(userId, tenantId);
@@ -108,6 +121,10 @@
        return session;
    }
    /**
     * 落库保存一整轮对话。
     * 这里会顺序写入本轮用户消息和模型回复,并在最后刷新会话标题、最后活跃时间和记忆画像。
     */
    @Override
    public void saveRound(AiChatSession session, Long userId, Long tenantId, List<AiChatMessageDto> memoryMessages, String assistantContent) {
        if (session == null || session.getId() == null) {
@@ -136,6 +153,7 @@
        refreshMemoryProfile(session.getId(), userId);
    }
    /** 删除整个会话及其消息。 */
    @Override
    public void removeSession(Long userId, Long tenantId, Long sessionId) {
        ensureIdentity(userId, tenantId);
@@ -169,6 +187,7 @@
        }
    }
    /** 更新会话标题并返回最新会话摘要。 */
    @Override
    public AiChatSessionDto renameSession(Long userId, Long tenantId, Long sessionId, AiChatSessionRenameRequest request) {
        ensureIdentity(userId, tenantId);
@@ -186,6 +205,7 @@
        return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
    }
    /** 更新会话置顶状态。 */
    @Override
    public AiChatSessionDto pinSession(Long userId, Long tenantId, Long sessionId, AiChatSessionPinRequest request) {
        ensureIdentity(userId, tenantId);
@@ -203,6 +223,7 @@
        return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId));
    }
    /** 清空某个会话的全部消息和派生记忆字段。 */
    @Override
    public void clearSessionMemory(Long userId, Long tenantId, Long sessionId) {
        ensureIdentity(userId, tenantId);
@@ -224,6 +245,7 @@
                .setLastMessageTime(session.getCreateTime()));
    }
    /** 只保留最近一轮问答,用于手动裁剪长会话。 */
    @Override
    public void retainLatestRound(Long userId, Long tenantId, Long sessionId) {
        ensureIdentity(userId, tenantId);
@@ -307,6 +329,7 @@
    }
    private List<AiChatMessageDto> normalizeMessages(List<AiChatMessageDto> memoryMessages) {
        /** 清洗前端上传的内存消息,只允许 user/assistant 两类角色落库。 */
        List<AiChatMessageDto> normalized = new ArrayList<>();
        if (Cools.isEmpty(memoryMessages)) {
            return normalized;
@@ -372,6 +395,10 @@
    }
    private String buildSessionTitle(String titleSeed) {
        /**
         * 把首轮用户问题压缩成适合作为会话标题的短摘要。
         * 这里会去掉换行、连续空白,并优先在自然语义断点处截断。
         */
        if (!StringUtils.hasText(titleSeed)) {
            throw new CoolException("AI 会话标题不能为空");
        }
@@ -429,6 +456,10 @@
    }
    private void refreshMemoryProfile(Long sessionId, Long userId) {
        /**
         * 重新计算会话的摘要记忆和关键事实。
         * 这是“持久化消息”和“模型上下文治理”之间的桥梁方法。
         */
        List<AiChatMessageDto> messages = listMessages(sessionId);
        List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(messages, AiDefaults.MEMORY_RECENT_ROUNDS);
        List<AiChatMessageDto> historyMessages = messages.size() > shortMemoryMessages.size()
@@ -454,6 +485,7 @@
    }
    private List<AiChatMessageDto> tailMessagesByRounds(List<AiChatMessageDto> source, int rounds) {
        /** 按“用户发言轮次”裁剪最近消息,而不是简单按条数截断。 */
        if (Cools.isEmpty(source) || rounds <= 0) {
            return List.of();
        }
@@ -492,6 +524,7 @@
    }
    private String buildMemorySummary(List<AiChatMessageDto> historyMessages) {
        /** 为较早历史生成可直接插入系统消息的文本摘要。 */
        StringBuilder builder = new StringBuilder("较早对话摘要:\n");
        for (AiChatMessageDto item : historyMessages) {
            if (item == null || !StringUtils.hasText(item.getContent())) {
@@ -511,6 +544,7 @@
    }
    private String buildMemoryFacts(List<AiChatMessageDto> messages) {
        /** 从最近用户关注点中提炼关键事实,作为轻量持久记忆。 */
        if (Cools.isEmpty(messages)) {
            return null;
        }