Skip to content

聊天服务

介绍

ChatService 是 LangChain4j 模块的核心服务,提供同步和流式的 AI 对话功能。它支持多种对话模式,自动管理会话记忆,是构建智能对话应用的关键组件。通过统一的服务接口,开发者可以轻松实现单轮问答、多轮对话、知识库问答和函数调用等功能。

核心特性:

  • 多对话模式 - 单轮对话、连续对话、RAG 对话、函数调用四种模式
  • 会话管理 - 自动管理对话历史,支持多轮上下文记忆
  • 流式响应 - 实时输出响应内容,提升用户体验
  • 灵活存储 - 支持内存和 Redis 两种会话存储方式
  • Token 统计 - 自动统计输入和输出 Token 使用量
  • 灵活配置 - 支持自定义系统提示词、温度参数等

模块架构

整体架构图

┌─────────────────────────────────────────────────────────────────┐
│                      应用层 (Application)                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐ │
│  │ Controller  │  │   Service   │  │     Scheduled Task      │ │
│  └──────┬──────┘  └──────┬──────┘  └───────────┬─────────────┘ │
└─────────┼────────────────┼─────────────────────┼───────────────┘
          │                │                     │
          ▼                ▼                     ▼
┌─────────────────────────────────────────────────────────────────┐
│                    ChatService (核心服务)                        │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                      对话处理引擎                          │ │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│  │  │  SINGLE  │ │CONTINUOUS│ │   RAG    │ │   FUNCTION   │ │ │
│  │  │  单轮    │ │  多轮    │ │ 知识库  │ │   函数调用   │ │ │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              │                                  │
│  ┌──────────────────────────┴────────────────────────────────┐ │
│  │                                                            │ │
│  │   ┌─────────────────┐          ┌─────────────────────┐   │ │
│  │   │  ModelFactory   │          │  ChatMemoryManager  │   │ │
│  │   │  模型工厂       │          │  会话记忆管理       │   │ │
│  │   └────────┬────────┘          └──────────┬──────────┘   │ │
│  │            │                               │              │ │
│  └────────────┼───────────────────────────────┼──────────────┘ │
└───────────────┼───────────────────────────────┼────────────────┘
                │                               │
                ▼                               ▼
┌───────────────────────────┐   ┌───────────────────────────────┐
│      AI Model Provider    │   │       Memory Store             │
│  ┌─────────────────────┐  │   │  ┌────────┐   ┌────────────┐  │
│  │ DeepSeek  │ OpenAI  │  │   │  │ Memory │   │   Redis    │  │
│  │ Qwen      │ Ollama  │  │   │  │ 内存   │   │  持久化    │  │
│  └─────────────────────┘  │   │  └────────┘   └────────────┘  │
└───────────────────────────┘   └───────────────────────────────┘

核心组件说明

组件类名职责
对话服务ChatService处理同步/流式对话请求,协调模型调用和会话管理
记忆管理ChatMemoryManager管理会话内存,支持内存和 Redis 存储
流式处理StreamChatHandler处理流式响应,实现增量内容推送
Redis 存储RedisChatStore实现 ChatMemoryStore 接口的 Redis 存储
模型工厂ModelFactory创建同步和流式聊天模型实例

数据流转图

┌──────────┐    ChatRequest     ┌─────────────┐
│  Client  │ ─────────────────► │ ChatService │
└──────────┘                    └──────┬──────┘

                    ┌──────────────────┼──────────────────┐
                    │                  │                  │
                    ▼                  ▼                  ▼
            ┌───────────┐      ┌───────────┐      ┌───────────┐
            │ getOrGen  │      │  create   │      │  handle   │
            │ SessionId │      │ ChatModel │      │  ByMode   │
            └─────┬─────┘      └─────┬─────┘      └─────┬─────┘
                  │                  │                  │
                  ▼                  ▼                  ▼
            ┌───────────┐      ┌───────────┐      ┌───────────┐
            │  Memory   │      │  Model    │      │ generate  │
            │  Manager  │◄────►│  Factory  │◄────►│  Response │
            └───────────┘      └───────────┘      └─────┬─────┘


                                                ┌───────────────┐
                                                │ ChatResponse  │
                                                │ + TokenUsage  │
                                                └───────────────┘

对话模式

聊天服务支持四种对话模式,每种模式适用于不同的业务场景:

模式枚举值说明使用场景
单轮对话SINGLE无上下文记忆简单问答、翻译、摘要
连续对话CONTINUOUS保持上下文记忆多轮对话、智能客服
RAG 对话RAG基于知识库检索文档问答、企业知识库
函数调用FUNCTION调用外部工具天气查询、计算、API调用

模式选择指南

                    ┌─────────────────────┐
                    │  需要记住上下文吗?   │
                    └──────────┬──────────┘

                   ┌───────────┴───────────┐
                   │                       │
                   ▼ 否                    ▼ 是
           ┌───────────────┐       ┌───────────────┐
           │    SINGLE     │       │  需要知识库?  │
           └───────────────┘       └───────┬───────┘

                               ┌───────────┴───────────┐
                               │                       │
                               ▼ 是                    ▼ 否
                       ┌───────────────┐       ┌───────────────┐
                       │      RAG      │       │  需要调工具?  │
                       └───────────────┘       └───────┬───────┘

                                           ┌───────────┴───────────┐
                                           │                       │
                                           ▼ 是                    ▼ 否
                                   ┌───────────────┐       ┌───────────────┐
                                   │   FUNCTION    │       │  CONTINUOUS   │
                                   └───────────────┘       └───────────────┘

ChatMode 枚举定义

java
/**
 * 对话模式枚举
 */
@Getter
@AllArgsConstructor
public enum ChatMode {
    /**
     * 单轮对话 - 不保存对话历史
     */
    SINGLE("single", "单轮对话"),

    /**
     * 多轮对话 - 保存对话历史,支持上下文
     */
    CONTINUOUS("continuous", "多轮对话"),

    /**
     * RAG增强对话 - 基于知识库检索增强
     */
    RAG("rag", "知识库问答"),

    /**
     * 函数调用 - 支持调用外部工具
     */
    FUNCTION("function", "函数调用");

    private final String code;
    private final String name;
}

基本用法

单轮同步对话

适合简单的问答场景,不需要记忆上下文:

java
@Autowired
private ChatService chatService;

public String simpleChat(String message) {
    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setMessage(message)
        .setMode(ChatMode.SINGLE)
        .setStream(false);

    ChatResponse response = chatService.chat(request);
    return response.getContent();
}

工作原理:

  1. 创建 ChatRequest 对象,设置对话模式为 SINGLE
  2. ChatService 调用 ModelFactory 创建聊天模型
  3. 调用 handleSingleChat 方法处理单轮对话
  4. 直接调用模型生成响应,不保存历史
  5. 构建 ChatResponse 返回结果

多轮连续对话

保持会话上下文,实现连续对话:

java
public String continuousChat(String sessionId, String message) {
    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSessionId(sessionId)          // 会话ID
        .setMessage(message)
        .setMode(ChatMode.CONTINUOUS)     // 连续对话模式
        .setStream(false);

    ChatResponse response = chatService.chat(request);
    return response.getContent();
}

对话示例:

java
String sessionId = "user-123";

// 第一轮
String response1 = continuousChat(sessionId, "我叫张三");
// AI: 你好张三!

// 第二轮(AI 记住了名字)
String response2 = continuousChat(sessionId, "我叫什么名字?");
// AI: 你叫张三。

// 第三轮(继续保持上下文)
String response3 = continuousChat(sessionId, "今年30岁");
// AI: 好的,张三,我记住了你今年30岁。

// 第四轮
String response4 = continuousChat(sessionId, "我的信息是什么?");
// AI: 根据我们的对话,你叫张三,今年30岁。

工作原理:

  1. ChatMemoryManager 根据 sessionId 获取或创建会话内存
  2. 如果是新会话且有系统提示词,先添加系统消息
  3. 添加用户消息到会话内存
  4. 调用模型生成响应时传入完整的消息历史
  5. 将 AI 回复保存到会话内存

流式对话

实时流式输出响应,提升用户体验:

java
public void streamChat(String message, Consumer<String> onContent) {
    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setMessage(message)
        .setMode(ChatMode.SINGLE)
        .setStream(true);

    chatService.streamChat(request, response -> {
        if (!response.getFinished()) {
            // 输出内容片段
            onContent.accept(response.getContent());
        } else {
            // 流式完成
            System.out.println("\n完成!");
        }
    });
}

// 使用
streamChat("讲个笑话", content -> System.out.print(content));

流式响应处理流程:

┌──────────────────────────────────────────────────────────────┐
│                  流式响应处理流程                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  StreamingChatLanguageModel.generate()                       │
│            │                                                 │
│            ▼                                                 │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              StreamChatHandler                        │   │
│  │  ┌────────────────────────────────────────────────┐  │   │
│  │  │ onNext(token)                                  │  │   │
│  │  │   └─► fullContent.append(token)                │  │   │
│  │  │   └─► responseConsumer.accept(partial)         │  │   │
│  │  └────────────────────────────────────────────────┘  │   │
│  │  ┌────────────────────────────────────────────────┐  │   │
│  │  │ onComplete(response)                           │  │   │
│  │  │   └─► 设置 TokenUsage                          │  │   │
│  │  │   └─► responseConsumer.accept(final)           │  │   │
│  │  │   └─► 执行 onComplete 回调                     │  │   │
│  │  └────────────────────────────────────────────────┘  │   │
│  │  ┌────────────────────────────────────────────────┐  │   │
│  │  │ onError(error)                                 │  │   │
│  │  │   └─► 记录错误日志                             │  │   │
│  │  │   └─► responseConsumer.accept(error)           │  │   │
│  │  └────────────────────────────────────────────────┘  │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

多轮流式对话

结合会话记忆和流式输出:

java
public void continuousStreamChat(String sessionId, String message,
                                  Consumer<String> onContent) {
    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSessionId(sessionId)
        .setMessage(message)
        .setMode(ChatMode.CONTINUOUS)
        .setStream(true);

    chatService.streamChat(request, response -> {
        if (!response.getFinished()) {
            onContent.accept(response.getContent());
        } else {
            // 流式完成,Token 统计已在 response.getTokenUsage() 中
            log.info("Token使用: {}", response.getTokenUsage());
        }
    });
}

高级功能

自定义系统提示词

为 AI 设置角色和行为规范:

java
public String techAssistant(String question) {
    String systemPrompt = """
        你是一个专业的 Java 技术顾问。
        你擅长 Spring Boot、微服务架构、数据库优化等领域。
        请用简洁专业的语言回答技术问题。
        """;

    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSystemPrompt(systemPrompt)
        .setMessage(question)
        .setMode(ChatMode.SINGLE)
        .setStream(false);

    ChatResponse response = chatService.chat(request);
    return response.getContent();
}

系统提示词处理逻辑:

java
// 单轮对话 - 每次请求都添加系统提示词
private ChatResponse handleSingleChat(ChatLanguageModel chatModel,
                                       ChatRequest request) {
    UserMessage userMessage = UserMessage.from(request.getMessage());

    Response<AiMessage> response;
    if (StrUtil.isNotBlank(request.getSystemPrompt())) {
        SystemMessage systemMessage = SystemMessage.from(request.getSystemPrompt());
        response = chatModel.generate(systemMessage, userMessage);
    } else {
        response = chatModel.generate(userMessage);
    }

    return buildResponse(response);
}

// 多轮对话 - 仅在新会话时添加系统提示词
private ChatResponse handleContinuousChat(...) {
    var memory = memoryManager.getOrCreateMemory(sessionId);

    // 只有在会话为空时才添加系统提示词
    if (memory.messages().isEmpty() && StrUtil.isNotBlank(request.getSystemPrompt())) {
        memory.add(SystemMessage.from(request.getSystemPrompt()));
    }

    // ... 后续处理
}

调整模型参数

根据场景调整温度、Token 限制等参数:

java
// 精确问答(低温度)- 输出更稳定、确定性更高
ChatRequest accurateRequest = new ChatRequest()
    .setProvider("deepseek")
    .setMessage("什么是 Spring Boot?")
    .setTemperature(0.1)      // 低温度
    .setMaxTokens(200);

// 创意写作(高温度)- 输出更多样化、创意性更强
ChatRequest creativeRequest = new ChatRequest()
    .setProvider("deepseek")
    .setMessage("写一首关于春天的诗")
    .setTemperature(1.5)      // 高温度
    .setMaxTokens(500);

// 代码生成(极低温度)- 最大确定性
ChatRequest codeRequest = new ChatRequest()
    .setProvider("deepseek")
    .setMessage("写一个快速排序算法")
    .setTemperature(0.0)      // 零温度
    .setMaxTokens(1000);

温度参数说明:

温度范围输出特性适用场景
0.0 - 0.3确定性高,输出稳定代码生成、翻译、事实问答
0.4 - 0.7适度多样性日常对话、客服
0.8 - 1.2创意性强写作、头脑风暴
1.3 - 2.0高度随机创意文案、诗歌

会话管理

java
// 获取会话历史
List<ChatMessage> history = chatService.getSessionMessages(sessionId);

// 清除会话
chatService.clearSession(sessionId);

// 获取所有活跃会话(需要扩展实现)
List<String> sessions = chatService.getActiveSessions();

知识库 ID 配置(RAG 模式)

java
public String ragChat(String question, List<Long> knowledgeBaseIds) {
    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setMessage(question)
        .setMode(ChatMode.RAG)
        .setKnowledgeBaseIds(knowledgeBaseIds)  // 指定知识库
        .setStream(false);

    ChatResponse response = chatService.chat(request);

    // RAG 模式会返回引用的文档
    List<DocumentReference> refs = response.getReferences();

    return response.getContent();
}

额外参数传递

java
public String chatWithExtraParams(String message) {
    Map<String, Object> extraParams = new HashMap<>();
    extraParams.put("topP", 0.9);
    extraParams.put("frequencyPenalty", 0.5);
    extraParams.put("presencePenalty", 0.5);

    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setMessage(message)
        .setMode(ChatMode.SINGLE)
        .setExtraParams(extraParams);

    return chatService.chat(request).getContent();
}

核心实现

ChatService 同步对话实现

java
/**
 * 同步对话
 */
public ChatResponse chat(ChatRequest request) {
    long startTime = System.currentTimeMillis();

    try {
        // 1. 获取或生成会话ID
        String sessionId = getOrGenerateSessionId(request);

        // 2. 获取模型提供商
        String provider = StrUtil.isNotBlank(request.getProvider())
            ? request.getProvider()
            : properties.getDefaultProvider();

        // 3. 创建聊天模型
        ChatLanguageModel chatModel = modelFactory.createChatModel(
            provider,
            request.getModelName()
        );

        // 4. 根据对话模式处理
        ChatResponse response = switch (request.getMode()) {
            case SINGLE -> handleSingleChat(chatModel, request);
            case CONTINUOUS -> handleContinuousChat(chatModel, request, sessionId);
            case RAG -> handleRagChat(chatModel, request, sessionId);
            case FUNCTION -> handleFunctionChat(chatModel, request, sessionId);
        };

        // 5. 设置响应信息
        response.setSessionId(sessionId);
        response.setResponseTime(System.currentTimeMillis() - startTime);

        return response;

    } catch (Exception e) {
        log.error("Chat error: {}", e.getMessage(), e);
        return ChatResponse.builder()
            .error(e.getMessage())
            .responseTime(System.currentTimeMillis() - startTime)
            .build();
    }
}

ChatService 流式对话实现

java
/**
 * 流式对话
 */
public void streamChat(ChatRequest request, Consumer<ChatResponse> responseConsumer) {
    try {
        // 1. 获取或生成会话ID
        String sessionId = getOrGenerateSessionId(request);

        // 2. 获取模型提供商
        String provider = StrUtil.isNotBlank(request.getProvider())
            ? request.getProvider()
            : properties.getDefaultProvider();

        // 3. 创建流式聊天模型
        StreamingChatLanguageModel streamingModel = modelFactory.createStreamingChatModel(
            provider,
            request.getModelName()
        );

        // 4. 根据对话模式处理
        switch (request.getMode()) {
            case SINGLE -> handleSingleStreamChat(streamingModel, request, responseConsumer);
            case CONTINUOUS -> handleContinuousStreamChat(streamingModel, request,
                                                          sessionId, responseConsumer);
            case RAG -> handleRagStreamChat(streamingModel, request, sessionId, responseConsumer);
            case FUNCTION -> handleFunctionStreamChat(streamingModel, request,
                                                      sessionId, responseConsumer);
        }

    } catch (Exception e) {
        log.error("Stream chat error: {}", e.getMessage(), e);
        responseConsumer.accept(ChatResponse.builder()
            .error(e.getMessage())
            .finished(true)
            .build());
    }
}

多轮流式对话处理

java
/**
 * 多轮流式对话
 */
private void handleContinuousStreamChat(
    StreamingChatLanguageModel streamingModel,
    ChatRequest request,
    String sessionId,
    Consumer<ChatResponse> responseConsumer) {

    // 获取会话内存
    var memory = memoryManager.getOrCreateMemory(sessionId);

    // 添加系统提示词(仅新会话)
    if (memory.messages().isEmpty() && StrUtil.isNotBlank(request.getSystemPrompt())) {
        memory.add(SystemMessage.from(request.getSystemPrompt()));
    }

    // 添加用户消息
    UserMessage userMessage = UserMessage.from(request.getMessage());
    memory.add(userMessage);

    String messageId = IdUtil.fastSimpleUUID();
    StringBuilder fullContent = new StringBuilder();

    // 创建流式处理器,包含完成回调
    var handler = new StreamChatHandler(messageId, responseConsumer, fullContent, () -> {
        // 流式完成后,将AI回复保存到内存
        memory.add(AiMessage.from(fullContent.toString()));
    });

    // 生成流式响应
    streamingModel.generate(memory.messages(), handler);
}

响应构建

java
/**
 * 构建响应对象
 */
private ChatResponse buildResponse(Response<AiMessage> response) {
    ChatResponse chatResponse = ChatResponse.builder()
        .messageId(IdUtil.fastSimpleUUID())
        .content(response.content().text())
        .finished(true)
        .build();

    // 设置Token使用情况
    if (response.tokenUsage() != null) {
        TokenUsage tokenUsage = new TokenUsage();
        tokenUsage.setPromptTokens(response.tokenUsage().inputTokenCount());
        tokenUsage.setCompletionTokens(response.tokenUsage().outputTokenCount());
        tokenUsage.setTotalTokens(response.tokenUsage().totalTokenCount());
        chatResponse.setTokenUsage(tokenUsage);
    }

    return chatResponse;
}

会话记忆管理

ChatMemoryManager 实现

ChatMemoryManager 负责管理所有会话的记忆,支持内存和 Redis 两种存储方式。

java
/**
 * 对话内存管理器
 * 负责管理会话的消息历史
 */
@Slf4j
public class ChatMemoryManager {

    private final LangChain4jProperties properties;
    private final RedisChatStore redisChatStore;

    /**
     * 内存缓存 - 用于快速访问
     */
    private final Map<String, ChatMemory> memoryCache = new ConcurrentHashMap<>();

    /**
     * 获取或创建会话内存
     */
    public ChatMemory getOrCreateMemory(String sessionId) {
        if (sessionId == null) {
            sessionId = generateSessionId();
        }
        return memoryCache.computeIfAbsent(sessionId, this::createMemory);
    }

    /**
     * 创建新的会话内存
     */
    private ChatMemory createMemory(String sessionId) {
        int maxMessages = properties.getChat().getHistorySize();

        if ("redis".equalsIgnoreCase(properties.getChat().getMemoryStoreType())) {
            log.debug("Creating Redis-backed chat memory for session: {}", sessionId);
            return MessageWindowChatMemory.builder()
                .id(sessionId)
                .maxMessages(maxMessages)
                .chatMemoryStore(redisChatStore)
                .build();
        } else {
            log.debug("Creating in-memory chat memory for session: {}", sessionId);
            return MessageWindowChatMemory.withMaxMessages(maxMessages);
        }
    }

    /**
     * 获取会话消息历史
     */
    public List<ChatMessage> getMessages(String sessionId) {
        ChatMemory memory = memoryCache.get(sessionId);
        if (memory == null) {
            return List.of();
        }
        return memory.messages();
    }

    /**
     * 清除会话内存
     */
    public void clearMemory(String sessionId) {
        ChatMemory memory = memoryCache.remove(sessionId);
        if (memory != null) {
            memory.clear();
            log.info("Cleared memory for session: {}", sessionId);
        }
    }

    /**
     * 清除所有会话内存
     */
    public void clearAllMemories() {
        memoryCache.values().forEach(ChatMemory::clear);
        memoryCache.clear();
        log.info("Cleared all chat memories");
    }

    /**
     * 生成会话ID
     */
    public String generateSessionId() {
        return IdUtil.fastSimpleUUID();
    }

    /**
     * 获取活跃会话数量
     */
    public int getActiveSessionCount() {
        return memoryCache.size();
    }
}

存储方式对比

┌─────────────────────────────────────────────────────────────────┐
│                    会话存储方式对比                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────┐    ┌─────────────────────────────┐│
│  │      内存存储 (Memory)   │    │      Redis 存储            ││
│  ├─────────────────────────┤    ├─────────────────────────────┤│
│  │ ✓ 访问速度极快          │    │ ✓ 支持持久化               ││
│  │ ✓ 无需额外依赖          │    │ ✓ 支持分布式部署           ││
│  │ ✓ 简单易用              │    │ ✓ 支持自动过期             ││
│  │ ✗ 重启后数据丢失        │    │ ✓ 可跨实例共享             ││
│  │ ✗ 不支持分布式          │    │ ✗ 需要 Redis 依赖          ││
│  │ ✗ 内存占用增长          │    │ ✗ 网络延迟开销             ││
│  └─────────────────────────┘    └─────────────────────────────┘│
│                                                                 │
│  适用场景:                       适用场景:                      │
│  - 开发测试环境                  - 生产环境                     │
│  - 单机部署                      - 集群部署                     │
│  - 临时会话                      - 需要持久化的会话             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Redis 存储实现

RedisChatStore 核心实现

java
/**
 * Redis聊天存储实现
 * 将聊天消息持久化到Redis
 */
@Slf4j
public class RedisChatStore implements ChatMemoryStore {

    private final LangChain4jProperties properties;
    private static final String CHAT_MEMORY_KEY_PREFIX = "langchain4j:chat:";

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String key = buildKey(memoryId);

        try {
            List<String> jsonMessages = RedisUtils.getCacheList(key);

            if (CollUtil.isEmpty(jsonMessages)) {
                return new ArrayList<>();
            }

            // 刷新过期时间(滑动窗口)
            refreshExpiration(key);

            return jsonMessages.stream()
                    .map(ChatMessageDeserializer::messageFromJson)
                    .toList();

        } catch (Exception e) {
            log.error("获取会话消息失败,会话ID: {}", memoryId, e);
            return new ArrayList<>();
        }
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String key = buildKey(memoryId);

        try {
            List<String> jsonMessages = messages.stream()
                    .map(ChatMessageSerializer::messageToJson)
                    .toList();

            // 使用RedisUtils设置缓存并指定过期时间
            Duration duration = Duration.ofMinutes(properties.getChat().getSessionTimeout());
            RedisUtils.setCacheList(key, jsonMessages, duration);

            log.debug("更新会话消息成功,会话ID: {},消息数量: {}", memoryId, messages.size());

        } catch (Exception e) {
            log.error("更新会话消息失败,会话ID: {}", memoryId, e);
        }
    }

    @Override
    public void deleteMessages(Object memoryId) {
        String key = buildKey(memoryId);

        try {
            RedisUtils.deleteObject(key);
            log.debug("删除会话消息成功,会话ID: {}", memoryId);
        } catch (Exception e) {
            log.error("删除会话消息失败,会话ID: {}", memoryId, e);
        }
    }

    /**
     * 获取所有会话ID
     */
    public List<String> getAllSessionIds() {
        Collection<String> keys = RedisUtils.keys(CHAT_MEMORY_KEY_PREFIX + "*");
        if (CollUtil.isEmpty(keys)) {
            return List.of();
        }

        return keys.stream()
                .map(key -> key.replace(CHAT_MEMORY_KEY_PREFIX, ""))
                .toList();
    }

    /**
     * 清空所有会话
     */
    public void clearAll() {
        RedisUtils.deleteKeys(CHAT_MEMORY_KEY_PREFIX + "*");
        log.info("已清空Redis中的所有聊天会话");
    }

    private String buildKey(Object memoryId) {
        return CHAT_MEMORY_KEY_PREFIX + memoryId;
    }

    private void refreshExpiration(String key) {
        long timeoutMinutes = properties.getChat().getSessionTimeout();
        RedisUtils.expire(key, Duration.ofMinutes(timeoutMinutes));
    }
}

Redis 数据结构

┌─────────────────────────────────────────────────────────────────┐
│                    Redis 存储结构示例                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Key: langchain4j:chat:session-abc123                           │
│  Type: List                                                     │
│  TTL: 30 分钟(滑动窗口)                                        │
│                                                                 │
│  Value:                                                         │
│  [                                                              │
│    {                                                            │
│      "type": "SYSTEM",                                          │
│      "text": "你是一个专业的技术顾问..."                         │
│    },                                                           │
│    {                                                            │
│      "type": "USER",                                            │
│      "text": "什么是 Spring Boot?"                              │
│    },                                                           │
│    {                                                            │
│      "type": "AI",                                              │
│      "text": "Spring Boot 是一个开源的 Java 框架..."            │
│    },                                                           │
│    {                                                            │
│      "type": "USER",                                            │
│      "text": "它有什么优势?"                                    │
│    },                                                           │
│    {                                                            │
│      "type": "AI",                                              │
│      "text": "Spring Boot 的主要优势包括..."                    │
│    }                                                            │
│  ]                                                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

流式响应处理

StreamChatHandler 实现

java
/**
 * 流式对话响应处理器
 */
@Slf4j
public class StreamChatHandler implements StreamingResponseHandler<AiMessage> {

    private final String messageId;
    private final Consumer<ChatResponse> responseConsumer;
    private final StringBuilder fullContent;
    private final Runnable onComplete;

    public StreamChatHandler(
            String messageId,
            Consumer<ChatResponse> responseConsumer,
            StringBuilder fullContent) {
        this(messageId, responseConsumer, fullContent, null);
    }

    public StreamChatHandler(
            String messageId,
            Consumer<ChatResponse> responseConsumer,
            StringBuilder fullContent,
            Runnable onComplete) {
        this.messageId = messageId;
        this.responseConsumer = responseConsumer;
        this.fullContent = fullContent;
        this.onComplete = onComplete;
    }

    @Override
    public void onNext(String token) {
        // 累积完整内容
        fullContent.append(token);

        // 推送增量内容
        ChatResponse response = ChatResponse.builder()
                .messageId(messageId)
                .content(token)
                .finished(false)
                .build();

        responseConsumer.accept(response);
    }

    @Override
    public void onComplete(Response<AiMessage> response) {
        // 推送完成消息
        ChatResponse finalResponse = ChatResponse.builder()
                .messageId(messageId)
                .content("")
                .finished(true)
                .build();

        // 设置Token使用情况
        if (response.tokenUsage() != null) {
            TokenUsage tokenUsage = new TokenUsage();
            tokenUsage.setPromptTokens(response.tokenUsage().inputTokenCount());
            tokenUsage.setCompletionTokens(response.tokenUsage().outputTokenCount());
            tokenUsage.setTotalTokens(response.tokenUsage().totalTokenCount());
            finalResponse.setTokenUsage(tokenUsage);
        }

        responseConsumer.accept(finalResponse);

        // 执行完成回调(如保存到会话记忆)
        if (onComplete != null) {
            onComplete.run();
        }
    }

    @Override
    public void onError(Throwable error) {
        log.error("❌ 流式错误: messageId={}, error={}", messageId, error.getMessage(), error);

        // 推送错误消息
        ChatResponse errorResponse = ChatResponse.builder()
                .messageId(messageId)
                .content(error.getMessage())
                .finished(true)
                .build();

        responseConsumer.accept(errorResponse);
    }
}

数据传输对象

ChatRequest 完整定义

java
/**
 * 聊天请求DTO
 */
@Data
@Accessors(chain = true)
public class ChatRequest implements Serializable {

    /**
     * 会话ID - 用于标识对话会话
     */
    private String sessionId;

    /**
     * 用户消息 - 必填
     */
    private String message;

    /**
     * 对话模式 - 默认 CONTINUOUS
     */
    private ChatMode mode = ChatMode.CONTINUOUS;

    /**
     * 模型提供商 - 如 deepseek, openai
     */
    private String provider;

    /**
     * 模型名称 - 如 deepseek-chat
     */
    private String modelName;

    /**
     * 是否流式返回 - 默认 true
     */
    private Boolean stream = true;

    /**
     * 系统提示词 - 设置 AI 角色和行为
     */
    private String systemPrompt;

    /**
     * 温度参数 - 控制输出随机性 (0.0-2.0)
     */
    private Double temperature;

    /**
     * 最大Token数 - 限制输出长度
     */
    private Integer maxTokens;

    /**
     * 知识库ID列表 - RAG模式使用
     */
    private List<Long> knowledgeBaseIds;

    /**
     * 额外参数 - 传递给模型的其他参数
     */
    private Map<String, Object> extraParams;
}

ChatResponse 完整定义

java
/**
 * 聊天响应DTO
 */
@Builder
@Data
@Accessors(chain = true)
public class ChatResponse implements Serializable {

    /**
     * 会话ID
     */
    private String sessionId;

    /**
     * 消息ID - 唯一标识本次响应
     */
    private String messageId;

    /**
     * AI回复内容
     * 流式模式下为增量内容,同步模式下为完整内容
     */
    private String content;

    /**
     * 是否完成
     * 流式模式下用于判断是否结束
     */
    @Builder.Default
    private Boolean finished = true;

    /**
     * Token使用情况
     * 仅在响应完成时提供
     */
    private TokenUsage tokenUsage;

    /**
     * 响应时间(毫秒)
     * 同步模式下为总耗时
     */
    private Long responseTime;

    /**
     * 引用的文档 - RAG模式
     */
    private List<DocumentReference> references;

    /**
     * 错误信息
     * 出错时包含错误详情
     */
    private String error;
}

TokenUsage 定义

java
/**
 * Token使用情况
 */
@Data
public class TokenUsage implements Serializable {

    /**
     * 输入 Token 数(提示词部分)
     */
    private Integer promptTokens;

    /**
     * 输出 Token 数(生成内容部分)
     */
    private Integer completionTokens;

    /**
     * 总 Token 数
     */
    private Integer totalTokens;
}

ChatRequest 配置

必填参数

参数类型说明
messageString用户消息
modeChatMode对话模式

可选参数

参数类型默认值说明
providerString配置默认值模型提供商
modelNameString配置默认值模型名称
sessionIdString自动生成会话ID
systemPromptString系统提示词
temperatureDouble0.7温度参数
maxTokensInteger2048最大Token数
streamBooleantrue是否流式
knowledgeBaseIdsList<Long>知识库ID列表
extraParamsMap<String, Object>额外参数

使用场景

智能客服

java
@RestController
@RequestMapping("/customer-service")
@RequiredArgsConstructor
public class CustomerServiceController {

    private final ChatService chatService;

    @PostMapping("/chat")
    public R<String> chat(@RequestParam String userId,
                          @RequestBody String message) {
        String systemPrompt = """
            你是一个友好的客户服务助手。
            请礼貌、专业地回答客户问题。
            如果不确定答案,请诚实地告知客户。
            """;

        ChatRequest request = new ChatRequest()
            .setProvider("deepseek")
            .setSessionId("customer-" + userId)
            .setSystemPrompt(systemPrompt)
            .setMessage(message)
            .setMode(ChatMode.CONTINUOUS)
            .setStream(false);

        ChatResponse response = chatService.chat(request);
        return R.ok(response.getContent());
    }
}

代码助手

java
public String codeAssistant(String question) {
    String systemPrompt = """
        你是一个专业的编程助手。
        回答时请包含:
        1. 清晰的解释
        2. 完整的代码示例
        3. 最佳实践建议
        """;

    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSystemPrompt(systemPrompt)
        .setMessage(question)
        .setMode(ChatMode.SINGLE)
        .setTemperature(0.3)      // 低温度,输出更准确
        .setMaxTokens(1000);

    return chatService.chat(request).getContent();
}

文档翻译

java
public String translate(String text, String targetLang) {
    String systemPrompt = String.format(
        "你是一个专业的翻译助手。请将文本翻译成%s。", targetLang
    );

    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSystemPrompt(systemPrompt)
        .setMessage(text)
        .setMode(ChatMode.SINGLE)
        .setTemperature(0.3);     // 低温度,翻译更准确

    return chatService.chat(request).getContent();
}

内容审核

java
public boolean contentModeration(String content) {
    String systemPrompt = """
        你是一个内容审核助手。
        请判断以下内容是否包含不当信息。
        只回答"是"或"否"。
        """;

    ChatRequest request = new ChatRequest()
        .setProvider("deepseek")
        .setSystemPrompt(systemPrompt)
        .setMessage(content)
        .setMode(ChatMode.SINGLE)
        .setTemperature(0.0)      // 最低温度,输出最稳定
        .setMaxTokens(10);

    String result = chatService.chat(request).getContent();
    return result.contains("是");
}

SSE 流式接口

java
@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
public class ChatController {

    private final ChatService chatService;

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamChat(@RequestBody ChatRequest request) {
        SseEmitter emitter = new SseEmitter(60000L);  // 60秒超时

        CompletableFuture.runAsync(() -> {
            try {
                chatService.streamChat(request, response -> {
                    try {
                        emitter.send(SseEmitter.event()
                            .data(response)
                            .name("message"));

                        if (response.getFinished()) {
                            emitter.complete();
                        }
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                });
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

WebSocket 流式接口

java
@ServerEndpoint("/ws/chat")
@Component
@RequiredArgsConstructor
public class ChatWebSocket {

    private static ChatService chatService;

    @Autowired
    public void setChatService(ChatService service) {
        chatService = service;
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        ChatRequest request = JSON.parseObject(message, ChatRequest.class);

        chatService.streamChat(request, response -> {
            try {
                session.getBasicRemote().sendText(JSON.toJSONString(response));
            } catch (IOException e) {
                log.error("WebSocket发送失败", e);
            }
        });
    }
}

配置说明

对话配置

yaml
langchain4j:
  chat:
    # 是否启用流式响应
    stream-enabled: true

    # 历史消息保留数量
    history-size: 10

    # 会话超时时间(分钟)
    session-timeout: 30

    # 是否启用内存管理
    memory-enabled: true

    # 存储类型: memory, redis
    memory-store-type: redis

参数说明

参数类型默认值说明
stream-enabledBooleantrue是否启用流式
history-sizeInteger10保留历史消息数
session-timeoutInteger30会话超时(分钟)
memory-enabledBooleantrue是否启用记忆
memory-store-typeStringredis存储类型

不同场景配置示例

yaml
# 开发环境 - 使用内存存储
langchain4j:
  chat:
    memory-store-type: memory
    history-size: 20
    session-timeout: 60

# 生产环境 - 使用 Redis 存储
langchain4j:
  chat:
    memory-store-type: redis
    history-size: 10
    session-timeout: 30

# 高并发场景 - 减少历史消息
langchain4j:
  chat:
    memory-store-type: redis
    history-size: 5
    session-timeout: 15

最佳实践

1. 合理设置历史消息数量

yaml
chat:
  history-size: 10  # 简单对话
  # history-size: 20  # 复杂对话

说明:

  • 历史消息过多会增加 Token 消耗
  • 建议根据对话复杂度设置 5-20 条
  • 复杂技术讨论可适当增加

2. 使用流式提升体验

java
// 长文本生成 - 使用流式
.setStream(true)

// 简短问答 - 使用同步
.setStream(false)

适用场景:

  • 流式:长文本生成、代码编写、文章创作
  • 同步:简短问答、分类、审核

3. 根据场景调整温度

java
// 代码生成、翻译 - 低温度
.setTemperature(0.1)

// 日常对话 - 中温度
.setTemperature(0.7)

// 创意写作 - 高温度
.setTemperature(1.5)

4. 设置合理的 Token 限制

java
// 简短回答
.setMaxTokens(100)

// 中等长度
.setMaxTokens(500)

// 长文章
.setMaxTokens(2000)

5. 定期清理过期会话

java
@Scheduled(cron = "0 0 * * * ?")  // 每小时执行
public void cleanExpiredSessions() {
    chatMemoryManager.removeExpiredSessions();
    log.info("已清理过期会话");
}

6. 设计合理的系统提示词

java
// ✅ 好的系统提示词
String goodPrompt = """
    你是一个专业的技术顾问。

    请遵循以下规则:
    1. 使用简洁专业的语言
    2. 提供代码示例时使用正确的格式
    3. 如果不确定,请诚实说明

    专业领域:Java、Spring Boot、微服务
    """;

// ❌ 差的系统提示词
String badPrompt = "你是助手";  // 太简单

7. 合理使用会话 ID

java
// ✅ 好的会话ID设计
String sessionId = "user:" + userId + ":chat:" + chatType;

// 或使用业务维度
String sessionId = "order:" + orderId + ":support";

// ❌ 差的会话ID
String sessionId = UUID.randomUUID().toString();  // 无法追踪

8. Token 使用监控

java
@Aspect
@Component
@Slf4j
public class ChatTokenMonitor {

    @AfterReturning(pointcut = "execution(* plus.ruoyi..*ChatService.chat(..))",
                    returning = "response")
    public void monitorTokenUsage(ChatResponse response) {
        if (response.getTokenUsage() != null) {
            TokenUsage usage = response.getTokenUsage();
            log.info("Token使用 - 输入:{}, 输出:{}, 总计:{}",
                usage.getPromptTokens(),
                usage.getCompletionTokens(),
                usage.getTotalTokens());

            // 可以记录到监控系统
            if (usage.getTotalTokens() > 4000) {
                log.warn("Token使用量较高,请检查对话历史长度");
            }
        }
    }
}

常见问题

1. 如何保持多轮对话

使用 CONTINUOUS 模式并提供会话ID:

java
ChatRequest request = new ChatRequest()
    .setMode(ChatMode.CONTINUOUS)
    .setSessionId(sessionId);

2. 流式响应如何判断结束

检查 finished 字段:

java
chatService.streamChat(request, response -> {
    if (response.getFinished()) {
        System.out.println("流式完成");
        // 此时可以获取 TokenUsage
        log.info("Token使用: {}", response.getTokenUsage());
    }
});

3. 如何限制响应长度

设置 maxTokens 参数:

java
.setMaxTokens(200)  // 限制最多200个Token

4. Redis 连接失败怎么办

切换到内存存储:

yaml
chat:
  memory-store-type: memory  # 使用内存(仅开发环境)

或添加降级逻辑:

java
@Component
public class ChatMemoryFallback {

    public ChatMemory createMemoryWithFallback(String sessionId) {
        try {
            // 尝试使用 Redis
            return createRedisMemory(sessionId);
        } catch (Exception e) {
            log.warn("Redis不可用,降级到内存存储");
            return createInMemoryStorage(sessionId);
        }
    }
}

5. 对话上下文过长导致错误

问题原因:

  • 历史消息过多,超出模型上下文窗口
  • Token 数量超过限制

解决方案:

yaml
# 减少历史消息数量
chat:
  history-size: 5
java
// 或手动清理历史
if (chatService.getSessionMessages(sessionId).size() > 20) {
    chatService.clearSession(sessionId);
}

6. 流式响应卡顿或延迟

问题原因:

  • 网络延迟
  • 模型响应慢
  • 客户端处理阻塞

解决方案:

java
// 使用异步处理
CompletableFuture.runAsync(() -> {
    chatService.streamChat(request, response -> {
        // 异步处理响应
        messageQueue.offer(response);
    });
});

7. 会话 ID 冲突

问题原因:

  • 不同用户使用相同会话 ID
  • 会话 ID 生成规则不当

解决方案:

java
// 使用复合会话 ID
String sessionId = String.format("tenant:%s:user:%s:chat:%s",
    tenantId, userId, chatType);

8. Token 统计不准确

问题原因:

  • 某些模型不返回 Token 统计
  • 流式模式下仅最后一条消息包含统计

解决方案:

java
// 确保在 finished=true 时获取统计
chatService.streamChat(request, response -> {
    if (response.getFinished() && response.getTokenUsage() != null) {
        // 记录 Token 使用
        recordTokenUsage(response.getTokenUsage());
    }
});

总结

ChatService 提供了强大而灵活的 AI 对话能力,支持多种对话模式和丰富的配置选项。通过合理使用系统提示词、温度参数、会话管理等功能,你可以构建出满足各种业务需求的智能对话应用。

核心要点:

  1. 选择合适的对话模式 - 根据业务场景选择 SINGLE、CONTINUOUS、RAG 或 FUNCTION 模式
  2. 合理配置会话存储 - 开发环境用内存,生产环境用 Redis
  3. 优化 Token 使用 - 控制历史消息数量和输出长度
  4. 利用流式响应 - 提升长文本生成的用户体验
  5. 监控和日志 - 记录 Token 使用和响应时间,便于优化和排查问题