LangChain4j
shiLangChain4j 是一个开源的 Java 库,旨在简化将大型语言模型(LLM)集成到 Java 应用中的工作。
创建项目及相关配置
创建好springboot项目后,导入
<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>
其中,knife4j是一款无需前端就可以调用后端接口测试的工具,调试好端口号之后,在页面输入localhost8080/doc.html即可进入knife4j调式页面
导入langchain4j依赖
<!-- 基于open-ai的langchain4j接口:ChatGPT、deepseek都是open-ai标准下的大模型 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</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>
接入测试的LLM
https://docs.langchain4j.dev/get-started官方文档
在接入任何大模型之前,都要获得对应LLM的api,langchain4j规定了引入大模型的规范,并且提供可测试的LLM。
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.modelName("gpt-4o-mini")
.build();
SpringBoot整合
若想更好的使用langchain4j,需要将依赖换成langchain4j-open-ai-spring-boot-starter,并且在properties里配置参数,下面是官方文档提供的5个参数,前3个是必须要填的,后面两个是打印请求和相应日志
langchain4j.open-ai.chat-model.base-url=http://langchain4j.dev/demo/openai/v1
langchain4j.open-ai.chat-model.api-key=demo
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
#请求和响应日志
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
#启用日志debug级别
logging.level.root=debug
整合完成后,就可以直接通过@autowired依赖注入的方式调用语言大模型,并不需要再去自己配置一边,可以观察前后差异
/**
* gpt-4o-mini语言模型接入测试
*/
@Test
public void testGPTDemo() {
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);
}
/**
* 整合SpringBoot
*/
@Autowired
private OpenAiChatModel openAiChatModel;
@Test
public void testSpringBoot() {
//向模型提问
String answer = openAiChatModel.chat("你好");
//输出结果
System.out.println(answer);
}
接入Deepseek
Comparison Table of all supported Language Models | LangChain4j我们可以看到官方文档指出哪些大模型的接入,其中deepseek和gpt都是遵循open-ai的规范。
从deepseek官网申请到API之后会得到一个key,如果直接在properties文件中设置,会有泄露的风险,所以在环境变量中加入自己的DEEP_SEEK_API_KEY这个变量,在引用就能防止泄露
langchain4j.open-ai.chat-model.base-url=https://api.deepseek.com/v1
langchain4j.open-ai.chat-model.api-key=${DEEP_SEEK_API_KEY}
langchain4j.open-ai.chat-model.model-name=deepseek-chat
由于OpenAiChatModel继承了chatlanguageModel,所以我们可以将实例更换为chatlanguageModel,因为chatlanguageModel可以接入更多的LLM,而OpenAiChatModel仅支持deepseek和chatgpt
![]()
输出
Ollama本地部署
- 数据隐私与安全:对于金融、医疗、法律等涉及大量敏感数据的行业,数据安全至关重要。
- 离线可用性:在网络不稳定或无法联网的环境中,本地部署的 Ollama 模型仍可正常运行。
- 降低成本:云服务通常按使用量收费,长期使用下来费用较高。而 Ollama 本地部署,只需一次性投 入硬件成本,对于需要频繁使用大语言模型且对成本敏感的用户或企业来说,能有效节约成本。
- 部署流程简单:只需通过简单的命令 “ollama run < 模型名>”,就可以自动下载并运行所需的模型。
- 灵活扩展与定制:可对模型微调,以适配垂直领域需求。
在CMD种输入ollama,证明安装成功

ollama run deepseek-r1:1.5b
之后我们也可以用上述命令行,在cmd中进行交互 
这样,Ollama的本地部署就完成了
整合Ollama
<!-- 接入ollama -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
</dependency>
langchain4j.ollama.chat-model.base-url=http://localhost:11434 langchain4j.ollama.chat-model.model-name=llama3.1 langchain4j.ollama.chat-model.temperature=0.8 langchain4j.ollama.chat-model.timeout=PT60Sbaseurl:ollama的本地端口
model-name:要从Ollama 服务器使用的模型的名称。
temperature:控制生成的响应的随机性。较高的值(例如 1.0)会产生更多样化的输出,而较低的值(例如 0.2)会产生更具确定性的响应。
timeout:允许 API 调用完成的最长时间。
这时,我们因为部署了两个LLM,这时候就会出现报错,及chatlanguage接口不知道应该注入哪一个llm,所以就需要屏蔽其中一个llm或者实现两个llm各自的接口
阿里百炼
接入阿里百炼通义千问qwen-max模型
<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>#阿里百炼平台 langchain4j.community.dashscope.chat-model.api-key=${DASH_SCOPE_API_KEY} langchain4j.community.dashscope.chat-model.model-name=qwen-max
人工智能服务 AIService
AIService使用面向接口和动态代理的方式完成程序的编写,更灵活的实现高级功能。
- 为大语言模型格式化输入内容
- 解析大语言模型的输出结果
- 聊天记忆 Chat memory
- 工具 Tools
- 检索增强生成 RAG
创建AIservice,测试交互
<!--langchain4j高级功能--> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> </dependencypublic interface Assistant { String chat(String userMessage); } @SpringBootTest public class AIServiceTest { @Autowired private QwenChatModel qwenChatModel; @Test public void testChat() { //创建AIService Assistant assistant = AiServices.create(Assistant.class, qwenChatModel); //调用service的接口 String answer = assistant.chat("Hello"); System.out.println(answer); } }@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatmodel") public interface Assistant { String chat(String userMessage); } @Autowired private Assistant assistant; @Test public void testAi1(){ String answer = assistant.chat("你是谁?"); System.out.println(answer); }更推荐下面这种方式,采取@Aiservice的方式,这样在用例中就可以直接注意注解标记过的方法
AiServices 会 组装 Assistant 接口以及其他组件 ,并使用反射机制创建一个实现 Assistant 接口的 代理对象 。这个代理对象会处理输入和输出的所有转换工作。在这个例子中, chat 方法的输入是一个字符串,但是大模型需要一个 UserMessage 对象。所以,代理对象将这个字符串转换为 UserMessage ,并调用聊天语言模型。 chat 方法的输出类型也是字符串,但是大模型返回的是 AiMessage 对象,代理对象会将其转换为字符串。简单理解就是:代理对象的作用是输入转换和输出转换
聊天记忆
若只是单纯的调用Api进行交互,LLM是没有上下文记忆的

我们可以采用以下方式实现上下文记忆
首先,会将用户发送的信息封装在usermessage1中,然后发送给AI,再从ChatResponse中提取aimessage对象并输出,然后再第二次封装是,要将上文中的usermessage1、aimessage以及userMessage2 一并封装发送才可以
@Autowired
private QwenChatModel qwenChatModel;
@Test
public void testChatMemory2() {
//第一轮对话
UserMessage userMessage1 = UserMessage.userMessage("我是环环");
ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);
AiMessage aiMessage1 = chatResponse1.aiMessage();
//输出大语言模型的回复
System.out.println(aiMessage1.text());
//第二轮对话
UserMessage userMessage2 = UserMessage.userMessage("你知道我是谁吗");
ChatResponse chatResponse2 = qwenChatModel.chat(Arrays.asList(userMessage1,
aiMessage1, userMessage2));
AiMessage aiMessage2 = chatResponse2.aiMessage();
//输出大语言模型的回复
System.out.println(aiMessage2.text());
}

但上述实现方式复杂,每一次都要将上次发送和收到的消息进行封装在发送
使用AIService实现聊天记忆
@AiService(
wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
chatMemory = "chatMemory"
)
public interface MemoryChatAssistant {
String chat(String message);
}
@Configuration
public class MemoryChatAssistantConfig {
@Bean
ChatMemory chatMemory() {
//设置聊天记忆记录的message数量
return MessageWindowChatMemory.withMaxMessages(10);
}
}
上述创建了配置类,也就是将chatMemory封装成bean注入到Aiservice中,并且规定上下文的100各消息都会包含
@Autowired
private MemoryChatAssistant memoryChatAssistant;
@Test
public void testChatMemory4() {
String answer1 = memoryChatAssistant.chat("我是环环");
System.out.println(answer1);
String answer2 = memoryChatAssistant.chat("我是谁");
System.out.println(answer2);
}
隔离聊天记忆
确保不同用户使用的上下文记忆是不一样的,所以定义Assistant时,方法中要定义一个memoryId的参数,确定每个记忆上下文的Id
同时配置Config类,这样就创建了一个可以自动注意memoryId的chatmemory对象,并且这个对象通过chatmemoryPrrovider形式提供给前面的Assistant智能体的
@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProvider" ) public interface SeparateChatAssistant { /** * 分离聊天记录 * @param memoryId 聊天id * @param userMessage 用户消息 * @return */ String chat(@MemoryId int memoryId, @UserMessage String userMessage); } @Configuration public class SeparateChatAssistantConfig { @Bean ChatMemoryProvider chatMemoryProvider(){ return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(10) .build(); } }
测试:可以根据输出结果看到,两个上下文的记忆被分开了
@Autowired private SeparateChatAssistant separateChatAssistant; @Test public void test5(){ String a = separateChatAssistant.chat(1,"我是章三"); System.out.println(a); String b = separateChatAssistant.chat(1,"你知道我是谁么?"); System.out.println(b); String a1 = separateChatAssistant.chat(2,"我是王四"); System.out.println(a1); String b1 = separateChatAssistant.chat(2,"你知道我是谁么?"); System.out.println(b1); }
原理:再chatmemory底层有一个store字段,定义了两种存储方式,一种基于数组,一种基于哈希图,都是内存存储,优点是读取查找快,缺点是无法持久化存储,因为内存存储再每次重启后都会消失。
持久化聊天记忆 Persistence
MongoDB
基于MangoDB持久化聊天
首先,创建实体类ChatMessages,用于封装消息
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document("chat_messages")
public class ChatMessages {
//唯一标识,映射到 MongoDB 文档的 _id 字段
@Id
private ObjectId messageId;
//用于区分上下文ID
private String memoryId;
//private Long messageId;
private String content; //存储当前聊天记录列表的json字符串
}
然后实现创建持久化类MemoryChatMemoryStore,并实现ChatMemoryStore接口并重写其中的方法,实现ManggoDB的增删改查操作
@Component@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);
}
}
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);
}
}
最后,被隔离记忆的数据会持久化的存储在mangoDb中
提示词Prompt
系统提示词
需要用到@SystemMessage设定AI角色,塑造AI助手的专业身份,明确助手的能力范围。
比如我们为AI设置一个东北人的角色,可以看到AI带着东北口音的回答,并且我们可以在ManggoDB中看到消息属性,可以看到预设的提示词在最前面,消息类型是System。
!!若更换的提示词,即使将聊天记忆都存储在一个上下文中,AI也会失去记忆。
@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); }@SpringBootTest public class PromptTest { @Autowired private SeparateChatAssistant separateChatAssistant; @Test public void testSystemMessage() { String answer = separateChatAssistant.chat(3,"今天几号"); System.out.println(answer); } }
若提示词过多,我们可以设置专门的文件来保存提示词,并且设置身份是传入文件即可,当预设的身份涉及多个参数时,我们要采取 @V 来指定要传递参数的名称
你是我的好朋友,我是{{username}},我的年龄是{{age}},请用东北话回答问题,回答问题的时候适当添加表情 符号。 今天是 {{current_date}} @SystemMessage(fromResource = "my-prompt-template.txt")//系统消息提示词 String chat(@MemoryId int memoryId, @UserMessage String userMessage, @V("username") String username, @V("age") int age); @SpringBootTest public class PromptTest { @Autowired private SeparateChatAssistant separateChatAssistant; @Test public void testSystemMessage() { String answer = separateChatAssistant.chat(4,"我是谁,到今天我出生多久了?","zk",134); System.out.println(answer); } }![]()
用户提示词
@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemory = "chatMemory" ) public interface MemoryChatAssistant { @UserMessage("你是我的好朋友,请用上海话回答问题,并且添加一些表情符号。 {{it}}") //{{it}}表示这里唯一的参数的占位符 String chat(String message); } @Autowired private MemoryChatAssistant memoryChatAssistant; @Test public void testUserMessage() { String answer = memoryChatAssistant.chat("我是环环"); System.out.println(answer); String answer1 = memoryChatAssistant.chat("我18了"); System.out.println(answer1); String answer2 = memoryChatAssistant.chat("我是谁?"); System.out.println(answer2); }
可以看见上述用户消息中,每一个用户消息都包含用户提示词的内容,所以当用户提示词不长的时候,我们可以采取上述方式
如果要传递多个参数,要用@V注解进行传递
实战:小智AI
创建智能体
@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProviderXiaozhi" ) public interface XiaoZhiAgent { @SystemMessage(fromResource = "xiaozhi-prompt.txt") String chat(@MemoryId String memoryId, @UserMessage String userMessage); }提示词模板
你的名字是“硅谷小智”,你是一家名为“北京协和医院”的智能客服。 你是一个训练有素的医疗顾问和医疗伴诊助手。 你态度友好、礼貌且言辞简洁。 1、请仅在用户发起第一次会话时,和用户打个招呼,并介绍你是谁。 2、作为一个训练有素的医疗顾问: 尚硅⾕ 3、配置小智助手 配置持久化和记忆隔离 4、封装对话对象 请基于当前临床实践和研究,针对患者提出的特定健康问题,提供详细、准确且实用的医疗建议。请同时考虑可能的病 因、诊断流程、治疗方案以及预防措施,并给出在不同情境下的应对策略。对于药物治疗,请特别指明适用的药品名 称、剂量和疗程。如果需要进一步的检查或就医,也请明确指示。 3、作为医疗伴诊助手,你可以回答用户就医流程中的相关问题,主要包含以下功能: AI分导诊:根据患者的病情和就医需求,智能推荐最合适的科室。 AI挂号助手:实现智能查询是否有挂号号源服务;实现智能预约挂号服务;实现智能取消挂号服务。 4、你必须遵守的规则如下: 在获取挂号预约详情或取消挂号预约之前,你必须确保自己知晓用户的姓名(必选)、身份证号(必选)、预约科室 (必选)、预约日期(必选,格式举例:2025-04-14)、预约时间(必选,格式:上午 或 下午)、预约医生(可 选)。 当被问到其他领域的咨询时,要表示歉意并说明你无法在这方面提供帮助。 5、请在回答的结果中适当包含一些轻松可爱的图标和表情。 6、今天是 {{current_date}}。相关配置信息:用mongodb储存上下文信息,上下文记忆20次对话为界限等信息
@Configuration public class XiaoZhiAgentConfig { @Autowired //使用manggoDB作为存储聊天的位置,以及使用mango的增辖改查方式 private MongoChatMemoryStore mongoChatMemoryStore; @Bean ChatMemoryProvider chatMemoryProviderXiaozhi() { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .chatMemoryStore(mongoChatMemoryStore) .build(); } }封装对话对象
@Data public class ChatForm { private Long memoryId;//对话id private String message;//用户问题 }实现controller层
@Tag(name = "硅谷小智") @RestController @RequestMapping("/xiaozhi") public class XiaozhiController { @Autowired private XiaoZhiAgent xiaozhiAgent; @Operation(summary = "对话") @PostMapping("/chat") public String chat(@RequestBody ChatForm chatForm) { return xiaozhiAgent.chat(String.valueOf(chatForm.getMemoryId()), chatForm.getMessage()); } }我们就可以在http://localhost:8080/doc.html页面中测试
Tools工具类
创建工具类@Component public class CalculatorTools { @Tool double sum(double a, double b) { System.out.println("调用加法运算"); return a + b; } @Tool double squareRoot(double x) { System.out.println("调用平方根运算"); return Math.sqrt(x); } }配置工具类:在智能体中加入tools参数,让智能体知道可以调用相关的工具进行计算
@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProviderXiaozhi", tools = "calculatorTools" //配置tools )然后我们测试一下,可以看到AI回答是,会调用计算工具类的方法。
@SpringBootTest public class calculateTest { @Autowired private XiaoZhiAgent xiaoZhiAgent; @Test public void Tset(){ String answer = xiaoZhiAgent.chat(String.valueOf(1),"45555+12123是多少?23423856289658的平方根又是多少?"); System.out.println(answer); } }
整体的流程是:LLM根据用户的问题,判断需不需要调用工具,若需要调用,则返回需要调用的工具,由Langchain4j通过Tools工具调用相关的工具类,工具类将对应的计算结果返回给LLM,LLM通过结合两个内容来回答问题
例如,我们计算45555+12123+2342342+235235是多少?因为我们在定义sum方法的时候传递的时ab两个参数,所以LLM将4个数的相加拆分为三次sum方法的调用一个方法名的无法准确的描述该方法的功能
@Tool 注解有两个可选字段:name (工具名称) :工具的名称。如果未提供该字段,方法名会作为工具的名称。value (工具描述) :工具的描述信息。方法参数可以选择使用 @P 注解进行标注。@P 注解有两个字段:value :参数的描述信息,这是必填字段。required :表示该参数是否为必需项,默认值为 true ,此为可选字段。如果你的 AIService 方法中有一个参数使用 @MemoryId 注解,那么你也可以使用 @ToolMemoryId 注解 , @Tool 方法中的一个参数。提供给 AIService 方法的值将自动传递给 @Tool 方法。如果你有多个用户, 或每个用户有多个聊天记忆,并且希望在 @Tool 方法中对它们进行区分,那么这个功能会很有用。@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, @P(value = "被平方根数") double x) { System.out.println("调用平方根运算 " + memoryId); return Math.sqrt(x); } }
实现预约挂号功能
实现硅谷小智的查询订单、预约订单、取消订单的功能Mysql
CREATE DATABASE `guiguxiaozhi`; USE `guiguxiaozhi`; 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`) );<!-- 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>
entity类
@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;}Service和impl
@Service public interface AppointmentService extends IService<Appointment> { Appointment getOne(Appointment appointment); }@Service public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements AppointmentService { /** * 查询订单是否存在 * @param * @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; } }controller
@Tag(name = "硅谷小智") @RestController @RequestMapping("/xiaozhi") public class XiaozhiController { @Autowired private XiaoZhiAgent xiaozhiAgent; @Operation(summary = "对话") @PostMapping("/chat") public String chat(@RequestBody ChatForm chatForm) { return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage()); } }创建Tools工具类实现AI预约订单功能
@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; } }在智能体中更新tools,!!!
AppointmentTools作为Bean名称appointmentTools才可以正确注册。bean名称都是小写@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProviderXiaozhi", tools = "appointmentTools")
测试,我们在网页上预约挂号后,会在数据库中保存
RAG检索增强生成
- 使用 RAG
- 使用你的数据微调 LLM
- 结合 RAG 和微调
微调大模型
- 优点:一次会话只需一次模型调用,速度快,在特定任务上性能更高,准确性也更高。
- 缺点:知识更新不及时,模型训成本高、训练周期长。
- 应用场景:适合知识库稳定、对生成内容准确性和风格要求高的场景,如对上下文理解和语言生成 质量要求高的文学创作、专业文档生成等。
RAG:是一种结合信息检索与生成模型的技术,旨在通过外部知识库增强生成内容的准确性和专业性。
- 优点:数据存储在外部知识库,可以实时更新,不依赖对模型自身的训练,成本更低。
- 缺点:需要两次查询:先查询知识库,然后再查询大模型,性能不如微调大模型
- 应用场景:适用于知识库规模大且频繁更新的场景,如企业客服、实时新闻查询、法律和医疗领域 的最新知识问答等。

RAG常用方法
- 全文(关键词)搜索。 这种方法通过将问题和提示词中的关键词与知识库文档数据库进行匹配来搜索文档。根据这些关键词在每个文档中的出现频率和相关性对搜索结果进行排序。
- 向量搜索 ,也被称为 “语义搜索”。文本通过 嵌入模型 被转换为 数字向量 。然后,它根据查询向量与文档向量之间的余弦相似度或其他相似性 / 距离度量来查找和排序文档,从而捕捉更深层次的语义含义。
- 混合搜索。 结合多种搜索方法(例如,全文搜索 + 向量搜索)通常可以提高搜索的效果
RAG 过程分为 2 个不同的阶段:索引和检索。
索引
加载知识库文档 ==> 将文档中的文本分段 ==> 利用向量大模型将分段后的文本转换成向量 ==> 将向量存入向量数据库
将文本转化为向量

文档加载器(Document Loader):从多种来源和格式中加载原始文档数据。
解析器(Parser):将原始文档转换为结构化文本,提取关键元素。
分割器(Splitter):将长文本切分为适合模型处理的语义块。
常见文档分割器
首先文本转化为向量需要文档加载器 Document Loader对文档进行文本读取
下面代码是对一段测试的文本进行读取
@SpringBootTest public class RAGTest { @Test public void testReadDocument() { //使用FileSystemDocumentLoader读取指定目录下的知识库文档 //并使用默认的文档解析器TextDocumentParser对文档进行解析 Document document = FileSystemDocumentLoader.loadDocument("D:/BaiduNetdiskDownload/资料/knowledge/knowledge/测试.txt"); System.out.println(document.text()); } }读取结果
测试文档分割器,使用的时基于内存的向量存储,
/** * 加载文档并存入向量数据库 尚硅⾕ 6.3、测试文档分割 */ @Test public void testReadDocumentAndStore() { //使用FileSystemDocumentLoader读取指定目录下的知识库文档 //并使用默认的文档解析器对文档进行解析(TextDocumentParser) Document document = FileSystemDocumentLoader.loadDocument("D:/BaiduNetdiskDownload/资料/knowledge/knowledge/人工智能.md"); //为了简单起见,我们暂时使用基于内存的向量存储 InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>(); //ingest //1、分割文档:默认使用递归分割器,将文档分割为多个文本片段,每个片段包含不超过 300个token,并且有30个token的重叠部分保证连贯性 //DocumentByParagraphSplitter(DocumentByLineSplitter(DocumentBySentenceSplitter(DocumentByWordSplitter))) //2、文本向量化:使用一个LangChain4j内置的轻量化向量模型对每个文本片段进行向量化 //3、将原始文本和向量存储到向量数据库中(InMemoryEmbeddingStore) EmbeddingStoreIngestor.ingest(document, embeddingStore); //查看向量数据库内容 System.out.println(embeddingStore); }可以看到文档分割器按照默认的段落文档分割器将文档分成了17部分,每一块包含一部分文本
实现RAG
首先在xiaozhiconfig中创建ContentRetriever,用于对外部资源库进行读取解析分割以及向量存储
@Bean ContentRetriever contentRetrieverXiaozhi() { //使用FileSystemDocumentLoader读取指定目录下的知识库文档 //并使用默认的文档解析器对文档进行解析 Document document1 = FileSystemDocumentLoader.loadDocument("D:/BaiduNetdiskDownload/资料/knowledge/knowledge/医院信息.md"); Document document2 = FileSystemDocumentLoader.loadDocument("D:/BaiduNetdiskDownload/资料/knowledge/knowledge/科室信息.md"); Document document3 = FileSystemDocumentLoader.loadDocument("D:/BaiduNetdiskDownload/资料/knowledge/knowledge/神经内科.md"); List<Document> documents = Arrays.asList(document1, document2, document3); //使用内存向量存储 InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>(); //使用默认的文档分割器 EmbeddingStoreIngestor.ingest(documents, embeddingStore); //从嵌入存储(EmbeddingStore)里检索和查询内容相关的信息 return EmbeddingStoreContentRetriever.from(embeddingStore); }在 XiaozhiAgent 中添加 contentRetriever 配置@AiService( wiringMode = EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProviderXiaozhi", tools = "appointmentTools", contentRetriever = "contentRetrieverXiaozhi" //配置向量存储 )即可在小智AI中实现RAG,比如外部资源库中包含了多个神经内科教授,我们就可以选择其中一个教授进行预约挂号
向量存储
更多推荐


若提示词过多,我们可以设置专门的文件来保存提示词,并且设置身份是传入文件即可,当预设的身份涉及多个参数时,我们要采取 @V 来指定要传递参数的名称













所有评论(0)