Langchain4j(一)会话功能
在CommonConfig类中,构建MessageWindowChatMemory对象,并注入到IOC容器中。构建的时候我们可以指定该对象中最大的会话存储数量,一般设置20就足够了。@Bean.maxMessages(20)//最大保存的会话记录数量.build();LangChain4j中提供了一个类,将来LangChain4j如果从容器中没有找到指定id的ChatMemory对象,就会调用Ch
一、会话功能的快速入门
1.创建一个普通的maven工程
2.引入依赖:JAVA要在17以上
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.0.1</version>
</dependency>
3.构建聊天对象OpenAiChatModel
在启动类里面构建一个OpenAiChatModel
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//url参考百炼平台API文档
.apiKey(System.getenv("API-KEY"))//获取环境变量API-KEY使用
.modelName("qwen-plus")//设置模型名称
.build();
- baseUrl:参考百炼平台API文档
- apiKey:API-KEY可以直接写死到代码中,也可以配置到操作系统的环境变量中,然后通过代码获取再使用。这里推荐大家把API-KEY配置到系统的环境变量中再使用,因为如果直接写死在代码里面,会存在API-KEY泄露的风险。(在用户变量里面配置,一定要重启IDEA)
- modelName:模型名称
4.调用方法与大模型交互
public class App {
public static void main(String[] args) {
//2.构建OpenAiChatModel对象
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("API-KEY"))
.modelName("qwen-plus")
.build();
//3.调用chat方法,交互
String result = model.chat("世界上最美的女人是谁?");
System.out.println(result);
}
}
5.打印日志信息
引入依赖
在构建OpenAiChatModel对象的时候调用logRequests和logResponses方法设置一下即可
public class App {
public static void main(String[] args) {
//2.构建OpenAiChatModel对象
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("API-KEY"))
.modelName("qwen-plus")
.logRequests(true)//设置打印请求日志
.logResponses(true)//设置打印响应日志
.build();
//3.调用chat方法,交互
String result = model.chat("世界上最美的女人是谁?");
System.out.println(result);
}
}
二、Spring Boot整合LangChain4j
1.构建Spring Boot+Web项目
2.引入starter依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.1-beta6</version>
</dependency>
3.在resources里面配置yml文件
langchain4j:
open-ai:
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${API-KEY}
model-name: qwen-plus
4.开发接口,调用大模型
@RestController
public class ChatController {
@Autowired
private OpenAiChatModel model;
@RequestMapping("/chat")
public String chat(String message){
String result = model.chat(message);
return result;
}
}
5.打印日志
langchain4j:
open-ai:
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${API-KEY}
model-name: qwen-plus
log-requests: true #请求消息日志
log-responses: true #响应消息日志
logging:
level:
dev.langchain4j: debug #日志级别
三、AiServices工具类
1.引入依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.1-beta6</version>
</dependency>
2.声明用于封装聊天方法的接口
public interface ConsultantService {
//用于聊天的方法,message为用户输入的内容
public String chat(String message);
}
3.使用AiServices为接口创建代理对象
AiServices.builder().build() 是LangChain4J 封装了 JDK 动态代理
@Configuration
public class CommonConfig {
@Autowired
private OpenAiChatModel model;
@Bean
public ConsultantService consultantService() {
//AiServices.builder().build() 是LangChain4J 封装了 JDK 动态代理
ConsultantService cs = AiServices.builder(ConsultantService.class)
.chatModel(model)//设置对话时使用的模型对象
.build();
return cs;
}
}
4.在controller中注入并使用
这里的consultantService是我们创建的代理对象。
@RestController
public class ChatController {
@Autowired
private ConsultantService consultantService;
@RequestMapping("/chat")
public String chat(String message){
String result = consultantService.chat(message);
return result;
}
}
5.AiServices的声明式使用
想为哪个接口创建代理对象,只需要在该接口上添加**@AiService**注解并指定要使用的模型,将来LangChain4j扫描到该注解后会自动的创建该接口的代理对象并注入到IOC容器中。就不需要在配置类里面手动创建代理对象了。
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,//手动装配模型
chatModel = "openAiChatModel"//指定模型
)
@AiService //自动装配
public interface ConsultantService {
//用于聊天的方法,message为用户输入的内容
public String chat(String message);
}
四、流式调用
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.1-beta6</version>
</dependency>
2.配置流式模型对象
langchain4j:
open-ai:
streaming-chat-model: #流式模型配置,下面的都一样
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${API-KEY}
model-name: qwen-plus
log-requests: true
log-responses: true
3.调整ConsultantService中的代码
ConsultantService中的chat方法的返回值类型,需要修改为支持流式处理的类型Flux,同时还需要在AiService注解中,通过streamingChatModel属性, 配置一下流式调用的模型对象,值为openAistreamingChatModel
- 注解中同时配置chatModel和streamingChatModel只是 “声明可用模型”,不决定最终调用模式;
- 核心规则:方法返回普通类型(String)→ 非流式(用 chatModel);返回流式类型(Flux/Publisher)→ 流式(用streamingChatModel);
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel"
)
public interface ConsultantService {
public Flux<String> chat(String message);
}
4.调整ChatController中的代码
修改返回类型
@RestController
public class ChatController {
@Autowired
private ConsultantService consultantService;
//produces:响应的数据编码类型
@RequestMapping(value = "/chat",produces = "text/html;charset=utf-8")
public Flux<String> chat(String memoryId,String message){
Flux<String> result = consultantService.chat(memoryId,message);
return result;
}
}
五、消息注解
接下来我们学习langchian4j中提供的消息注解,将来我们开发的项目叫做Ai志愿填报顾问,它只能回答志愿填报相关的问题,如果用户问其他的问题,则不予回答。比如你问它特朗普靠谱吗?它是不能回答你的。如果要实现这样的效果,我们就需要通过设定系统消息的方式来完成了。
1.SystemMessage
用于设置系统消息的,你可以直接在接口的方法上添加这个注解,在注解中书写系统消息即可。当然了, 如果我们的系统消息很长, 直接在代码中写不方便,它还提供了另外一种使用方式,通过fromResource属性,指定一个外部的文件。fromResource是从resources目录下查询文件的,注意文件的相对路径。
2.UserMessage
假设现在没有SystemMessage,那么我们可以借助于UserMessage注解完成同样的效果,我们可以在用户消息前后,拼接提前预设的内容
@UserMessage("你是东哥的助手小月月,温柔貌美又多金。{{it}}")
public Flux<String> chat(String message);
可以通过{{it}}的方式, 动态的获取到用户传递的消息,然后再往它的前后拼接上预设的内容即可,想拼什么拼什么。
这里有一点需要说明,这个花括号内的it是固定的,不能随便写。假设你不想使用it这个名字,langchain4j提供了一个V注解,用于解决这个问题。我们在参数前面通过V注解给这个参数起一个名字,然后在花括号内写上同样的名字就能获取到了
@UserMessage("你是东哥的助手小月月,温柔貌美又多金。{{msg}}")
public Flux<String> chat(@V("msg") String message);
这个时候发送的是role为user的用户信息,而不是系统消息
六、会话记忆
1.会话记忆原理
大模型不具备记忆能力,每次会话都是独立的。要想让大模型产生记忆的效果,唯一的方法就是把之前聊天的内容和新的内容一起发送给大模型。
有了langchian4j了之后就不需要这么麻烦了,它能够帮我们记录聊天消息并自动发送!
- 请求:后端接收到消息后,会自动把消息存放到存储对象中,然后再获取存储对象中记录的所有会话消息,一块发送给大模型
- 响应:大模型根据接收到的消息,生成答案,比如说是的,再把答案响应给web后端,此时web后端会把得到的响应消息往存储对象中拷贝一份,然后再把响应消息发送给用户。
2.会话记忆基本实现
langchain4j提供了一个接口叫做ChatMemory,该接口中提供了
- add方法用于添加一条记录,
- messages方法用于获取所有的会话记录,
- clear方法用于清除所有的会话记录,
- 这里还有一个id方法,它是用于唯一的标识一个存储对象,当然这个id暂时我们用不着,等会儿我们讲解会话记忆隔离的时候再给大家详细的讲解。
- 同时LangChain4j还提供了该接口的两个实现类,一个是TokenWindowChatMemory,另外一个是MessageWindowChatMemory, 咱们暂时先使用MessageWindowChatMemory来存储会话记录。
public interface ChatMemory {
Object id();//记忆存储对象的唯一标识
void add(ChatMessage var1);//添加一条会话记忆
List<ChatMessage> messages();//获取所有会话记忆
void clear();//清除所有会话记忆
}
2.1定义会话记忆对象
在CommonConfig类中,构建MessageWindowChatMemory对象,并注入到IOC容器中。构建的时候我们可以指定该对象中最大的会话存储数量,一般设置20就足够了。
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20)//最大保存的会话记录数量
.build();
}
2.2配置会话记忆对象
我们需要在ConsultantService接口上的AiService注解中借助于chatMemory属性完成配置,值就是IOC容器中ChatMemory对象的名字,也就是我们构建该对象时使用的方法名chatMemory。
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
chatMemory = "chatMemory"//配置会话记忆对象
)
public interface ConsultantService {
@SystemMessage(fromResource = "system.txt")
public Flux<String> chat(String message);
}
3.会话记忆隔离
刚才我们借助于MessageWindowChatMemory实现了会话记忆的效果,看起来还不错, 但是还是有一些小问题的。当不同的用户访问我们的程序时,无法区分不同用户的会话记录,因为刚才实现的会话记忆,所有用户存储会话记录都是用的是同一个会话记忆对象,所以会话记忆并没有做到隔离
在LangChain4j中可以准备一个容器,专门用于存储当前程序中所有的会话记忆对象。假设有一个用户访问我们的程序,此时它除了要把用户问题message携带给后端,还需要携带一个memoryId,我们只需要把对应memoryId的会话记忆对象的内容传给大模型
3.1定义会话记忆对象提供者
LangChain4j中提供了一个类ChatMemoryProvider,将来LangChain4j如果从容器中没有找到指定id的ChatMemory对象,就会调用ChatMemoryProvider对象的get方法获取一个新的ChatMemory对象使用,因此我们需要提供这个ChatMemoryProvider对象,实现get方法。这里的get方法,会接收一个参数,这个参数就是memoryId,返回一个结果就是ChatMemory对象。
@Bean
public ChatMemoryProvider chatMemoryProvider() {
ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
@Override
public ChatMemory get(Object memoryId) {
return MessageWindowChatMemory.builder()
.id(memoryId)//id值
.maxMessages(20)//最大会话记录数量
.build();
}
};
return chatMemoryProvider;
}
3.2配置会话记忆对象提供者
既然提供了ChatMemoryProvider,之前提供的这个公有的ChatMemory就没有必要了,可以把它注释掉。
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
//chatMemory = "chatMemory",
chatMemoryProvider = "chatMemoryProvider"//配置会话记忆对象提供者
)
3.3ConsultantService接口的方法中添加参数memoryId
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
//chatMemory = "chatMemory",
chatMemoryProvider = "chatMemoryProvider"//配置会话记忆对象提供者
)
public interface ConsultantService {
@SystemMessage(fromResource = "system.txt")
public Flux<String> chat(@MemoryId String memoryId, @UserMessage String message);
}
3.4ChatController中chat接口接收前端传递的memoryId
@RequestMapping(value = "/chat",produces = "text/html;charset=utf-8")
public Flux<String> chat(String memoryId,String message){
Flux<String> result = consultantService.chat(memoryId,message);
return result;
}
3.5前端访问/chat接口是提交memoryId参数
我们使用的是Date.now.toString()来充当memoryId,每次初始化页面会生成一个新的memoryId,当新建对话的时候会重置memoryId
4.会话记忆持久化
4.1MessageWindowChatMemory不持久化的原理
刚才我们完成了会话记忆隔离,其实我们的会话记忆还是有一些瑕疵的,只要后端重启,会话记忆就没有了,丢失了。先分析一下为什么会存在这种问题,之前我们一直构建的用于存储会话记录的对象是MessageWindowChatMemory,而这个对象内部维护了一个成员变量ChatMemoryStore,其实我们使用MessageWindowChatMemory对象的add方法添加会话记录的时候,真正用于存储的对象是这个ChatMemoryStore,所以要分析为什么重启之后会话记录会丢失,我们得分析ChatMemoryStore是如何存储会话记录的。
public class MessageWindowChatMemory implements ChatMemory {
private final String id;
private final ChatMemoryStore store;//这个对象用于存储会话记录
public void add(ChatMessage message) {
this.store.updateMessages(this.id, messages);
}
public List<ChatMessage> messages() {
return this.store.getMessages(this.id);
}
public void clear() {
this.store.deleteMessages(this.id);
}
}
ChatMemoryStore是一个接口,它里面提供了getMessages、updateMessages、deleteMessages方法分别用于根据memoryId获取会话记录,根据memoryId更新会话记录以及根据memoryId删除会话记录。
public interface ChatMemoryStore {
List<ChatMessage> getMessages(Object memoryId);
void updateMessages(Object memoryId,List<ChatMessage> messages);
void deleteMessages(Object memoryId);
}
LangChain4j为该接口提供了两个实现类,分别是InMemoryChatMemoryStore和SingleSlotMemoryStore。而我们MessageWindowChatMemory中默认使用的Store对象就是这个SingleChatMemoryStore。
接下来我们重点分析它里面又是如何存储会话记录的。
class SingleSlotChatMemoryStore implements ChatMemoryStore {
private List<ChatMessage> messages = new ArrayList();//用于存储会话记录
public List<ChatMessage> getMessages(Object memoryId) {
return this.messages;
}
public void updateMessages(Object memoryId,
List<ChatMessage> messages) {
this.messages = messages;
}
public void deleteMessages(Object memoryId) {
this.messages = new ArrayList();
}
}
在SingleSlotChatMemoryStore中维护了一个集合对象messages,它就是使用这个集合存储会话消息的,所以很明显这是内存存储,一旦当服务器重启后这些消息必然会丢失!
4.2会话记录持久化——Redis
(1)准备Redis环境
下载docker和redis
如果你的wsl出现了需要更新的问题,可以直接下载我的安装包,重启docker。
夸克网盘wls
//安装redis:
docker run --name redis -d -p 6379:6379 redis
下载redis-insight图形化界面客户端,6379端口连接
(2)引入redis起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(3) 配置redis连接信息
spring:
data:
redis:
host: localhost
port: 6379
(4)提供ChatMemory实现类操作redis
定义实现类实现ChatMemory接口,重写getMessages、updateMessages、deleteMessages方法,用于操作redis,并且把实现类的对象注入到IOC容器中。
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import java.time.Duration;
import java.util.List;
@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
//注入RedisTemplate
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
//获取会话消息
String json = redisTemplate.opsForValue().get(memoryId);
//把json字符串转化成List<ChatMessage>,反序列化
List<ChatMessage> list = ChatMessageDeserializer.messagesFromJson(json);
return list;
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> list) {
//更新会话消息
//1.把list转换成json数据,序列化
String json = ChatMessageSerializer.messagesToJson(list);
//2.把json数据存储到redis中
redisTemplate.opsForValue().set(memoryId.toString(),json, Duration.ofDays(1));
}
@Override
public void deleteMessages(Object memoryId) {
//删除会话消息
redisTemplate.delete(memoryId.toString());
}
}
(5)将我们提供的ChatMemoryStore配置给MessageWindowChatMemory对象使用。
@Autowired
private ChatMemoryStore redisChatMemoryStore;
@Bean
public ChatMemoryProvider chatMemoryProvider(){
ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
@Override
public ChatMemory get(Object memoryId) {
return MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(redisChatMemoryStore)//配置ChatMemoryStore
.build();
}
};
return chatMemoryProvider;
}
更多推荐

所有评论(0)