首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >DeepSeek+Spring AI 实战:构建智能家庭医生应用全解析

DeepSeek+Spring AI 实战:构建智能家庭医生应用全解析

原创
作者头像
搜weiranit.fun
发布2026-08-18 14:31:35
发布2026-08-18 14:31:35
1390
举报

DeepSeek+Spring AI 实战:构建智能家庭医生应用全解析

随着大语言模型(LLM)技术的飞速发展,医疗健康领域正迎来智能化变革的新机遇。本文将以 DeepSeek 大模型和 Spring AI 框架为核心,从零到一搭建一个具备多轮对话、症状预分析、用药提醒和健康知识问答的 AI 家庭医生 应用。文章将深入技术细节,提供完整可运行的代码,并分享生产级落地的关键考量。


一、为什么选择 DeepSeek + Spring AI?

DeepSeek 作为国产高性能开源大模型,在医疗常识问答、逻辑推理和中文语义理解上表现优异,且支持 128K 上下文窗口,非常适合承载复杂的健康咨询场景。其 API 兼容 OpenAI 格式,调用便捷,成本可控。

Spring AI 是 Spring 生态下的 AI 应用开发框架,它抽象了与大模型的交互细节,提供了统一的 ChatClientPromptTemplateStreamingResponse 等组件,并内置了向量存储(RAG)、函数调用(Function Calling)等高级特性,能极大降低 Java 开发者接入 LLM 的门槛。

二者结合,可以快速构建一个健壮、可扩展、云原生友好的智能医疗应用。


二、系统架构与核心功能模块

2.1 整体架构

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────┐
│                     前端(Web/小程序)                  │
└─────────────────┬───────────────────────────────────────┘
                  │ RESTful API / SSE
┌─────────────────▼───────────────────────────────────────┐
│              Spring Boot 3.x + Spring AI                │
│  ┌───────────────┐  ┌──────────────┐  ┌─────────────┐ │
│  │ 对话管理模块   │  │ 症状分析引擎  │  │ 用药提醒服务 │ │
│  └───────────────┘  └──────────────┘  └─────────────┘ │
│  ┌───────────────┐  ┌──────────────┐  ┌─────────────┐ │
│  │ 上下文记忆仓储 │  │ RAG 知识库   │  │ 日志/监控   │ │
│  └───────────────┘  └──────────────┘  └─────────────┘ │
└─────────────────┬───────────────────────────────────────┘
                  │ DeepSeek API (HTTP/SSE)
┌─────────────────▼───────────────────────────────────────┐
│              DeepSeek 大模型(deepseek-chat)           │
└─────────────────────────────────────────────────────────┘

2.2 核心功能模块

  • 智能对话:支持多轮连续对话,维护会话上下文,理解用户健康诉求。
  • 症状预分析:根据用户描述的症状,给出可能的疾病方向及就医建议(非诊断,仅辅助)。
  • 用药提醒:解析用户输入的药品名称、用法用量,生成定时提醒计划(模拟)。
  • 健康知识问答:结合内置医疗知识库(RAG),回答常见病预防、营养膳食等问题。
  • 流式响应:采用 Server-Sent Events(SSE)实现逐字输出,提升用户体验。

三、项目初始化与依赖配置

3.1 创建 Spring Boot 项目

使用 Spring Initializr 或 IDEA 创建项目,选择 JDK 17+,依赖如下:

代码语言:javascript
复制
<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI OpenAI 起步依赖(兼容 DeepSeek) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.0.0-M6</version>
    </dependency>
    <!-- 用于 SSE 流式输出 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <!-- 缓存和状态管理 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.2 配置 DeepSeek 接入

application.yml 中配置 DeepSeek API 地址和密钥(注意 DeepSeek 兼容 OpenAI 接口):

代码语言:javascript
复制
spring:
  ai:
    openai:
      base-url: https://api.deepseek.com/v1
      api-key: ${DEEPSEEK_API_KEY}   # 环境变量注入
      chat:
        options:
          model: deepseek-chat
          temperature: 0.7
          max-tokens: 2048

安全提示:切勿将 API Key 硬编码,建议使用环境变量或配置中心。


四、核心代码实现

4.1 构建 ChatClient 并配置增强功能

Spring AI 的 ChatClient 是核心交互对象,我们通过配置类创建具备函数调用和记忆能力的 Bean。

代码语言:javascript
复制
@Configuration
public class AiConfig {

    @Bean
    public ChatClient chatClient(OpenAiChatModel chatModel) {
        return ChatClient.builder(chatModel)
                // 系统提示词,定义角色为家庭医生
                .systemPrompt("""
                        你是一位专业的AI家庭医生助手,拥有丰富的医学常识。请遵循以下原则:
                        1. 仅提供健康建议和科普信息,不代替线下诊断。
                        2. 对症状进行逻辑分析,给出可能的病因及建议就医科室。
                        3. 回答要温和、清晰,使用通俗语言。
                        4. 当用户描述紧急症状(如胸痛、大出血)时,立即提醒拨打120。
                        """)
                // 注册自定义函数(用药提醒)
                .function("setMedicineReminder", new MedicineReminderFunction())
                .build();
    }
}

4.2 对话管理服务(核心业务)

我们设计 HealthConsultationService,负责维护会话上下文(使用内存缓存,生产可替换为 Redis)并调用 ChatClient。

代码语言:javascript
复制
@Service
public class HealthConsultationService {
    private final ChatClient chatClient;
    private final Cache<String, List<Message>> sessionCache;

    public HealthConsultationService(ChatClient chatClient) {
        this.chatClient = chatClient;
        this.sessionCache = CacheBuilder.newBuilder()
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .maximumSize(1000)
                .build();
    }

    /**
     * 同步对话(非流式)
     */
    public String chat(String sessionId, String userMessage) {
        // 获取历史消息
        List<Message> history = sessionCache.get(sessionId, ArrayList::new);
        // 构建用户消息
        UserMessage userMsg = new UserMessage(userMessage);
        history.add(userMsg);

        // 调用模型
        ChatResponse response = chatClient.prompt()
                .messages(history)
                .call()
                .chatResponse();

        // 保存助手回复
        AssistantMessage assistantMsg = response.getResult().getOutput();
        history.add(assistantMsg);
        sessionCache.put(sessionId, history);

        return assistantMsg.getText();
    }

    /**
     * 流式对话(SSE),返回 Flux<String>
     */
    public Flux<String> streamChat(String sessionId, String userMessage) {
        List<Message> history = sessionCache.get(sessionId, ArrayList::new);
        UserMessage userMsg = new UserMessage(userMessage);
        history.add(userMsg);

        // 流式调用
        Flux<ChatResponse> flux = chatClient.prompt()
                .messages(history)
                .stream()
                .chatResponse();

        // 收集完整响应后保存至历史
        return flux.doOnComplete(() -> {
            // 注意:流式响应无法直接获取完整消息,需另存拼接结果
            // 可采用 StringBuilder 在 subscriber 中拼接,这里简化处理
        }).map(response -> {
            if (response.getResult() != null) {
                return response.getResult().getOutput().getText();
            }
            return "";
        });
    }
}

改进点:流式场景下,完整回复需要在外层拼接,这里为简化仅展示逐字输出。生产环境建议使用 FluxreducecollectList 获取完整内容再存历史。

4.3 函数调用(Function Calling)实现用药提醒

Spring AI 支持将 Java 方法注册为可被模型调用的工具。当用户说“我每天需要吃两次阿莫西林,每次一粒”时,模型可自动调用 setMedicineReminder 函数。

代码语言:javascript
复制
@Component
@Description("设置用药提醒,参数:药品名称、用法、间隔小时数")
public class MedicineReminderFunction implements Function<MedicineReminderFunction.Request, MedicineReminderFunction.Response> {

    @Override
    public Response apply(Request request) {
        // 实际可存入数据库或发送通知
        log.info("设置用药提醒:药品={}, 用法={}, 间隔={}小时", 
                 request.medicineName, request.usage, request.intervalHours);
        // 模拟生成提醒ID
        return new Response("提醒已设置,ID: " + UUID.randomUUID().toString());
    }

    public record Request(String medicineName, String usage, Integer intervalHours) {}
    public record Response(String result) {}
}

在配置中通过 .function("setMedicineReminder", new MedicineReminderFunction()) 注册后,模型在需要时会自动生成 JSON 参数调用该方法。

4.4 构建 RAG 知识库(检索增强)

对于常见病科普、药品说明书等静态知识,我们使用向量检索 + 文档加载器。这里以 Spring AI 的 VectorStoreDocumentReader 为基础(需引入 spring-ai-pgvector-store 或内存实现)。

代码语言:javascript
复制
@Service
public class HealthKnowledgeService {
    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public String askWithRag(String question) {
        // 1. 向量检索相似文档
        List<Document> relevantDocs = vectorStore.similaritySearch(question, 3);
        String context = relevantDocs.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n---\n"));

        // 2. 构建增强提示
        String prompt = """
                基于以下医学知识资料,回答用户问题。若资料不足,请根据通用医学常识回答。
                资料:
                %s
                问题:%s
                回答:""".formatted(context, question);

        return chatClient.prompt(prompt).call().content();
    }
}

实际部署时,需预先将医疗指南、药品说明等文档切分、向量化并存入 PGVector 或 Elasticsearch。

4.5 控制器层:暴露 REST 和 SSE 接口

代码语言:javascript
复制
@RestController
@RequestMapping("/api/health")
public class HealthController {
    private final HealthConsultationService consultationService;

    @PostMapping("/chat")
    public ResponseEntity<String> chat(@RequestParam String sessionId, @RequestBody String message) {
        return ResponseEntity.ok(consultationService.chat(sessionId, message));
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> streamChat(@RequestParam String sessionId, @RequestParam String message) {
        return consultationService.streamChat(sessionId, message)
                .map(content -> ServerSentEvent.<String>builder()
                        .data(content)
                        .event("message")
                        .build());
    }
}

前端可通过 EventSourcefetch 流式接收。


五、流式响应的完整实现与优化

为了在流式场景下正确保存对话历史,我们需要重构 streamChat 方法,使用 Fluxbuffer 或自定义 Subscriber 拼接完整内容。

代码语言:javascript
复制
public Flux<String> streamChatWithHistory(String sessionId, String userMessage) {
    List<Message> history = sessionCache.get(sessionId, ArrayList::new);
    UserMessage userMsg = new UserMessage(userMessage);
    history.add(userMsg);

    Flux<ChatResponse> flux = chatClient.prompt()
            .messages(history)
            .stream()
            .chatResponse();

    // 用于拼接完整回复
    StringBuilder fullReply = new StringBuilder();

    return flux.map(response -> {
                String chunk = response.getResult().getOutput().getText();
                fullReply.append(chunk);
                return chunk;
            })
            .doOnComplete(() -> {
                // 将完整回复作为 AssistantMessage 存入历史
                AssistantMessage assistantMsg = new AssistantMessage(fullReply.toString());
                history.add(assistantMsg);
                sessionCache.put(sessionId, history);
            })
            .doOnError(e -> log.error("流式对话异常", e));
}

这样既保证了实时输出,又不丢失上下文。


六、异常处理与降级策略

医疗场景对稳定性要求极高,我们需处理以下异常:

  • API 限流 / 超时:配置重试机制(Spring Retry)和超时设置。
  • 模型输出不安全内容:使用内容过滤(可集成阿里云绿网或本地正则)。
  • 会话过期:返回友好提示,引导用户重新开始。

示例:使用 @Retryable 处理 API 调用失败。

代码语言:javascript
复制
@Retryable(value = {AiApiException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public String chatWithRetry(String sessionId, String message) {
    // ...
}

同时,提供降级响应(如“系统繁忙,请稍后再试”)。


七、性能与成本优化建议

  1. 上下文窗口管理:限制历史消息数量(如最近10轮),避免超过 128K token 上限。
  2. 缓存常见问答:对于高频问题(如“感冒了怎么办”),使用本地缓存或 Redis 缓存结果,减少 API 调用。
  3. 批量处理:若需推送健康资讯,可使用 Batch API(DeepSeek 支持)。
  4. 监控与日志:接入 SLF4J + ELK,记录每次调用的 token 消耗和延迟,用于成本核算。

八、部署与云原生适配

本应用可一键部署至腾讯云 TKE(Kubernetes)或轻量应用服务器。提供 Dockerfile:

代码语言:javascript
复制
FROM openjdk:17-jdk-slim
COPY target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

配置环境变量 DEEPSEEK_API_KEY,并可结合腾讯云 API 网关对外暴露,启用限流和鉴权。


九、总结与展望

本文完整展示了如何利用 DeepSeekSpring AI 构建一个具备生产能力的 AI 家庭医生应用。我们实现了多轮对话、流式输出、函数调用、RAG 检索等关键技术,并充分考虑了异常处理和性能优化。

该应用可作为远程问诊、健康管理的智能底座,未来可扩展接入医学影像分析、电子病历结构化等更复杂功能。DeepSeek 的高性价比和 Spring AI 的标准化接口,使得 Java 开发者能够快速拥抱 LLM 时代,为医疗数字化注入新动能

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • DeepSeek+Spring AI 实战:构建智能家庭医生应用全解析
    • 一、为什么选择 DeepSeek + Spring AI?
    • 二、系统架构与核心功能模块
      • 2.1 整体架构
      • 2.2 核心功能模块
    • 三、项目初始化与依赖配置
      • 3.1 创建 Spring Boot 项目
      • 3.2 配置 DeepSeek 接入
    • 四、核心代码实现
      • 4.1 构建 ChatClient 并配置增强功能
      • 4.2 对话管理服务(核心业务)
      • 4.3 函数调用(Function Calling)实现用药提醒
      • 4.4 构建 RAG 知识库(检索增强)
      • 4.5 控制器层:暴露 REST 和 SSE 接口
    • 五、流式响应的完整实现与优化
    • 六、异常处理与降级策略
    • 七、性能与成本优化建议
    • 八、部署与云原生适配
    • 九、总结与展望
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档