目录
- [ChatModel API 概述](#ChatModel API 概述)
- [ChatMessage 类型详解](#ChatMessage 类型详解)
- [LLM 的无状态特性](#LLM 的无状态特性)
- 会话管理实现
- 实际应用示例
- 常见问题解决
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 包含两个重要部分:
- AiMessage: AI 的回复内容
- 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 是无状态的,如果你想支持多轮对话,你需要:
- 自己管理对话状态
- 在每次请求时发送完整的对话历史
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 的核心功能:
- 自动生成 sessionId: 使用 UUID 确保唯一性
- 存储对话历史: 维护所有 ChatMessage 的列表
- 便捷的消息添加方法 :
addUserMessage(): 添加用户消息addAiMessage(): 添加 AI 回复
- 历史管理: 可以清空历史或获取消息数量
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);
}
}
工作流程:
-
创建会话:
用户调用 createSession() -> 生成 sessionId -> 存储在 Map 中 -
发送消息:
用户调用 multiTurnChat() -> 添加用户消息到历史 -> 发送完整历史给 AI -> 接收 AI 回复 -> 添加 AI 回复到历史 -> 返回回复 -
查询会话:
用户调用 getSession() -> 从 Map 中获取会话 -
清空/删除会话:
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()
- SystemMessage:
- 提取用户名(如果存在)
- 统计消息总数
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 "会话已删除";
}
}
使用流程:
-
创建会话:
bashPOST /api/chat/session?userId=user123&systemPrompt=你是一个Java专家响应:
json{ "sessionId": "550e8400-e29b-41d4-a716-446655440000", "userId": "user123", "systemPrompt": "你是一个Java专家", "conversationHistory": [...], "messageCount": 1 } -
发送消息:
bashPOST /api/chat/message?sessionId=550e8400...&message=什么是Spring Boot? -
查看会话历史:
bashGET /api/chat/session/550e8400... -
清空历史:
bashPOST /api/chat/session/550e8400.../clear -
删除会话:
bashDELETE /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();
}
}
场景示例:
-
简单对话:
请求: GET /api/advanced-chat/simple?message=你好 响应: 你好!有什么可以帮助你的吗? -
系统提示对话:
请求: GET /api/advanced-chat/with-system? systemPrompt=你是一个Java专家& userMessage=解释一下多态 响应: 多态是面向对象编程的三大特性之一... -
多消息对话(Klaus 示例):
bashPOST /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
原因分析:
- Spring Boot 的
langchain4j-community-dashscope-spring-boot-starter
自动配置创建了qwenChatModelBean - 你的配置类也创建了
chatLanguageModelBean - 两个都是
ChatModel类型,导致依赖注入时冲突
解决方案:
添加 @Primary 注解,告诉 Spring 在有多个 ChatModel Bean 时优先使用哪个:
java
@Bean
@Primary // 添加这个注解
public ChatModel chatLanguageModel() {
return QwenChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.build();
}
其他解决方式:
- 使用 @Qualifier:
java
@Resource
@Qualifier("chatLanguageModel")
private ChatModel chatModel;
- 禁用自动配置:
java
@SpringBootApplication(exclude = DashScopeAutoConfiguration.class)
- 修改 Bean 名称:
java
@Bean("myCustomChatModel")
public ChatModel myChatModel() { ... }
6.2 其他注意事项
- 消息类型判断 : 使用
instanceof判断 ChatMessage 的具体类型 - 文本提取 : 不同消息类型的文本提取方法不同
- SystemMessage:
text() - UserMessage:
singleText() - AiMessage:
text()
- SystemMessage:
- 并发安全 : 使用
ConcurrentHashMap存储会话,确保线程安全 - 内存管理: 及时清理不需要的会话,避免内存泄漏
总结
本教程涵盖了 LangChain4j 多轮对话开发的核心要点:
- ChatModel API: 理解低级 API 的使用方式
- ChatMessage 类型: 掌握 UserMessage、AiMessage、SystemMessage 的使用
- 无状态特性: 理解为什么需要会话管理
- 会话管理: 实现完整的多轮对话系统
- REST API: 构建可用的对话接口
- 问题解决: 处理常见的 Bean 冲突等问题
通过学习这些内容,你应该能够:
- 理解 LangChain4j 的核心概念
- 实现多轮对话功能
- 构建完整的会话管理系统
- 处理实际开发中的常见问题