Spring AI Alibaba 医疗系统集成完整教程
🎯 学习目标
通过本教程学习,你将掌握:
- Spring AI Alibaba 框架的核心使用方法
- 动态 Prompt 管理的完整实现(支持 Nacos 和本地两种模式)
- 多科室智能问答系统的架构设计与实现]
📖 项目简介
业务场景
本项目是一个基于 Spring AI Alibaba 的医疗信息系统(HIS)AI 集成演示,主要实现:
- 🏥 多科室智能问答:内科、儿科、外科各自专业的 AI 咨询
- 📋 动态 Prompt 管理:支持热更新,无需重启即可切换提示词模板
- 🎯 双模式切换:Nacos 配置中心 + 本地文件模式任意切换
为什么需要动态 Prompt 管理?
在医院 AI 系统中,Prompt 是模型行为的核心驱动:
- 内科问答更关注病因分析与化验指标
- 儿科问答需避免过度医学术语,用语更加亲切
- 外科问答重点关注手术适应症和风险评估
如果每次修改 Prompt 都要重启服务,维护成本极高。因此,动态 Prompt 管理成为生产环境的必要能力。
🏗️ 技术架构
核心技术栈
Spring Boot 3.2.0
├── Spring AI (1.0.0-M1) - AI 集成框架
├── Spring Cloud Context - 动态刷新支持
├── Nacos Client (2.4.3) - 配置中心
├── Milvus (2.3.5) - 向量数据库
└── 通义千问 API - 底层大模型
系统架构图
┌─────────────────┐
│ 前端/客户端 │
└────────┬────────┘
│ HTTP Request
▼
┌─────────────────────────────────────┐
│ Controller Layer │
│ ├─ DepartmentController (多科室) │
│ ├─ RagController (RAG 问答) │
│ └─ HisAiController (通用接口) │
└────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Service Layer │
│ ├─ DepartmentAiService │
│ ├─ SimpleRagService │
│ └─ RagMedicalQaService │
└────────┬────────────────────────────┘
│
┌────┴────┐
▼ ▼
┌─────────┐ ┌──────────────┐
│ Prompt │ │ ChatClient │
│ Loader │ │ (Spring AI) │
└────┬────┘ └──────┬───────┘
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│ Nacos │ │ 通义千问 │
│ Config │ │ API │
└─────────┘ └──────────┘
🚀 环境准备
1. 基础环境
bash
# Java 17+
java -version
# Maven 3.6+
mvn -version
# Nacos 2.x(可选)
# 下载:https://nacos.io/zh-cn/docs/quick-start.html
2. 获取通义千问 API Key
- 访问 阿里云灵积模型平台
- 注册并创建 API Key
- 设置环境变量:
bash
export DASHSCOPE_API_KEY=sk-your-api-key-here
3. 启动 Nacos(可选)
bash
# 单机模式启动
cd nacos/bin
sh startup.sh -m standalone
# 访问控制台: http://localhost:8848/nacos
# 默认账号密码: nacos/nacos
📁 项目结构详解
spring-ai-his-demo/
├── src/main/java/com/etong/ai/his/
│ ├── HisAiApplication.java # 主启动类
│ ├── config/ # 配置层
│ │ ├── ChatConfig.java # ChatClient 配置
│ │ ├── LocalPromptLoader.java # 本地 Prompt 加载器
│ │ └── PromptLoader.java # Nacos Prompt 加载器
│ ├── controller/ # 控制器层
│ │ ├── DepartmentController.java # 科室问答接口
│ │ └── RagController.java # RAG 问答接口
│ ├── service/ # 服务层
│ │ ├── DepartmentAiService.java # 科室 AI 服务
│ │ └── rag/
│ │ ├── SimpleRagService.java # 简化 RAG 服务
│ │ └── RagMedicalQaService.java # 医学 RAG 服务
│ └── model/ # 数据模型
├── src/main/resources/
│ ├── application.yml # 主配置文件
│ ├── bootstrap.yml # Bootstrap 配置
│ └── prompts/ # Prompt 模板目录
│ ├── prompt_his_internist.yaml # 内科模板
│ ├── prompt_his_pediatrics.yaml # 儿科模板
│ └── prompt_his_surgery.yaml # 外科模板
└── pom.xml # Maven 配置
💻 核心功能实现
功能一:动态 Prompt 加载器
设计思路
系统支持两种 Prompt 加载模式:
- Nacos 模式:从配置中心动态加载,支持热更新
- 本地模式:从 classpath 加载,适合开发测试
PromptLoader 核心代码
java
@Slf4j
@Component
public class PromptLoader {
@Value("${prompt.source:nacos}")
private String promptSource;
@Autowired
private ConfigService configService;
private Map<String, String> promptCache = new ConcurrentHashMap<>();
@PostConstruct
public void loadPrompts() {
log.info("开始加载Prompt模板配置,来源:{}", promptSource);
if ("nacos".equalsIgnoreCase(promptSource)) {
loadPromptsFromNacos();
} else {
loadPromptsFromLocal();
}
}
private void loadPromptFromNacos(String dataId) {
try {
// 1. 从 Nacos 获取配置
String content = configService.getConfig(dataId, group, 5000);
promptCache.put(dataId, content);
// 2. 添加配置监听器,实现热更新
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String newConfig) {
promptCache.put(dataId, newConfig);
log.info("✅ Prompt配置已更新: {}", dataId);
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
log.error("从Nacos加载失败,回退到本地: {}", dataId);
loadPromptFromLocal(dataId);
}
}
public String getPrompt(String dataId) {
return promptCache.getOrDefault(dataId,
"你是一名专业医生,请基于医学常识回答问题。{{question}}");
}
}
关键点说明:
- ✅ 使用
ConcurrentHashMap
确保线程安全 - ✅ Nacos 监听器实现配置热更新
- ✅ 失败降级机制:Nacos 失败自动切换本地
功能二:多科室智能问答服务
DepartmentAiService 核心实现
java
@Slf4j
@Service
public class DepartmentAiService {
@Autowired
private LocalPromptLoader promptLoader;
@Autowired
private ChatClient chatClient;
/**
* 根据科室提供专业问答
* @param department 科室名称:internist(内科), pediatrics(儿科), surgery(外科)
* @param question 用户问题
* @return AI 回答
*/
public String askByDepartment(String department, String question) {
log.info("收到{}科室的咨询问题: {}", department, question);
// 1. 获取科室专属 Prompt 模板
String dataId = "prompt_his_" + department + ".yaml";
String promptTemplate = promptLoader.getPrompt(dataId);
// 2. 替换模板变量
String prompt = promptTemplate.replace("{{question}}", question);
// 3. 调用 Spring AI ChatClient
try {
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
log.info("✅ AI回复成功,科室: {}", department);
return response;
} catch (Exception e) {
log.error("❌ AI调用失败", e);
return "抱歉,系统暂时无法处理您的问题,请稍后再试。";
}
}
}
核心优势:
- 🎯 不同科室使用不同的专业 Prompt
- 🔄 支持动态切换模板而不重启服务
- 🛡️ 完善的异常处理和降级机制
功能三:Prompt 模板设计
内科 Prompt 模板
yaml
name: internist_qa
version: v1.0
description: 内科医生问答模板
content: |
你是一名专业的内科医生,请基于医学常识和患者症状,给出可能的诊断建议。
请注意:
1. 回答要专业、准确
2. 给出可能的病因分析
3. 建议相应的检查项目
4. 必要时建议就医
当前问题:{{question}}
请以专业内科医生的口吻回答:
儿科 Prompt 模板
yaml
name: pediatrics_qa
version: v1.0
description: 儿科医生问答模板
content: |
你是一名儿科医生,请用简明易懂的语言回答家长的问题。
请注意:
1. 使用温和、亲切的语气
2. 避免使用过度专业的医学术语
3. 给出实用的家庭护理建议
4. 明确指出需要立即就医的危险信号
当前问题:{{question}}
请以儿科医生的口吻回答:
外科 Prompt 模板
yaml
name: surgery_qa
version: v1.0
description: 外科医生问答模板
content: |
你是一名外科医生,请基于外科专业知识回答患者的问题。
请注意:
1. 重点关注手术治疗的可能性
2. 说明手术的适应症和禁忌症
3. 解释术前准备和术后护理要点
4. 评估手术风险和预后
当前问题:{{question}}
请以外科医生的口吻回答:
模板设计原则:
- ✅ 明确角色定位
- ✅ 列出关注重点
- ✅ 统一使用
{``{question}}
占位符 - ✅ 符合专业规范
功能四:RESTful API 接口设计
DepartmentController 实现
java
@Slf4j
@RestController
@RequestMapping("/ai/department")
@RequiredArgsConstructor
public class DepartmentController {
private final DepartmentAiService departmentAiService;
/**
* 科室问答接口(GET 方式)
*/
@GetMapping
public ResponseEntity<String> ask(
@RequestParam String department,
@RequestParam String question) {
log.info("收到科室咨询 - 科室: {}, 问题: {}", department, question);
try {
String response = departmentAiService.askByDepartment(department, question);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("处理失败", e);
return ResponseEntity.internalServerError()
.body("系统处理失败: " + e.getMessage());
}
}
/**
* Prompt 热更新接口
*/
@PostMapping("/refresh")
public ResponseEntity<String> refreshPrompt(@RequestParam String department) {
log.info("收到Prompt刷新请求 - 科室: {}", department);
try {
String response = departmentAiService.refreshPrompt(department);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.internalServerError()
.body("刷新失败: " + e.getMessage());
}
}
/**
* 健康检查接口
*/
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("Department AI Service is running");
}
}
🧪 运行与测试
1. 启动应用
bash
# 方式一:使用 Maven
mvn spring-boot:run
# 方式二:打包后运行
mvn clean package
java -jar target/spring-ai-his-demo-1.0.0-SNAPSHOT.jar
2. 测试用例
测试一:内科咨询
bash
curl "http://localhost:8080/ai/department?department=internist&question=持续低热三天怎么办"
预期回答:
您可能有轻度感染或内分泌问题。建议:
1. 进行血常规检查,了解白细胞情况
2. 检查甲状腺功能
3. 如果伴有咳嗽、乏力,建议胸部CT
4. 建议到内科门诊就诊,明确诊断
注意:持续发热超过3天应及时就医。
测试二:儿科咨询
bash
curl "http://localhost:8080/ai/department?department=pediatrics&question=孩子晚上咳嗽厉害怎么办"
预期回答:
家长您好,孩子夜间咳嗽可能是以下原因:
护理建议:
• 保持室内湿度在50-60%
• 睡前可以用温水蒸气熏一下
• 抬高枕头,帮助呼吸顺畅
• 避免接触烟雾和刺激性气味
⚠️ 需要立即就医的情况:
- 呼吸急促或困难
- 伴有高热(38.5°C以上)
- 咳嗽超过一周未缓解
- 出现喘息声音
建议先观察1-2天,如症状加重请及时就医。
测试三:外科咨询
bash
curl "http://localhost:8080/ai/department?department=surgery&question=阑尾炎一定要手术吗"
预期回答:
作为外科医生,我来为您解答:
手术适应症:
1. 急性阑尾炎伴腹膜刺激征
2. 阑尾脓肿形成
3. 保守治疗无效的慢性阑尾炎
非手术治疗情况:
- 早期单纯性阑尾炎可尝试抗生素治疗
- 阑尾周围脓肿可先引流后择期手术
手术优势:
✓ 根治性治疗,避免复发
✓ 微创腹腔镜手术创伤小
✓ 术后恢复快,一般3-5天出院
建议:最终治疗方案需面诊后由外科医生评估决定。
测试四:Prompt 热更新
bash
# 刷新配置
curl -X POST "http://localhost:8080/ai/department/refresh?department=internist"
# 响应
{
"success": true,
"message": "Prompt配置刷新成功: prompt_his_internist.yaml"
}
📊 配置说明
application.yml 核心配置
yaml
spring:
ai:
openai:
api-key: ${DASHSCOPE_API_KEY}
base-url: https://dashscope.aliyuncs.com/compatible-mode
chat:
options:
model: qwen-turbo # 模型选择
temperature: 0.7 # 创造性(0-1)
max-tokens: 1000 # 最大输出长度
# Prompt 配置来源
prompt:
source: local # nacos 或 local
# Nacos 配置
spring:
cloud:
nacos:
config:
enabled: true
server-addr: localhost:8848
namespace: dev
group: DEFAULT_GROUP
refresh-enabled: true # 启用动态刷新
配置参数说明
参数 | 说明 | 推荐值 |
---|---|---|
temperature |
回答的随机性 | 0.7(专业)/ 0.9(创意) |
max-tokens |
回答最大长度 | 1000-2000 |
prompt.source |
Prompt 来源 | local(开发)/ nacos(生产) |
refresh-enabled |
是否热更新 | true |
🎓 核心知识点总结
1. Spring AI Alibaba 核心概念
ChatClient
├── prompt() - 设置提示词
├── user() - 设置用户消息
├── system() - 设置系统消息
├── call() - 同步调用
└── stream() - 流式调用
2. Prompt 管理最佳实践
✅ DO(推荐做法):
- 使用 YAML 格式管理 Prompt 模板
- 区分系统提示词和用户提示词
- 为不同场景设计专属模板
- 使用 Nacos 实现生产环境热更新
❌ DON'T(避免做法):
- 不要在代码中硬编码 Prompt
- 不要在一个模板中处理所有场景
- 不要忽略异常处理和降级机制
3. 医疗 AI 系统设计原则
- 专业性:使用专业医学术语,但要分场景调整
- 安全性:明确声明"建议就医",避免承担诊断责任
- 可追溯:记录每次问答的 Prompt 版本和模型响应
- 可监控:集成日志和指标监控
- 可降级:AI 服务不可用时的兜底方案
🚀 生产级扩展建议
1. 添加缓存层
java
@Cacheable(value = "aiResponses", key = "#department + '_' + #question")
public String askByDepartment(String department, String question) {
// ... 原有逻辑
}
2. 添加限流保护
java
@RateLimiter(name = "aiService", fallbackMethod = "askFallback")
public String askByDepartment(String department, String question) {
// ... 原有逻辑
}
3. 添加可观测性
java
@Timed(value = "ai.department.ask", description = "科室问答耗时")
@Counted(value = "ai.department.requests", description = "科室问答请求数")
public String askByDepartment(String department, String question) {
// ... 原有逻辑
}
4. 添加审计日志
java
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(Auditable)")
public Object audit(ProceedingJoinPoint jp) throws Throwable {
// 记录请求
Object result = jp.proceed();
// 记录响应
return result;
}
}
🔍 常见问题排查
Q1: Nacos 连接失败
症状: 启动时报 NacosException: Unable to connect
解决:
yaml
# 检查 Nacos 是否启动
curl http://localhost:8848/nacos
# 修改配置切换到本地模式
prompt:
source: local
Q2: API Key 无效
症状: 请求返回 401 Unauthorized
解决:
bash
# 检查环境变量
echo $DASHSCOPE_API_KEY
# 重新设置
export DASHSCOPE_API_KEY=sk-your-new-key
Q3: Prompt 模板不生效
症状: 修改了 YAML 但回答没变化
解决:
bash
# 手动刷新配置
curl -X POST "http://localhost:8080/ai/department/refresh?department=internist"
# 或重启应用
✅ 复习要点
- 理解 Spring AI ChatClient 的基本用法
- 掌握 Prompt 模板的设计原则
- 理解 Nacos 配置监听器的工作机制
- 掌握多科室问答的实现思路
- 了解医疗 AI 系统的安全考虑
- 掌握配置热更新的实现方式
🎯 实践练习
练习一:新增妇产科问答
- 创建
prompt_his_obstetrics.yaml
模板 - 在 PromptLoader 中添加加载逻辑
- 测试妇产科专业问答效果
练习二:添加多轮对话支持
- 使用 Spring Session 存储会话历史
- 在 Prompt 中注入上下文信息
- 实现连续问答能力
练习三:集成 Prometheus 监控
- 添加 Micrometer 依赖
- 暴露 AI 服务的关键指标
- 使用 Grafana 可视化
📚 参考资料
👨💻 关于作者
Mr.Krab 🦀
- GitHub: 蟹老板仓库
- Email: pqjrkwem@gmail.com
📄 许可证
本项目仅供学习和演示使用,请勿用于商业用途。
感谢阅读!如有问题欢迎提 Issue 或 PR! ⭐