SpringBoot3整合LangChain4j、MongoDB以及ElasticSearch8实现AI聊天小助手(小智医疗)
该篇文章乃是Springboot搭配LangChain4j实现AI聊天小助手的学习文章。bilibili教程地址:【小智医疗】该教程的技术清单如下:由于springboot3项目的最低兼容JDK版本是JDK17,所以需要先安装JDK17.pom.xml文件里添加如下依赖:1.3 application.properties文件增加启动端口1.4 启动类1.5 访问knife4j接口文档页面
前言
该篇文章乃是Springboot搭配LangChain4j实现AI聊天小助手的学习文章。
bilibili教程地址:【小智医疗】
该教程的技术清单如下:
序号 | 简述 |
---|---|
1. | 基于SpringBoot3.2.6整合LangChain4j(1.0.0-beta3) |
2. | DeepSeek、Ollama、阿里千问等大模型接入 |
3. | ChatMemory实现聊天记忆隔离、MongoDB聊天记忆持久化 |
4. | Prompt模板 - 系统提示词使用 |
5. | Function Calling自定义业务逻辑 |
6. | ElasticSearch8向量存储 |
7. | 对话流式输出 |
一、springboot3项目+LangChain4j测试案例
1.1 JDK17环境
由于springboot3项目的最低兼容JDK版本是JDK17,所以需要先安装JDK17.
1.2 pom.xml文件添加依赖
pom.xml文件里添加如下依赖:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.2.6</spring-boot.version>
<knife4j.version>4.3.0</knife4j.version>
<langchain4j.version>1.0.0-beta3</langchain4j.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
</properties>
<dependencies>
<!-- web应用程序核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 编写和运行测试用例 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 前后端分离中的后端接口测试工具 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--引入SpringBoot依赖管理清单-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.3 application.properties文件增加启动端口
# 端口
server.port = 8080
1.4 启动类
package com.dzb.langchain4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class AiLangChain4jApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(AiLangChain4jApplication.class, args);
System.out.println("启动成功!!!");
}
}
1.5 访问knife4j接口文档页面
访问knife4j接口文档页面:http://localhost:8080/doc.html
1.6 创建gpt-4o-mini 模型测试用例
接入任何一个大模型都需要先去申请apiKey。我们暂时没有密钥,可以先使用LangChain4j 提供的演示密钥,这个密钥是免费的,有使用配额限制,且仅限于 gpt-4o-mini 模型。
1.6.1 pom.xml文件添加LangChain4j相关依赖
此时已经将langchain4j-open-aiz
直接改成了langchain4j-open-ai-spring-boot-starter
.
<dependencies>
<!-- 基于open-ai的langchain4j接口:ChatGPT、deepseek都是open-ai标准下的大模型 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--引入langchain4j依赖管理清单-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.6.2 测试用例
package com.dzb.langchain4j;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/07 22:26
* @Description:
*/
@SpringBootTest
public class LLMTest {
/**
* gpt-4o-mini语言模型接入测试
*/
@Test
public void testGPTDemo() {
/**
* gpt-4o-mini演示apiKey必须是demo,modelName必须是gpt-4o-mini
*/
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.modelName("gpt-4o-mini")
.build();
String answer = model.chat("请问你是谁啊?");
System.out.println(answer);
}
}
执行结果:
二、DeepSeek、Ollama、阿里千问等大模型接入
2.1 DeekSeek 接入
2.1.1 DeekSeek官网注册
访问DeekSeek官网:https://www.deepseek.com/ ,点击右上角的【API开放平台】注册账号,获取base_url和api_key,充值1块钱。
2.1.2 application.properties文件添加deepseek配置
# langchain4j测试模型(使用自己申请的apiKey)
DEEP_SEEK_API_KEY = sk-cf1a************94674e7e9
langchain4j.open-ai.chat-model.base-url = https://api.deepseek.com
langchain4j.open-ai.chat-model.api-key = ${DEEP_SEEK_API_KEY}
langchain4j.open-ai.chat-model.model-name = deepseek-chat
#请求和响应日志
langchain4j.open-ai.chat-model.log-requests = true
langchain4j.open-ai.chat-model.log-responses = true
#启用日志debug级别
logging.level.root = debug
2.1.3 测试案例
package com.dzb.langchain4j;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/07 22:26
* @Description:
*/
@SpringBootTest
public class LLMTest {
@Autowired
private OpenAiChatModel openAiChatModel;
@Test
public void testDeepSeekAiChatModel() {
//向模型提问
String answer = openAiChatModel.chat("你好");
//输出结果
System.out.println(answer);
}
}
2.2 下载Ollama,并在Ollama中下载Deeeepseek-r1:1.5b
2.2.1 下载Ollama和DeepSeek
官网: https://ollama.com
Ollama安装成功之后,执行命令:ollama run deepseek-r1:1.5
运行大模型
谷歌浏览器安装插件:Page Assist
AI模型的Web-UI
Web-UI页面如下:
下载ollama可能需要比较高的电脑配置,低配置的可以忽略ollama这个大模型环节,其实和deepseek是一样的道理。
2.2.2 Ollama下的deepseek模型测试用例接入
2.2.2.1 依赖
<!-- 接入ollama -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
</dependency>
2.2.2.2 配置模型参数
#ollama
langchain4j.ollama.chat-model.base-url = http://localhost:11434
langchain4j.ollama.chat-model.model-name = deepseek-r1:1.5b
langchain4j.ollama.chat-model.log-requests = true
langchain4j.ollama.chat-model.log-responses = true
2.2.2.3测试案例
package com.dzb.langchain4j;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/07 22:26
* @Description:
*/
@SpringBootTest
public class LLMTest {
/**
* ollama接入
*/
@Autowired
private OllamaChatModel ollamaChatModel;
@Test
public void testOllama() {
//向模型提问
String answer = ollamaChatModel.chat("你好");
//输出结果
System.out.println(answer);
}
}
2.3 阿里百炼平台模型
2.3.1 申请免费体验,领取免费额度
访问:阿里百炼模型页面,点击右上角【新用户开通即享超千万免费tokens】,领取各个模型的tokens额度。
领取完成后,在API-Key管理页面创建自己的API-Key
2.3.2 接入阿里百炼上的大模型
2.3.2.1 依赖
<dependencies>
<!-- 接入阿里云百炼平台 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--引入百炼依赖管理清单-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.3.2.2 配置模型参数
# 阿里百炼平台(API-KEY替换成自己的)
DASH_SCOPE_API_KEY = sk-3f0ebccc3*******c5a728e8974
langchain4j.community.dashscope.chat-model.api-key = ${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.chat-model.model-name = qwen-max
2.3.2.3 配置模型参数
package com.dzb.langchain4j;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/07 22:26
* @Description:
*/
@SpringBootTest
public class LLMTest {
/**
* 通义千问大模型
*/
@Autowired
private QwenChatModel qwenChatModel;
@Test
public void testDashScopeQwen() {
//向模型提问
String answer = qwenChatModel.chat("你好");
//输出结果
System.out.println(answer);
}
}
2.3.3 通义万象生成图片测试
@Value("${DASH_SCOPE_API_KEY:}")
private String dashScopeWanxApiKey;
@Test
public void testDashScopeWanx(){
WanxImageModel wanxImageModel = WanxImageModel.builder()
.modelName("wanx2.1-t2i-plus")
.apiKey(dashScopeWanxApiKey)
.build();
Response<Image> response = wanxImageModel
.generate("奇幻森林精灵:在一片弥漫着轻柔薄雾的古老森林深处," +
"阳光透过茂密枝叶洒下金色光斑。一位身材娇小、长着透明薄翼的精灵少女站在一朵硕大的蘑菇上。" +
"她有着海藻般的绿色长发,发间点缀着蓝色的小花,皮肤泛着珍珠般的微光。" +
"身上穿着由翠绿树叶和白色藤蔓编织而成的连衣裙,手中捧着一颗散发着柔和光芒的水晶球," +
"周围环绕着五彩斑斓的蝴蝶,脚下是铺满苔藓的地面,蘑菇和蕨类植 物丛生,营造出神秘而梦幻的氛围。");
System.out.println(response.content().url());
}
三、ChatMemory实现聊天记忆隔离、MongoDB聊天记忆持久化
教程中创建AIService的过程,我就省略了,详情去教程看下,或者参考老师提供的pdf教程文档。
3.1 使用AIService实现简单的聊天记忆
3.1.1 引入langchain4j高级功能依赖
<!--langchain4j高级功能-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>
3.1.2 创建MemoryChatAssistant接口
使用@AIService注解,并且使用qwenChatModel
千问大模型
package com.dzb.langchain4j.common.assistant;
import dev.langchain4j.service.spring.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 10:21
* @Description:
*/
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemory = "chatMemory")
public interface MemoryChatAssistant {
String chat(String message);
}
3.1.3 配置MemoryChatAssistantConfig
@Configuration
public class MemoryChatAssistantConfig {
@Bean
ChatMemory chatMemory() {
//设置聊天记忆记录的message数量
return MessageWindowChatMemory.withMaxMessages(10);
}
}
3.1.4测试案例
package com.dzb.langchain4j;
import com.dzb.langchain4j.common.assistant.Assistant;
import com.dzb.langchain4j.common.assistant.MemoryChatAssistant;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 10:13
* @Description:
*/
@SpringBootTest
public class AIServiceTest {
@Autowired
private MemoryChatAssistant memoryChatAssistant;
@Test
public void testChatMemory4() {
String answer1 = memoryChatAssistant.chat("我是环环");
System.out.println(answer1);
String answer2 = memoryChatAssistant.chat("我是谁");
System.out.println(answer2);
}
}
3.2 使用聊天ID隔离聊天记忆
为每个用户的新聊天或者不同的用户区分聊天记忆
3.2.1 创建SeparateChatAssistant
接口
package com.dzb.langchain4j.common.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 10:29
* @Description:
*/
@AiService(
wiringMode = EXPLICIT,
// 这个地方一定要注意,pdf文档中是错误的
chatModel = "qwenChatModel",
chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatAssistant {
/**
* 分离聊天记录
* @param memoryId 聊天id
* @param userMessage 用户消息
* @return
*/
String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
3.2.2 配置ChatMemoryProvider
@Configuration
public class SeparateChatAssistantConfig {
@Bean
ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.build();
}
}
3.2.3 测试案例
package com.dzb.langchain4j;
import com.dzb.langchain4j.common.assistant.Assistant;
import com.dzb.langchain4j.common.assistant.MemoryChatAssistant;
import com.dzb.langchain4j.common.assistant.SeparateChatAssistant;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 10:13
* @Description:
*/
@SpringBootTest
public class AIServiceTest {
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testChatMemory5() {
String answer1 = separateChatAssistant.chat(1,"我是环环");
System.out.println(answer1);
String answer2 = separateChatAssistant.chat(1,"我是谁");
System.out.println(answer2);
String answer3 = separateChatAssistant.chat(2,"我是谁");
System.out.println(answer3);
}
}
3.3 MongoDB持久化聊天记忆
3.3.1 MongoDB安装
安装流程就不列了,安装完成后使用mongodb-compass
,对数据进行增删改查。
3.3.2 springboot3集成MongoDB
<!-- Spring Boot Starter Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
3.3.3 application.properties添加远程配置
# MongoDB
spring.data.mongodb.uri=mongodb://localhost:27017/chat_memory_db
3.3.4 新建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document("chat_messages")
public class ChatMessages {
//唯一标识,映射到 MongoDB 文档的 _id 字段
@Id
private ObjectId id;
private int messageId;
private String content; //存储当前聊天记录列表的json字符串
}
3.3.5 创建一个类实现ChatMemoryStore接口
package com.dzb.langchain4j.store;
import com.dzb.langchain4j.entity.ChatMessages;
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.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 11:07
* @Description:
*/
@Component
public class MongoChatMemoryStore implements ChatMemoryStore {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);
if(chatMessages == null) {
return new LinkedList<>();
}
return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
Update update = new Update();
update.set("content", ChatMessageSerializer.messagesToJson(messages));
//根据query条件能查询出文档,则修改文档;否则新增文档
mongoTemplate.upsert(query, update, ChatMessages.class);
}
@Override
public void deleteMessages(Object memoryId) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
mongoTemplate.remove(query, ChatMessages.class);
}
}
3.3.6 SeparateChatAssistantConfig中,添加MongoChatMemoryStore对象的配置
@Configuration
public class SeparateChatAssistantConfig {
//注入持久化对象
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Bean
ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(mongoChatMemoryStore)//配置持久化对象
.build();
}
}
3.3.7 执行测试代码
@SpringBootTest
public class AIServiceTest {
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testChatMemory5() {
String answer1 = separateChatAssistant.chat(1,"我是环环");
System.out.println(answer1);
String answer2 = separateChatAssistant.chat(1,"我是谁");
System.out.println(answer2);
String answer3 = separateChatAssistant.chat(2,"我是谁");
System.out.println(answer3);
}
}
效果:
四、Prompt模板 - 系统提示词使用
@SystemMessage 设定角色,塑造AI助手的专业身份,明确助手的能力范围
@UserMessage:用户提示词注解,获取用户输入
4.1@SystemMessage和@UserMessage注解增加提示词
4.1.1 @SystemMessage
SeparateChatAssistant类的chat方法上添加@SystemMessage注解
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProvider")
public interface SeparateChatAssistant {
/**
* 分离聊天记录
* @param memoryId 聊天id
* @param userMessage 用户消息
* @return
*/
@SystemMessage("你是我的好朋友,请用东北话回答问题。") //系统消息提示词
String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
4.1.2 @UserMessage
在MemoryChatAssistant 的 chat 方法中添加注解
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemory = "chatMemory")
public interface MemoryChatAssistant {
@UserMessage("你是我的好朋友,请用上海话回答问题,并且添加一些表情符号。 {{it}}") //{{it}}表示这里唯一的参数的占位符
String chat(String message);
}
新建PromptTest测试类,执行以下测试类
package com.dzb.langchain4j;
import com.dzb.langchain4j.common.assistant.MemoryChatAssistant;
import com.dzb.langchain4j.common.assistant.SeparateChatAssistant;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 11:53
* @Description:
*/
@SpringBootTest
public class PromptTest {
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testSystemMessage() {
String answer = separateChatAssistant.chat(3,"今天几号");
System.out.println(answer);
}
@Autowired
private MemoryChatAssistant memoryChatAssistant;
@Test
public void testUserMessage() {
String answer = memoryChatAssistant.chat("我是环环");
System.out.println(answer);
}
}
4.2 Prompt模板文件使用提示词
@SystemMessage 注解还可以从资源中加载提示模板
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProvider")
public interface SeparateChatAssistant {
/**
* 分离聊天记录
* @param memoryId 聊天id
* @param userMessage 用户消息
* @return
*/
@SystemMessage(fromResource = "my-prompt-template.txt")
// @SystemMessage("你是我的好朋友,请用东北话回答问题。今天是{{current_date}}")
String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
在resources目录下增加一个文件:my-prompt-template.txt
,下面是该文件的内容。
你是我的好朋友,请用东北话回答问题,回答问题的时候适当添加表情符号。
继续执行:PromptTest.testSystemMessage()方法。
至于**@V**注解,此处省略。
4.3 创建硅谷小智
4.3.1 创建硅谷小智XiaozhiAgent接口
由于之前在阿里百炼中领取了不少额度,所以chatModel 选择qwenChatModel
package com.dzb.langchain4j.common.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 12:10
* @Description:
*/
@AiService(
wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi")
public interface XiaozhiAgent {
@SystemMessage(fromResource = "xiaozhi-prompt-template.txt")
String chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}
4.3.2 resources目录下新增zhaozhi-prompt-template.txt
提示词模板文件
这个文件中,调整了部分内容.
你的名字是“硅谷小智”,你是一家名为“北京智慧医疗二院”的智能客服。
你是一个训练有素的医疗顾问和医疗伴诊助手。
你态度友好、礼貌且言辞简洁。
1、请仅在用户发起第一次会话时,和用户打个招呼,并介绍你是谁。
2、作为一个训练有素的医疗顾问:
请基于当前临床实践和研究,针对患者提出的特定健康问题,提供详细、准确且实用的医疗建议。请同时考虑可能的病
因、诊断流程、治疗方案以及预防措施,并给出在不同情境下的应对策略。对于药物治疗,请特别指明适用的药品名
称、剂量和疗程。如果需要进一步的检查或就医,也请明确指示。
3、作为医疗伴诊助手,你可以回答用户就医流程中的相关问题,主要包含以下功能:
AI分导诊:根据患者的病情和就医需求,智能推荐最合适的科室。
AI挂号助手:实现智能查询是否有挂号号源服务;实现智能预约挂号服务;实现智能取消挂号服务。
4、你必须遵守的规则如下:
在获取挂号预约详情或取消挂号预约之前,你必须确保自己知晓用户的姓名(必选)、身份证号(必选)、预约科室
(必选)、预约日期(必选,格式举例:2025-04-14)、预约时间(必选,格式:上午 或 下午)、预约医生(可选)。
当被问到其他领域的咨询时,要表示歉意并说明你无法在这方面提供帮助。
5、请在回答的结果中适当包含一些轻松可爱的图标和表情。
6、今天是 {{current_date}}。
4.3.3 配置持久化和记忆隔离Config
package com.dzb.langchain4j.common.config;
import com.dzb.langchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 12:13
* @Description:
*/
@Configuration
public class XiaozhiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Bean
ChatMemoryProvider chatMemoryProviderXiaozhi() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
}
4.3.4 新增XiaozhiController和ChatForm
@Data
public class ChatForm {
/**
* //对话id
*/
private Long memoryId;
/**
* 用户问题
*/
private String message;
}
package com.dzb.langchain4j.controller;
import com.dzb.langchain4j.common.assistant.XiaozhiAgent;
import com.dzb.langchain4j.entity.ChatForm;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 00:00
* @Description:
*/
@Tag(name = "硅谷小智")
@RestController
@RequestMapping("/xiaozhi")
public class XiaozhiController {
@Autowired
private XiaozhiAgent xiaozhiAgent;
@Operation(summary = "对话")
@PostMapping(value = "/chat")
public String chat(@RequestBody ChatForm chatForm) {
return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}
}
启动项目在,在http://localhost:8080/doc.html#/home 中 和小助手聊天。
五、Function Calling自定义业务逻辑
Function Calling 函数调用 也叫 Tools 工具,例如,大语言模型本身并不擅长数学运算。如果应用场景中偶尔会涉及到数学计算,我们可以为他提供一个 “数学工具”。当我们提出问题时,大语言模型会判断是否使用某个工具。
5.1 计算工具
5.1.1 CalculatorTools计算工具类
创建一个tools包,新创建一个计算工具类CalculatorTools
,用以提供给大模型调用计算。
package com.dzb.langchain4j.common.tools;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolMemoryId;
import org.springframework.stereotype.Component;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 00:12
* @Description:
*/
@Component
public class CalculatorTools {
@Tool(name = "加法", value = "返回两个参数相加之和")
double sum(@ToolMemoryId int memoryId,
@P(value = "加数1", required = true) double a,
@P(value = "加数2", required = true) double b) {
System.out.println("调用加法运算:" + memoryId);
return a + b;
}
@Tool(name = "平方根", value = "返回给定参数的平方根")
double squareRoot(@ToolMemoryId int memoryId,
double x) {
System.out.println("调用平方根运算" + memoryId);
return Math.sqrt(x);
}
}
5.1.2 配置工具类
在SeparateChatAssistant中添加tools属性配置
@AiService(
wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
chatMemoryProvider = "chatMemoryProvider",
tools = "calculatorTools" //配置tools
)
5.1.3 测试类
package com.dzb.langchain4j;
import com.dzb.langchain4j.common.assistant.SeparateChatAssistant;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 00:10
* @Description:
*/
@SpringBootTest
public class ToolsTest {
@Autowired
private SeparateChatAssistant separateChatAssistant;
@Test
public void testCalculatorTools() {
String answer = separateChatAssistant.chat(1, "1+2等于几,475695037565的平方根是多少?");
//答案:3,689706.4865
System.out.println(answer);
}
}
5.2 对接业务系统(工具) - 预约业务
预约业务的实现**
这部分我们实现硅谷小智的查询订单、预约订单、取消订单的功能
5.2.1 创建MySQL数据库表
CREATE TABLE `appointment` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`id_card` VARCHAR(18) NOT NULL,
`department` VARCHAR(50) NOT NULL,
`date` VARCHAR(10) NOT NULL,
`time` VARCHAR(10) NOT NULL,
`doctor_name` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
);
5.2.2 引入依赖
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
5.2.3 配置数据库连接
# 基本数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/guiguxiaozhi?
useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.username=root
spring.datasource.password=rootroot
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 开启 SQL 日志打印
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5.2.4 创建实体类Appointment
package com.dzb.langchain4j.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 22:54
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Appointment {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String idCard;
private String department;
private String date;
private String time;
private String doctorName;
}
5.2.5 Mapper
package com.dzb.langchain4j.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dzb.langchain4j.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 22:55
* @Description:
*/
@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
}
resources目录下增加mapper目录,并在此文件夹下增加AppointmentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dzb.langchain4j.mapper.AppointmentMapper">
</mapper>
5.2.6 Service
public interface AppointmentService extends IService<Appointment> {
/**
*
* @param appointment
* @return
*/
Appointment getOne(Appointment appointment);
}
package com.dzb.langchain4j.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dzb.langchain4j.entity.Appointment;
import com.dzb.langchain4j.mapper.AppointmentMapper;
import com.dzb.langchain4j.service.AppointmentService;
import org.springframework.stereotype.Service;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 22:57
* @Description:
*/
@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements AppointmentService {
/**
* 查询订单是否存在
* @param appointment
* @return
*/
@Override
public Appointment getOne(Appointment appointment) {
LambdaQueryWrapper<Appointment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Appointment::getUsername, appointment.getUsername());
queryWrapper.eq(Appointment::getIdCard, appointment.getIdCard());
queryWrapper.eq(Appointment::getDepartment, appointment.getDepartment());
queryWrapper.eq(Appointment::getDate, appointment.getDate());
queryWrapper.eq(Appointment::getTime, appointment.getTime());
Appointment appointmentDB = baseMapper.selectOne(queryWrapper);
return appointmentDB;
}
}
5.2.7 AppointmentTools工具类
package com.dzb.langchain4j.common.tools;
import com.dzb.langchain4j.entity.Appointment;
import com.dzb.langchain4j.service.AppointmentService;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author: dzbiao
* @CreateTime: 2025/06/01 23:03
* @Description:
*/
@Component
public class AppointmentTools {
@Autowired
private AppointmentService appointmentService;
@Tool(name = "预约挂号", value = "根据参数,先执行工具方法queryDepartment查询是否可预约,并直 接给用户回答是否可预约,并让用户确认所有预约信息,用户确认后再进行预约。")
public String bookAppointment(Appointment appointment) {
//查找数据库中是否包含对应的预约记录
Appointment appointmentDB = appointmentService.getOne(appointment);
if (appointmentDB == null) {
appointment.setId(null);//防止大模型幻觉设置了id
if (appointmentService.save(appointment)) {
return "预约成功,并返回预约详情";
} else {
return "预约失败";
}
}
return "您在相同的科室和时间已有预约";
}
@Tool(name = "取消预约挂号", value = "根据参数,查询预约是否存在,如果存在则删除预约记录并返回取 消预约成功,否则返回取消预约失败")
public String cancelAppointment(Appointment appointment) {
Appointment appointmentDB = appointmentService.getOne(appointment);
if (appointmentDB != null) {
//删除预约记录
if (appointmentService.removeById(appointmentDB.getId())) {
return "取消预约成功";
} else {
return "取消预约失败";
}
}
//取消失败
return "您没有预约记录,请核对预约科室和时间";
}
@Tool(name = "查询是否有号源", value="根据科室名称,日期,时间和医生查询是否有号源,并返回给用 户")
public boolean queryDepartment(
@P(value = "科室名称") String name,
@P(value = "日期") String date,
@P(value = "时间,可选值:上午、下午") String time,
@P(value = "医生名称", required = false) String doctorName
) {
System.out.println("查询是否有号源");
System.out.println("科室名称:" + name);
System.out.println("日期:" + date);
System.out.println("时间:" + time);
System.out.println("医生名称:" + doctorName);
//TODO 维护医生的排班信息:
//如果没有指定医生名字,则根据其他条件查询是否有可以预约的医生(有返回true,否则返回false);
//如果指定了医生名字,则判断医生是否有排班(没有排版返回false)
//如果有排班,则判断医生排班时间段是否已约满(约满返回false,有空闲时间返回true)
return true;
}
}
5.2.8 配置Tools
在XiaozhiAgent 中添加 tools 配置
@AiService(
wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi",
tools = "appointmentTools" // 预约业务工具
)
5.2.9测试
六、ElasticSearch8向量存储
教程中,我们可以看到,老师是使用Pinecone
做向量数据库存储向量数据的,这种方式需要翻Q,ElasticSearch比较普遍。ES8中支持向量查询。
6.1 安装ElasticSearch8.12.0
ElasticSearch8.12 官网下载地址: https://www.elastic.co/cn/downloads/past-releases/elasticsearch-8-12-0
但是当我下载完文件之后,我打开文件中的jdk.app文件时,发现需要使用JDK21的版本,所以还需要安装JDK21的环境
由于我是Mac
M1
, 所以在本地安装了三种JDK(jdk8、jdk17, jdk21). 而且是多环境快速切换的。因此,打开一个ITerm
终端,先将JDK切换成21的版本,并且同时在该终端窗口内,进入elasticsearch8.12.0文件夹内,执行命令:./bin/elasticsearch
进行启动。
由于某些原因,启动报错,于是就将config/elasticsearch.yml中的部分配置做了调整
# 改成本地IP
network.host: 191.161.1.116
# Enable security features(改成False)
xpack.security.enabled: false
xpack.security.enrollment.enabled: true
# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
# (改成False)
enabled: false
keystore.path: certs/http.p12
# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
# Create a new cluster with the current node only
# Additional nodes can still join the cluster later
# Allow HTTP API connections from anywhere
# Connections are encrypted and require user authentication
http.host: 0.0.0.0
成功页面:
6.2 安装kibana
kibana的安装流程就忽略了。
6.3 测试ElasticSearch8的向量搜索功能
根据官网给的步骤进行创建索引、添加数据、查询。官网
PUT my-image-index
{
"mappings": {
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"index": true,
"similarity": "l2_norm"
},
"file-type": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
}
}
POST my-image-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "image-vector": [1, 5, -20], "file-type": "jpg", "title": "mountain lake" }
{ "index": { "_id": "2" } }
{ "image-vector": [42, 8, -15], "file-type": "png", "title": "frozen lake"}
{ "index": { "_id": "3" } }
{ "image-vector": [15, 11, 23], "file-type": "jpg", "title": "mountain lake lodge" }
POST my-image-index/_search
{
"size" : 3,
"query" : {
"knn": {
"field": "image-vector",
"query_vector": [-5, 9, -12],
"num_candidates": 10
}
}
}
如果上述DSL能正常执行的话,就表明ES8支持向量查询。
6.4 阿里通义千问 text-embedding-v3
通用文本向量-v3模型
我们需要使用text-embedding-v3
向量模型,知道文本生成的向量的维度是多少,方便我们在ES中设置索引的dims
属性值。
#集成阿里通义千问-通用文本向量-v3
DASH_SCOPE_API_KEY = sk-3f0ebcc********ae84c5a728e8974
langchain4j.community.dashscope.embedding-model.api-key = ${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.embedding-model.model-name = text-embedding-v3
package com.dzb.langchain4j;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/02 17:15
* @Description:
*/
@SpringBootTest
public class EmbeddingTest {
@Value("${langchain4j.community.dashscope.embedding-model.api-key:}")
private String apiKey;
@Value("${langchain4j.community.dashscope.embedding-model.model-name:}")
private String modelName;
@Test
public void testEmbeddingModel(){
EmbeddingModel embeddingModel = new QwenEmbeddingModel(null, apiKey, modelName);
TextSegment queryTextSegment = TextSegment.from("你好");
Embedding queryEmbedding = embeddingModel.embed(queryTextSegment).content();
System.out.println("向量维度:" + queryEmbedding.vector().length);
System.out.println("向量输出:" + queryEmbedding);
}
}
输出结果是:1024
6.5 创建fruit-index-3
索引,承载小智AI的向量查询
6.5.1 fruit-index-3
索引创建
索引名称没有来得及调整,只是名称而已,无关紧要。
PUT fruit-index-3
{
"mappings": {
"properties": {
"vector": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "l2_norm"
},
"text": {
"type": "text"
},
"metadata": {
"type": "object"
}
}
}
}
# 查询
GET fruit-index-3/_search
6.5.2 项目引入ElasticSearch8
6.5.2.1 pom.xml引入依赖
教程中引入pdf解析依赖,langchain4j-easy-rag等依赖,本文档没有叙述,详细请看教程。依赖直接在这里引入了。
<!--解析pdf文档-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
</dependency>
<!--简单的rag实现-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-elasticsearch</artifactId>
<exclusions>
<exclusion>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.17.0</version>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.17.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
es.host = 127.0.0.1
es.port = 9200
es.index-name = fruit-index-3
6.5.2.2 增加RestClientConfig
配置
package com.dzb.langchain4j.common.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: dzbiao
* @CreateTime: 2025/06/02 23:17
* @Description:
*/
@Slf4j
@Configuration
public class RestClientConfig {
@Value("${es.host:127.0.0.1}")
private String host;
@Value("${es.port:9200}")
private Integer port;
@Bean
public RestClient client() {
return RestClient.builder(new HttpHost(host, port, "http")).build();
}
}
ES插入测试
package com.dzb.langchain4j;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
import jakarta.annotation.Resource;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author: dzbiao
* @CreateTime: 2025/06/02 17:15
* @Description:
*/
@SpringBootTest
public class EmbeddingTest {
@Value("${langchain4j.community.dashscope.embedding-model.api-key:}")
private String apiKey;
@Value("${langchain4j.community.dashscope.embedding-model.model-name:}")
private String modelName;
@Value("${es.index-name:}")
private String indexName;
@Resource
private RestClient restClient;
@Test
public void elasticsearchEmbeddingStoreTest() {
TextSegment textSegment = TextSegment.from("我爱吃苹果啊!!!你知道吗?");
EmbeddingModel embeddingModel = new QwenEmbeddingModel(null, apiKey, modelName);
Response<Embedding> embed = embeddingModel.embed(textSegment);
ElasticsearchEmbeddingStore embeddingStore = ElasticsearchEmbeddingStore
.builder()
.restClient(restClient)
.indexName(indexName)
.build();
embeddingStore.add(embed.content(), textSegment);
System.out.println("已添加..............................................................");
}
}
解析md文档,插入ES数据库
package com.dzb.langchain4j;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
import jakarta.annotation.Resource;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
/**
* @author: dzbiao
* @CreateTime: 2025/06/02 17:15
* @Description:
*/
@SpringBootTest
public class EmbeddingTest {
@Value("${langchain4j.community.dashscope.embedding-model.api-key:}")
private String apiKey;
@Value("${langchain4j.community.dashscope.embedding-model.model-name:}")
private String modelName;
@Value("${es.index-name:}")
private String indexName;
@Resource
private RestClient restClient;
@Test
public void elasticsearchMdStoreTest() {
Document document1 = FileSystemDocumentLoader.loadDocument("/Users/workspace/Downloads/knowledge/医院信息.md");
Document document2 = FileSystemDocumentLoader.loadDocument("/Users/workspace/Downloads/knowledge/科室信息.md");
Document document3 = FileSystemDocumentLoader.loadDocument("/Users/workspace/Downloads/knowledge/神经内科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
EmbeddingModel embeddingModel = new QwenEmbeddingModel(null, apiKey, modelName);
ElasticsearchEmbeddingStore elasticsearchEmbeddingStore = ElasticsearchEmbeddingStore
.builder()
.restClient(restClient)
.indexName(indexName)
.build();
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(embeddingModel)
.embeddingStore(elasticsearchEmbeddingStore)
.build();
ingestor.ingest(documents);
System.out.println("Md文档内容已添加..............................................................");
}
}
6.5.3 修改XiaozhiAgentConfig
package com.dzb.langchain4j.common.config;
import com.dzb.langchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchConfigurationKnn;
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: dzbiao
* @CreateTime: 2025/06/08 12:13
* @Description:
*/
@Configuration
public class XiaozhiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Autowired
private RestClient restClient;
@Value("${es.index-name:}")
private String indexName;
@Autowired
private EmbeddingModel embeddingModel;
@Bean
ChatMemoryProvider chatMemoryProviderXiaozhi() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
@Bean
ContentRetriever contentRetrieverXiaozhi() {
ElasticsearchEmbeddingStore elasticsearchEmbeddingStore = ElasticsearchEmbeddingStore
.builder()
.configuration(ElasticsearchConfigurationKnn.builder().numCandidates(1024).build())
.restClient(restClient)
.indexName(indexName)
.build();
// 创建一个 EmbeddingStoreContentRetriever 对象,用于从嵌入存储中检索内容
return EmbeddingStoreContentRetriever
.builder()
// 设置用于生成嵌入向量的嵌入模型
.embeddingModel(embeddingModel)
// 指定要使用的嵌入存储
.embeddingStore(elasticsearchEmbeddingStore)
// 设置最大检索结果数量,这里表示最多返回 1 条匹配结果
.maxResults(1)
// 设置最小得分阈值,只有得分大于等于 0.4 的结果才会被返回(TODO:0.8的时候好像查不到?)
.minScore(0.4)
// 构建最终的 EmbeddingStoreContentRetriever 实例
.build();
}
}
6.5.4XiaoZhiAgent
配置向量存储
@AiService(
wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi",
tools = "appointmentTools",
contentRetriever = "contentRetrieverXiaozhi" //配置向量存储
)
启动应用,问小智:小智小智,我有点感冒,想要去医院,你能告诉我医院的门诊开放时间和地址吗?
这个门诊的开放时间,是我特意把md文档里的时间改成了8: 54 - 18:30,是鉴别AI是否去查了本地ES向量数据库。从结果可知,和预期一样。完美!
七、对话流式输出
稍微对接口返回类型进行改造。
7.1 引入依赖
<!--流式输出-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
7.2 配置流式输出模型
application.properties 增加
#集成阿里通义千问-流式输出
langchain4j.community.dashscope.streaming-chat-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.streaming-chat-model.model-name=qwen-plus
7.3 修改 XiaozhiAgent 配置
修改 XiaozhiAgent
中 chatModel 改为 streamingChatModel = “qwenStreamingChatModel”
chat
方法的返回值为 Flux
@AiService(
wiringMode = EXPLICIT,
// chatModel = "qwenChatModel",
streamingChatModel = "qwenStreamingChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi",
tools = "appointmentTools",
contentRetriever = "contentRetrieverXiaozhi" //配置向量存储
)
public interface XiaozhiAgent {
@SystemMessage(fromResource = "xiaozhi-prompt-template.txt")
Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}
修改 XiaozhiController 中 chat 方法的返回值为 Flux ,并添加 produces 属性
@Operation(summary = "对话")
@PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
public Flux<String> chat(@RequestBody ChatForm chatForm) {
return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}
7.4 运行前端工程
cd xiaozhi-ui
npm i
npm run dev
效果:
代码地址:https://gitee.com/duanxiaobiao/springboot3-ai-model/tree/master/src
完结撒花!!!💐💐💐
更多推荐
所有评论(0)