用 Spring Boot + Ollama + Dify,让 AI 帮你自动审核体检报告

一、背景与挑战

在招聘流程中,HR 需要审核大量候选人的体检报告,手动核对血压、血糖、血脂等十几项指标是否达标。传统方式面临三个核心痛点:

痛点具体表现
效率低一份报告人工核对需 5-10 分钟,批量处理成本高
格式多样不同医院排版各异,字段名称不统一("WBC" vs "白细胞计数")
标准不一同一指标在不同医院的参考范围可能不同

二、技术选型

经过对比,我选择了"OCR + 本地大模型"的混合架构:

组件选型理由
OCR 引擎百度 OCR(主)+ Tesseract(备)百度 OCR 对医疗报告有专项优化,Tesseract 做离线兜底
本地大模型Ollama + Qwen2.5:7B开源免费,支持中文,可本地部署
工作流编排Dify可视化 Prompt 编排,提供标准化 API
后端框架Spring Boot 3.x + Java 17企业级微服务架构,便于集成

三、整体架构

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  上传报告    │───▶│  OCR 识别   │───▶│  大模型解析  │───▶│  录用建议    │
│  (PDF/图片) │    │ (百度/Tesseract) │    │ (Qwen2.5)  │    │  (结构化输出)│
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

3.1 核心流程

  1. 文件上传:支持 PDF、Word、JPG、PNG 等格式

  2. 文本提取:文本型 PDF 直接提取,扫描件走 OCR

  3. 大模型解析:提取 14 项体检指标 + 被检人基本信息

  4. 规则校验:基于参考范围二次确认

  5. 录用建议生成:输出 HR 可直接阅读的结构化报告

3.2 多策略容灾设计

层级主方案备用方案
OCR百度 OCR(云 API)Tesseract(本地离线)
解析大模型(语义理解)正则表达式(兜底匹配)

四、核心实现

4.1 OCR 引擎接口设计

public interface OcrEngine {
    String extractText(byte[] imageBytes) throws IOException;
    String getEngineName();
    boolean isAvailable();
}

百度 OCR 实现要点:

@Component
public class BaiduOcrEngine implements OcrEngine {
    
    private final BaiduOcrClient baiduOcrClient;
    
    @Override
    public String extractText(byte[] imageBytes) throws IOException {
        // 1. 图片压缩(百度 OCR 限制图片 < 4M)
        BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
        byte[] preparedBytes = prepareImage(image);
        
        // 2. 调用百度 OCR
        JSONObject response = baiduOcrClient.basicGeneral(preparedBytes, options);
        
        // 3. 解析结果
        return parseResponse(response);
    }
}

4.2 Dify 工作流配置

LLM 节点 Prompt(核心指令):

你是一个专业的体检报告解析助手。请从以下体检报告文本中提取:

一、被检人基本信息:姓名、性别、年龄、电话、身份证号、体检日期
二、14项体检指标:血常规(3项)、血压(2项)、血脂(2项)、血糖(1项)、肾功能(3项)、同型半胱氨酸(1项)、心电图、胸部DR

请严格按照 JSON 格式输出,status 取值为:normal / abnormal / missing

4.3 Java 规则引擎配置

@Configuration
public class RuleConfig {
    @Bean
    public List<CheckRule> getCheckRules() {
        return Arrays.asList(
            CheckRule.builder()
                .id("tg").name("甘油三酯")
                .aliases(Arrays.asList("TG", "甘油三酯"))
                .unit("mmol/L")
                .min(0.5).max(1.7)
                .passMsg("甘油三酯正常")
                .failMsg("甘油三酯异常")
                .build()
            // ... 其他指标
        );
    }
}

4.4 异步处理优化

针对大模型推理耗时(30-60秒),设计了异步处理方案:

@PostMapping("/upload")
public ResponseEntity<Map<String, String>> upload(@RequestParam("file") MultipartFile file) {
    String taskId = UUID.randomUUID().toString();
    
    CompletableFuture.runAsync(() -> {
        String ocrText = documentParserService.extractText(file);
        DifyResponse result = difyClient.parseReport(ocrText);
        resultCache.put(taskId, result);
    });
    
    return ResponseEntity.ok(Map.of("taskId", taskId, "status", "processing"));
}

五、踩坑与解决方案

5.1 百度 OCR 图片尺寸超限

问题:image size error (code=216202)

原因:PDF 渲染为图片后尺寸过大(5162×7308)

解决方案:

private byte[] prepareImage(BufferedImage image) {
    // 1. 缩放至最长边 1024 像素
    BufferedImage resized = resizeImage(image, 1024);
    // 2. 转为 JPG 格式(PNG 体积太大)
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(resized, "jpg", baos);
    return baos.toByteArray();
}

5.2 大模型推理速度慢

问题:7B 模型在 CPU 上推理需 40-60 秒

解决方案:

  1. 换用更小模型:qwen2.5:1.5b(速度提升 3-5 倍)

  2. 异步处理 + 轮询结果

  3. 增加超时配置

5.3 参考范围不一致

问题:不同医院同一指标的参考范围不同

解决方案:规则配置化,支持按医院动态调整

# 扩展设计
rules:
  tg:
    default: {min: 0.5, max: 1.7}
    "南京秦淮新城医院": {min: 0.5, max: 1.7}
    "北京协和医院": {min: 0.56, max: 1.7}

六、项目成果

指标

效果
指标提取准确率>95%
单份报告处理时间25-45秒
支持指标数量14项
支持文件格式PDF、Word、JPG、PNG、BMP

七、技术栈全景

text

┌─────────────────────────────────────────────────────────────┐
│  前端层:HTTP API / Postman                                │
├─────────────────────────────────────────────────────────────┤
│  业务层:Spring Boot 3.x / Java 17                         │
│          - 文件解析:PDFBox / Apache POI                    │
│          - 规则引擎:自定义配置化规则                       │
│          - 服务治理:Eureka / Resilience4j                  │
├─────────────────────────────────────────────────────────────┤
│  AI 层:百度 OCR + Dify + Ollama + Qwen2.5                 │
├─────────────────────────────────────────────────────────────┤
│  部署层:Docker Compose / Maven                            │
└─────────────────────────────────────────────────────────────┘

八、总结与展望

经验总结

  1. 大模型是"理解"问题的答案,不是"匹配"问题:正则表达式适合固定格式,大模型适合理解语义,两者结合效果最佳

  2. 容灾设计要足够务实:用百度 OCR 做主力,Tesseract 做备用;用大模型做主力,正则做备用

  3. 异步处理是长耗时任务的标配:不要把 60 秒的推理时间放在同步请求里

优化方向

  • 针对特定医院报告格式进行微调

  • 增加更多异常指标的业务解释

  • 支持批量报告处理

  • 考虑数据内部流转,可以深耕正则匹配替代外部API,防止数据泄露

同时总结了工具安装的教程,详见Dify + Ollama + Docker Desktop 本地部署配置指南-CSDN博客

欢迎各位大神评论区留言、交流

更多推荐