一、会话功能的快速入门

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();
  1. baseUrl:参考百炼平台API文档
  2. apiKey:API-KEY可以直接写死到代码中,也可以配置到操作系统的环境变量中,然后通过代码获取再使用。这里推荐大家把API-KEY配置到系统的环境变量中再使用,因为如果直接写死在代码里面,会存在API-KEY泄露的风险。(在用户变量里面配置,一定要重启IDEA
  3. 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;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐