langchain4j入门基本使用
1. 初始准备工作
1.1 创建springboot 项目
正常引入springboot所需要的一些依赖以后,添加langchain4j的依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
1.2 获取API-Key
由于我们需要获取一个API-Key来调用大模型的接口,所以我们可以在一些提供免费接口试用的厂商申请一个额apikey,比如DeepSeek、BaiChuan、阿里云百炼、智谱清言等。我们以阿里云百炼为例
链接:阿里云百炼
点击免费体验
然后登陆账号,进入页面点击左上角的账户中的API-key
然后创建一个API-KEY
然后得到的API-KEY好好保存,然后开始进行配置文件
1.3 配置文件
langchain4j:
open-ai:
chat-model:
api-key: sk-8d83 # 换成自己的
model-name: qwen-plus # 模型可以换成自己想要的模型
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 # 阿里云的接口链接,如果访问失败,去官网找最新的链接
# 流式返回的配置
streaming-chat-model:
api-key: sk-8d83
model-name: qwen-plus
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
2 功能实现
我们首先编写一个最简单的接口进行测试,先看看我们的接口是否能够访问到阿里的接口并得到相应的信息
package com.jolly.chatmat.chat;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class chat2QW_plus {
// 创建一个实例对象用于调用接口
private ChatLanguageModel chatLanguageModel;
// 创建一个构造函数,这样会自动创建一个OpenAiChatModel的对象,不需要我们手动创建
public chat2QW_plus(ChatLanguageModel chatLanguageModel) {
this.chatLanguageModel = chatLanguageModel;
}
@GetMapping("/chat")
public String model(@RequestParam(value = "message", defaultValue = "Hello") String message) {
return chatLanguageModel.chat(message);
}
}
调用chat接口,可以得到返回的结果
但是这种方式是偏向于底层的方式,我们不能有效的对其进行扩展使用,比如日志打印等等。
所以我们使用更高级的方式进行使用,首先创建一个对应模型的接口
2.1 编写接口
package com.jolly.chatmat.models;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;
@AiService
public interface QWen {
@SystemMessage("你是一个计算机专家")
String chat(String userMessage);
}
可以使用注入的方式获取到对应的模型实体
@Resource
private QWen qWen;
@GetMapping("/chat")
public String model(@RequestParam(value = "message", defaultValue = "算法的特性是什么") String message) {
return qWen.chat(message);
}
模型也是进行了有效的输出
2.2 实现流式返回
LLM 每次生成一个标记,因此许多 LLM 提供商都提供逐个标记流式传输响应的方法,而不是等待生成整个文本。这显著改善了用户体验,因为用户无需等待未知的时间,几乎可以立即开始阅读响应。
我们常见大模型的对话都是流式传输。然后前端有打印机的效果,这样可以更快的响应和更好的体验,所以流式返回都是最基本的操作。
为了实现流式传输,需要导入一个依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.0-beta2</version>
</dependency>
然后在刚才的**QWen **接口种,添加一个方法
@SystemMessage("You are a polite assistant")
Flux<String> chatWithStream(String userMessage);
然后添加一个流式返回的接口
@GetMapping(value = "/chatWithStream",produces = TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatWithStream(@RequestParam(value = "message", defaultValue = "算法的特性是什么") String message) {
return qWen.chatWithStream(message);
}
得到的结果为
这里想要吐槽一下,为什么流式传输的需要进行配置文件的方式进行配置,同样的模型直接在接口上添加一个注解的方式进行实现不好吗?
2.3 实现日志打印
首先创建一个监听类,对调用接口前和调用接口后的返回值进行打印,实现**ChatModelListener **接口的方法
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyChatModelListener implements ChatModelListener {
private static final Logger log = LoggerFactory.getLogger(MyChatModelListener.class);
@Override
public void onRequest(ChatModelRequestContext requestContext) {
log.info("onRequest(): {}", requestContext.chatRequest());
}
@Override
public void onResponse(ChatModelResponseContext responseContext) {
log.info("onResponse(): {}", responseContext.chatResponse());
}
@Override
public void onError(ChatModelErrorContext errorContext) {
log.info("onError(): {}", errorContext.error().getMessage());
}
}
然后将监听类创建为一个Bean对象给SpringBoot管理,创建一个config类
import dev.langchain4j.model.chat.listener.ChatModelListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QwenConfig {
@Bean
ChatModelListener chatModelListener() {
return new MyChatModelListener();
}
}
这样在使用过程中就可以实现日志打印功能
2.4 对话记忆
请注意,“记忆”和“历史”是相似但又不同的概念。
历史记录保存了用户和 AI 之间的所有消息。历史记录是用户在 UI 中看到的内容。它代表了实际说过的内容。
记忆会保留一些信息,这些信息会呈现给 LLM,使其表现得好像“记住”了对话一样。
实现记忆的方式很简单,在刚才的Config文件中加入一下内容,我们可以实现对话的记忆
@Bean
ChatModelListener chatModelListener() {
return new MyChatModelListener();
}
我们可以在日志打印中查看到效果
但是注意,这个对话历史是没有和任何用户信息绑定的,所以不管换一个链接请求,还是什么的都会有对话历史,所以是不安全的,对话历史需要和用户进行绑定
如果需要实现此功能,需要使用声明式的创建方式进行创建,我们首先创建一个新的接口
public interface QWen {
@SystemMessage("你是一个高效的人工智能助手")
String chatWithMemory(@MemoryId int memoryId, @UserMessage String userMessage);
}
然后使用@Bean将创建的模型对象交给springboot管理,这里可以将参数解析换成对配置文件的解析,就可以实现解耦,withMaxMessages就是设置的最大历史对话长度,如果太长了会导致我们的对话非常消耗token,这是很不好的。
@Bean(name = "Qwen01")
public QWen Qwen_Assistant() {
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey("sk-8d83")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-plus")
.build();
return AiServices.builder(QWen.class).chatLanguageModel(model).
chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();
}
然后创建一个接口用于接受请求,这里我使用的是userid,但是在真正的开发环境中,是不可以这样进行使用的,需要将userid和当前会话的id进行结合,得到一个当前用户会话独一无二的id
@RestController
public class chat2QW_plus {
@Resource(name = "Qwen01")
private QWen qWen01;
@GetMapping(value = "/chatWithMemory",produces = TEXT_EVENT_STREAM_VALUE)
public String chatWithMemory(@RequestParam(value = "message", defaultValue = "算法的特性是什么") String message,@RequestParam("userid") Integer userid) {
return qWen01.chatWithMemory(userid,message);
}
结果如下:
我们可以明显的看到,已经实现了对话记忆的功能,当然如果需要实现持久化的记忆需要进行另外的实现,当我们修改userid后,就可以实现每个用户都有临时的对话记忆能力
如果需要进行持久化的对话记忆存储,可以参考以下代码
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import java.util.List;
import java.util.Map;
import static dev.langchain4j.data.message.ChatMessageDeserializer.messagesFromJson;
import static dev.langchain4j.data.message.ChatMessageSerializer.messagesToJson;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
import static org.mapdb.Serializer.INTEGER;
import static org.mapdb.Serializer.STRING;
public class ServiceWithPersistentMemoryForEachUserExample {
interface Assistant {
String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
public static void main(String[] args) {
PersistentChatMemoryStore store = new PersistentChatMemoryStore();
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(store)
.build();
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(ApiKeys.OPENAI_API_KEY)
.modelName(GPT_4_O_MINI)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(chatMemoryProvider)
.build();
System.out.println(assistant.chat(1, "Hello, my name is Klaus"));
System.out.println(assistant.chat(2, "Hi, my name is Francine"));
// Now, comment out the two lines above, uncomment the two lines below, and run again.
// System.out.println(assistant.chat(1, "What is my name?"));
// System.out.println(assistant.chat(2, "What is my name?"));
}
// You can create your own implementation of ChatMemoryStore and store chat memory whenever you'd like
static class PersistentChatMemoryStore implements ChatMemoryStore {
private final DB db = DBMaker.fileDB("multi-user-chat-memory.db").transactionEnable().make();
private final Map<Integer, String> map = db.hashMap("messages", INTEGER, STRING).createOrOpen();
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = map.get((int) memoryId);
return messagesFromJson(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String json = messagesToJson(messages);
map.put((int) memoryId, json);
db.commit();
}
@Override
public void deleteMessages(Object memoryId) {
map.remove((int) memoryId);
db.commit();
}
}
}
后续将更新langchain4j使用RAG进行检索增强