2026-08-04-report-parsing-migration-design.md 3.6 KB

报告解析迁移设计:Java → LangGraph(Python 算法 + LLM 兜底)

背景

当前健康报告(菌群检测 PDF)解析在 Java 侧通过 PdfParseService(PDFBox + 规则解析)实现,但存在以下问题:

  • 格式兼容性差:inline 格式频繁抛出 StringIndexOutOfBoundsException
  • 采集不全:Python 版 extract_full_report_v5.py 覆盖 13 个分组,Java 版只有 8 个
  • 维护成本高:同一套解析逻辑在 Java 和 Python 各维护一份

目标架构

用户上传 PDF
    ↓
Java HealthReportController
    ├─ 保存文件到磁盘
    ├─ 调 LangGraph /api/v1/report/parse
    └─ 返回结构化数据给前端
         ↓
LangGraph report_parse_agent
    ├─ Python 算法解析(复用 extract_full_report_v5.py)
    │   ├─ 成功 → 返回结构化 JSON
    │   └─ 失败 → LLM 兜底解析
    └─ 返回结构化数据给 Java

组件职责

Java 侧(HealthReportController

  • 接收文件上传(MultipartFile)
  • 保存文件到磁盘(/tmp/uploads/ 或配置路径)
  • 调用 LangGraph 解析接口(POST /api/v1/report/parse
  • 将 LangGraph 返回的结构化数据转换为 ParsedReportPayload.Payload
  • 后续流程不变(草稿 → 确认 → 入库)

LangGraph 侧(新增 report_parse_agent

  • 接收文件路径
  • 调用 Python 解析脚本(从 extract_full_report_v5.py 提取核心逻辑)
  • 算法解析失败时,调用 LLM 进行结构化提取
  • 返回统一格式的 JSON

API 接口设计

LangGraph 解析接口

POST /api/v1/report/parse
Content-Type: application/json

Request:
{
    "file_path": "/tmp/uploads/xxx.pdf",
    "family_id": 123,
    "user_id": 456
}

Response:
{
    "code": 200,
    "data": {
        "summary": { ... },
        "disease_risks": [ ... ],
        "indicators": [ ... ],
        "gut_flora": [ ... ],
        "probiotic_species": [ ... ],
        "food_suitability": [ ... ],
        "extracted_name": "某人",
        "extracted_gender": "male",
        "extracted_age": 53
    }
}

数据流

  1. 用户上传 PDF → Java HealthReportController.parsePreview()
  2. Java 保存文件 → POST /api/v1/report/parse?file_path=...
  3. LangGraph 收到请求: a. 调用 Python 算法解析模块(复用 extract_full_report_v5.py 核心逻辑) b. 算法成功 → 返回结构化数据 c. 算法失败 → 调用 LLM(GPT-4o 等)提取结构化数据
  4. LangGraph 返回结构化 JSON 给 Java
  5. Java 将 JSON 转为 ParsedReportPayload.Payload,后续流程不变

算法解析模块(Python)

extract_full_report_v5.py 提取核心逻辑,封装为可调用的 Python 函数:

def parse_report_pdf(file_path: str) -> dict:
    """解析菌群报告 PDF,返回结构化数据"""
    # 1. 读取 PDF 文本(PyPDF2)
    # 2. 检测格式(triplet / inline)
    # 3. 提取各分组数据
    # 4. 返回统一格式字典

LLM 兜底

当算法解析失败时(返回空数据或关键字段缺失),调用 LLM:

async def parse_with_llm(file_path: str) -> dict:
    """LLM 兜底解析"""
    # 1. 提取 PDF 文本
    # 2. 构造 prompt,要求 LLM 按指定 JSON Schema 输出
    # 3. 解析 LLM 输出
    # 4. 返回结构化数据

迁移步骤

  1. 在 LangGraph 中创建 report_parse_agent(算法 + LLM 兜底)
  2. 创建 POST /api/v1/report/parse 接口
  3. 修改 Java HealthReportController.parsePreview(),先调 LangGraph,失败时回退到本地 Java 解析
  4. 验证通过后,移除 Java 端的 PdfParseService
  5. 编写测试用例覆盖 triplet/inline 两种格式