LangChain4j入门学习
LangChain4j 学习文档
目录
- 环境准备与依赖安装
- 简单流式调用
- 记忆对话功能
- 工具调用
- 工具流式返回调用结果
- Redis流式调用
- 总结
环境准备与依赖安装
1. 项目依赖配置
首先,我们需要在 pom.xml
中添加必要的依赖:
<!-- LangChain4j 核心依赖 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>1.4.0</version>
</dependency><!-- LangChain4j OpenAI Spring Boot Starter -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai-spring-boot-starter</artifactId><version>1.4.0-beta10</version>
</dependency><!-- LangChain4j Reactor 支持 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId><version>1.4.0-beta10</version>
</dependency><!-- LangChain4j Redis 支持 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-redis-spring-boot-starter</artifactId><version>1.1.0-beta7</version>
</dependency><!-- Spring Boot Web Starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
2. 配置文件设置
在 application.yml
中配置AI模型参数:
# AI 配置
langchain4j:open-ai:chat-model:base-url: http://your-ai-service-url/v1 # AI服务地址api-key: your-api-key # API密钥model-name: Qwen3-8B-FP8 # 模型名称log-requests: true # 是否记录请求日志log-responses: true # 是否记录响应日志# 服务器配置
server:port: 9999servlet:context-path: /api# Redis 配置(用于记忆功能)
spring:data:redis:host: 127.0.0.1port: 6379database: 0password: # 如果有密码则填写ttl: 3600 # 过期时间(秒)
3. 基础配置类
创建流式聊天模型配置:
@Configuration
public class DeepSeekStreamingChatModel {@Value("${langchain4j.open-ai.chat-model.api-key}")private String apiKey;@Value("${langchain4j.open-ai.chat-model.base-url}")private String baseUrl;@Value("${langchain4j.open-ai.chat-model.model-name}")private String modelName;@Value("${langchain4j.open-ai.chat-model.log-requests}")private Boolean logRequests;@Value("${langchain4j.open-ai.chat-model.log-responses}")private Boolean logResponses;@Bean("deepStreamingChatModel")public StreamingChatModel streamingChatModel() {return OpenAiStreamingChatModel.builder().baseUrl(baseUrl).apiKey(apiKey).modelName(modelName).temperature(0.7) // 控制回答的随机性.maxTokens(1000) // 最大token数.logRequests(logRequests) // 记录请求日志.logResponses(logResponses) // 记录响应日志.returnThinking(true) // 返回思考过程.build();}
}
配置说明:
baseUrl
: AI服务的API地址apiKey
: 访问AI服务的密钥modelName
: 使用的模型名称temperature
: 控制回答的随机性(0-1,越高越随机)maxTokens
: 单次回答的最大token数returnThinking
: 是否返回AI的思考过程
简单流式调用
1. 基础概念
流式调用是指AI模型逐步返回回答内容,而不是等待完整回答后再一次性返回。这种方式可以提供更好的用户体验,让用户能够实时看到AI的思考过程。
2. 创建AI服务接口
首先,我们需要定义一个AI服务接口:
public interface AssistantChatResponse {@SystemMessage("你是一个java架构师,如果需要调用工具的话即时通知用户,如果不需要调用工具的话,不要给用户返回多余的关于工具的信息")TokenStream chat(@MemoryId String memoryId, @UserMessage String query);
}
接口说明:
@SystemMessage
: 定义系统提示词,设置AI的角色和行为@MemoryId
: 用于标识对话会话,实现记忆功能@UserMessage
: 标记用户输入的消息TokenStream
: 返回流式响应
3. 创建响应实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class XingGuiChatResponse {private String content; // AI回答内容private String reasoningContent; // AI思考过程private String toolName; // 工具名称private String toolOutput; // 工具输出结果
}
4. 简单流式调用实现
@RestController
@Slf4j
public class TestController {@Resource(name = "deepStreamingChatModel")private StreamingChatModel streamingChatModel;@GetMapping(value = "/simple-stream", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})public Flux<XingGuiChatResponse> simpleStream(@RequestParam("query") String query) {// 创建AI服务AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).build();return Flux.create(sink -> {assistant.chat("simple-session", query).onPartialResponse(partialResponse -> {// 处理部分响应XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {// 处理完整响应XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("流式调用出错: ", throwable);sink.error(throwable);}).start();});}
}
5. 测试简单流式调用
使用curl测试:
curl -N "http://localhost:9999/api/simple-stream?query=你好,请介绍一下Java"
流式调用特点:
- 实时返回:AI回答会逐步返回,不需要等待完整回答
- 更好的用户体验:用户可以实时看到AI的思考过程
- 支持中断:客户端可以随时中断请求
- 资源友好:不需要等待完整响应,可以更早释放资源
记忆对话功能
1. 记忆功能概述
记忆对话功能允许AI记住之前的对话内容,实现上下文连贯的对话体验。LangChain4j提供了多种记忆存储方式,包括内存存储、Redis存储等。
2. Redis记忆存储配置
首先配置Redis作为记忆存储:
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
@Data
public class RedisChatMemoryStoreConfig {private String host;private int port;private long ttl;private String password;@Bean("redisChatMemoryStore")public RedisChatMemoryStore redisChatMemoryStore() {return RedisChatMemoryStore.builder().host(host).port(port).password(password).ttl(ttl) // 记忆过期时间.build();}
}
3. 带记忆的AI服务配置
@Configuration
public class DeepSeekGeneratorCodeService {@Resource(name = "redisChatMemoryStore")private RedisChatMemoryStore redisChatMemoryStore;@Resource(name = "deepStreamingChatModel")private StreamingChatModel streamingChatModel;@Beanpublic AssistantChatResponse assistantChatResponse() {return AiServices.builder(AssistantChatResponse.class).streamingChatModel(streamingChatModel).chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory) // 记忆ID.chatMemoryStore(redisChatMemoryStore) // 使用Redis存储.maxMessages(20) // 最大记忆消息数.build();}).build();}
}
4. 记忆对话实现
@GetMapping(value = "/chat-with-memory", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> chatWithMemory(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10) // 保持最近10条对话.build();}).build();return Flux.create(sink -> {assistant.chat(memoryId, query).onPartialResponse(partialResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("记忆对话出错: ", throwable);sink.error(throwable);}).start();});
}
5. 记忆功能测试
测试记忆功能:
# 第一次对话
curl -N "http://localhost:9999/api/chat-with-memory?query=我叫张三&memoryId=user123"# 第二次对话(AI应该记住你的名字)
curl -N "http://localhost:9999/api/chat-with-memory?query=我的名字是什么?&memoryId=user123"
6. 记忆管理
// 清除特定用户的记忆
@GetMapping("/clear-memory")
public String clearMemory(@RequestParam("memoryId") String memoryId) {redisChatMemoryStore.deleteMessages(memoryId);return "记忆已清除";
}// 获取记忆统计信息
@GetMapping("/memory-stats")
public Map<String, Object> getMemoryStats(@RequestParam("memoryId") String memoryId) {List<ChatMessage> messages = redisChatMemoryStore.getMessages(memoryId);Map<String, Object> stats = new HashMap<>();stats.put("messageCount", messages.size());stats.put("memoryId", memoryId);return stats;
}
记忆功能特点:
- 持久化存储:使用Redis存储对话历史
- 自动过期:可设置记忆过期时间
- 消息限制:可控制最大记忆消息数量
- 会话隔离:不同memoryId的对话相互独立
- 灵活管理:支持清除和查询记忆
工具调用
1. 工具调用概述
工具调用允许AI模型调用外部函数来获取信息或执行操作。这是实现AI Agent功能的核心特性,让AI能够与外部系统交互。
2. 创建自定义工具
首先创建一个简单的工具类:
@Slf4j
public class DateTimeTool {@Tool("获取当前时间,返回格式为 yyyy-MM-dd HH:mm:ss")public String getCurrentDateTime() {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String time = LocalDateTime.now().format(formatter);log.info("调用获取时间工具,返回: {}", time);return time;}
}
工具注解说明:
@Tool
: 标记方法为可调用的工具- 注解中的字符串:工具的描述,AI会根据这个描述决定是否调用该工具
3. 创建更复杂的工具
@Slf4j
public class CalculatorTool {@Tool("计算两个数字的和")public double add(@P("第一个数字") double a, @P("第二个数字") double b) {double result = a + b;log.info("计算: {} + {} = {}", a, b, result);return result;}@Tool("计算两个数字的差")public double subtract(@P("被减数") double a, @P("减数") double b) {double result = a - b;log.info("计算: {} - {} = {}", a, b, result);return result;}@Tool("计算两个数字的乘积")public double multiply(@P("第一个数字") double a, @P("第二个数字") double b) {double result = a * b;log.info("计算: {} * {} = {}", a, b, result);return result;}@Tool("计算两个数字的商")public double divide(@P("被除数") double a, @P("除数") double b) {if (b == 0) {throw new IllegalArgumentException("除数不能为零");}double result = a / b;log.info("计算: {} / {} = {}", a, b, result);return result;}
}
参数注解说明:
@P
: 为工具参数提供描述,帮助AI理解参数含义
4. 配置带工具的AI服务
@GetMapping(value = "/chat-with-tools", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> chatWithTools(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).tools(new DateTimeTool(), new CalculatorTool()) // 注册工具.chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10).build();}).build();return Flux.create(sink -> {assistant.chat(memoryId, query).onPartialResponse(partialResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("工具调用出错: ", throwable);sink.error(throwable);}).start();});
}
5. 工具调用测试
测试工具调用功能:
# 测试时间工具
curl -N "http://localhost:9999/api/chat-with-tools?query=现在几点了?&memoryId=user123"# 测试计算工具
curl -N "http://localhost:9999/api/chat-with-tools?query=帮我计算 25 + 17 等于多少?&memoryId=user123"# 测试复杂计算
curl -N "http://localhost:9999/api/chat-with-tools?query=计算 (10 + 5) * 3 - 8 的结果&memoryId=user123"
工具调用特点:
- 自动选择:AI会根据用户问题自动选择合适的工具
- 参数解析:AI能够理解并正确传递工具参数
- 错误处理:工具执行失败时AI会给出相应提示
- 结果整合:AI会将工具结果整合到回答中
- 链式调用:AI可以连续调用多个工具完成复杂任务
工具流式返回调用结果
1. 工具流式调用概述
工具流式返回调用结果是指在工具调用过程中,实时返回工具的执行状态和结果,让用户能够看到AI的思考过程、工具调用过程以及最终结果。
2. 完整的工具流式调用实现
基于你现有的代码,这里展示完整的工具流式调用实现:
@GetMapping(value = "/ai", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> ai(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse xingGuiChatResponse = chatClient.streamingChatModel(streamingChatModel).tools(new DateTimeTool()) // 注册工具.chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10).build();}).build();return Flux.create(sink -> {xingGuiChatResponse.chat(memoryId, query)// 1. 处理AI思考过程.onPartialThinking(partialThinking -> {log.info("partialThinking: {}", partialThinking);XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(partialThinking.text()) // AI思考内容.content(null).toolName(null).toolOutput(null).build();sink.next(chatResponse);})// 2. 处理AI部分响应.onPartialResponse(partialResponse -> {XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(partialResponse) // AI回答内容.toolName(null).toolOutput(null).build();sink.next(chatResponse);})// 3. 工具调用开始.beforeToolExecution(toolExecutionRequest -> {log.info("开始调用工具: {}", toolExecutionRequest.request().name());XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(null).toolName(toolExecutionRequest.request().name()) // 工具名称.toolOutput(toolExecutionRequest.request().arguments()) // 工具参数.build();sink.next(chatResponse);})// 4. 工具调用完成.onToolExecuted(toolExecutionResult -> {log.info("工具调用结束: {}", toolExecutionResult.request().name());XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(null).toolName(toolExecutionResult.request().name()) // 工具名称.toolOutput(toolExecutionResult.request().arguments()) // 工具参数.build();sink.next(chatResponse);})// 5. 完整响应完成.onCompleteResponse(completeResponse -> {log.info("completeResponse: {}", completeResponse);XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(completeResponse.aiMessage().text()) // 最终回答.toolName(null).toolOutput(null).build();sink.next(chatResponse);sink.complete();})// 6. 错误处理.onError(throwable -> {log.error("工具流式调用出错: ", throwable);sink.error(throwable);}).start();});
}
3. 流式响应数据结构
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class XingGuiChatResponse {private String content; // AI回答内容private String reasoningContent; // AI思考过程private String toolName; // 工具名称private String toolOutput; // 工具输出结果
}
4. 前端处理流式响应
前端Vue3代码示例:
<template><div class="chat-container"><div class="messages"><div v-for="(message, index) in messages" :key="index" class="message":class="{ 'ai-message': message.type === 'ai' }"><!-- AI思考过程 --><div v-if="message.reasoningContent" class="reasoning"><strong>AI思考:</strong> {{ message.reasoningContent }}</div><!-- 工具调用信息 --><div v-if="message.toolName" class="tool-call"><strong>调用工具:</strong> {{ message.toolName }}<div v-if="message.toolOutput" class="tool-args">参数: {{ message.toolOutput }}</div></div><!-- AI回答内容 --><div v-if="message.content" class="content">{{ message.content }}</div></div></div><div class="input-area"><input v-model="userInput" @keyup.enter="sendMessage"placeholder="请输入您的问题...":disabled="isLoading"/><button @click="sendMessage" :disabled="isLoading">{{ isLoading ? '发送中...' : '发送' }}</button></div></div>
</template><script setup>
import { ref, reactive } from 'vue'const userInput = ref('')
const isLoading = ref(false)
const messages = ref([])const sendMessage = async () => {if (!userInput.value.trim() || isLoading.value) returnconst query = userInput.valueuserInput.value = ''isLoading.value = true// 添加用户消息messages.value.push({type: 'user',content: query})try {const response = await fetch(`/api/ai?query=${encodeURIComponent(query)}&memoryId=user123`)const reader = response.body.getReader()const decoder = new TextDecoder()let currentMessage = {type: 'ai',content: '',reasoningContent: '',toolName: '',toolOutput: ''}while (true) {const { done, value } = await reader.read()if (done) breakconst chunk = decoder.decode(value)const lines = chunk.split('\n')for (const line of lines) {if (line.startsWith('data: ')) {try {const data = JSON.parse(line.slice(6))// 更新消息内容if (data.reasoningContent) {currentMessage.reasoningContent = data.reasoningContent}if (data.content) {currentMessage.content += data.content}if (data.toolName) {currentMessage.toolName = data.toolNamecurrentMessage.toolOutput = data.toolOutput}// 更新或添加消息到列表const existingIndex = messages.value.findIndex(m => m.type === 'ai' && m === currentMessage)if (existingIndex >= 0) {messages.value[existingIndex] = { ...currentMessage }} else {messages.value.push({ ...currentMessage })}} catch (e) {console.error('解析流式数据失败:', e)}}}}} catch (error) {console.error('发送消息失败:', error)messages.value.push({type: 'ai',content: '抱歉,发生了错误,请稍后重试。'})} finally {isLoading.value = false}
}
</script><style scoped>
.chat-container {max-width: 800px;margin: 0 auto;padding: 20px;
}.messages {height: 400px;overflow-y: auto;border: 1px solid #ddd;padding: 10px;margin-bottom: 20px;
}.message {margin-bottom: 15px;padding: 10px;border-radius: 5px;
}.ai-message {background-color: #f5f5f5;
}.reasoning {color: #666;font-style: italic;margin-bottom: 5px;
}.tool-call {color: #007bff;margin-bottom: 5px;
}.tool-args {font-size: 0.9em;color: #666;margin-left: 10px;
}.content {margin-top: 5px;
}.input-area {display: flex;gap: 10px;
}.input-area input {flex: 1;padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}.input-area button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;
}.input-area button:disabled {background-color: #ccc;cursor: not-allowed;
}
</style>
5. 测试工具流式调用
# 测试带工具调用的流式响应
curl -N "http://localhost:9999/api/ai?query=现在几点了?&memoryId=user123"# 测试复杂工具调用
curl -N "http://localhost:9999/api/ai?query=帮我计算 100 + 200 等于多少,然后告诉我现在的时间&memoryId=user123"
6. 流式调用优势
用户体验优势:
- 实时反馈:用户可以看到AI的思考过程
- 透明性:工具调用过程完全透明
- 交互性:用户可以实时了解AI在做什么
- 可中断:用户可以随时中断长时间的操作
技术优势:
- 资源友好:不需要等待完整响应
- 错误处理:可以实时处理错误
- 可扩展:支持复杂的工具链调用
- 监控友好:便于监控和调试
总结
学习路径回顾
通过本文档,我们从简单到复杂逐步学习了LangChain4j的各个核心功能:
- 环境准备与依赖安装 - 搭建基础开发环境
- 简单流式调用 - 实现基础的AI对话功能
- 记忆对话功能 - 让AI记住对话历史
- 工具调用 - 让AI能够调用外部工具
- 工具流式返回调用结果 - 实时展示工具调用过程
- Redis流式调用 - 分布式环境下的高性能实现
核心技术要点
1. 流式调用架构
用户请求 → AI服务 → 流式响应 → 前端实时展示↓
Redis记忆存储 ← 工具调用 ← 思考过程
2. 关键技术组件
- StreamingChatModel: 流式聊天模型
- AiServices: AI服务构建器
- MessageWindowChatMemory: 记忆管理
- RedisChatMemoryStore: Redis记忆存储
- @Tool: 工具注解
- Flux: 响应式流处理
3. 数据流转过程
- 用户发送消息
- AI开始思考(onPartialThinking)
- 调用工具(beforeToolExecution → onToolExecuted)
- 生成回答(onPartialResponse)
- 完成响应(onCompleteResponse)
- 存储到Redis记忆
最佳实践建议
1. 性能优化
- 合理设置记忆消息数量限制
- 使用Redis连接池
- 设置合适的TTL过期时间
- 监控Redis内存使用
2. 错误处理
- 实现完善的异常捕获
- 提供友好的错误提示
- 记录详细的错误日志
- 实现重试机制
扩展方向
1. 功能扩展
- 支持更多AI模型
- 实现工具链调用
- 添加文件上传处理
- 支持多模态输入
2. 架构扩展
- 微服务架构
- 消息队列集成
- 负载均衡
- 容器化部署
3. 业务扩展
- 多租户支持
- 用户权限管理
- 对话模板
- 知识库集成
2. 流式响应中断
// 处理客户端断开连接
.onCancel(() -> {log.info("客户端取消连接");// 清理资源
})
3. 工具调用超时
// 设置工具调用超时
@Tool("长时间运行的工具")
@Timeout(30) // 30秒超时
public String longRunningTool() {// 工具实现
}
项目结构建议
src/main/java/com/xinggui/xinggui_code_langchain/
├── ai/
│ ├── chatModel/ # 聊天模型配置
│ ├── service/ # AI服务
│ ├── tool/ # 工具类
│ └── response/ # 响应实体
├── config/ # 配置类
├── controller/ # 控制器
├── service/ # 业务服务
└── utils/ # 工具类
学习建议
- 循序渐进: 从简单功能开始,逐步添加复杂特性
- 实践为主: 多写代码,多测试,多调试
- 关注性能: 注意内存使用和响应时间
- 文档记录: 记录遇到的问题和解决方案
- 社区参与: 关注LangChain4j官方文档和社区
结语
LangChain4j是一个功能强大的Java AI应用开发框架,通过本文档的学习,你应该已经掌握了:
- 基础的流式AI对话实现
- 记忆功能的配置和使用
- 工具调用的开发和集成
- Redis在AI应用中的应用
- 完整的生产级架构设计
继续深入学习,探索更多高级特性,构建更强大的AI应用!