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

生活家装饰官方网站澄海建网站

生活家装饰官方网站,澄海建网站,小企业管理软件排名,如何做网站代码Java开发者の模型召唤术:LangChain4j咏唱指南(二) 往期回顾: Java开发者の模型召唤术:LangChain4j咏唱指南(一)_langchain4j 千问-CSDN博客 上期博客中简单的为大家介绍了langchain4j是什么、java 集成 langchain4j、集成阿里云百炼平台的各…

Java开发者の模型召唤术:LangChain4j咏唱指南(二)


往期回顾:

Java开发者の模型召唤术:LangChain4j咏唱指南(一)_langchain4j 千问-CSDN博客


上期博客中简单的为大家介绍了langchain4j是什么、java 集成 langchain4j、集成阿里云百炼平台的各个模型以及本地安装的ollama模型的调用流程。

那么本期教程将会与大家分享常规的Spring-boot集成、流式输出、记忆对话、function-call、预设角色等更深层次的知识,码字不易,各位喜欢请一键三连~


1.springboot 项目集成langchain4j

1.1创建项目

不再过多介绍,重要的事情说三遍

! JDK选择17

! JDK选择17

! JDK选择17

本人spring-boot 版本使用的是3.1.5

1.2引入依赖

spring-boot-starter-web

spring-boot-starter-test

spring相关的基础依赖不在过多赘述

核心依赖包有以下两个:

<langchain4j.version>1.0.0-beta2</langchain4j.version>
<!--阿里云百炼平台qwen-boot-starter-->  
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId><version>${langchain4j.version}</version></dependency>
<!--langchain4j基础依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version></dependency>

同时引入依赖版本自动化管理

 <dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!--        自动化langchain4j-community依赖版本管理--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain4j.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

1.3引入配置

配置文件 application.properties

server.port=8888
# langchain4j-qwen
langchain4j.community.dashscope.chat-model.api-key=sk-XXXXX
langchain4j.community.dashscope.chat-model.model-name=qwen-max#sse qianwen
langchain4j.community.dashscope.streaming-chat-model.api-key=sk-XXXX
## qwq-32b  deepseek 都不支持function call(deepseek-v3)
langchain4j.community.dashscope.streaming-chat-model.model-name= qwen-max

1.4编写controller

1.4.1 最简单的例子

编写controller 进行简单的对话测试,那么先来个最简单的叭,代码示例

@RestController
@RequestMapping("/ai/simple")
public class LangChain4jController {@Resourceprivate QwenChatModel qwenChatModel;@GetMapping("/doAsk")public String langChain4j(@RequestParam(defaultValue = "你是谁?") String question) {return qwenChatModel.chat(question);}
}

可以看到能够请求到qwen,得到返回内容

请添加图片描述

1.4.2 自定义注解实现预设角色

基础大模型是没有目的性的, 你聊什么给什么,但是如果我们开发的是一个星座智能助手, 我需要他以一个星座专家的角色跟我对话, 那么就需要进行一些预设角色的实现与设定。

各位小伙伴但凡是接触各大智能体API调用的时候,一般是使用promt进行与各大只能体平台进行交互,那么在交互过程中一般会通过promt指定模型角色,然后在调用过程中通过传入到SystemMessage中,搭配用户的问题传入userMessage中实现与模型有效的沟通。

langchain4j其实是有类似于SystemMessage的注解,标注模型是什么角色,通过**@SystemMessage**注解进行使用。但是此处的话,我们进行自定义注解的方式进行自定义这一行为

首先自定义一个注解SystemPrompt

/*** @version 1.0* @Author jerryLau* @Date 2025/3/24 8:45* @注释 自定义注解 用于指定系统提示词*/@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemPrompt {String systemPrompt() default "你是一个星座专家";}

定义aiService接口,定义执行方式

/*** @version 1.0* @Author jerryLau* @Date 2025/3/24 8:41* @注释 aiService 接口*/public interface AiService {/**** 提问 ai 方法 同步方法* @param question* @return* @throws NoSuchMethodException*/@SystemPrompt()ChatResponse doAsk(String question) throws NoSuchMethodException;
}

创建impl实现具体的调用方式

/*** @version 1.0* @Author jerryLau* @Date 2025/3/24 10:46* @注释*/
@Service
public class AiServiceImpl implements AiService {@Resourceprivate QwenChatModel qwenChatModel;/**** 提问 ai 方法 同步阻塞方法* @param question 问题* @return* @throws NoSuchMethodException*/@Overridepublic ChatResponse doAsk(String question) throws NoSuchMethodException {//反射获取注释中的内容Method doAsk = AiService.class.getMethod("doAsk", String.class);SystemPrompt annotation = doAsk.getAnnotation(SystemPrompt.class);if (annotation != null) {String s = annotation.systemPrompt();List<ChatMessage> messages = new ArrayList<>();//传入SystemMessage和userMessagemessages.add(SystemMessage.from(s));messages.add(UserMessage.from(question));ChatResponse chat = qwenChatModel.chat(messages);//返回回答结果return chat;} else {throw new NoSuchMethodException("no annotation");}}
}

这一套下来,可以在controller中添加请求方法

 @GetMapping("/doAsk2")public String langChain4j2(@RequestParam(defaultValue = "你是谁?") String question) throws NoSuchMethodException {System.out.println("进入doAsk2方法");ChatResponse aiMessageResponse = aiService.doAsk(question);System.out.println(aiMessageResponse);return aiMessageResponse.aiMessage().text();}

当我们再次请求“你是谁”的时候,会发现智能体的回答不同于1.3.1中简单的回答,已经发生了一些变化。

请添加图片描述

1.4.3 非阻塞式调用

好多小伙伴可能不大了解什么是阻塞什么是非阻塞,那么我们先观察一下下面的两个gif图对比一下

请添加图片描述

请添加图片描述

可以比较直观的看到第一个gif图在请求的时候,响应过程是 -> -> -> boom 一下子蹦出整个响应内容,而第二个过程感觉像是这样的 ->A…->B… ->C…

哈哈哈 上面是比较通俗的感觉,那么什么是阻塞调用什么是非阻塞调用呢?

其实在各个智能体API调用时,一般都会分为阻塞和非阻塞,小伙伴们应该都了解,在智能体调用时,有个叫做token的东西,简单理解就类似于字符这样,通常一句话可能被分开成为好几个部分,比如“你好,我是千问,我是你的ai智慧助手”,会在智能体回复你之前被划分为【你好;我;是;千问;我是;你的;ai ; 智慧助手;】每次智能体响应会相应其中的一个词语。阻塞式响应的话,智能体回答我们的问题时,一般是收集所有的片段,等待片段收集完成后,归接在一起返回相应给我们。而非阻塞式响应的话,就是实时的返回智能体的响应,不做最后的等待归集。因此就会出现上面两个gif中示例中感官上的区别。

那么应该怎实现sse即流式实时响应呢?

首先要实现sse对于spring-boot项目而言,当然是先引入依赖了,先引入web flux的依赖,支持流式响应:

 <!--        springboot flux--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

在之前aiService中添加流式请求的方法

    /**** 提问 ai 方法 sse方法* @param question* @return* @throws NoSuchMethodException*/Flux<String> doAskSSE(String question) ;

impl实现具体的流式响应的方法

 /**** 提问 ai 方法 sse方法* @param question* @return* @throws NoSuchMethodException*/@Overridepublic Flux<String> doAskSSE(String question)  {Flux<String> flux = Flux.create(emitter -> {qwenStreamingChatModel.chat(question, new StreamingChatResponseHandler() {@Overridepublic void onPartialResponse(String partialResponse) {emitter.next(partialResponse);}@Overridepublic void onCompleteResponse(ChatResponse completeResponse) {emitter.complete();}@Overridepublic void onError(Throwable error) {emitter.error(error);}});});return flux;}

至此大功告成,运行后在浏览器调用,效果应该和上述第二个gif效果一致。

2.记忆对话

简单的springboot集成、阻塞式调用及流式调用已经大功告成。不知道小伙伴们有没有发现下面这个问题:

问题分析:

以上述阻塞案例为例子,我将代码搬过来,加了调用次数,换了接口路径为:/ai/memoryChart/doAsk

我现在有以下调用流程:

1-调用接口告诉qwen 我是jerry

2-调用qwen 问我是谁?

第一次调用:

请添加图片描述

第二次调用:

请添加图片描述

很显然,qwen不知道我的名字,但是我明明在第一次对话中告诉过他了呀;可见对于qwen来说,他暂时是没有记忆的,我们要进行一点改造使得他知道我是谁。

那么改造的方式呢有两种,第一种是将第一次的上下文再通过一定的方式再喂给它,具体的形式如下

2.1手动获取上下文并传递给模型

我们创建一个新的方法,书写如下代码,通过写死用户消息及系统打印的方式,打印模型给到我们的返回值,将第一次的返回消息及问题一起放入第二次的请求参数中,手动喂给qwen上下文:

 @GetMapping("/doAskWithMemory1")public String langChain4j_memory() {UserMessage userMessage = new UserMessage("halo 我是 jerry");ChatResponse chat1Resp = qwenChatModel.chat(userMessage);AiMessage aiMessage = chat1Resp.aiMessage();System.out.println("第一次调用返回:" + aiMessage.text());System.out.println("--------------------");String question2 = "我是谁?";ChatResponse chat2Resp = qwenChatModel.chat(userMessage, aiMessage, UserMessage.from(question2));System.out.println("第二次调用返回:" + chat2Resp.aiMessage().text());return "langChain4j_memory方法结束";}

我们看看执行结果:

由于我们直接进行系统打印,请求返回将不在重要,直接看控制台out输出即可

请添加图片描述

此时发现,通过手动给qwen模型喂上下文的方式,使得他拥有了一些记忆,也知道了我是谁。但是大家不妨想一想,如果有一些复杂的逻辑结构,难道要将每一次的问题结果做收集,再喂给大模型嘛?这样未免太麻烦了叭。

所以这个方式,可以但是不推荐。⚠️

2.2Chat 记忆缓存组件✅

记忆缓存是聊天系统中的一个重要组件,用于存储和管理对话的上下文信息。它的主要作用是让AI助手能够”记住”之前的对话内容,从而提供连贯和个性化的回复。

ChatMemory是一个用于管理聊天上下文的组件,它可以解决以下问题:

  • 防止上下文超出大模型的token限制
  • 隔离不同用户的上下文信息
  • 简化ChatMessage的管理

其核心可以理解为以下几点:

  • 容器管理: 管理ChatMessage的生命周期
  • 淘汰机制: 防止存储过多消息
  • 持久化: 避免聊天上下文丢失

摘自[Chat 记忆缓存 - 零基础入门Java AI](https://javaai.pig4cloud.com/docs/07Chat 记忆)

对于ChatMemory而言,Langchain4j提供了两种实现方式,每一种呢都有自己的特点与应用方式

1.MessageWindowChatMemory

开箱即用,简单实现

MessageWindowChatMemory是一个基于消息数量的简单实现。维护一个固定大小的消息窗口,保存最近的N条交互信息,确保系统不会处理过长的上下文,同时保留关键的历史信息。如果窗口大小是5,那么保存最近的5条消息,新的消息进来后,最旧的那条会被丢掉。这样做的好处是防止内存或处理负担过重,尤其是在长时间对话中,避免累积太多数据

特点:

  • 易于理解和实现
  • 基于消息数量进行管理
  • 适用于不需要精确token控制的场景

那么对于性能优化的小伙伴来说,可能得注意以下几点

实践:

  • 评估窗口大小:通过A/B测试选择最佳N值,例如监控任务完成率与响应时间。
  • 异常处理:窗口大小为0时禁用历史记录,或抛出配置错误。
  • 性能监控:在高并发场景下,监测内存使用与消息处理延迟,优化数据结构(如使用循环缓冲区)
2.TokenWindowChatMemory

TokenWindowChatMemory以token数量为限制的窗口。也就是说,它不会限制消息的数量,而是限制总token数,动态维护上下文窗口,确保总Token数不超过预设阈值(如LLM模型的最大输入限制),这样能更精确地控制内存和模型输入的长度。

特点:

  1. Token容量限制
    • 设定总Token上限(如4096 Token),新消息加入时,若总Token数超过阈值,按时间顺序淘汰最早的消息,直至满足限制。
    • 相比固定消息数量的窗口,更精准适配模型输入长度限制(如GPT-4的上下文窗口)。
  2. 动态调整
    • 消息可能因Token长度差异被部分保留(如截断长消息)或完全移除,需权衡完整性与容量。

实践:

  • 大语言模型(LLM)集成:适配模型的固定Token输入限制(如GPT-3的2048 Token)。
  • 变长消息处理:消息长度差异大时(如用户发送长文本或短指令),避免固定条数窗口导致的Token不可控。
  • 成本敏感场景:按Token计费的API调用(如OpenAI),精确控制输入长度以降低成本。

2.3记忆实现-共享记忆

那么在此我们简单的实现一下基于MessageWindowChatMemory的记忆存储,让我们的大模型拥有记忆。

那么官方提供了如下的集成代码,首先呢声明一个配置类

/*** @version 1.0* @Author jerryLau* @Date 2025/3/26 13:33* @注释 ai配置类*/
@Configuration
public class AIConf {}

然后再配置类中声明一个ChatAssistant接口

/**** 聊天助手接口*/public interface ChatAssistant {/**** 普通聊天 非隔离上下文* @param question* @return*/String chat(String question);/**** 流式输出 非隔离上下文* @param question* @return*/TokenStream streamChat(String question);}

然后通过@Bean注解,将这个ChatAssitant当成一个SpringBean

    /**** 注入聊天服务  非隔离上下文* @param chatLanguageModel* @param streamingChatLanguageModel* @return*/@Beanpublic ChatAssistant chatAssistant(ChatLanguageModel chatLanguageModel,StreamingChatLanguageModel streamingChatLanguageModel,ToolService toolService) {//使用简单的ChatMemory  - MessageWindowChatMemory来保存聊天记录MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder().maxMessages(10).build(); //最多保存10条记录ChatAssistant build = AiServices.builder(ChatAssistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemory(messageWindowChatMemory).build();return build;}

那具体的实现逻辑解释如下:

1-当Spring Boot应用启动时,会加载配置类AIConf,并尝试创建Bean。被注释的@Bean方法会接收ChatLanguageModel和StreamingChatLanguageModel作为参数,这两个是与AI模型交互的组件,像前面用到的qwenChatModel,qwenStreamingChatModel分别实现了这两个接口。

2-创建了一个MessageWindowChatMemory实例,设置最大保存10条消息

3-通过LangChain4j 的 AiServices 类负责为接口创建动态代理。核心逻辑在 build(),这里使用 JDK 动态代理 (Proxy.newProxyInstance),所有接口方法的调用会被路由到自定义的 InvocationHandler,当Controller中调用 chatAssistant.chat(question) 时,动态代理会触发 InvocationHandler.invoke(),其中会判断当前传入的是什么模型以及是否存在记忆存储,在底层拼装记忆,并存储新的记忆后,调用传入模型的chat方法进行请求得到结果。

那么我们就可以定义controller进行使用定义的ChatAssistant

 @Resourceprivate AIConf.ChatAssistant chatAssistant;/**** 记忆调用 2* 使用langchain4j 自带的记忆化工具进行上下文记忆* @param* @return*/@GetMapping("/doAskWithMemory2")public String langChain4j_memory2(@RequestParam(defaultValue = "halo 我是jerry") String question) {String chat = chatAssistant.chat(question);return chat;}

请添加图片描述

请添加图片描述

可以看到,qwen现在知道我是谁了

但是伴随着上述方式的实现,又出现了一个问题,小伙伴们可以再想想看,我上面两次都是通过idea插件apifox实现的接口调用,qwen知道我是谁了,那么如果我用浏览器调用这个接口,模拟一个新用户进行使用,发送请求询问“我是谁”,有会出现什么样的结果呢

在这里插入图片描述

通过实践我们发现,即使是一个新用户,我询问qwen我是谁,qwen仍然知道我是之前的jerry,这显然是不大合理的。那么对于上述的记忆的代码实现而言,虽然实现了记忆存储但是不同的用户之间没有隔离,怎么实现不同用户之间的记忆隔离呢,继续往下看~

2.4记忆实现-非共享记忆

在进行记忆存储时,会有一个类似于数据库主键的id,叫做memoryId,在进行记忆实现时,对于不同德用户而言,带一个唯一的id,在记忆存储时,根据这个id作为唯一标识进行存储,这样一来是不是实现了不同用户的记忆分离。那我们来实现一下

chatAssistant 添加一个新的方法,参数包括一个memoryId 和 一个用户消息

 /**** 普通聊天 隔离上下文 通过id 进行上下文隔离 达到不通用户聊天互不影响的效果* @param id 上下文id* @param question 用户问题* @return*/String chat(@MemoryId int id, @UserMessage String question);

注入的bean需要做id的配置

 /**** 注入聊天服务  隔离上下文* @param chatLanguageModel* @param streamingChatLanguageModel* @return*/@Beanpublic ChatAssistant chatAssistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel) {//使用简单的ChatMemory  - MessageWindowChatMemory来保存聊天记录
//        MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build()ChatAssistant build = AiServices.builder(ChatAssistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build()).build();return build;}

通过

 .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build())

实现自定义memoryId作为用户记忆消息id,实现用户消息记忆的分离存储,接下来编写controller层代码进行实现调用

 /**** 记忆调用 2* 使用langchain4j 自带的记忆化工具进行上下文记忆* @param* @return*/@GetMapping("/doAskWithMemory2")public String langChain4j_memory2(@RequestParam(defaultValue = "halo 我是jerry") String question, @RequestParam long id) {String chat = chatAssistant.chat(id, question);return chat;}

那我们模拟1号玩家,先告诉模型我叫jerry,再接着问我是谁

在这里插入图片描述

再模拟2号玩家,接着问我是谁,发现模型不知道我是谁,在像模型介绍我是谁后,模型知道了我是谁

在这里插入图片描述
在这里插入图片描述

至此呢,我们实现了用户的隔离,即不同的用户可以单独的运用模型进行相关操作。

2.5记忆持久化存储

那么对于上面的记忆相关的内容,是存在内存变量中的,程序在启停时,内存变量中的数据也会被刷掉。于是我们想到的能不能将这些记忆话的内容进行持久化的存储,比如说存储到量数据库。

而此处我们选择将数据存储在一个mapDB中,要实现自定义的记忆持久化,首先要实现langchain4j提供的记忆持久化的接口ChatMemoryStore

//ChatMemoryStore源码,提供了获取,更新,删除等基本操作
import dev.langchain4j.data.message.ChatMessage;
import java.util.List;public interface ChatMemoryStore {List<ChatMessage> getMessages(Object var1);void updateMessages(Object var1, List<ChatMessage> var2);void deleteMessages(Object var1);
}

我们自定义一个PersistentChatMemoryStore 实现ChatMemoryStore接口

/**** @version 1.0* @Author jerryLau* @Date 2025/3/26 13:33* @注释 持久化内存*/
public class PersistentChatMemoryStore implements ChatMemoryStore {// 创建数据库chat-memory.dbDB db = DBMaker.fileDB("langchain4j-springBoot-demo/src/main/java/com/jerry/langchain4jspringbootdemo/store/db/chat-memory.db").transactionEnable().make();//创建集合 messages,数据key为int,value为 string类型private final Map<Integer, String> map = db.hashMap("messages", INTEGER, STRING).createOrOpen();/**** 从map 中获取记忆信息* key 是 memoryId*/@Overridepublic List<ChatMessage> getMessages(Object memoryId) {String json = map.get((int) memoryId);return messagesFromJson(json);}/**** 更新map 中记忆信息* key 是 memoryId*/@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {String json = messagesToJson(messages);map.put((int) memoryId, json);db.commit();}/**** 从map 中删除记忆信息* key 是 memoryId*/@Overridepublic void deleteMessages(Object memoryId) {map.remove((int) memoryId);db.commit();}

那么在aiConf中将设置好的PersistentChatMemoryStore注解进入

/**** 注入聊天服务  隔离上下文 - 持久化聊天上下文记录-mapdb* @param chatLanguageModel* @param streamingChatLanguageModel* @return*/@Beanpublic ChatAssistant chatAssistantWithStore(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel) {//使用mapdb 来保存聊天记录PersistentChatMemoryStore persistentChatMemoryStore = new PersistentChatMemoryStore();ChatMemoryProvider chatMemoryProvider = (memoryId) -> {return MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).chatMemoryStore(persistentChatMemoryStore).build();};ChatAssistant build = AiServices.builder(ChatAssistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemoryProvider(chatMemoryProvider).build();return build;}

controller中的请求方法还是与langChain4j_memory2一致,在发送请求后,看到在相应的目录路径下生成了 一个名为chat-memory.db的数据库文件

在这里插入图片描述

但是有个问题哈 这个数据库文件 应该怎么打开,本人试着用dateDrip打开但是没打开,有没有大佬指导一下⚠️⚠️⚠️

最后实在是没办法了,我直接写了个main,将数据读了出来,代码供各位参考:

/*** @version 1.0* @Author jerryLau* @Date 2025/3/26 17:12* @注释 读取mapdb文件*/public class MapDBViewer {public static void main(String[] args) {// 打开数据库文件(只读模式,避免意外修改)DB db = DBMaker.fileDB("langchain4j-springBoot-demo/src/main/java/com/jerry/langchain4jspringbootdemo/store/db/chat-memory.db").readOnly().make();// 获取存储的Map(假设数据存储在名为"myMap"的HTreeMap中)HTreeMap<Integer, String> map = db.hashMap("messages").keySerializer(org.mapdb.Serializer.INTEGER).valueSerializer(org.mapdb.Serializer.STRING).createOrOpen();// 遍历并打印所有键值对map.forEach((key, value) -> System.out.println("Key: " + key.toString() + ", Value: " + value));// 关闭数据库db.close();}
}

读取到的内容如下:

Key: 11112, Value: [{“contents”:[{“text”:“为我讲个笑话吧”,“type”:“TEXT”}],“type”:“USER”},{“text”:“当然可以,接下来是一个轻松的笑话:\n\n为什么电脑经常生病?\n\n因为它的窗户(Windows)总是开着!”,“type”:“AI”}]

3.Function-call(Tools)

LangChain4j 的 Tools 机制 是一种让开发者将自定义 Java 方法动态暴露给大语言模型(LLM)的框架,允许 LLM 调用外部工具(如计算、API 接口、数据库查询等),突破纯文本生成的限制。

例如,我们知道大型语言模型本身并不擅长数学。如果您的用例偶尔涉及数学计算,您可能希望为 LLM 提供一个“数学工具”。通过在向 LLM 发出的请求中声明一个或多个工具,它就可以在认为合适时调用其中一个。给定一个数学问题以及一组数学工具,LLM 可能会决定为了正确回答该问题,它应该首先调用所提供的数学工具之一。

3.1简单算数

例如我们提供两个数学方法:

@Service
public class ToolService {@Tool("Sums 2 given numbers")double sum(double a, double b) {return a + b;}@Tool("Returns a square root of a given number")double squareRoot(double x) {return Math.sqrt(x);}
}

修改aiConf,为注入的bean 添加tools,

 @Beanpublic ChatAssistant chatAssistant(ChatLanguageModel chatLanguageModel,StreamingChatLanguageModel streamingChatLanguageModel,ToolService toolService) {//使用简单的ChatMemory  - MessageWindowChatMemory来保存聊天记录MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder().maxMessages(10).build(); //最多保存10条记录ChatAssistant build = AiServices.builder(ChatAssistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemory(messageWindowChatMemory).tools(toolService) //添加tools.build();return build;}

继续使用langChain4j_memory2方法调用,会发现LLM通过toolsService的sum方法返回3.2,再结合LLM进行结果输出,存在控制台输出以及结果输出

在这里插入图片描述

正如你 所见,当大型语言模型能够使用工具时,它可以在适当的时候决定调用其中一个工具。

3.2更加复杂的案例

@Tools非常强大的功能。在上个简单的示例中,我们为语言模型提供了基本的数学工具,但想象一下,如果我们给它提供例如 sendEmail(发送电子邮件)这样的工具,并且给出这样的查询:“我的朋友想了解人工智能领域的近期新闻。将简短摘要发送至 friend@email.com”,那么它就可以使用 googleSearch 工具查找近期新闻,然后进行总结,并通过 sendEmail 工具将摘要发送出去。

我们简单的实现这个例子,优先创建一个tool

 @Tool("Send the short recent news in any field to the user.")public String generateAI(@P("mailAddr") String mailAddress, @P("filed") String filed) throws MessagingException {System.out.println("输入:" + mailAddress);System.out.println("输入:" + filed);UserMessage userMessage = new UserMessage("generate the short recent news in " + filed + " field");ChatResponse chat1Resp = qwenChatModel.chat(userMessage);String text = chat1Resp.aiMessage().text();boolean b = mailService.sendEmail("XXXX@qq.com", "QQ邮箱的授权码", mailAddress, "Short recent news in " + filed + " filed", text);
//mailAddressif (b) {System.out.println("邮件发送成功!");}return "邮件发送成功!";}

具体实现发送邮件的方法mailService.sendEmail

/*** @version 1.0* @Author jerryLau* @Date 2025/3/28 11:18* @注释 邮件服务*/
@Service
public class MailService {public boolean sendEmail(String addr, String password, // 此处 password 实际是授权码String toAddress, String subject, String message)throws MessagingException {Properties props = new Properties();props.put("mail.smtp.host", "smtp.qq.com");props.put("mail.smtp.port", "465");props.put("mail.smtp.auth", "true");props.put("mail.smtp.ssl.enable", "true"); // 开启ssl 验证props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");props.put("mail.smtp.socketFactory.port", "465");
//        props.put("mail.debug", "true"); // 启用调试日志Authenticator authenticator = new Authenticator() {protected PasswordAuthentication getPasswordAuthentication() {return new PasswordAuthentication(addr, password); // 密码参数传入授权码}};Session session = Session.getInstance(props, authenticator);try {Message mimeMessage = new MimeMessage(session);mimeMessage.setFrom(new InternetAddress(addr));mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(toAddress));mimeMessage.setSubject(subject);mimeMessage.setText(message);Transport.send(mimeMessage);System.out.println("邮件发送成功!");return true;} catch (MessagingException e) {throw new RuntimeException(e);}}}

在这里插入图片描述

查看邮件发现,邮件也正常收到了

在这里插入图片描述

4.预设角色SystemMessage

在1.4.2中其实已经通过自定义注解的方式实现了系统角色预定。其实Langchain4j也提供了自己的注解配置实现系统预定角色–@SystemMessage

在这里插入图片描述


那么至此呢我们已经学习完了Spring-boot集成Langchain4j以及阿里云百炼模型。感兴趣的同学可以自己体会应用啦

码字不易,各位观众老爷如果喜欢,希望一键三连哈~~~


参考文档:

Introduction | LangChain4j官方文档

Langchain4j中文版本文档


个人代码demo

后面有时间了,我再整理放一下~~~


文章转载自:

http://ggxCrIfV.wjtwn.cn
http://0uqeUhg5.wjtwn.cn
http://1lUcT3Rs.wjtwn.cn
http://Uk16xCg8.wjtwn.cn
http://sstz4ru6.wjtwn.cn
http://wKpsyboz.wjtwn.cn
http://u2YqwtPm.wjtwn.cn
http://VlinzUsQ.wjtwn.cn
http://EVxPA0pC.wjtwn.cn
http://g2GOXQea.wjtwn.cn
http://x6vBKkje.wjtwn.cn
http://bnCWL2Jx.wjtwn.cn
http://pVGCDb65.wjtwn.cn
http://0XaWoBW5.wjtwn.cn
http://cdHGsPLA.wjtwn.cn
http://ZvB0Xrko.wjtwn.cn
http://VLYGk2ml.wjtwn.cn
http://uAXtpatv.wjtwn.cn
http://qcOX3Iih.wjtwn.cn
http://xwUQwq64.wjtwn.cn
http://pH0fi9jm.wjtwn.cn
http://v5tLwdv9.wjtwn.cn
http://ylS4LLny.wjtwn.cn
http://5XVzUaSM.wjtwn.cn
http://EfaJcxok.wjtwn.cn
http://IPVhF6uX.wjtwn.cn
http://Bx1lQBNH.wjtwn.cn
http://8KGy7J5H.wjtwn.cn
http://KDC28RiH.wjtwn.cn
http://kBWCS661.wjtwn.cn
http://www.dtcms.com/wzjs/688961.html

相关文章:

  • 哪一款软件可以自己做网站Sensei wordpress插件
  • 肇庆网站建设制作公司php网站开发系统
  • 高端品牌网站建设兴田德润怎么联系品牌建设 宣传
  • 团购网站的交易流程网站logo是什么意思
  • 常州seo网站推广白银网站建设白银
  • 生物科技 网站模板推盟
  • 精品网站建设教程青岛谁做网站多少钱
  • 石家庄最新状况锦绣大地seo
  • 2万元建设网站贵吗网站制作厦门
  • 模板手机网站建设价格明细表网业进不去什么原因
  • 辽源网站优化示范高校建设网站
  • 做网站较好的框架静态网站源码
  • 怎么查看网站收录大丰网站建设哪家好
  • iis7.5 配置网站中国十大网站建设
  • 岳池做网站电话wordpress xml 导入失败
  • 龙岩网站开发较好的公司网站摸板
  • 高端网站开发哪家强青岛北京网站建设公司
  • 怎么做网站的301网站建设任务
  • 知名网站制作网站建设中++模板
  • 利用虚拟主机建设网站的实验报告郑州网站维护推广
  • 企业形象成品网站注册公司什么网站
  • 怎样创建网站挣钱全屏网站代码
  • 成都网站建设推荐到访率公司徐州网站制作案例
  • 建网站的程序免费电子商务怎样建立网站的
  • 做好网站如何发布网站建设公司的优势
  • 建设网站是要先建站在备案么中药网站模板
  • 温州专业微网站制作公司做网站推广对电脑有什么要求
  • 申请一个免费的网站空间网站推广的岗位要求
  • 网站建设用模板好吗中小企业有哪些公司名单
  • 徐水区住房和城乡建设局网站51源码