什么是Langchain4j?Langchain4j框架实践指南
Langchain4J
结合之前学习的SpringAI,个人觉得Langchain4j是对SpringAI实现的功能进行进一步的封装,方便大模型应用开发
提示:本次实践基于
Spring Boot 3.x + Langchain4j 1.0.1-beta6 + Qwen(通义千问)模型
快速入门
-
引入依赖
<!--langchain4j依赖--> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> <version>1.0.1-beta6</version> </dependency>在SpringAI和Langchain4j依赖中二选一,不能多选,否则会出现报错
-
构建OpenAIChatModel对象
在配置类中配置
OpenAiChatModel model = OpenAiChatModel.builder() .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1") .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("qwen-plus") .logRequests(true) .logResponses(true) .build();开启了logback日志,包括request和response,后续可以在yml文件中进行配置而不用在构建模式进行操作
-
调用Chat方法与大模型交互
model.chat(message)
Spring整合Langchain4j
-
构建springboot项目
这里不再展示
-
引入起步依赖
<dependency> <!--AIService相关依赖--> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>1.0.1-beta6</version> </dependency> -
yml配置
langchain4j: open-ai: chat-model: base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${OPENAI_API_KEY} model-name: qwen-turbo #开启log日志 log-requests: true log-responses: true使用不同的模型配置不同
-
开发接口
会话功能-AIService工具类
Langchain4j最核心特性就是通过接口代理简化开发,其中AIService是核心交互
会话功能包含:会话记忆、Rag知识库、Tools工具
架构图解
通过代理对象自动处理 Prompt 组装
代码实现
-
引入
langchain4j-reactor依赖<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-reactor</artifactId> <version>1.0.1-beta6</version> </dependency> -
声明接口
通过创建ConsultantService文件对软件功能进行定义。
在Langchain4j中给出了注解@AiService(),方便对AIService进行自定义配置
@AiService( wiringMode = AiServiceWiringMode.EXPLICIT, chatModel = "openAiChatModel" ) public interface ConsultantService { //在创建代理对象时,可选自动或者手动装配,这里EXPLICIT是手动 public String chat2(String message); } -
AiService为接口创建代理对象
在配置类中给Spring容器扫描到ConsultantService并进行注册
@Bean public ConsultantService consultantService(){ return AiServices.builder(ConsultantService.class) .chatModel(model) .build(); } -
Controller中注入并使用
在控制层中字段注入ConsultantService,并在方法里调用chat2方法
@Autowired private ConsultantService consultantService; @RequestMapping("/chat2") public String chat2(String question){ return consultantService.chat2(question); }
流式调用
原理流程
使用 Spring WebFlux 的
Flux来处理数据流
代码实现
- 引入
spring-boot-starter-webflux依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
-
配置流式模型对象
在yml文件中进行配置
streaming-chat-model: base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${OPENAI_API_KEY} model-name: qwen-turbo #开启log日志 log-requests: true log-responses: true -
切换接口中方法返回值
@AiService public interface ConsultantService { // 返回 Flux<String> 实现流式输出 Flux<String> chatStream(@UserMessage String message); } -
修改控制层代码
@RequestMapping(value = "/chat",produces = "text/html;charset=UTF-8") public Flux<String> chat(String message, String memoryId){ return consultantService.chat(message,memoryId); }
会话功能-消息注解
-
@SystemMessage:给大模型的系统提示词
//1.引入外部文件 @SystemMessage(fromResource="system.txt")//2.字符串撰写 @SystemMessage("xxx") //3.使用模板变量 @SystemMessage("你是{{role}}") -
@UserMessage:用户输入的提示词
//1.模板变量 @UserMessage("请回答这个问题:{{question}}") public String chat(@V("question") String question); //2.多参数组合 @UserMessage("用户{{name}}的问题是:{{message}}") public String chat(@V("name") String name, @V("message") String message); //3.显示标注 public String chat(@UserMessage String message);使用多参数组合或者是模板变量时需要在变量参数前添加
@V(“”)告诉大模型当前变量是什么
会话记忆
通过SpringAI的学习,我们可以知道大模型是无状态的,这就需要通过ChatMemory引入会话记忆
-
定义会话记忆对象
@Bean public ChatMemory chatMemory(){ return MessageWindowChatMemory.builder() .maxMessages(20)//最多存储20条信息 .build(); } -
配置会话记忆对象
@Autowired private ChatMemoryStore chatMemoryStore; @Bean public ChatMemoryProvider chatMemoryProvider(){ ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() { @Override public ChatMemory get(Object memoryId) { return MessageWindowChatMemory.builder() .maxMessages(20) .id(memoryId) .chatMemoryStore(chatMemoryStore)//存储对象 .build(); } }; return chatMemoryProvider; }
会话记忆隔离
那么搭建好初步的会话记忆以后,会出现一个问题:新建一个会话或者重启服务,都会记得其他会话的内容,出现会话记忆混合的问题。这就需要通过userID或者其他标志进行隔离区分
记忆隔离架构
代码实现
-
定义会话记忆对象提供者
@Configuration public class MemoryConfig { @Bean public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore store) { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) // 保留最近20条 .chatMemoryStore(store) .build(); } // 简单的内存存储,生产环境建议替换为 RedisChatMemoryStore @Bean public ChatMemoryStore chatMemoryStore() { return new InMemoryChatMemoryStore(); } } -
配置会话记忆对象提供者
在
service类的@AiService中添加chatMemory = "chatMemory",chatMemoryProvider = "chatMemoryProvider" -
ConsultantService接口方法中添加参数memoryId
-
Controller中chat接口接收memoryId
@AiService public interface ConsultantService { // 必须包含 @MemoryId 参数才能启用隔离记忆 String chat(@UserMessage String message, @MemoryId String userId); } -
前端请求转发memoryId
通过js进行异步请求
memoryId
会话记忆持久化
上阶段是采用内存存储用户会话的方式实现会话记忆。但是如果电脑关机或者服务重启,那么数据会被清空,所以本节通过引入Redis进行缓存用户对话数据
redis存储逻辑
-
准备redis环境
可在Docker或者虚拟机中进行启动部署
-
引入redis起步依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> -
配置redis连接信息
langchain4j: openai: community: redis: host: your-host-name port: 6379 -
提供ChatMemoryStore实现类
RedisChatMemory中实现了:获取信息、添加信息和删除信息三种方法。其中Redis一般存储JSON格式的用户对话数据,而这需要对信息进行序列化和反序列化
@Repository public class RedisChatMemory implements ChatMemoryStore { @Autowired private StringRedisTemplate redisTemplate; @Override public List<ChatMessage> getMessages(Object memoryId) { //获取会话消息 String json = redisTemplate.opsForValue().get(memoryId.toString()); if (json == null || json.isEmpty()) { return List.of(); } //反序列化 List<ChatMessage> chatMessages = ChatMessageDeserializer.messagesFromJson(json); return chatMessages; } @Override public void updateMessages(Object memoryId, List<ChatMessage> list) { //更新会话消息 //通过序列化将list转为JSON String json = ChatMessageSerializer.messagesToJson(list); //存储到redis redisTemplate.opsForValue().set(memoryId.toString(),json, Duration.ofDays(1)); } @Override public void deleteMessages(Object memoryId) { redisTemplate.delete(memoryId.toString()); } } -
配置ChatMeomoryStore
@Autowired
private ChatMemoryStore chatMemoryStore;
@Bean
public ChatMemoryProvider chatMemoryProvider(){
ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
@Override
public ChatMemory get(Object memoryId) {
return MessageWindowChatMemory.builder()
.maxMessages(20)
.id(memoryId)
.chatMemoryStore(chatMemoryStore)//存储对象
.build();
}
};
return chatMemoryProvider;
}
Rag知识库
单单通过机器训练跟不上时代发展,外挂知识库就显得额外重要,他能够解决知识滞后、胡说八道的问题
原理
通过检索外部知识库的方式增强大模型的生成能力、
向量数据库:Milvus、Chroma、Pinecone、RedisSerach、pgvector
RAG处理流程
Langchain4j相对于SpringAI提供了简洁的API
入门
-
存储
-
引入依赖
<!-- 基础RAG向量模型--> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-easy-rag</artifactId> <version>1.0.1-beta6</version> </dependency> <!--langchain4j对redis向量数据库的支持--> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-redis-spring-boot-starter</artifactId> <version>1.0.1-beta6</version> </dependency> -
加载数据库
-
构建向量数据库操作对象
@Autowired private EmbeddingModel embeddingModel ; //构建向量数据库 // @Bean public EmbeddingStore store(){//easy-rag中本身就有注册了一个embeddingStore的bean // List<Document> content = ClassPathDocumentLoader.loadDocuments("content"); List<Document> content = ClassPathDocumentLoader.loadDocuments("content",new ApachePdfBoxDocumentParser()); //构建向量数据库操作的是内存版本的向量数据库 //InMemoryEmbeddingStore store = new InMemoryEmbeddingStore(); DocumentSplitter ds = DocumentSplitters.recursive(500,100); //构建EmbeddingStoreIngestor对象 EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .embeddingStore(redisEmbeddingStore) .documentSplitter(ds) .embeddingModel(embeddingModel)//嵌入模型 .build(); //存储数据 ingestor.ingest(content); return redisEmbeddingStore; } -
文档切割、向量化存储到向量数据库
-
-
检索
-
构建向量数据库检索对象
@Autowired private RedisEmbeddingStore redisEmbeddingStore; @Bean //放到IOC容器 public ContentRetriever contentRetriever(){ EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(redisEmbeddingStore) .maxResults(3) .embeddingModel(embeddingModel) .minScore(0.6) .build(); return retriever; } -
配置向量数据检索对象
在
service类中添加contentRetriever = "contentRetriever",配置向量数据库检索对象
-
EmbeddingStore
这里使用的是redisEmbeddingStore
操作向量数据库
- 准备向量数据库
- 引入依赖
- 配置向量数据库信息
- 注入RedisEmbeddingStore使用
Tools工具-准备环境
调用流程
- 准备数据库环境
- 引入依赖
- 配置链接信息
- 准备实体类
- 开发Mapper
- 开发Service
- 完成测试
Tools工具-实现
-
准备工具方法
@Component public class ReservationTool { @Autowired private ReservationService reservationService; //添加预约 @Tool(name = "添加预约信息") public void addReservation( @P("考生姓名") String name, @P("考生性别")String gender, @P("考生手机号")String phone, @P("考生预约沟通时间,格式为:yyy-MM-dd'T'HH:mm")String communcationTime, @P("考生所在省份")String province, @P("考生预估分数")String estimatedScore ){ Reservation reservation = new Reservation(null,name,gender,phone, LocalDateTime.parse(communcationTime),province,Integer.parseInt(estimatedScore)); reservationService.insert(reservation); } //查询预约 @Tool(name = "查询预约信息") public Reservation findReservationByPhone(@P("考生手机号")String phone){ return reservationService.findByPhone(phone); } } -
配置工具方法
在service类的@AiService中添加tools = "reservationTool"
总结
通过Langchain4j的学习,这极大的简化了AI应用的开发流程:
- AiService
- Memory
- Rag
- Tool
更多推荐




所有评论(0)