LangChain4j之Chat and Language

目录

  1. [ChatModel API 概述](#ChatModel API 概述)
  2. [ChatMessage 类型详解](#ChatMessage 类型详解)
  3. [LLM 的无状态特性](#LLM 的无状态特性)
  4. 会话管理实现
  5. 实际应用示例
  6. 常见问题解决

1. ChatModel API 概述

1.1 什么是 ChatModel

ChatModel 是 LangChain4j 中与 LLMs(大语言模型)交互的低级 API,提供了最强大和最灵活的功能。

ChatModel 的核心特点:

  • 接受一个或多个 ChatMessage 作为输入
  • 返回一个 AiMessage 作为输出
  • 支持多种消息类型(文本、图像、音频等)
  • 提供 ChatRequest 用于自定义请求参数

1.2 ChatModel 的 API 形式

LangChain4j 中的 ChatModel 提供了多种 chat 方法:

简单方法(快速实验用):

java 复制代码
String response = chatModel.chat("Hello");

标准方法(单个消息):

java 复制代码
ChatResponse response = chatModel.chat(UserMessage.from("Hello"));

多消息方法(多轮对话):

java 复制代码
ChatResponse response = chatModel.chat(
    firstUserMessage, 
    firstAiMessage, 
    secondUserMessage
);

自定义请求方法:

java 复制代码
ChatRequest chatRequest = ChatRequest.builder()
    .messages(...)
    .modelName(...)
    .temperature(...)
    .topP(...)
    .maxOutputTokens(...)
    .build();

ChatResponse chatResponse = chatModel.chat(chatRequest);

1.3 ChatResponse 的组成

ChatResponse 包含两个重要部分:

  1. AiMessage: AI 的回复内容
  2. ChatResponseMetadata : 响应元数据
    • TokenUsage: 令牌使用情况(输入、输出、总计)
    • FinishReason: 生成停止的原因

2. ChatMessage 类型详解

2.1 消息类型概览

LangChain4j 中有五种主要的 ChatMessage 类型,每种对应一个"消息来源":

2.2 UserMessage(用户消息)

来源: 来自用户(应用程序的最终用户或应用程序本身)

包含内容:

  • contents(): 消息内容(文本、图像、音频等)
  • name(): 用户名称(并非所有模型都支持)
  • attributes(): 附加属性(存储在 ChatMemory 中)

创建方式:

java 复制代码
// 最简单的方式
UserMessage message1 = new UserMessage("Hi");
UserMessage message2 = UserMessage.from("Hi");

// 多模态内容
UserMessage message3 = UserMessage.from(
    TextContent.from("Describe this image"),
    ImageContent.from("https://example.com/cat.jpg")
);

2.3 AiMessage(AI 消息)

来源: 由 AI 生成的响应

包含内容:

  • text(): 文本内容
  • thinking(): 思考/推理内容
  • toolExecutionRequests(): 工具执行请求
  • attributes(): 特定于提供者的附加属性

创建方式:

java 复制代码
AiMessage aiMessage = AiMessage.from("This is AI response");

2.4 SystemMessage(系统消息)

来源: 来自系统(由开发者定义)

作用: 定义 LLM 在对话中的角色、行为风格等

重要提示:

  • LLM 对 SystemMessage 的关注度高于其他消息
  • 不应让最终用户自由定义或注入内容到 SystemMessage
  • 通常位于对话的开头

创建方式:

java 复制代码
SystemMessage systemMessage = SystemMessage.from(
    "你是一个专业的Java开发助手,请用简洁的语言回答问题"
);

2.5 ChatMessageDTO(数据传输对象)

在我们的项目中,使用 ChatMessageDTO 来进行数据传输:

ChatMessageDTO.java 核心代码:

java 复制代码
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "聊天消息数据传输对象")
public class ChatMessageDTO {

    @Schema(description = "消息类型", example = "SYSTEM, USER, AI", required = true)
    private String type;

    @Schema(description = "消息内容", example = "你好,我是AI助手")
    private String text;

    @Schema(description = "用户名", example = "张三")
    private String userName;

    public ChatMessageDTO(String type, String text) {
        this.type = type;
        this.text = text;
    }

    public ChatMessageDTO(String type, String text, String userName) {
        this.type = type;
        this.text = text;
        this.userName = userName;
    }
}

这个 DTO 类用于:

  • 接收前端发送的消息数据
  • 向前端返回消息数据
  • 在 API 文档中清晰展示消息结构

3. LLM 的无状态特性

3.1 什么是无状态

LLMs(大语言模型)是天生无状态的,这意味着:

  • LLM 不会维护对话状态
  • 每次调用都是独立的
  • 模型不记得之前的对话内容

3.2 为什么需要管理对话状态

由于 LLM 是无状态的,如果你想支持多轮对话,你需要:

  1. 自己管理对话状态
  2. 在每次请求时发送完整的对话历史

3.3 Klaus 示例

假设你想构建一个聊天机器人,用户和 AI 的对话如下:

复制代码
用户: Hello, my name is Klaus
AI: Hi Klaus, how can I help you?
用户: What is my name?
AI: Klaus

使用 ChatModel 的实现:

java 复制代码
// 第一轮对话
UserMessage firstUserMessage = UserMessage.from("Hello, my name is Klaus");
AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage(); 
// 返回: Hi Klaus, how can I help you?

// 第二轮对话
UserMessage secondUserMessage = UserMessage.from("What is my name?");
AiMessage secondAiMessage = model.chat(
    firstUserMessage,  // 必须包含之前的消息!
    firstAiMessage,
    secondUserMessage
).aiMessage(); 
// 返回: Klaus

关键点:

  • 在第二个 chat 方法调用中,我们不仅提供了 secondUserMessage
  • 还提供了对话中的所有先前消息
  • 这样 AI 才能记住上下文,知道用户的名字是 Klaus

3.4 手动管理的麻烦

手动维护和管理这些消息很麻烦,因为:

  • 需要自己存储所有历史消息
  • 每次调用都要传递完整历史
  • 需要处理不同类型的消息
  • 需要管理对话的生命周期

解决方案: 使用会话管理系统(ChatMemory),我们将在下一节详细介绍。


4. 会话管理实现

4.1 ChatSession(会话模型)

ChatSession.java 用于表示一个完整的对话会话:

java 复制代码
@Data
public class ChatSession {
    private String sessionId;           // 会话唯一标识
    private String userId;              // 用户ID
    private List<ChatMessage> conversationHistory;  // 对话历史

    public ChatSession(String userId, String systemPrompt) {
        this.sessionId = UUID.randomUUID().toString();
        this.userId = userId;
        this.conversationHistory = new ArrayList<>();

        // 如果有系统提示词,添加到对话开头
        if (systemPrompt != null && !systemPrompt.isEmpty()) {
            this.conversationHistory.add(SystemMessage.from(systemPrompt));
        }
    }

    public void addUserMessage(String message) {
        conversationHistory.add(UserMessage.from(message));
    }

    public void addAiMessage(String text) {
        conversationHistory.add(AiMessage.from(text));
    }

    public void clearHistory() {
        conversationHistory.clear();
    }

    public int getMessageCount() {
        return conversationHistory.size();
    }
}

ChatSession 的核心功能:

  1. 自动生成 sessionId: 使用 UUID 确保唯一性
  2. 存储对话历史: 维护所有 ChatMessage 的列表
  3. 便捷的消息添加方法 :
    • addUserMessage(): 添加用户消息
    • addAiMessage(): 添加 AI 回复
  4. 历史管理: 可以清空历史或获取消息数量

4.2 MultiTurnChatService(会话服务)

MultiTurnChatService.java 实现了完整的会话管理逻辑:

java 复制代码
@Service
public class MultiTurnChatService {

    @Resource
    private ChatModel chatLanguageModel;

    // 使用 ConcurrentHashMap 存储所有会话
    private final Map<String, ChatSession> sessions = new ConcurrentHashMap<>();

    // 创建新会话
    public ChatSession createSession(String userId, String systemPrompt) {
        ChatSession session = new ChatSession(userId, systemPrompt);
        sessions.put(session.getSessionId(), session);
        return session;
    }

    // 多轮对话(维护上下文)
    public String multiTurnChat(String sessionId, String userMessage) {
        ChatSession session = sessions.get(sessionId);
        if (session == null) {
            throw new IllegalArgumentException("会话不存在: " + sessionId);
        }

        // 1. 添加用户消息到历史
        session.addUserMessage(userMessage);

        // 2. 发送完整历史给 AI 模型
        ChatResponse response = chatLanguageModel.chat(
            session.getConversationHistory()
        );
        String aiResponse = response.aiMessage().text();

        // 3. 添加 AI 回复到历史
        session.addAiMessage(aiResponse);

        return aiResponse;
    }

    // 获取会话
    public ChatSession getSession(String sessionId) {
        return sessions.get(sessionId);
    }

    // 清空会话历史
    public void clearSession(String sessionId) {
        ChatSession session = sessions.get(sessionId);
        if (session != null) {
            session.clearHistory();
        }
    }

    // 删除会话
    public void deleteSession(String sessionId) {
        sessions.remove(sessionId);
    }
}

工作流程:

  1. 创建会话:

    复制代码
    用户调用 createSession() -> 生成 sessionId -> 存储在 Map 中
  2. 发送消息:

    复制代码
    用户调用 multiTurnChat() -> 
    添加用户消息到历史 -> 
    发送完整历史给 AI -> 
    接收 AI 回复 -> 
    添加 AI 回复到历史 -> 
    返回回复
  3. 查询会话:

    复制代码
    用户调用 getSession() -> 从 Map 中获取会话
  4. 清空/删除会话:

    复制代码
    clearSession(): 清空对话历史
    deleteSession(): 完全删除会话

为什么使用 ConcurrentHashMap?

  • 线程安全:支持多用户同时访问
  • 高性能:并发读写效率高
  • 自动同步:不需要手动加锁

4.3 ChatSessionDTO(会话数据传输)

ChatSessionDTO.java 用于将内部会话对象转换为 API 响应:

java 复制代码
@Data
@Schema(description = "会话数据传输对象")
public class ChatSessionDTO {

    private String sessionId;
    private String userId;
    private String systemPrompt;
    private List<ChatMessageDTO> conversationHistory;
    private int messageCount;

    public static ChatSessionDTO fromChatSession(ChatSession session) {
        ChatSessionDTO dto = new ChatSessionDTO();
        dto.setSessionId(session.getSessionId());
        dto.setUserId(session.getUserId());

        final String[] systemPrompt = {null};
        
        // 将 ChatMessage 转换为 ChatMessageDTO
        List<ChatMessageDTO> history = session.getConversationHistory().stream()
            .map(msg -> {
                String type = msg.type().toString();
                String text = null;
                String userName = null;

                // 根据消息类型提取内容
                if (msg instanceof SystemMessage) {
                    SystemMessage systemMsg = (SystemMessage) msg;
                    text = systemMsg.text();
                    if (systemPrompt[0] == null) {
                        systemPrompt[0] = text;
                    }
                } else if (msg instanceof UserMessage) {
                    UserMessage userMsg = (UserMessage) msg;
                    text = userMsg.singleText();
                    userName = userMsg.name();
                } else if (msg instanceof AiMessage) {
                    AiMessage aiMsg = (AiMessage) msg;
                    text = aiMsg.text();
                }

                return new ChatMessageDTO(type, text, userName);
            })
            .collect(Collectors.toList());

        dto.setSystemPrompt(systemPrompt[0]);
        dto.setConversationHistory(history);
        dto.setMessageCount(session.getMessageCount());

        return dto;
    }
}

关键转换逻辑:

  • 使用 instanceof 判断消息类型
  • 使用对应的方法提取文本内容:
    • SystemMessage: text()
    • UserMessage: singleText()
    • AiMessage: text()
  • 提取用户名(如果存在)
  • 统计消息总数

5. 实际应用示例

5.1 多轮对话 REST API

MultiTurnChatController.java 提供了完整的会话管理接口:

java 复制代码
@RestController
@RequestMapping("/api/chat")
@Tag(name = "多轮对话接口", description = "基于SessionId的上下文对话管理")
public class MultiTurnChatController {

    @Resource
    private MultiTurnChatService multiTurnChatService;

    // 创建新会话
    @PostMapping("/session")
    @Operation(summary = "创建新会话")
    public ChatSessionDTO createSession(
        @RequestParam String userId,
        @RequestParam(required = false) String systemPrompt
    ) {
        ChatSession session = multiTurnChatService.createSession(
            userId, systemPrompt
        );
        return ChatSessionDTO.fromChatSession(session);
    }

    // 发送消息(维护上下文)
    @PostMapping("/message")
    @Operation(summary = "发送消息(维护上下文)")
    public String sendMessage(
        @RequestParam String sessionId,
        @RequestParam String message
    ) {
        return multiTurnChatService.multiTurnChat(sessionId, message);
    }

    // 获取会话详情
    @GetMapping("/session/{sessionId}")
    @Operation(summary = "获取会话详情")
    public ChatSessionDTO getSession(@PathVariable String sessionId) {
        ChatSession session = multiTurnChatService.getSession(sessionId);
        if (session == null) {
            throw new IllegalArgumentException("会话不存在");
        }
        return ChatSessionDTO.fromChatSession(session);
    }

    // 清空会话历史
    @PostMapping("/session/{sessionId}/clear")
    @Operation(summary = "清空会话历史")
    public String clearSession(@PathVariable String sessionId) {
        multiTurnChatService.clearSession(sessionId);
        return "会话历史已清空";
    }

    // 删除会话
    @DeleteMapping("/session/{sessionId}")
    @Operation(summary = "删除会话")
    public String deleteSession(@PathVariable String sessionId) {
        multiTurnChatService.deleteSession(sessionId);
        return "会话已删除";
    }
}

使用流程:

  1. 创建会话:

    bash 复制代码
    POST /api/chat/session?userId=user123&systemPrompt=你是一个Java专家

    响应:

    json 复制代码
    {
      "sessionId": "550e8400-e29b-41d4-a716-446655440000",
      "userId": "user123",
      "systemPrompt": "你是一个Java专家",
      "conversationHistory": [...],
      "messageCount": 1
    }
  2. 发送消息:

    bash 复制代码
    POST /api/chat/message?sessionId=550e8400...&message=什么是Spring Boot?
  3. 查看会话历史:

    bash 复制代码
    GET /api/chat/session/550e8400...
  4. 清空历史:

    bash 复制代码
    POST /api/chat/session/550e8400.../clear
  5. 删除会话:

    bash 复制代码
    DELETE /api/chat/session/550e8400...

5.2 高级对话功能

AdvancedChatController.java 演示了多种对话场景:

java 复制代码
@RestController
@RequestMapping("/api/advanced-chat")
@Tag(name = "多轮对话", description = "基于多个ChatMessage的多轮对话功能")
public class AdvancedChatController {

    @Resource
    private AdvancedChatService advancedChatService;

    // 简单对话
    @GetMapping("/simple")
    @Operation(summary = "简单对话", description = "最简单的对话方式")
    public String simpleChat(
        @RequestParam String message
    ) {
        return advancedChatService.simpleChat(message);
    }

    // 系统提示对话
    @GetMapping("/with-system")
    @Operation(summary = "系统提示对话", description = "使用系统提示词设置AI角色")
    public String chatWithSystem(
        @RequestParam String systemPrompt,
        @RequestParam String userMessage
    ) {
        return advancedChatService.chatWithSystem(systemPrompt, userMessage);
    }

    // 多个 ChatMessage 对话
    @PostMapping("/multiple-messages")
    @Operation(summary = "多个ChatMessage对话", 
               description = "传入多个ChatMessage进行多轮对话")
    public String chatWithMultipleMessages(
        @RequestBody ChatWithHistoryRequest request
    ) {
        return advancedChatService.chatWithMultipleMessages(
            request.getMessages()
        );
    }

    // 多消息示例(Klaus 示例)
    @PostMapping("/messages-demo")
    @Operation(summary = "多消息示例", 
               description = "演示UserMessage和AiMessage组合使用")
    public String chatWithMessagesDemo(
        @RequestParam String firstUserMessage,
        @RequestParam String firstAiMessage,
        @RequestParam String secondUserMessage
    ) {
        // 创建消息对象
        UserMessage user1 = UserMessage.from(firstUserMessage);
        AiMessage ai1 = AiMessage.from(firstAiMessage);
        UserMessage user2 = UserMessage.from(secondUserMessage);

        // 调用服务
        var response = advancedChatService.chatWithMessages(
            user1, ai1, user2
        );
        
        return response.aiMessage().text();
    }
}

场景示例:

  1. 简单对话:

    复制代码
    请求: GET /api/advanced-chat/simple?message=你好
    响应: 你好!有什么可以帮助你的吗?
  2. 系统提示对话:

    复制代码
    请求: GET /api/advanced-chat/with-system?
          systemPrompt=你是一个Java专家&
          userMessage=解释一下多态
    响应: 多态是面向对象编程的三大特性之一...
  3. 多消息对话(Klaus 示例):

    bash 复制代码
    POST /api/advanced-chat/messages-demo?
    firstUserMessage=Hello, my name is Klaus&
    firstAiMessage=Hi Klaus, how can I help you?&
    secondUserMessage=What is my name?

    响应: Klaus

5.3 ChatService(基础服务)

ChatService.java 提供了最简单的对话功能:

java 复制代码
@Service
@RequiredArgsConstructor
public class ChatService {

    @Resource
    private ChatModel chatLanguageModel;

    public String chat(String message) {
        return chatLanguageModel.chat(message);
    }
}

这是 LangChain4j 中最基础的用法:

  • 输入:字符串消息
  • 输出:字符串回复
  • 不维护任何上下文
  • 适合单次问答场景

6. 常见问题解决

6.1 Bean 冲突问题

问题描述:

当使用 Spring Boot 自动配置时,可能会遇到以下错误:

复制代码
Description:

A component required a single bean, but 2 were found:
	- chatLanguageModel: defined by method 'chatLanguageModel' 
	  in class path resource [com/atg/langchain4jcode/config/LangChain4jConfig.class]
	- qwenChatModel: defined by method 'qwenChatModel' 
	  in class path resource [dev/langchain4j/community/dashscope/spring/DashScopeAutoConfiguration.class]

Action:
Consider marking one of the beans as @Primary, updating the consumer to 
accept multiple beans, or using @Qualifier to identify the bean that should be consumed

原因分析:

  1. Spring Boot 的 langchain4j-community-dashscope-spring-boot-starter
    自动配置创建了 qwenChatModel Bean
  2. 你的配置类也创建了 chatLanguageModel Bean
  3. 两个都是 ChatModel 类型,导致依赖注入时冲突

解决方案:

添加 @Primary 注解,告诉 Spring 在有多个 ChatModel Bean 时优先使用哪个:

java 复制代码
@Bean
@Primary  // 添加这个注解
public ChatModel chatLanguageModel() {
    return QwenChatModel.builder()
            .apiKey(apiKey)
            .modelName(modelName)
            .build();
}

其他解决方式:

  1. 使用 @Qualifier:
java 复制代码
@Resource
@Qualifier("chatLanguageModel")
private ChatModel chatModel;
  1. 禁用自动配置:
java 复制代码
@SpringBootApplication(exclude = DashScopeAutoConfiguration.class)
  1. 修改 Bean 名称:
java 复制代码
@Bean("myCustomChatModel")
public ChatModel myChatModel() { ... }

6.2 其他注意事项

  1. 消息类型判断 : 使用 instanceof 判断 ChatMessage 的具体类型
  2. 文本提取 : 不同消息类型的文本提取方法不同
    • SystemMessage: text()
    • UserMessage: singleText()
    • AiMessage: text()
  3. 并发安全 : 使用 ConcurrentHashMap 存储会话,确保线程安全
  4. 内存管理: 及时清理不需要的会话,避免内存泄漏

总结

本教程涵盖了 LangChain4j 多轮对话开发的核心要点:

  1. ChatModel API: 理解低级 API 的使用方式
  2. ChatMessage 类型: 掌握 UserMessage、AiMessage、SystemMessage 的使用
  3. 无状态特性: 理解为什么需要会话管理
  4. 会话管理: 实现完整的多轮对话系统
  5. REST API: 构建可用的对话接口
  6. 问题解决: 处理常见的 Bean 冲突等问题

通过学习这些内容,你应该能够:

  • 理解 LangChain4j 的核心概念
  • 实现多轮对话功能
  • 构建完整的会话管理系统
  • 处理实际开发中的常见问题
相关推荐
心在飞扬3 小时前
LangChain 工具创建方法总结
langchain
鞋带松了6 小时前
LangChain入门初体验-实现简单智能体
langchain·llm
寻见9031 天前
解决大模型 5 大痛点:LangChain 核心组件全解析
langchain
Sailing1 天前
LLM 调用从 60s 卡死降到 3s!彻底绕过 tiktoken 网络阻塞(LangChain.js 必看)
前端·langchain·llm
UIUV2 天前
RAG技术学习笔记(含实操解析)
javascript·langchain·llm
神秘的猪头2 天前
🚀 拒绝“一本正经胡说八道”!手把手带你用 LangChain 实现 RAG,打造你的专属 AI 知识库
langchain·llm·openai
栀秋6662 天前
重塑 AI 交互边界:基于 LangChain 与 MCP 协议的全栈实践
langchain·llm·mcp
大模型真好玩2 天前
LangChain DeepAgents 速通指南(三)—— 让Agent告别混乱:Tool Selector与Todo List中间件解析
人工智能·langchain·trae
是一碗螺丝粉3 天前
LangChain 链(Chains)完全指南:从线性流程到智能路由
前端·langchain·aigc
前端付豪3 天前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain