小智医疗后端实现总结
此文章是小智医疗个人学习项目的后端实现流程。
学习地址:https://www.bilibili.com/video/BV1cpLTz1EVp/?spm_id_from=333.1391.0.0&p=66

步骤二:配置文件设置(application.properties)
一、项目概述与技术架构
小智医疗是一款基于人工智能的智能医疗客服系统,能够为用户提供医疗咨询、科室导诊、预约挂号等服务。
1.1 项目功能特性
- AI 医疗咨询:基于大语言模型提供专业的医疗建议
- 智能分导诊:根据患者症状智能推荐合适的科室
- 预约挂号:支持查询号源、预约、取消预约功能
- 多轮对话:基于 MongoDB 实现持久化的对话记忆
- 知识库增强:使用 Pinecone 向量数据库存储医疗知识
- 流式响应:使用 WebFlux 实现打字机效果的实时回复
1.2 技术架构图
┌─────────────────────────────────────────────────────────────┐
│ 小智医疗系统架构 │
├─────────────────────────────────────────────────────────────┤
│ 表现层 │ Vue3 + Element Plus (前端,本项目不涉及) │
├──────────┼──────────────────────────────────────────────────┤
│ 控制层 │ Spring Boot 3.2.6 + WebFlux (SSE流式响应) │
├──────────┼──────────────────────────────────────────────────┤
│ AI层 │ LangChain4J 0.36.2 + 通义千问 Qwen │
│ │ - AiService 接口代理 │
│ │ - Tools 工具调用 │
│ │ - RAG 向量检索 │
├──────────┼──────────────────────────────────────────────────┤
│ 数据层 │ MySQL (预约信息) + MongoDB (对话历史) │
│ │ Pinecone (向量数据库) + MyBatis-Plus │
└──────────┴─────────────────────
: Knife4j─────────────────────────────┘
二、开发环境搭建与项目初始化
2.1 环境要求
- JDK 17+(Spring Boot 3.x 最低要求)
- Maven 3.8+
- MySQL 8.0+
- MongoDB 5.0+
- Pinecone 账号(向量数据库)
- 阿里云百炼 API Key
2.2 阿里云百炼 API Key 获取
1. 访问阿里云百炼控制台:https://bailian.console.aliyun.com
2. 登录阿里云账号
3. 进入「我的应用」页面
4. 创建新的应用或使用默认应用
5. 复制 API Key 供项目使用
2.3 Pinecone 向量数据库配置
Pinecone 是一个托管的向量数据库服务,适合存储和检索高维向量数据(如文本嵌入)。
【重要】Pinecone 提供免费套餐,包含 1 个索引和 10 万个向量,足够开发测试使用。
三、核心技术详解
3.1 LangChain4J 框架入门
LangChain4J 是 LangChain 的 Java 实现,提供了与 LLM(大语言模型)交互的统一抽象。核心概念:
- ChatLanguageModel:普通对话模型接口,同步返回完整响应
- StreamingChatLanguageModel:流式对话模型接口,支持逐字返回
- EmbeddingModel:嵌入模型,将文本转换为向量
- ChatMemory:对话记忆,保存多轮对话上下文
- AiService:声明式 AI 服务接口,自动实现代理
- Tool:工具注解,让 AI 能够调用外部方法
3.2 Spring Boot 3.x 整合 AI 大模型
Spring Boot 3.x 相比 2.x 的主要变化:
- 最低 Java 版本要求从 8 提升到 17
- Spring Framework 从 5.x 升级到 6.x
- 默认使用 Jakarta EE 命名空间(javax.* → jakarta.*)
- 内嵌服务器升级:Tomcat 10.x、Jetty 11.x
- 自动配置机制优化
3.3 流式响应与 SSE 技术
SSE(Server-Sent Events)是一种服务器向浏览器推送实时更新的技术。相比 WebSocket,SSE 更轻量,适合单向数据流场景。
// 流式响应返回类型
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestBody ChatForm chatForm) {
return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}
四、项目开发步骤详解
根据代码分析,项目开发按照以下顺序进行:
步骤一:项目初始化与依赖配置(pom.xml)
这是项目的第一步,需要配置所有必要的依赖。
<!-- 核心依赖配置 -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot.version>3.2.6</spring-boot.version>
<langchain4j.version>0.36.2</langchain4j.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
</properties>
<!-- LangChain4J BOM 管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
【要点】langchain4j-bom 使用 import 作用域统一管理所有 LangChain4J 相关依赖的版本,避免版本冲突。
步骤二:配置文件设置(application.properties)
# MySQL 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/guiguxiaozhi
spring.datasource.username=root
spring.datasource.password=xxx
# MongoDB 配置(对话历史)
spring.data.mongodb.uri=mongodb://localhost:27017/chat_memory_db
# 阿里云百炼 API 配置
dashscope.api-key=sk-xxxxx
dashscope.chat-model=qwen-max
dashscope.embedding-model=text-embedding-v3
# 向量数据库配置
vectorstore.type=pinecone
pinecone.api-key=pcsk_xxxxx
pinecone.index=xiaozhi-index
步骤三:实体类与数据传输对象设计
3.3.1 Appointment 预约实体类(MySQL)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Appointment {
@TableId(type = IdType.AUTO) // MyBatis-Plus 主键自增
private Long id;
private String username;
private String idCard;
private String department;
private String date;
private String time;
private String doctorName;
}
3.3.2 ChatMessages 对话消息实体类(MongoDB)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document("chat_messages") // MongoDB 集合名称
public class ChatMessages {
@Id
private ObjectId messageId; // BSON 格式,自动填充
private Long memoryId; // 对话标识
private String content; // JSON 格式的消息列表
}
【关键区别】@TableId 用于 MyBatis-Plus(关系型数据库),@Document 和 @Id(org.springframework.data.annotation)用于 MongoDB(文档数据库)
步骤四:数据访问层实现(MyBatis-Plus)
4.4.1 Mapper 接口定义
@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
// 继承 BaseMapper 获得基础的 CRUD 方法
}
4.4.2 Service 接口与实现
public interface AppointmentService extends IService<Appointment> {
Appointment getOne(Appointment appointment);
}
@Service
public class AppointmentServiceImpl
extends ServiceImpl<AppointmentMapper, Appointment>
implements AppointmentService {
@Override
public Appointment getOne(Appointment appointment) {
LambdaQueryWrapper<Appointment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Appointment::getUsername, appointment.getUsername())
.eq(Appointment::getIdCard, appointment.getIdCard())
.eq(Appointment::getDepartment, appointment.getDepartment())
.eq(Appointment::getDate, appointment.getDate())
.eq(Appointment::getTime, appointment.getTime());
return baseMapper.selectOne(queryWrapper);
}
}
【LambdaQueryWrapper 优势】使用 Lambda 表达式避免硬编码字段名,编译期类型检查,IDE 自动补全,重构安全。
步骤五:MongoDB 对话记忆存储实现
这是项目的核心难点之一,需要自定义 ChatMemoryStore 实现。
@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<>();
}
// 将 JSON 反序列化为 ChatMessage 列表
return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> list) {
Criteria criteria = Criteria.where("memoryId").is(memoryId);
Query query = new Query(criteria);
Update update = new Update();
// 将消息列表序列化为 JSON
update.set("content", ChatMessageSerializer.messagesToJson(list));
mongoTemplate.upsert(query, update, ChatMessages.class);
}
@Override
public void deleteMessages(Object memoryId) {
mongoTemplate.remove(
new Query(Criteria.where("memoryId").is(memoryId)),
ChatMessages.class
);
}
}
【难点解析】1. ChatMessageSerializer/Deserializer 是 LangChain4J 提供的工具类,用于消息的序列化与反序列化;2. upsert 方法表示"存在则更新,不存在则插入";3. memoryId 用于区分不同用户的不同对话会话。
步骤六:AI 大模型配置
@Configuration
public class DashScopeConfig {
@Value("${dashscope.api-key:}")
private String apiKey;
@Bean("qwenStreamingChatModel")
public StreamingChatLanguageModel streamingChatLanguageModel() {
String key = apiKey.isEmpty() ? System.getenv("DASHSCOPE_API_KEY") : apiKey;
return QwenStreamingChatModel.builder()
.apiKey(key)
.modelName("qwen-max") // 通义千问最大模型
.build();
}
@Bean
public EmbeddingModel embeddingModel() {
return QwenEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-v3") // 嵌入模型
.build();
}
}
步骤七:向量数据库配置(Pinecone)
@Configuration
public class EmbeddingStoreConfig {
@Value("${vectorstore.type:memory}")
private String vectorStoreType;
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
if ("pinecone".equalsIgnoreCase(vectorStoreType)
&& pineconeApiKey != null && !pineconeApiKey.isEmpty()) {
return createPineconeEmbeddingStore();
}
return new InMemoryEmbeddingStore<>(); // 降级方案
}
private EmbeddingStore<TextSegment> createPineconeEmbeddingStore() {
return PineconeEmbeddingStore.builder()
.apiKey(apiKey)
.index("xiaozhi-index")
.nameSpace("xiaozhi-namespace")
.createIndex(PineconeServerlessIndexConfig.builder()
.cloud("AWS")
.region("us-east-1")
.dimension(embeddingModel.dimension()) // 向量维度
.build())
.build();
}
}
步骤八:AI Tools 工具类开发
@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 "预约成功";
}
return "预约失败";
}
return "您在相同的科室和时间已有预约";
}
@Tool(name = "查询是否有号源",
value = "根据科室名称,时间和医生查询是否有号源")
public Boolean queryAppointment(
@P(value = "科室名称") String name,
@P(value = "日期") String date,
@P(value = "时间,可选值:上午、下午") String time,
@P(value = "医生名称", required = false) String doctorName) {
return true;
}
}
【@Tool 和 @P 注解】@Tool 的 value 属性是对 AI 的描述,帮助 AI 理解何时调用此工具;@P 注解为参数添加描述,帮助 AI 正确提取参数值。
步骤九:AI Agent 接口定义
@AiService(
wiringMode = EXPLICIT, // 显式指定依赖 Bean
streamingChatModel = "qwenStreamingChatModel", // 流式模型
chatMemoryProvider = "chatMemoryProviderXiaoZhi", // 记忆提供者
tools = "appointmentTools", // 工具类 Bean
contentRetriever = "contentRetrieverXiaozhiPinecone" // RAG检索
)
public interface XiaozhiAgent {
@SystemMessage(fromResource = "XiaoZhi-prompt-template.txt")
Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}
【@MemoryId 关键作用】@MemoryId 用于标识不同的对话会话,相同的 memoryId 会共享同一份对话历史,实现多轮对话上下文理解。
步骤十:Controller 控制器实现
@Tag(name = "硅谷小智")
@RestController
@RequestMapping("/xiaozhi")
public class XiaozhiController {
@Autowired
private XiaozhiAgent xiaozhiAgent;
@Operation(summary = "对话")
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestBody ChatForm chatForm) {
return xiaozhiAgent.chat(
chatForm.getMemoryId(),
chatForm.getMessage()
);
}
}
五、关键技术难点解析
5.1 @AiService 注解深度解析
@AiService 是 LangChain4J Spring Boot 集成的核心注解,它会在运行时自动生成接口的实现类。
// wiringMode 的两种模式
EXPLICIT // 显式模式:通过 Bean 名称精确指定依赖
AUTOMATIC // 自动模式:自动从 Spring 容器中查找匹配的 Bean
配置项详解:
- streamingChatModel:流式对话模型 Bean 名称,用于流式响应
- chatModel:普通对话模型 Bean 名称,用于同步响应
- chatMemoryProvider:对话记忆提供者,实现多轮对话
- tools:工具类 Bean 名称数组,AI 可调用的方法
- contentRetriever:内容检索器,RAG 实现的关键组件
5.2 ChatMemoryProvider 的工作原理
ChatMemoryProvider 是一个函数式接口,用于为每个对话会话创建独立的 ChatMemory 实例。
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId) // 对话唯一标识
.maxMessages(10) // 最大保留消息数
.chatMemoryStore(mongoChatMemoryStore) // 持久化存储
.build();
}
工作流程:
- 用户发起对话,携带 memoryId(如用户ID)
- LangChain4J 调用 ChatMemoryProvider.apply(memoryId)
- 从 MongoDB 加载该 memoryId 对应的历史消息
- 将历史消息与新消息一起发送给大模型
- 收到响应后,更新 MongoDB 中的对话记录
5.3 RAG 检索增强生成实现
RAG(Retrieval-Augmented Generation)是一种将外部知识库与 LLM 结合的技术。
@Bean
public ContentRetriever contentRetrieverXiaozhi() {
return EmbeddingStoreContentRetriever.builder()
.embeddingModel(embeddingModel) // 嵌入模型
.embeddingStore(embeddingStore) // 向量存储
.maxResults(1) // 最多返回1条
.minScore(0.8) // 最小相似度阈值
.build();
}
【RAG 流程】1. 用户提问 → 2. 使用 EmbeddingModel 将问题转为向量 → 3. 在向量数据库中检索相似文档 → 4. 将检索结果作为上下文与问题一起发给 LLM → 5. LLM 基于上下文生成回答
5.4 工具调用(Function Calling)机制
Function Calling 允许 LLM 根据对话上下文决定何时调用外部 API 或方法。
// 工具调用流程(以预约挂号为例)
用户:"我想预约明天上午的神经内科"
↓
LLM 分析:需要调用 "预约挂号" 工具
↓
LLM 提取参数:
- date: "2025-04-22"
- time: "上午"
- department: "神经内科"
↓
调用 bookAppointment(appointment)
↓
返回结果给 LLM
↓
LLM 生成友好回复:"预约成功!您已成功预约..."
六、项目总结与学习建议
6.1 核心知识点总结
LangChain4J 框架:掌握 AiService、ChatMemory、Tool 等核心概念
Spring Boot 3.x:理解自动配置、Bean 管理、条件装配
向量数据库:理解 Embedding 原理、向量相似度计算
流式响应:掌握 WebFlux、Flux、SSE 技术
多数据源:MySQL + MongoDB 在 Spring Boot 中的整合
API 设计:RESTful 接口设计、Swagger 文档生成
6.2 学习路径建议
- 阶段一:夯实基础 - Spring Boot、MyBatis-Plus、MySQL、MongoDB
- 阶段二:AI 入门 - 了解 LLM、Prompt Engineering、Function Calling
- 阶段三:框架学习 - LangChain4J 官方文档、示例项目
- 阶段四:项目实战 - 从简单对话到复杂多轮对话、工具调用
- 阶段五:进阶优化 - RAG 优化、性能调优、生产部署
6.3 扩展学习方向
- 其他 LLM 接入:OpenAI、Claude、文心一言、讯飞星火
- 更复杂的 RAG:文档分块策略、重排序、多路召回
- Agent 进阶:ReAct、Plan-and-Solve、Multi-Agent
- 性能优化:缓存、限流、异步处理、连接池
- 生产部署:Docker、K8s、监控、日志、链路追踪
—— 学习笔记完 ——
更多推荐



所有评论(0)