解决的问题:
1.将组件的连接封装起来,对外只提供接口方法调用,具体实现用代理封装,调用变简单
2.自动处理Prompt模板,使用@SystemMessage @UserMessage注解、{{}}占位符来拼装完整的Prompt
3.可配置自动管理聊天记忆
4.自动处理Tool的调用
5.自动处理返回的结构化
基础用法:
1.一个接口:
public interface AiServiceDemo {
@SystemMessage("你是一个旅游小助手") //系统Prompt
// @SystemMessage(fromResource = "system-prompt.txt") //从resources下的system-prompt.txt加载消息模板
@UserMessage("你好,我是张三,这是我的问题:{{question}}") //userPrompt模板
String chat(@V("question") String userMessage); //使用@V指定占位符的变量名
}
2.简单使用:
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("AI_KEY"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
//简单封装
//注意:会将AiServiceDemo接口内的所有方法都做一次代理
AiServiceDemo service = AiServices.create(AiServiceDemo.class, chatModel);
String response = service.chat("1+1=几?");
System.out.println(response);
2.1.AiServices的另一种创建方式:
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
.chatModel(chatModel)
.systemMessageProvider(chatMemoryId -> "你是一个机票小助手")//可手动注入systemPrompt
.build();
不同的响应
1.可获取除内容外的原信息
//使用Result包裹响应,可得到除了响应内容以外的一些元信息
Result<List<String>> chat2(String userMessage);
Result<List<String>> response = service.chat2("你好");
//响应内容
System.out.println(response.content());
//ai服务调用期间消耗的token总数
System.out.println(response.tokenUsage());
//在 RAG 检索期间检索到的 Content
System.out.println(response.sources());
//AI 服务调用过程中执行过的工具记录
System.out.println(response.toolExecutions());
//模型这次生成结束的原因
System.out.println(response.finishReason());
2.自定义Java对象响应
1.接口层:
//!!!返回类型信息会在请求时携带给llm,如下
//You must answer strictly in the following JSON format:
// {\n\"name\": (type: string),\n\"age\": (type: integer),\n\"birthDate\": (type: date string (2023-12-31)),\n\"address\":
// (type: com.demo.AiServiceDemo$Person$Address: {\n\"street\": (type: string),\n\"streetNumber\": (type: integer),\n\"city\": (type: string)\n})\n}"
//响应时为json数据,如下
//"content":"{\n \"name\": \"小明\",\n \"age\": 20,\n \"birthDate\": \"2006-04-03\",\n \"address\": {\n \"street\": \"山村街道\",\n \"streetNumber\": 200,\n \"city\": \"北京市\"\n }\n}","role":"assistant"}
@UserMessage("帮我提取一下用户信息为用户信息对象,信息如下{{it}}")
Person extratPersonInfo(String userInfoStr);
2.响应对象层:
@Data
@Description("一个人的信息")
class Person{
String name;
int age;
LocalDate birthDate;
Address address;
@Data
@Description("地址信息")
static class Address{
String street;
Integer streetNumber;
String city;
}
}
3.对话层:
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("AI_KEY"))
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-plus")
.logRequests(true)
.logResponses(true)
.build();
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
.chatModel(chatModel)
.systemMessageProvider(chatMemoryId -> "你是一个提取信息小助手")
.build();
String personInfo = """
小明今年20岁,出生于2006年4月3日,住在北京市山村街道200号
""";
//自动将响应解析为Java对象
AiServiceDemo.Person person = service.extratPersonInfo(personInfo);
System.out.println(person);
3.流式处理响应
方式1:
@SystemMessage("你是一个旅行规划小助手")
TokenStream streamChat(String userMessage); //返回类型为:TokenStream
//OpenAiStreamingChatModel为流式处理ChatModel
OpenAiStreamingChatModel streamingChatModel = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("AI_KEY"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
CountDownLatch latch = new CountDownLatch(1);
AiServiceDemo service = AiServices.create(AiServiceDemo.class, streamingChatModel);
TokenStream tokenStream = service.streamChat("你好");
tokenStream.onPartialResponse(System.out::print) // 模型流式生成部分文本片段时触发
.onCompleteResponse(r-> {System.out.println("已完成"); latch.countDown();}) // 本次模型响应完整结束后触发,可拿到完整 ChatResponse
.onError(e-> {e.printStackTrace();latch.countDown();}) //在报错时触发的函数
.onRetrieved(System.out::println) // RAG 检索完成后触发,拿到本次从知识库召回的 Content 列表/片段
.onToolExecuted(System.out::println) //工具执行完成后触发,拿到本次工具调用的执行记录,如工具名、入参、返回结果
.start();
latch.await();//阻塞代码,防止程序在响应前终止
方式2:
需引入依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.12.2-beta22</version>
</dependency>
//返回Flux<String>类型
@SystemMessage("你是一个旅行规划小助手")
Flux<String> fluxChat(String userMessage);
OpenAiStreamingChatModel streamingChatModel = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("AI_KEY"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
AiServiceDemo service = AiServices.create(AiServiceDemo.class, streamingChatModel);
//流式处理方式2
Flux<String> fluxResponse = service.fluxChat("你好");
fluxResponse.doOnNext(System.out::print) // 模型流式生成部分文本片段时触发
.doOnComplete(() -> System.out.println("\n已完成")) // 本次模型响应完整结束后触发,可拿到完整 ChatResponse
.doOnError(Throwable::printStackTrace) //在报错时触发的函数
.blockLast(); //阻塞住
对话记忆
简要知识:
记忆与历史的区别:
历史:真正的完整对话记录,保存用户与ai的完整交互内容,可用于审计、回放、问题追踪。
记忆:从历史记录、用户画像、业务数据或 RAG 检索结果中提炼/筛选出的上下文信息,用于在当前对话中帮助模型理解用户意图和背景。
淘汰策略
原因:
llm模型的对话上下文窗口大小有限,过大的上下文无法处理;
太长的上下文会导致延迟增加(控制延迟);
一些无用的上下文会浪费token(控制成本)
langchain4j自带的实现:
1.MessageWindowChatMemory: 以消息数量为限制,保留最近的N条消息;特点:适合简单场景,实现直观,但不能精确控制 token 数量
2.TokenWindowChatMemory:以token数量为限制,在 token 上限内尽量保留最近的完整消息,消息通常是不可分割的,如果加入某条消息后超过 token 上限,会淘汰更早的消息,而不是把某条消息截断成一半。
消息持久化(历史对话)
默认情况下,ChatMemory实现在内存中存储ChatMessage
自定义:实现ChatMemoryStore做自定义持久化
自定义历史对话持久化
public class ChatMemoryDemo implements ChatMemoryStore {
//目前是demo,真实场景可改为MySQL/PgSQL存储
private final Map<Object,List<ChatMessage>> testMsgStore = new ConcurrentHashMap<>();
@Override
public List<ChatMessage> getMessages(Object memoryId) {
System.out.println("获取历史对话...");
List<ChatMessage> chatMessages = testMsgStore.get(memoryId);
return chatMessages == null || chatMessages.isEmpty() ? List.of() : chatMessages;
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> list) {
System.out.println("更新历史对话...");
testMsgStore.put(memoryId, list);
}
@Override
public void deleteMessages(Object memoryId) {
testMsgStore.remove(memoryId);
}
}
使用
1.直接使用Langchain4j官方Memory
//方法使用memoryId入参
String memoryChat(@MemoryId int memoryId,@UserMessage String userMessage);
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
.chatModel(chatModel)
//chatMemoryProvider需要配合带有memoryId入参的chat方法
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();
//验证:
String response1 = service.memoryChat(1,"你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat(1,"你好,你是谁");
System.out.println("回答2:"+response2);
2.使用自定义持久化(共享会话记忆版)
//使用不带memoryId入参的会话方法
String memoryChat2(String userMessage);
//使用MessageWindowChatMemory包装自定义持久memory(共享会话记忆)
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
//自定义持久化历史记录
.chatMemoryStore(new ChatMemoryDemo())
.build();
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
.chatModel(chatModel)
//共享会话记忆
//chatMemory配合不带memoryId的chat方法,只能共享会话记忆
.chatMemory(chatMemory)
.build();
//验证:
String response1 = service.memoryChat2("你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat2("你好,你是谁");
System.out.println("回答2:"+response2);
3.使用自定义持久化(根据memoryId隔离记忆版)
//方法使用memoryId入参
String memoryChat(@MemoryId int memoryId,@UserMessage String userMessage);
//包装自定义的持久化
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(new ChatMemoryDemo())
.build();
AiServiceDemo service = AiServices.builder(AiServiceDemo.class)
.chatModel(chatModel)
//隔离会话记忆使用
.chatMemoryProvider(chatMemoryProvider)
.build();
//验证:
String response1 = service.memoryChat(1,"你好,你现在是张三");
System.out.println("回答1:"+response1);
System.out.println("-------------------------------------------");
String response2 = service.memoryChat(1,"你好,你是谁");
System.out.println("回答2:"+response2);
评论区