目录
[一 技术实现:](#一 技术实现:)
[0 依赖管理](#0 依赖管理)
[1 AI代理调用](#1 AI代理调用)
[2 向量模型](#2 向量模型)
[3 向量数据库](#3 向量数据库)
[4 记忆存储](#4 记忆存储)
[5 流式聊天大模型](#5 流式聊天大模型)
[6 Tools工具/Functional Calling](#6 Tools工具/Functional Calling)
[二 调用链路分析](#二 调用链路分析)
[1 请求处理流程](#1 请求处理流程)
[2 AI代理调用流程](#2 AI代理调用流程)
[3 工具调用流程](#3 工具调用流程)
[4 知识库检索调用流程](#4 知识库检索调用流程)
[三 技术积累](#三 技术积累)
一 技术实现:
0 依赖管理
导入OpenAi的依赖项
XML
<!-- 基于open-ai标准的大模型(ChatGPT、DeepSeek) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>
<!-- langchain4j核心starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>
完整依赖
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itysky</groupId>
<artifactId>java-ai-longchain4j</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
<!-- 覆盖dashscope-sdk-java版本至2.19.5,支持text()和voice()方法,排除slf4j-simple避免与Logback冲突 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.19.5</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- web应用程序核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 基于open-ai标准的大模型(ChatGPT、DeepSeek) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>
<!-- langchain4j核心starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>
<!-- 接入ollama -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
</dependency>
<!-- 接入阿里云百炼平台(通义千问) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
<!-- 文档解析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>
<!-- PgVector 向量数据库(PostgreSQL + pgvector 扩展) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
</dependency>
<!-- PostgreSQL JDBC 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- 流式输出依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!-- MySQL -->
<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>
<!-- MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 修复Maven编译插件,无弃用参数、无冗余配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<!-- Spring Boot 依赖管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- LangChain4j 核心依赖管理 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- LangChain4j 社区版依赖管理(通义千问) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
1 AI代理调用
1 底层API调用
大模型标准协议需要自己去进行转换代码实现。(命令式编程)
java
@Autowired
private QwenChatModel qwenChatModel;
@Test
public void testChatMemory2() {
//从String -> UserMessage
UserMessage userMessage1 = UserMessage.userMessage("我是小明");
ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);
AiMessage aiMessage1 = chatResponse1.aiMessage();
//从AiMessage -> String
String text = aiMessage1.text();
System.out.println(text);
}
2 AiServices
这种方式不在接口上打注解,而是需要在 @Configuration 类里面手动写一个 @Bean 方法。(编程式声明)
java
@Bean
public LegalAssistant legalAssistant(ChatLanguageModel model) {
// 手动调用 builder 组装
return AiServices.builder(LegalAssistant.class)
.chatLanguageModel(model)
.build();
}
3 @AiService
1 实现一个无状态,单轮对话的AI服务,没有记忆,没有RAG。(注解式声明)
java
package com.itsky.java.ai.longchain4j.assistant;
import dev.langchain4j.service.spring.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
/*定义聊天助手接口-指定对应的ChatModel*/
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel")
public interface Assistant {
String chat(String userMassage);
}
- @AiService声明式代理服务
- writingMode:chatModel,chatMemory,tools,RAG组件进行管理
- AUTOMATIC:自动装配(去找那唯一的一个组件,出现多个会冲突报错)
- EXPLICIT:显式注入(点名显示调用)
- chatModel:对话的核心大脑。
- tools:指定代码当中实现的工具类。
- retrievalAugmentor:(RAG)检索增强生成,大模型私有知识库,配合EmbeddingModel向量模型+EmbeddingStore向量数据库实现。
- chatmemoryProbider:聊天记忆组件,一般都是配合MongDB实现。
测试:
java
// 1.1直接注入Assistant对象
@Autowired
private Assistant assistant;
@Test
public void testChat2() {
// 2.直接调用注入的Assistant对象
String answer = assistant.chat("写一首创造性的七言律诗(要求结合时事,以美国兴衰为背景)");
System.out.println(answer);
}
2 实际使用
这里配置了持久化记忆,工具调用,私有知识库,流式输出
java
package com.itsky.java.ai.longchain4j.assistant;
import dev.langchain4j.service.*;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
wiringMode = EXPLICIT,
streamingChatModel = "qwenStreamingChatModel",
chatMemoryProvider = "chatMemoryProviderXiaozhi", // 使用 XiaozhiAgentConfig 中的 MongoDB 持久化记忆提供者
tools = "appointmentTools", // tools配置
contentRetriever = "contentRetrieverXiaozhipgsql" // 配置向量存储
)
public interface XiaozhiAgent {
@SystemMessage(fromResource = "xiaozhi-prompt-template.txt")//提示词
Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}
2 向量模型
这里使用的是阿里通义千问的text-embedding-v3
java
package com.itsky.java.ai.longchain4j.config;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ABC
*/
@Configuration
public class DashscopeEmbeddingConfig {
@Value("${langchain4j.community.dashscope.embedding-model.api-key}")
private String apiKey;
@Value("${langchain4j.community.dashscope.embedding-model.model-name}")
private String modelName;
@Bean(name = "dashscopeEmbeddingModel")
public EmbeddingModel dashscopeEmbeddingModel() {
return QwenEmbeddingModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.build();
}
}
3 向量数据库
使用的是pinecone免费版,性能优秀适合实时检索,属于直接就能用。
java
package com.itsky.java.ai.longchain4j.config;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* PgVector 向量存储配置。
* 使用 PostgreSQL + pgvector 扩展替代 Pinecone,本地部署,成本低。
* 启动时自动建表(createTable=true),无需手动执行 DDL。
* PostgreSQL 侧只需提前执行:CREATE EXTENSION IF NOT EXISTS vector;
*/
@Configuration
public class EmbeddingStoreConfig {
@Value("${pgvector.host}")
private String host;
@Value("${pgvector.port:5432}")
private int port;
@Value("${pgvector.database}")
private String database;
@Value("${pgvector.user}")
private String user;
@Value("${pgvector.password}")
private String password;
@Value("${pgvector.table:langchain4j_embeddings}")
private String table;
@Autowired
@Qualifier("dashscopeEmbeddingModel")
private EmbeddingModel dashscopeEmbeddingModel;
@Bean(name = "embeddingStore")
public PgVectorEmbeddingStore embeddingStore() {
return PgVectorEmbeddingStore.builder()
.host(host)
.port(port)
.database(database)
.user(user)
.password(password)
.table(table)
// 向量维度:由嵌入模型决定(text-embedding-v3 为 1024 维)
.dimension(dashscopeEmbeddingModel.dimension())
// 首次启动自动在 PostgreSQL 中创建向量表,已存在则跳过
.createTable(true)
.build();
}
}
| 维度 | Milvus | Elasticsearch | PostgreSQL (pgvector) | Redis (RediSearch) |
|---|---|---|---|---|
| 主要吃什么硬件? | 内存 + CPU + 网络带宽 | JVM 内存 + OS Cache + SSD IO | 高频 CPU + SSD IO | 纯物理大内存 |
| 高可用与灾备实现 | 云原生多活、数据存 S3 | 自身分片副本机制 (Replicas) | 主从流复制 (WAL 流) | 哨兵 / 集群切片机制 |
| 整体运维难度 | 🔴 极难 (需 K8s 专家) | 🟠 偏难 (需调优 JVM) | 🟢 极低 (常规 DBA 即可) | 🟡 中等 (需死盯内存监控) |
| 适合的公司基建 | 大型微服务架构团队 | 已有 ES 运维专家的团队 | 绝大多数中小互联网团队 | 强依赖全量缓存的高频交易/推荐团队 |
4 记忆存储
java
package com.itsky.java.ai.longchain4j.store;
import com.itsky.java.ai.longchain4j.bean.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;
@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);
}
}
5 流式聊天大模型
这里并没有单独去进行定义,而是使用SDK(核心逻辑包),结合starter,使用约定大于配置的理念,只需要引入依赖编写对应的yml配置就可以减少一些繁琐的配置。

进行整合
java
package com.itsky.java.ai.longchain4j.config;
import com.itsky.java.ai.longchain4j.store.MongoChatMemoryStore;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
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.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
@Configuration
public class XiaozhiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
/**
* 小智Agent专用的聊天记忆提供者(基于MongoDB持久化)
* Bean名称为 chatMemoryProviderXiaozhi,供 XiaozhiAgent 的 chatMemoryProvider 使用。
* 注意:SeparateChatAssistantConfig 中也定义了名为 chatMemoryProvider 的 Bean,
* 此处使用不同名称以避免冲突。
*/
@Bean
ChatMemoryProvider chatMemoryProviderXiaozhi() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
/**
* 基于内存向量存储的内容检索器(使用本地知识库文档,InMemory方式)
*/
@Bean
ContentRetriever contentRetrieverXiaozhi(
@Qualifier("dashscopeEmbeddingModel") EmbeddingModel embeddingModel) {
// 使用 ClassPathResource 获取 resources 目录下的知识库文档路径
Document document1 = loadClassPathDocument("医院信息.md");
Document document2 = loadClassPathDocument("科室信息.md");
Document document3 = loadClassPathDocument("神经内科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
// 使用内存向量存储
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 使用指定的嵌入模型进行文档向量化
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
ingestor.ingest(documents);
// 从嵌入存储中检索和查询内容相关的信息
return EmbeddingStoreContentRetriever.builder()
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.maxResults(3)
.minScore(0.6)
.build();
}
/**
* 基于 Pinecone 向量存储的内容检索器
*/
@Bean
ContentRetriever contentRetrieverXiaozhipgsql(
@Qualifier("embeddingStore") EmbeddingStore<TextSegment> embeddingStore,
@Qualifier("dashscopeEmbeddingModel") EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever
.builder()
// 设置用于生成嵌入向量的嵌入模型
.embeddingModel(embeddingModel)
// 指定要使用的嵌入存储(Pinecone)
.embeddingStore(embeddingStore)
// 最多返回 3 条匹配结果
.maxResults(3)
// 只有得分大于等于 0.7 的结果才会被返回
.minScore(0.7)
.build();
}
/**
* 从 classpath 加载文档(兼容 jar 包运行)
*/
private Document loadClassPathDocument(String filename) {
try {
ClassPathResource resource = new ClassPathResource(filename);
Path path = resource.getFile().toPath();
return FileSystemDocumentLoader.loadDocument(path);
} catch (Exception e) {
throw new RuntimeException("无法加载知识库文档: " + filename, e);
}
}
}
6 Tools工具/Functional Calling
这里编写的Tools,当LLM调用时,会自动的将这些namevlaue值进行注入,后续大模型会去进行检查,判断是否有对应的方法,如果有的话就会去进行调用。结合一定的提示词进行约束条件。
代码展示:实现预约挂号,取消挂号,查询号源的功能。
java
package com.itsky.java.ai.longchain4j.tools;
import com.itsky.java.ai.longchain4j.entity.Appointment;
import com.itsky.java.ai.longchain4j.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;
@Component
public class AppointmentTools {
@Autowired
private AppointmentService appointmentService;
/**
* 预约挂号工具。
* 调用前请先执行 queryDepartment 查询号源,确认有号后再调用此方法。
* Appointment 对象的 JSON 字段说明:
* - username: 患者姓名(必填)
* - id_card: 患者身份证号(必填)
* - department: 预约科室名称(必填)
* - date: 预约日期,格式 yyyy-MM-dd(必填)
* - time: 预约时间段,可选值:上午、下午(必填)
* - doctor_name: 预约医生姓名(必填,若用户未指定请从知识库中选择一位合适医生)
*/
@Tool(name = "预约挂号",
value = "根据患者信息进行挂号预约。调用此工具前必须先调用 queryDepartment 确认有号," +
"并向用户确认全部预约信息后再执行。" +
"参数 appointment 为 JSON 对象,字段名必须使用驼峰格式:" +
"username(患者姓名)、idCard(身份证号)、department(科室)、" +
"date(预约日期 yyyy-MM-dd)、time(上午或下午)、doctorName(医生姓名)。" +
"若用户未提供医生姓名,请从向量知识库中选择一位合适的医生填入。")
public String bookAppointment(
@P("预约信息,JSON对象,字段名用驼峰格式:username(患者姓名)、idCard(身份证号)、department(科室)、date(日期yyyy-MM-dd)、time(上午或下午)、doctorName(医生姓名)")
Appointment appointment) {
// 打印日志,便于调试
System.out.println("=== 预约挂号工具被调用 ===");
System.out.println("患者姓名: " + appointment.getUsername());
System.out.println("身份证号: " + appointment.getIdCard());
System.out.println("科室: " + appointment.getDepartment());
System.out.println("日期: " + appointment.getDate());
System.out.println("时间: " + appointment.getTime());
System.out.println("医生: " + appointment.getDoctorName());
// 查找数据库中是否包含对应的预约记录(防重复预约)
Appointment appointmentDB = appointmentService.getOne(appointment);
if (appointmentDB == null) {
// 防止大模型幻觉设置了 id,强制置 null 让数据库自增
appointment.setId(null);
boolean saved = appointmentService.save(appointment);
System.out.println("预约保存结果: " + saved);
if (saved) {
return String.format("预约成功!预约详情:患者 %s,科室 %s,日期 %s %s,主治医生 %s。",
appointment.getUsername(),
appointment.getDepartment(),
appointment.getDate(),
appointment.getTime(),
appointment.getDoctorName());
} else {
return "预约失败,数据库写入错误,请稍后重试。";
}
}
return String.format("您在 %s %s %s 已有预约(医生:%s),请勿重复预约。",
appointmentDB.getDepartment(),
appointmentDB.getDate(),
appointmentDB.getTime(),
appointmentDB.getDoctorName());
}
/**
* 取消预约挂号工具。
*/
@Tool(name = "取消预约挂号",
value = "根据患者信息取消已有预约。" +
"参数 appointment 为 JSON 对象,字段名使用驼峰格式:" +
"username(患者姓名)、idCard(身份证号)、department(科室)、" +
"date(预约日期)、time(上午或下午)。")
public String cancelAppointment(
@P("取消预约信息,JSON对象,字段名用驼峰格式:username(患者姓名)、idCard(身份证号)、department(科室)、date(日期)、time(上午或下午)")
Appointment appointment) {
System.out.println("=== 取消预约工具被调用 ===");
Appointment appointmentDB = appointmentService.getOne(appointment);
if (appointmentDB != null) {
if (appointmentService.removeById(appointmentDB.getId())) {
return String.format("取消预约成功!已取消 %s %s %s 的预约(医生:%s)。",
appointmentDB.getDepartment(),
appointmentDB.getDate(),
appointmentDB.getTime(),
appointmentDB.getDoctorName());
} else {
return "取消预约失败,数据库操作错误,请稍后重试。";
}
}
return "未找到对应的预约记录,请核对科室、日期和时间是否正确。";
}
/**
* 查询号源工具。
*/
@Tool(name = "查询是否有号源",
value = "根据科室名称、日期、时间段和医生姓名查询是否有可用号源,并将结果告知用户。")
public boolean queryDepartment(
@P(value = "科室名称") String name,
@P(value = "日期,格式 yyyy-MM-dd") 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;
}
}
二 调用链路分析
1 请求处理流程
-1 前端请求 :用户通过前端发送聊天请求到 /xiaozhi/chat 接口
-
2 控制器处理 : XiaozhiController 接收请求并调用 AI 代理
-
3 AI 代理调用 : XiaozhiAgent 使用大模型生成回复
-
4 流式返回 :使用 WebFlux 实现流式响应,提升用户体验
2 AI代理调用流程
-
1代理配置 : @AiService 注解配置大模型、工具和内容检索器
-
2系统提示 :加载 xiaozhi-prompt-template.txt 作为系统提示
-
3聊天记忆 :使用 MongoChatMemoryStore 存储聊天历史
-
4工具调用 :根据用户需求调用 AppointmentTools 中的工具,实现增删改查
-
5内容检索 :从向量存储中检索相关知识
3 工具调用流程
-
1工具定义 : AppointmentTools 定义预约相关工具
-
2 工具调用 :大模型根据用户需求调用相应工具
-
3 服务调用 :工具调用 AppointmentService 提供的业务逻辑
-
4 数据库操作 : AppointmentServiceImpl 操作 MySQL 数据库
4 知识库检索调用流程
-
1内容检索器 : contentRetrieverXiaozhiPincone 从向量存储中检索相关内容
-
2向量模型 :使用阿里通义千问 text-embedding-v3 模型生成查询向量
-
3向量存储 :在 Pinecone 中搜索相似向量
-
4结果返回 :将检索到的内容返回给大模型
三 技术积累
1 在DataInitializer.java类当中实现了CommandLineRunner接口
java
@Component
public class DataInitializer implements CommandLineRunner {
原理 : CommandLineRunner 是 Spring Boot 提供的一个接口,任何实现了该接口的 Bean 都会在 Spring Boot 应用完全启动后、开始接收请求前 自动执行其 run() 方法。这是执行初始化逻辑的理想位置
@Value值注入:去读取数据,默认是false不去进行初始化
java
@Value("${pgvector.init-data:false}")
private boolean initData;
配置文件展示示例:pgvector.init-data 配置项
XML
pgvector:
# 首次部署时设为 true 注入知识库文档,完成后改回 false 避免重复注入
init-data: false
代码逻辑
java
@Override
public void run(String... args) throws Exception {
if (initData) {
initKnowledgeBase(); // 执行初始化
} else {
System.out.println("PgVector 知识库初始化已跳过(pgvector.init-data=false)");
}
}
完整代码
java
package com.itsky.java.ai.longchain4j.config;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.ClassPathDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 应用启动时初始化 PgVector 向量存储中的知识库数据。
* 通过配置项 pgvector.init-data 控制是否执行初始化(默认为 false,避免重复注入)。
*/
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
@Qualifier("embeddingStore")
private EmbeddingStore<TextSegment> embeddingStore;
@Autowired
@Qualifier("dashscopeEmbeddingModel")
private EmbeddingModel embeddingModel;
/**
* 是否在启动时向 PgVector 注入知识库文档。
* 首次部署时设为 true,之后改为 false 避免重复注入。
*/
@Value("${pgvector.init-data:false}")
private boolean initData;
@Override
public void run(String... args) throws Exception {
if (initData) {
initKnowledgeBase();
} else {
System.out.println("PgVector 知识库初始化已跳过(pgvector.init-data=false)");
}
}
private void initKnowledgeBase() {
System.out.println("开始向 PgVector 注入知识库数据...");
// 读取知识库文档(从 classpath 加载)
Document document1 = ClassPathDocumentLoader.loadDocument("医院信息.md");
Document document2 = ClassPathDocumentLoader.loadDocument("科室信息.md");
Document document3 = ClassPathDocumentLoader.loadDocument("神经内科.md");
List<Document> documents = Arrays.asList(document1, document2, document3);
// 使用指定的嵌入模型将文档向量化并存入 PgVector
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
ingestor.ingest(documents);
System.out.println("PgVector 知识库初始化完成,共注入 " + documents.size() + " 个文档");
}
}