当前位置: 首页 > news >正文

langchain4j入门(跟随官网学习)第一章

1. 什么是langchain4j

LangChain4j 的目标是简化将 LLM 集成到 Java 应用程序中的过程。

  1. 也就是说专门使用java去操作模型的一个集成API

2.在pom文件添加依赖

		<!--OpenAI 集成依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>1.0.0-beta3</version></dependency><!--langchain4j API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>1.0.0-beta3</version></dependency>

3. 进行模型调用(简单示例)

1.如果没有 OpenAI API 密钥,直接使用langChain4j官网提供的免费案例

OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("http://langchain4j.dev/demo/openai/v1") // 请求地址(你用谁提供的模型,这里是langchain4j提供的模型).apiKey("demo")// api密钥(这里编写demo,会自动获取langchain4j提供的密钥).modelName("gpt-4o-mini") // 模型名称.build(); //构建// 这里调用模型,并将用户提供的问题给到模型,并返回模型响应的结果String answer = model.chat("你好");// 输出模型响应的结果System.out.println(answer);

2.下面我使用硅基流动提供的模型进行操作

package com.xr.langchain4jdemo.demo;import dev.langchain4j.model.openai.OpenAiChatModel;public class Demo1 {public static void main(String[] args) {OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动官网调用地址.apiKey("密钥从硅基流动官网获取") // 密钥.modelName("deepseek-ai/DeepSeek-R1") //deepseek-r1模型.build();// 提出问题并获取响应String answer = model.chat("你好");// 输出响应System.out.println(answer);}
}

最后输出的模型响应结果:
在这里插入图片描述

4.聊天和语言模型(LLM)

LLM(大型语言模型)两种API:

  1. LanguageModel
    接收 String 作为输入并返回 String 作为输出,但正在被聊天ChatLanguageModel API所取代
  2. ChatLanguageModel
    这些接受多个 ChatMessage 作为输入并返回单个 AiMessage 作为输出。 ChatMessage 通常包含文本,但某些 LLM 也支持其他模态(例如,图像、音频等)。比LanguageModel更强大

除了 ChatLanguageModel 和 LanguageModel 外,LangChain4j 还支持以下类型的模型:

  1. EmbeddingModel - 这种模型可以将文本转换为 Embedding。
  2. ImageModel - 这种模型可以生成和编辑 Image。
  3. ModerationModel - 这种模型可以检查文本是否包含有害内容。
  4. ScoringModel - 这种模型可以对查询的多个文本片段进行评分(或排名), 本质上确定每个文本片段与查询的相关性。这对 RAG 很有用。 这些将在后面介绍。

5. ChatMessage 的类型

目前有四种类型的聊天消息,每种对应消息的一个"来源":

  1. UserMessage:这是来自用户的消息。 用户可以是您应用程序的最终用户(人类)或您的应用程序本身。 根据 LLM 支持的模态,UserMessage 可以只包含文本(String), 或其他模态。(就是用户输入的问题)

  1. AiMessage:这是由 AI 生成的消息,通常是对 UserMessage 的回应,AiMessage 可以包含文本响应(String)或执行工具的请求(ToolExecutionRequest)。(就是用户提出的问题后,模型对用户提出的问题进行一个响应)

  1. ToolExecutionResultMessage:这是 ToolExecutionRequest 的结果。(ai就行工具调用,这个消息决定了模型是否去执行调用对应的工具)

  1. SystemMessage:这是来自系统的消息。 通常,您作为开发人员应该定义此消息的内容。 通常,您会在这里写入关于 LLM 角色是什么、它应该如何行为、以什么风格回答等指令。 LLM 被训练为比其他类型的消息更加关注 SystemMessage, 所以要小心,最好不要让最终用户自由定义或在 SystemMessage 中注入一些输入。 通常,它位于对话的开始。(就是预定提示词,给模型先定义角色,让他更精准回答某方面的内容)

  1. CustomMessage:这是一个可以包含任意属性的自定义消息。这种消息类型只能由 支持它的 ChatLanguageModel 实现使用(目前只有 Ollama)

6. 多个 ChatMessage

多个ChatMessage的作用:
为什么要多个ChatMessage, 这是因为 LLM 本质上是无状态的,意味着它们不维护对话的状态。 因此,如果您想支持多轮对话,您应该负责管理对话的状态。(意思就是说当你先提出一个问题的时候,在提出第二个问题问模型上一个问题是什么,模型却不知道你上一个问题是什么(模型是不具备记忆的),所以需要使用多个ChatMessage让模型相当于有记忆)

package com.xr.langchain4jdemo.demo;import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.openai.OpenAiChatModel;public class Demo1 {public static void main(String[] args) {OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动调用地址.apiKey("密钥从硅基流动官网获取") // 密钥.modelName("deepseek-ai/DeepSeek-R1") // deepseek-r1模型.build();// 第一次用户提出的问题UserMessage firstUserMessage = UserMessage.from("你好,我的名字是张三");AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage();// 第一次模型回答的问题System.out.println(firstAiMessage.text());System.out.println("--------------------------------------------------------------------");// 第二次用户提出的问题UserMessage secondUserMessage = UserMessage.from("我的名字是?");// 将第一次用户的问题,第一次模型的回答,和第二次用户的问题,一起传给模型AiMessage secondAiMessage = model.chat(firstUserMessage, firstAiMessage, secondUserMessage).aiMessage();// 第二次模型回答的问题System.out.println(secondAiMessage.text());}
}

最后模型响应的结果:

在这里插入图片描述

7. 多模态

UserMessage 不仅可以包含文本,还可以包含其他类型的内容(意思为,用户提出的问题给模型不只是文本类型,还可以提其他类型类型的问题,前提是模型要支持这些类型)。 UserMessage 包含 List contents。 Content 是一个接口,有以下实现:

  1. TextContent:(基础文本类型)
  2. ImageContent:(图像数据类型)
  3. AudioContent:(音频数据类型)
  4. VideoContent:(视频数据类型)
  5. PdfFileContent:(文档处理类型)

以下是向 LLM 发送文本和图像的示例:

  1. 现在网上随便找一张图片,获取对应图片的网址,我使用的是蜡笔小新的图片,对应的网址为:https://pgw.udn.com.tw/gw/photo.php?u=https://uc.udn.com.tw/photo/2023/10/22/0/26413514.jpg&x=0&y=0&sw=0&sh=0&exp=3600
    在这里插入图片描述
  2. 在硅基流动里选择支持视觉的模型,(一定需要有支持视觉的模型,不然无效)
  3. 将文本类型和图片类型作为参数交给UserMessage,最后一起交给模型
package com.xr.langchain4jdemo.demo;import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.openai.OpenAiChatModel;public class Demo1 {public static void main(String[] args) {OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动调用地址.apiKey("密钥从硅基流动官网获取") // 密钥.modelName("Qwen/Qwen2.5-VL-32B-Instruct") // 从硅基流动选择支持视觉的模型.build();UserMessage firstUserMessage = UserMessage.from( // 用户输入的内容TextContent.from("请描述以下图像"), // 文本类型ImageContent.from("https://pgw.udn.com.tw/gw/photo.php?u=https://uc.udn.com.tw/photo/2023/10/22/0/26413514.jpg&x=0&y=0&sw=0&sh=0&exp=3600") //图片类型,图片的网址);// 模型响应的结果AiMessage aiMessage = model.chat(firstUserMessage).aiMessage();System.out.println(aiMessage.text());}
}

最后响应的结果:
在这里插入图片描述

8. 聊天记忆

上面说了多个 ChatMessage来让模型具有简单的记忆,但是这种方式需要开发者自己拼接历史消息,不仅代码冗余,而且在复杂对话场景下极易出错。因此,LangChain4j提供了ChatMemory抽象以及多种开箱即用的实现。来解决上面的手动维护聊天记忆 的方式


ChatMemory作为ChatMessage的容器(由List支持),具有以下额外功能:
1. 淘汰策略
2. 持久化
3. 对SystemMessage的特殊处理
4. 对工具消息的特殊处理

8.1 记忆与历史

1. 记忆:保存一些信息,这些信息呈现给LLM,使其表现得好像"记住"了对话。 记忆与历史有很大不同。根据使用的记忆算法,它可以以各种方式修改历史: 淘汰一些消息,总结多条消息,总结单独的消息,从消息中删除不重要的细节, 向消息中注入额外信息(例如,用于RAG)或指令(例如,用于结构化输出)等等(只记重点,不记非重点)


2.历史:历史保持用户和AI之间的所有消息完整无缺。历史是用户在UI中看到的内容。它代表实际对话内容(所有内容全部记)


LangChain4j目前只提供"记忆",而不是"历史"。如果您需要保存完整的历史记录,请手动进行。

8.2 淘汰策略

1. 每次用户发送问题给LLM,但是LLM一次可以处理的令牌数量是有上限的,在某些时候,对话可能会超过这个限制。在这种情况下,应该淘汰一些消息。 通常,最旧的消息会被淘汰,但如果需要,可以实现更复杂的算法。(用户提出的问题转换后就称之为token,每次发送消息给LLM的时候,通常会将前面发送的消息和前面ai回答的消息在加上当前发送的消息一起在给到LLM,所以随着提出的问题越多,那么很快就会超出LLM的token限制)


2. 控制成本。每个令牌都有成本,使每次调用LLM的费用逐渐增加。 淘汰不必要的消息可以降低成本。(调用一次LLM是需要money的,每次发送的消息数量与money,消息越多,money越多)


3. 控制延迟。发送给LLM的令牌越多,处理它们所需的时间就越长

目前,LangChain4j提供了2种开箱即用的实现:
1. MessageWindowChatMemory(消息窗口内存):保留最近的N条消息,并淘汰不再适合的旧消息。 然而,由于每条消息可能包含不同数量的令牌, MessageWindowChatMemory主要用于快速原型设计。(当新消息到来时,自动淘汰最旧的一条消息,不关心每条消息的token数量,可能导致总token数不可控)


      2. TokenWindowChatMemory(token窗口内存):但专注于保留最近的N个令牌, 根据需要淘汰旧消息。 消息是不可分割的。如果一条消息不适合,它会被完全淘汰。 TokenWindowChatMemory需要一个Tokenizer来计算每个ChatMessage中的令牌数。(当新消息加入时,会计算当前总token数,从最旧的消息开始淘汰,直到新消息能放入窗口,整条消息淘汰策略:如果单条消息超过窗口容量,会直接拒绝该条消息,更精确地控制上下文长度,符合LLM的token限制要求)

什么是滑动窗口机制:
1. 窗口:固定大小的容器,用于存放最近的N条数据(如聊天消息)
2. 当新数据到达时,最旧的数据被淘汰,保持窗口大小不变(类似FIFO队列的自动淘汰)。
3.滑动窗口机制的目的:防止内存无限增长,同时保留最近的上下文。

8.3 持久化

默认情况下,ChatMemory实现在内存中存储ChatMessage(就是不管是用户的消息还是模型的消息等等消息,都是存储在内存当中)

如果需要持久化,可以实现自定义的ChatMemoryStore, 将ChatMessage存储在您选择的任何持久化存储中:
1. 下面代码中就是一个自定义的类实现了ChatMemoryStore接口,并且有三个方法,通过这三个就可以实现持久化、聊天记忆
2. 这三个方法的调用时机是通过api提供的方法隐式调用的(代码上有注释)

class PersistentChatMemoryStore implements ChatMemoryStore {/*** 通过传入过来的持久化到数据库id获取当前的所有历史消息* 当调用 chatMemory.messages()时,隐式调用该方法* (获取历史消息)*/@Overridepublic List<ChatMessage> getMessages(Object memoryId) {// TODO: 实现通过内存ID从持久化存储中获取所有消息。// 可以使用ChatMessageDeserializer.messageFromJson(String)和// ChatMessageDeserializer.messagesFromJson(String)辅助方法// 轻松地从JSON反序列化聊天消息。}/*** 通过传入过来的持久化到数据库id和多个ChatMessage消息,修改历史消息记录* 当调用 chatMemory.add() 添加新消息时,隐式调用该方法* (更新历史消息)*/@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {// TODO: 实现通过内存ID更新持久化存储中的所有消息。// 可以使用ChatMessageSerializer.messageToJson(ChatMessage)和// ChatMessageSerializer.messagesToJson(List<ChatMessage>)辅助方法// 轻松地将聊天消息序列化为JSON。}/*** 通过传入过来的持久化到数据库id,删除历史消息* 当调用 chatMemory.clear() 时,隐式调用该方法* (删除历史消息)*/@Overridepublic void deleteMessages(Object memoryId) {// TODO: 实现通过内存ID删除持久化存储中的所有消息。}}

下面将消息持久化到redis中为例,进行模型的聊天记忆和聊天消息的持久化

  1. 首先,增加redis依赖
        <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version></dependency>
  1. 实现ChatMemoryStore类,来完成消息的持久化
package com.xr;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 redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.params.SetParams;import java.time.Duration;
import java.util.List;/*** 基于Redis的对话记忆存储实现* 实现ChatMemoryStore接口,提供对话消息的持久化能力*/
public class RedisChatMemoryStore implements ChatMemoryStore {/*** Redis连接池实例*/private final JedisPool jedisPool;/*** 构造函数,初始化Redis连接池* @param host Redis服务器地址* @param port Redis服务器端口*/public RedisChatMemoryStore(String host, int port) {// 配置连接池参数JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(8);      // 最大连接数poolConfig.setMaxIdle(8);       // 最大空闲连接数poolConfig.setMinIdle(1);       // 最小空闲连接数poolConfig.setTestOnBorrow(true); // 获取连接时校验可用性poolConfig.setTestOnReturn(true); // 归还连接时校验poolConfig.setTestWhileIdle(true); // 空闲时定期校验// 连接最小空闲时间(60秒)poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());// 创建连接池实例this.jedisPool = new JedisPool(poolConfig, host, port);}/*** 根据记忆ID获取历史消息列表* @param memoryId 对话记忆的唯一标识 (通常为sessionId)* @return 消息列表,不存在时返回空列表* (通过存储到reids中的唯一key,获取历史消息)*/@Overridepublic List<ChatMessage> getMessages(Object memoryId) {try (Jedis jedis = jedisPool.getResource()) { // 自动管理连接资源// 构建Redis键名格式:chat:memory:{memoryId}String key = "chat:memory:" + memoryId.toString();// 从Redis获取JSON格式的对话历史String messagesJson = jedis.get(key);// 处理空值情况if (messagesJson == null || messagesJson.isEmpty()) {return List.of(); // 返回不可变空列表}// 将JSON反序列化为ChatMessage对象列表return ChatMessageDeserializer.messagesFromJson(messagesJson);}}/*** 更新指定记忆ID的消息列表* @param memoryId 对话记忆的唯一标识* @param messages 新的消息列表* (通过存储到reids中的唯一key,更新历史消息)*/@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {try (Jedis jedis = jedisPool.getResource()) {String key = "chat:memory:" + memoryId.toString();// 将消息列表序列化为JSON字符串String messagesJson = ChatMessageSerializer.messagesToJson(messages);// 设置Redis存储参数:过期时间为7天(60*60*24*7秒)SetParams params = SetParams.setParams().ex(60 * 60 * 24 * 7);// 存储到Redis并设置TTLjedis.set(key, messagesJson, params);// 调试日志(生产环境应使用Logger)System.out.println("持久化存储更新: " + key);}}/*** 删除指定记忆ID的消息记录* @param memoryId 要删除的记忆ID* (通过存储到reids中的唯一key,删除历史消息)*/@Overridepublic void deleteMessages(Object memoryId) {try (Jedis jedis = jedisPool.getResource()) {String key = "chat:memory:" + memoryId.toString();// 删除Redis中的键long deleted = jedis.del(key);System.out.println("持久化存储删除: " + key + (deleted > 0 ? " (成功)" : " (键不存在)"));}}/*** 关闭连接池(应用程序退出时调用)*/public void close() {if (jedisPool != null && !jedisPool.isClosed()) {jedisPool.close(); // 优雅关闭连接池System.out.println("Redis连接池已关闭");}}
}
  1. 编写mian方法进行测试(消息的持久化,模型的记忆)
package com.xr;import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;public class Main {public static void main(String[] args) {// 1. 初始化OpenAI模型配置ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 2. 创建Redis持久化存储实例 (连接到本地Redis)RedisChatMemoryStore memoryStore = new RedisChatMemoryStore("localhost", 6379);// 3. 配置对话记忆系统String chatId = "key1-user1";  // 对话唯一标识,可用于多用户隔离ChatMemory chatMemory = MessageWindowChatMemory.builder().id(chatId)             // 绑定对话ID.maxMessages(4)        // 记忆窗口大小(用户提出的问题为1条,ai回答的问题为一条,超过了自动淘汰第一条消息).chatMemoryStore(memoryStore)  // 注入持久化存储实现.build();try {// —— 第一轮对话 ——String userMessage1 = "你好,我叫张三";System.out.println("用户: " + userMessage1);// 将用户消息添加到记忆(自动持久化到Redis)chatMemory.add(UserMessage.from(userMessage1));// 获取当前对话上下文并发送给AI模型AiMessage response1 = model.chat(chatMemory.messages()).aiMessage();System.out.println("AI: " + response1.text());// 将AI回复加入记忆(同样会自动持久化)chatMemory.add(response1);// —— 第二轮对话(测试记忆功能)——String userMessage2 = "我刚才说我叫什么名字?";System.out.println("\n用户: " + userMessage2);// 添加新用户消息chatMemory.add(UserMessage.from(userMessage2));// 核心优势:这里chatMemory.messages()会自动包含历史上下文// 包括第一轮的"我叫张三"和第二轮的新问题AiMessage response2 = model.chat(chatMemory.messages()).aiMessage();System.out.println("AI: " + response2.text());  // 应回答"张三"// 存储AI回复chatMemory.add(response2);} finally {// 确保资源释放(非常重要)memoryStore.close();  // 关闭Redis连接池}}
}
  1. 执行的结果为
    在这里插入图片描述
    reid管理工具可以查看持久化的消息
    在这里插入图片描述
    在这里插入图片描述

1 每当向ChatMemory添加新的ChatMessage时,都会调用updateMessages()方法。 这通常在与LLM的每次交互中发生两次: 一次是添加新的UserMessage时,另一次是添加新的AiMessage时。 updateMessages()方法预期会更新与给定内存ID关联的所有消息。 ChatMessage可以单独存储(例如,每条消息一条记录/行/对象) 或一起存储(例如,整个ChatMemory一条记录/行/对象)。


2. 从ChatMemory中淘汰的消息也将从ChatMemoryStore中淘汰。 当消息被淘汰时,会调用updateMessages()方法, 传入不包含被淘汰消息的消息列表。


3. 当ChatMemory的用户请求所有消息时,会调用getMessages()方法。 这通常在与LLM的每次交互中发生一次。 Object memoryId参数的值对应于创建ChatMemory时指定的id。 它可以用来区分多个用户和/或对话。 getMessages()方法预期会返回与给定内存ID关联的所有消息。


4. 当调用ChatMemory.clear()时,会调用deleteMessages()方法。 如果您不使用此功能,可以将此方法留空。

8.4 SystemMessage的特殊处理

什么是SystemMessage:
SystemMessage 是 AI 对话系统中的一种控制指令消息,专门用于:
1. 设定 AI 的行为模式(如角色、语气、专业知识)
2. 控制输出格式(如强制使用 JSON、Markdown)
3. 添加安全约束(如内容过滤规则)
4 这个是由系统开发者设定的,隐藏(用户不可见的),并且永久保留


SystemMessage是一种特殊类型的消息,因此它的处理方式与其他消息类型不同:
1.一旦添加,SystemMessage总是被保留。 (当滑动窗口(历史消息)指定只有4条可保留时,超过4条消息,(SystemMessage也算一条消息)),他不会淘汰SystemMessage(会被保留),只会淘汰非SystemMessage消息)
2. 一次只能保存一条SystemMessage,如果添加了具有相同内容的新SystemMessage,它会被忽略。如果添加了具有不同内容的新SystemMessage,它会替换之前的消息。

更改上面案例:

package com.xr;import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;public class Main {public static void main(String[] args) {// 1. 初始化OpenAI模型配置ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 2. 创建Redis持久化存储实例 (连接到本地Redis)RedisChatMemoryStore memoryStore = new RedisChatMemoryStore("localhost", 6379);// 3. 配置对话记忆系统String chatId = "key1-user1";  // 对话唯一标识,可用于多用户隔离ChatMemory chatMemory = MessageWindowChatMemory.builder().id(chatId)             // 绑定对话ID.maxMessages(4)        // 记忆窗口大小(SystemMessage系统消息也为1条,用户提出的问题为1条,ai回答的问题为一条,超过了自动淘汰第一条消息(不从系统消息开始(保留),从用户消息或者ai消息开始淘汰)).chatMemoryStore(memoryStore)  // 注入持久化存储实现.build();// 定义系统消息,指定模型的角色和专业领域SystemMessage systemMessage = SystemMessage.from("""你是一个专业的对话助手,名字叫小深。请用中文回答,保持礼貌和专业。如果询问你的身份,请回答你是DeepSeek模型。""");// 将系统消息也持久化到redis中chatMemory.add(systemMessage);try {// —— 第一轮对话 ——String userMessage1 = "你好,我叫张三,你是谁";System.out.println("用户: " + userMessage1);// 将用户消息添加到记忆(自动持久化到Redis)chatMemory.add(UserMessage.from(userMessage1));// 获取当前对话上下文并发送给AI模型AiMessage response1 = model.chat(chatMemory.messages()).aiMessage();System.out.println("AI: " + response1.text());chatMemory.add(response1);// —— 第二轮对话 ——String userMessage2 = "我是是谁";System.out.println("用户: " + userMessage1);// 将用户消息添加到记忆(自动持久化到Redis)chatMemory.add(UserMessage.from(userMessage2));// 获取当前对话上下文并发送给AI模型AiMessage response2 = model.chat(chatMemory.messages()).aiMessage();System.out.println("AI: " + response1.text());chatMemory.add(response2);} finally {// 确保资源释放(非常重要)memoryStore.close();  // 关闭Redis连接池}}
}

1. 首先查看控制台输出的日志信息 在这里插入图片描述
2. 查看reids中的消息
在这里插入图片描述
在这里插入图片描述
前面4条消息体数据

[{"text": "你是一个专业的对话助手,名字叫小深。\n请用中文回答,保持礼貌和专业。\n如果询问你的身份,请回答你是DeepSeek模型。\n","type": "SYSTEM"},{"contents": [{"text": "你好,我叫张三,你是谁","type": "TEXT"}],"type": "USER"},{"text": "\n你好,张三!我是 **小深**,一名由深度求索公司(DeepSeek)开发的人工智能助手。我是基于 DeepSeek R1 模型构建的,可以帮你解答各种问题、查找资料、整理文本甚至陪你聊天放松心情~😊  \n\n如果你有任何需要帮助的地方,尽管告诉我!我很乐意陪你一起解决各种大大小小的事情~你现在还好吗?我可以和你聊点什么呢?🌟","type": "AI"},{"contents": [{"text": "我是谁???","type": "TEXT"}],"type": "USER"}
]

3. 存储到第五条消息时,淘汰用户提出的第一个问题的消息,保留系统消息
存储第5条消息
在这里插入图片描述
查看淘汰的消息
在这里插入图片描述
最后的消息体数据

[{"text": "你是一个专业的对话助手,名字叫小深。\n请用中文回答,保持礼貌和专业。\n如果询问你的身份,请回答你是DeepSeek模型。\n","type": "SYSTEM"},{"text": "\n你好,张三!我是 **小深**,一名由深度求索公司(DeepSeek)开发的人工智能助手。我是基于 DeepSeek R1 模型构建的,可以帮你解答各种问题、查找资料、整理文本甚至陪你聊天放松心情~😊  \n\n如果你有任何需要帮助的地方,尽管告诉我!我很乐意陪你一起解决各种大大小小的事情~你现在还好吗?我可以和你聊点什么呢?🌟","type": "AI"},{"contents": [{"text": "我是谁???","type": "TEXT"}],"type": "USER"},{"text": "\n(笑)你当然是 **张三** 呀!我们刚刚才互相认识呢~✨  \n是不是在调皮地考考我?别担心,我一直都记得你在对话中的信息!不过如果你还想分享更多关于自己的事(比如爱好、正在做的事……),我超乐意当树洞哦~😄  \n现在在想什么呢?在忙什么吗?🌟","type": "AI"}
]

8.5 对工具消息的特殊处理

如果包含ToolExecutionRequest的AiMessage被淘汰, 随后的孤立ToolExecutionResultMessage也会自动被淘汰, 以避免与某些LLM提供商(如OpenAI)出现问题, 这些提供商禁止在请求中发送孤立的ToolExecutionResultMessage。


ToolExecutionRequest的AiMessage(工具执行请求消息) :
Ai模型要求调用外部工具产生的特殊消息类型(就是相当于ai发送了一个请求要调用某个工具,比如查询天气,AI可能生成一个请求气象API的工具调用)


ToolExecutionResultMessage (工具执行结果消息):
Ai模型执行外部工具后返回结果的消息(就是接上例,有请求就有响应,气象api就会返回天气数据封装好的消息)


理解完了这两个消息类型再来理解一开始说的,就是设定了记忆几条消息时,如果存在工具消息,当需要淘汰工具执行请求的消息或者工具执行结果消息时,对应的工具执行结果消息工具执行请求的消息会一起淘汰,意思就是说1个请求会对应1个响应,某个请求被淘汰时,这个请求的响应信息也会跟着淘汰,或者某个响应要被淘汰时,这个响应对应的请求也会被淘汰,
(这个两个消息(工具执行请求消息,工具执行结果消息)要么全部淘汰,要么全部保留,不肯能保留1个淘汰1个)

9. 模型参数

调整模型的参数,就可以控制模型的输出结果(调整不同的参数,模型的输出都不同),比如你调整让模型每次输出的字数数量为100字(限制模型最大输出token)
1. 模型的输出:生成内容(文本、图像)的创造性或确定性水平,生成内容的数量等。
2. 连接性:基础 URL、授权密钥、超时、重试、日志记录等。

在这里插入图片描述

两种方式创建 Model:

  1. 一个静态工厂,只接受必需的参数,如 API 密钥,其他所有必需参数都设置为合理的默认值。
  2. 构建器模式:在这里,您可以为每个参数指定值。

模型构建器设置模型参数(案例):

OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") //供应商提供的地址,这里使用硅基流动为例子.apiKey(System.getenv("OPENAI_API_KEY")) // 供应商提供的key.modelName("gpt-4o-mini") // 供应商提供的模型名称.temperature(0.3) // 模型温度,越低越回答精准,越高回答约有想象力(没那么精准).timeout(ofSeconds(60)) //设置API请求的超时时间,就是请求该供应上提供的模型,如果硅基流动服务器60秒内没响应,就抛出超时异常.logRequests(true) //开启请求日志,请求内容:你发送给硅基流动的提示词(Prompt),会在控制台打印.logResponses(true) //开启响应日志,响应内容:AI返回的完整结果,会在控制台打印.build();

10. 响应流式传输

LLM 一次生成一个标记(token),因此许多 LLM 提供商提供了一种方式,可以逐个标记地流式传输响应,而不是等待整个文本生成完毕。 这显著改善了用户体验,因为用户不需要等待未知的时间,几乎可以立即开始阅读响应。


之前的案例都是模型全部生成完成文本后,在响应给我们打印到控制台,但是等模型全部生成完才响应的话要等挺久的,所以我们改变一下,让模型生成的文本像打字机一样,一段一段的输出到我们的控制台上,这样改善了体验

对于 ChatLanguageModel 和 LanguageModel 接口,有相应的 StreamingChatLanguageModel 和 StreamingLanguageModel 接口。 这些接口有类似的 API,但可以流式传输响应。 它们接受 StreamingChatResponseHandler 接口的实现作为参数。


ChatLanguageModel 和 LanguageModel 接口都是聊天API,但是他们都是等模型生成完内容后在一次性响应,不像打字机那样流式输出,所以想要实现打字机的效果,这两个接口有对应流式输出的聊天API(StreamingChatLanguageModel ,StreamingLanguageModel )(下面重新在看看官网介绍非流式输出的api)
在这里插入图片描述

1. 先看一个流式聊天API接口(StreamingChatLanguageModel ),另外一个也是一样(StreamingLanguageModel )
StreamingChatLanguageModel 接口
在这里插入图片描述

2. 然后这上面两个流式接口(StreamingChatLanguageModel ,StreamingLanguageModel )接收到的参数是>StreamingChatResponseHandler这个接口的实现类
StreamingChatResponseHandle接口
在这里插入图片描述

package com.xr.langchain4jdemo.demo;import dev.langchain4j.model.chat.StreamingChatLanguageModel;import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;public class Demo2 {public static void main(String[] args) throws InterruptedException {// 构建一个StreamingChatLanguageModel流式API(OpenAiStreamingChatModel是StreamingChatLanguageModel实现类)StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 使用一个userMessage的方法,模拟一个用户发送消息,并使用一个StreamingChatResponseHandler流式处理响应(这是使用的是内部类作为参数,也可以在写一个实现类实现StreamingChatResponseHandler接口)model.chat("你好,你是谁?", new StreamingChatResponseHandler() {/*** 模型每次生成一个Token调用一次这个方法,比如预计模型要生成50个token,那么这个方法会被调用50次* 每次调用的时候就打印到输出台上*/@Overridepublic void onPartialResponse(String partialResponse) {System.out.print(partialResponse);}/*** 模型生成完成调用这个方法,这里打印出完整的响应(包含模型生成的完的内容,和元信息)*/@Overridepublic void onCompleteResponse(ChatResponse completeResponse) {System.out.println("\n\n======================模型全部生成完时调用====================================");System.out.println(completeResponse);}/*** 模型生成错误调用这个方法,他的作用是(调用模型时,比如用户网络不行了,就会触发方法,可以告诉用户网络有问题请重试)*/@Overridepublic void onError(Throwable error) {error.printStackTrace();}});/*** 阻塞线程,不阻塞的话,还没等到模型生成结果,线程就结束了,所以这里要阻塞线程,让模型生成结果,然后才能结束线程*/while (true) {try {Thread.sleep(1000); // 避免 CPU 空转,每秒检查一次} catch (InterruptedException e) {e.printStackTrace();}}}
}

在这里插入图片描述

上面的chat方法还支持lambda表达式来编写,更简单的方式来定义事件
在这里插入图片描述

11. AI Services

前面一直在讲解底层组件,如 ChatLanguageModel、ChatMessage、ChatMemory,但是如果需要一一编写配置,会很繁琐麻烦,因为一个完整的LLM,所以LangChain4j提供了AI Services这个API来简化上面的这些内容,其思想是将与 LLM 和其他组件交互的复杂性隐藏在简单的 API 后面开发者只需用调用Ai Services这个服务就会自动使用底层组件来帮我们完成对应的内容

使用最简单的AI 服务(案例)

  1. 首先先定义一个接口,里面定义一个带有单个方法 chat 的接口,该方法接受 String 作为输入并返回 String
package com.xr.langchain4jdemo.demo1;/*** 创建一个接口*/
public interface Assistant {/*** 用户输入的内容作为参数,模型回答的内容作为返回值*/String chat(String userMessage);
}
  1. 使用AiServices进行查看效果
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法String answer = assistant.chat("你好");// 输出模型回答的结果System.out.println(answer);}
}
  1. 查看模型输出的结果
    在这里插入图片描述

Ai services底层是如何实现的:
在这里插入图片描述

11.1 @SystemMessage注解

使用该注解可以插入系统消息

  1. 改写上面的案例
package com.demo1;import dev.langchain4j.service.SystemMessage;public interface Assistant {/*** 1.用户输入的内容作为参数,模型回答的内容作为返回值* 2.使用SystemMessage注解,自定义系统消息,用户每次调用chat方法时,会将系统消息和用户消息一起发送给模型*/@SystemMessage("你是一个专业的对话助手,名字叫小深。请用中文回答,保持礼貌和专业。如果询问你的身份,请回答你是小深。一个专业的对话助手")String chat(String userMessage);
}
package com.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {
// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法(调用时,会将用户消息和系统消息一起发送给模型)String answer = assistant.chat("你好,你是谁?");// 输出模型回答的结果System.out.println(answer);}
}
  1. 模型响应结果
    在这里插入图片描述

11.1.1 从静态资源加载系统消息

首先现在resources目录下创建一个文本文件
在这里插入图片描述
在这里插入图片描述
改写一下代码,使用注解中的fromResource属性加载文本文件

package com.demo1;import dev.langchain4j.service.SystemMessage;public interface Assistant {/*** 1.用户输入的内容作为参数,模型回答的内容作为返回值* 2.使用SystemMessage注解,自定义系统消息,用户每次调用chat方法时,会将系统消息和用户消息一起发送给模型* 3. 使用fromResource属性加载静态资源*/@SystemMessage(fromResource = "message.txt")String chat(String userMessage);
}

最后的输出效果
在这里插入图片描述

11.1.2 动态设置系统消息

也可以不同的用户设置不同的系统消息,就是让系统消息改变成动态的

改写一下代码

package com.demo1;import dev.langchain4j.service.SystemMessage;public interface Assistant {/*** 删除@SystemMessage注解,改为构建AiServices动态绑定系统消息*/String chat(String userMessage);
}
package com.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 构建AiServices时,通过chatMemoryId可以判断不同用户动态绑定系统消息Assistant aiServices = AiServices.builder(Assistant.class).chatLanguageModel(model) // 模型.systemMessageProvider(chatMemoryId -> "你叫惜若,是一个专业的对话助手,请用中文回答,保持礼貌和专业。") // 这里可以通过chatMemoryId来判断设置不同用户的系统消息.build(); // 构建String answer = aiServices.chat("你好,你是谁?");System.out.println(answer);}
}

11.2 @UserMessage注解

当模型不支持系统消息时,可以使用@UserMessage来完成需要指定系统消息的效果

package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.UserMessage;/*** 创建一个接口     */
public interface Assistant {/*** 1.用户输入的内容作为参数,模型回答的内容作为返回值* 2.讲系统消息注解换成用户消息注解,并使用{{it}}作为占位符,当调用chat方法时传递的用户消息会被嵌入到@UserMessage中* 3.@UserMessage也是有fromResource属性,可以加载静态资源的*/@UserMessage("你是一个暴躁的客服,回答时充满不耐烦的语气。用户的问题是:{{it}}")String chat(String userMessage);
}
package com.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法,这里的用户消息会嵌入到@UserMessage中的{{it}}占位符中String answer = assistant.chat("我的订单怎么还没到?");// 输出模型回答的结果System.out.println(answer);}
}

最后执行结果
在这里插入图片描述

如果占位符名称想自定义,而不是叫it,那么就要使用**@V**注解,这样就可以自定义占位符名称

package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;/*** 创建一个接口*/
public interface Assistant {/*** 1.用户输入的内容作为参数,模型回答的内容作为返回值* 2.使用@V注解自定义在@UserMessage中的占位符名称*/@UserMessage("你是一个暴躁的客服,回答时充满不耐烦的语气。用户的问题是:{{message}}")String chat(@V("message") String userMessage);
}

下面是两个注解的有效使用方式,就是注解放在哪个位置才有效

  1. 只使用@UserMessage注解
    在这里插入图片描述

  2. 使用@SystemMessage和@UserMessage注解组合使用
    在这里插入图片描述

11.3 返回类型

之前我们定义的chat方法返回的类型为String,在这种情况下,LLM 生成的输出将不经任何处理/解析直接返回
Ai services 可以结构化输出支持的任何类型 - 在这种情况下, AI 服务将在返回之前将 LLM 生成的输出解析为所需类型(就是模型输出的时候可以设置我们想要的类型,如boolean,Enum,如实体类等等…)

11.3.1 Result < T > 类型

任何类型都可以额外包装在 Result 中,以获取有关 AI 服务调用的额外元数据,如:

  1. 想要模型输出的结果转为实体,并且也有ai服务的元数据,就可以Result<实体类>
  1. 查看一下Result里面封装的元数据信息,通过这个对象我们可以获取到:
    在这里插入图片描述
  2. 创建一个助手接口
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.Result;
import dev.langchain4j.service.UserMessage;
import java.util.List;/*** 创建一个接口*/
public interface Assistant {/*** 1.Result对象里面又模型的元数据,比如模型回答的分数,模型回答的tokens,模型回答的tokensUsage等等* 2.这里最终指定模型返回的是一个文本,但是由于模型是一次生成几个token(一次生成有很多个token),* 所以是一个List<String>,然后我们又需要模型的元数据信息,所以这里返回的是Result<List<String>>*/@UserMessage("为以下主题生成一个大纲: {{it}}")Result<List<String>> generateOutlineFor(String topic);
}
  1. 进行一个测试
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;import java.util.List;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法,这里的用户消息会嵌入到@UserMessage中的{{it}}占位符中Result<List<String>> list = assistant.generateOutlineFor("java");// 模型输出结果System.out.println(list.content());System.out.println("=============================================");// 模型调用的token使用情况System.out.println(list.tokenUsage());System.out.println("=============================================");// 模型调用的源数据,引用本地知识库的内容,就是相当于ragSystem.out.println(list.sources());System.out.println("=============================================");// 模型调用结束的原因System.out.println(list.finishReason());System.out.println("=============================================");// 模型调用返回的tool执行结果System.out.println(list.toolExecutions());}
}
  1. 最终执行结果
    在这里插入图片描述

11.3.2 boolean 类型

package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.UserMessage;/*** 创建一个接口*/
public interface Assistant {/*** 设置模型返回的类型为boolean*/@UserMessage("这里{{it}}有积极的情绪吗")boolean generateOutlineFor(String topic);
}
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法,这里的用户消息会嵌入到@UserMessage中的{{it}}占位符中boolean result = assistant.generateOutlineFor("好难过,好伤心,不想活了");System.out.println(result);}
}

最终模型输出转成成boolean类型的结果
在这里插入图片描述

11.3.3 Enum 类型

  1. 先定义一个Enum类
package com.xr.langchain4jdemo.demo1;/*** 优先级枚举类*/
public enum Priority {CRITICAL, // 危急HIGH, // 高LOW // 低
}
  1. 创建一个助手接口
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.UserMessage;/*** */
public interface Assistant {/*** 设置模型返回的类型为自定义的Priority枚举类*/@UserMessage("分析一下问题的优先级:{{it}}")Priority generateOutlineFor(String topic);
}
  1. 进行测试
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey("硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法,这里的用户消息会嵌入到@UserMessage中的{{it}}占位符中Priority result = assistant.generateOutlineFor("主要支付平台出现了故障,导致用户无法进行交易操作");System.out.println(result);}
}
  1. 最后输出的结果
    在这里插入图片描述

11.3.4 POJO类型

@Description:描述注解,可以放在类上和属性上,描述这个类或者这个属性是什么,让模型更好的理解,并进行转换

  1. 定义两个实体类
package com.demo1;import jdk.jfr.Description;import java.time.LocalDate;/*** 人实体类*/
public class Person {@Description("一个人的姓") // 使用描述注解,描述这个属性是什么String lastName; // 姓String firstName; // 名LocalDate birthDate; // 生日Address address; // 地址public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public LocalDate getBirthDate() {return birthDate;}public void setBirthDate(LocalDate birthDate) {this.birthDate = birthDate;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}
}
package com.demo1;import jdk.jfr.Description;/*** 地址实体类*/
@Description("一个地址") // 使用描述注解,描述这个类是什么
public class Address {String city; // 城市String street; // 街道Integer streetNumber; // 门牌号public String getStreet() {return street;}public void setStreet(String street) {this.street = street;}public Integer getStreetNumber() {return streetNumber;}public void setStreetNumber(Integer streetNumber) {this.streetNumber = streetNumber;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}
}
  1. 创建助手接口
package com.demo1;import dev.langchain4j.service.UserMessage;public interface Assistant {@UserMessage("以下内容提取某人相关的信息:{{it}}")Person generateOutlineFor(String topic);
}
  1. 进行测试
package com.demo1;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;import java.util.List;public class AssistantMain {public static void main(String[] args) {// 创建低级组件ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1")  // 硅基流动API端点.apiKey(" 硅基流动密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1")  // 使用DeepSeek-R1模型.build();Assistant assistant = AiServices.create(Assistant.class, model);String userMessage = "我出生2005年10月22日,姓张名三,全名叫张三,住在北京市圆明园22号";Person person = assistant.generateOutlineFor(userMessage);System.out.println("姓:"+person.getLastName());System.out.println("====================");System.out.println("名:"+person.getFirstName());System.out.println("====================");System.out.println("生日:"+person.getBirthDate());System.out.println("====================");System.out.println("城市:"+person.getAddress().getCity());System.out.println("====================");System.out.println("街道:"+person.getAddress().getStreet());System.out.println("====================");System.out.println("门牌号:"+person.getAddress().getStreetNumber());}
}
  1. 最后模型输出的效果
    在这里插入图片描述

11.3.5 Json模式

JSON模式不是让模型最终输出JSON,而是要求LLM在生成响应时就严格遵循预定义的JSON Schema契约,从而实现从文本到类型安全Java对象的可靠转换。(JSON Schema就是规范这个json中的字段是什么类型,添加约束,如果不规范,假如实体类中的age为int类型,模型转换json中的age为 “30”(这是一个字符串30),最后进行json反序列化成对象时就会出错)

转换为POJO对象的过程
在这里插入图片描述

1. 以往的要模型生成的内容格式化成Json形式是通过工具调用完成的,但是这种方式耗时耗资源,那么Langchain4j提供了模型输出的内容强制转换的功能,这样效率比工具调用的方式更优解
2. 如果模型不支持强制转换Json功能,那么写提示词的方式是最好的选择

openAi模型开启json的案例

        ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动官网调用地址.apiKey(" 硅基流动官网密钥") // 硅基流动密钥.modelName("deepseek-ai/DeepSeek-R1") //deepseek R1模型.responseFormat("json_object") // 开启简单的json模式.build();

由于硅基流动的模型好像不支持开启这个模式所以就不写案例了(这里是更高级的json模式,就是json数据带了类型校验)

在这里插入图片描述

还有不同的供应商提供的开启json模式,可以去官网搜json模式
在这里插入图片描述

11.4 流式处理

1. AI 服务可以使用 TokenStream 返回类型逐个令牌流式处理响应
2. 也可以使用Flux代替TokenStream,复杂的使用Flux,简单的使用默认的TokenStream

11.4.1 使用TokenStream

  1. 创建助手接口,定义一个方法,返回值为TokenStream
package com.demo1;import dev.langchain4j.service.TokenStream;public interface Assistant {// 返回值使用默认的TokenStreamTokenStream generateOutlineFor(String message);
}
  1. 测试
package com.demo1;import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.rag.content.Content;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.tool.ToolExecution;import java.util.List;public class AssistantMain {public static void main(String[] args) {// 创建低级组件StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder().baseUrl("https://api.siliconflow.cn/v1").apiKey("硅基流动密钥").logResponses(true).modelName("deepseek-ai/DeepSeek-R1").build();Assistant assistant = AiServices.create(Assistant.class, model);String userMessage = "给我讲个笑话吧";TokenStream tokenStream = assistant.generateOutlineFor(userMessage);tokenStream.onPartialResponse((String partialResponse) -> {System.out.print(partialResponse); //模型每次生成一个片段(token),就进行回调}).onRetrieved((List<Content> contents) -> {System.out.println("==============检索到的内容后进行回调==============");System.out.println(contents);}).onToolExecuted((ToolExecution toolExecution) -> {System.out.println("==============工具执行后进行回调==============");System.out.println(toolExecution);}).onCompleteResponse((ChatResponse response) ->{System.out.println("\n==============完整响应后进行回调==============");System.out.println(response);}).onError((Throwable error) -> {System.out.println("==============错误进行回调==============");error.printStackTrace();}).start();// 阻塞线程while (true) {try {Thread.sleep(1000); // 避免 CPU 空转,每秒检查一次} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 最后执行结果
    在这里插入图片描述

11.4.2 使用Flux

  1. 首先导入Flux的依赖
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId><version>1.0.0-beta3</version>
</dependency>
  1. 编写助手接口,返回Flux< String >
package com.xr.langchain4jdemo.demo1;import reactor.core.publisher.Flux;/*** 编写助手接口*/
public interface Assistant {// 返回Flux<String>Flux<String> generateOutlineFor(String userMessage);
}
  1. 测试
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import reactor.core.publisher.Flux;public class AssistantMain {public static void main(String[] args) {// 创建低级组件StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder().baseUrl("https://api.siliconflow.cn/v1").apiKey("硅基流动密钥").logResponses(true).modelName("deepseek-ai/DeepSeek-R1").build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】Assistant assistant = AiServices.create(Assistant.class, model);// 然后调用我们接口中被AiServices代理的实现方法,这里的用户消息会嵌入到@UserMessage中的{{it}}占位符中Flux<String> flux = assistant.generateOutlineFor("给我讲个笑话");// 直接打印到控制台flux.subscribe(System.out::print);while (true) {try {Thread.sleep(1000); // 避免 CPU 空转,每秒检查一次} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 最后的运行效果
    在这里插入图片描述

11.5 AiServices 的聊天记忆

之前使用的是定义一个实现类实现ChatMemoryStore 接口,并重写里面的方法,来完成对聊天记忆进行一个获取,修改,删除的操作,然后在构建ChatMemory 对象,并调用ChatMemory 的方法来调用我们重写的方法,来完成对聊天记忆的操作,但是每次要操作聊天记忆都要调用一次方法,这样的方式太过重复和繁琐了。


使用AiServices可以直接简化重复的步骤,将重复的代码封装到底层,开发者只需要配置就可以实现聊天记忆了,大大简化了代码的重复性

11.5.1 单个 ChatMemory 示例(单用户的聊天记忆案例)

只有一个用户的聊天记忆

  1. 首先定义一个服务接口
package com.xr.langchain4jdemo.demo1;/**
* 定义服务接口
*/
public interface Assistant {// 接收用户消息,返回ai消息String chat(String userMessage);
}
  1. 进行测试
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级聊天模型ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动官网调用地址.apiKey("硅基流动密钥") // 密钥.modelName("deepseek-ai/DeepSeek-R1") //deepseek-r1模型.build();// 创建chatMemory组件,并设置缓存消息数(这里不是持久化,是将消息缓存在内存中,缓存的消息为4条)ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(4);// 构建服务Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(model) // 设置模型.chatMemory(chatMemory) // 设置消息缓存组件.build(); //构建服务// 调用服务(第一次调用)String chat = assistant.chat("你好,我叫张三,请记住我说的这句话");System.out.println(chat);System.out.println("===========================================================================");// 第二次调用String chat1 = assistant.chat("请问我叫什么");System.out.println(chat1);/*可以看见第二次调用的时候,模型的回答依然记得我们的名字,这是因为AiServices是通过代理来帮我进行操作的,自动将我们的消息和模型的消息都存入到了内存中*/}
}
  1. 最后执行结果
    在这里插入图片描述

11.5.2 每个用户 ChatMemory 示例(多个用户不同的聊天记录案例)

不同用户的聊天记忆,不用用户对应不同的聊天记忆

  1. 修改服务类
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;public interface Assistant {/*** 1. @MemoryId: 这个注解的作用就是记录不同的用户的id的(唯一标识)* 如果不使用@MemoryId注解,那么就不能使用int类型,应该使用String类型进行标识* 2.@UserMessage : 这个注解表示这个传递的参数为用户消息*/String chat(@MemoryId int memoryId,@UserMessage String userMessage);
}
  1. 修改测试类
package com.xr.langchain4jdemo.demo1;import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;public class AssistantMain {public static void main(String[] args) {// 创建低级聊天模型ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动官网调用地址.apiKey("硅基流动密钥") // 密钥.modelName("deepseek-ai/DeepSeek-R1") //deepseek-r1模型.build();// 构建服务Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(model) // 设置模型// 注意这里要将设置缓存消息写在chatMemoryProvider方法里面// 不然如果写在上面就是局部变量,那么就无法将不同用户的消息分开.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(4)).build(); //构建服务/*======================用户1,2第一次调用====================*/// 传递了1作为用户1的唯一标识,和用户1的用户消息String chat = assistant.chat(1,"你好,我叫张三");System.out.println(chat);System.out.println("============================用户1第1次调用结束========================================");/*======================用户2第一次调用====================*/// 传递了2作为用户2的唯一标识,和用户2的用户消息String chat2 = assistant.chat(2,"你好,我叫李四,");System.out.println(chat2);System.out.println("============================用户2第1次调用结束================================");/*======================用户1,2第二次调用====================*/// 调用服务(用户1第二次调用)String chat1 = assistant.chat(1,"请问我叫什么");System.out.println(chat1);System.out.println("=========================用户1第二次调用结束==================================");// 调用服务(用户2第二次调用)String chat3 = assistant.chat(2,"请问我叫什么");System.out.println(chat3);System.out.println("=========================用户2第二次调用结束==================================");}
}
  1. 最终执行结果为
    在这里插入图片描述

11.5.3 单个持久化 ChatMemory 示例(单用户的聊天记忆持久化案例)

11.6 工具(简单的函数调用)

函数调用就是让模型调用自己编写的方法。(比如你自己写了一个删除某条数据的方法),然后用户输出问题(提示词),如果接近这个方法的意思那么模型就会进行函数调用,比如:用户:帮我删除id为1的数据,模型:进行函数调用删除对应的数据


简单来说就是让模型装上手让他去做事情,不只是聊天。

  1. 首先创建一个工具类,编写自定义的方法
package com.xr.langchain4jdemo.demo3;import dev.langchain4j.agent.tool.Tool;/*** 定义工具类*/
public class Tools {/*** 使用@Tool注解标注的方法就是工具(函数方法)* 俩数相加函数*/@Toolint add(int a, int b) {return a+b;}/*** 使用@Tool注解标注的方法就是工具(函数方法)* 俩数相乘函数*/@Toolint multiply(int a, int b) {return a * b;}
}
  1. 编写助手接口
package com.xr.langchain4jdemo.demo3;import dev.langchain4j.service.Result;import java.util.List;/*** 定义一个助手接口*/
public interface Assistant {/*** 接收用户的消息,返回langchain4j封装的消息体* 因为我要看模型是否进行了函数调用,所以返回 Result<List<String>>可以进行更好的查看*/Result<List<String>> chat(String message);
}
  1. 进行测试
package com.xr.langchain4jdemo.demo3;import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;import java.util.List;public class Main {public static void main(String[] args) {// 创建低级聊天模型ChatLanguageModel model = OpenAiChatModel.builder().baseUrl("https://api.siliconflow.cn/v1") // 硅基流动官网调用地址.apiKey("硅基流动密钥") // 密钥.modelName("deepseek-ai/DeepSeek-R1") //deepseek-r1模型.build();// 创建AiServices示例(之前定义的接口和上面的构建的模型作为参数传递给AiServices)【这里其实是通过了代理进行了我们的接口的一个实现】// 并指定我们的工具类Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(model) // 设置模型.tools(new Tools()) // 设置工具类.build();// 因为设置了工具类,ai回思考是否要进行函数调用,(有概率调用,肯能调用,可能不调用)Result<List<String>> result = assistant.chat("1+1等于几?3*4等于几?");// 获取模型输出的结果System.out.println(result.content());System.out.println("=============================================");// 获取模型进行函数调用的信息,是否进行了函数调用System.out.println(result.toolExecutions());}
}
  1. 最终执行结果

这里模型并不是一定会进行函数调用的,要看模型的决策,模型决定调用时才会有消息返回,如果需要强制模型进行函数调用时,则需要写提示词

在这里插入图片描述

---------------------------------------------------接着学请看第二章-------------------------------------------------------


文章转载自:

http://pT2jLWPv.jyknk.cn
http://10qBKahc.jyknk.cn
http://AjhTEi0g.jyknk.cn
http://7K649LMP.jyknk.cn
http://RDFoEHtT.jyknk.cn
http://kcZFOOsz.jyknk.cn
http://Jux9NAqz.jyknk.cn
http://AYVrca0C.jyknk.cn
http://xsV0oZsk.jyknk.cn
http://oOoyjput.jyknk.cn
http://gzBhWxok.jyknk.cn
http://qmIWZRgI.jyknk.cn
http://USEe1BT0.jyknk.cn
http://BNQGv36P.jyknk.cn
http://3EidKOkp.jyknk.cn
http://nS2ievGa.jyknk.cn
http://PN3pUedx.jyknk.cn
http://ffHj2QbI.jyknk.cn
http://T7F5ytvJ.jyknk.cn
http://tlc5bZ78.jyknk.cn
http://WBKJgKvv.jyknk.cn
http://xXlbKTT3.jyknk.cn
http://T5g7cJNi.jyknk.cn
http://lVZ9hTzD.jyknk.cn
http://LGdaBM3n.jyknk.cn
http://c5bqJwHA.jyknk.cn
http://69RipMNZ.jyknk.cn
http://BT7GQDCZ.jyknk.cn
http://feiHirLP.jyknk.cn
http://hXKryb13.jyknk.cn
http://www.dtcms.com/a/378864.html

相关文章:

  • ASSIGN (LV_NAME) TO <FS_NAME>. 通过变量名动态访问变量
  • 二、WPF——Style样式玩法(通过资源字典将Style独立,全局调用)
  • 基于Hadoop进程的分布式计算任务调度与优化实践——深入理解分布式计算引擎的核心机制
  • 用工招聘小程序:功能版块与前端设计解析
  • Golang高效JSON处理:easyjson性能提升6倍
  • Golang语言入门之数组、切片与子切片
  • Go 死锁全解析:4个条件+5个场景+6个解决方案
  • Go语言快速入门教程(JAVA转go)——1 概述
  • 【leetcode】139. 单词拆分
  • 使用yocto工具链交叉编译lsof命令
  • vue项目的main.js规划设计与合理使用
  • FPGA入门-无源蜂鸣器驱动
  • 使用Langchain生成本地rag知识库并搭载大模型
  • [第一章] web入门—N1book靶场详细思路讲解
  • uniapp 文件查找失败:main.js
  • 第7篇、Kafka Streams 与 Connect:企业级实时数据处理架构实践指南
  • Linux redis 8.2.1源码编译
  • logging 模块升级版 loguru
  • 【Flask】实现一个前后端一体的项目-脚手架
  • 小说阅读系统Java源码 小说阅读软件开发 小说app小程序
  • 如何在 Debian 12 上安装 MySQL
  • GA-PNN: 基于遗传算法的光子神经网络硬件配置方法(未做完)
  • STM32基础篇--GPIO
  • 无人机遥控器射频模块技术解析
  • Docker 命令核心语法
  • 第五章:Python 数据结构:列表、元组与字典(一)
  • Python快速入门专业版(二十一):if语句基础:单分支、双分支与多分支(判断用户权限案例)
  • 学习笔记:JavaScript(4)——DOM节点
  • 软考中级习题与解答——第四章_软件工程(3)
  • 消息队列-kafka完结