交付出件箱

← 返回出件箱

C 端「添加检查单」识别功能 · 复用报告解读 GPU1 27B 的物理 verify 台账

2026-08-22 · dev2 · 长官令:8+ 维严打 verify · 不漏证据 · 能复用就复用不新建 ·
报告解读功能切勿做任何改动
全部证据取自 origin/test(HEAD 86d47fac · 2026-08-21),每维带对照组。


结论先行

问题 结论
报告解读识别打的是不是 GPU1 27B ✅ 是。生产 VLM_SERVER_URL=http://100.68.51.72:8080,该端点跑 cyankiwi/Qwen3.6-27B-AWQ-INT4
C 端能不能复用 ✅ 能,且是一行调用。VLLMClient().extract(path, type) 是纯函数式,不碰 DB / 队列 / 进度总线
会不会动到报告解读 ✅ 不会。新建独立 service 自己调 extract,four_l_* 五个文件一行不改
要不要新建模型/端点 ❌ 不要。全部复用现有

一、报告解读识别全链路(逐跳 grep 追出来)

前端 upload-sheet.tsx
  → POST /api/h5/4l-report                     four_l_report.py:269
  → _save_upload(refraction/axial/oct/fundus)  four_l_report.py:66-69   存 /tmp + OSS
  → _schedule_orchestration()                  four_l_report.py:348
  → FourLOrchestrator                          four_l_orchestrator.py:221
      ├ VLMQueue 排队 / VLMProgressBus 推进度   :258-334      ← 报告解读专属
      ├ self.vllm.extract(p, image_type)        :290 / :408   ← 🔴 识别就是这一步
      ├ ocr_persistence_service                 → vision_exams + treatment_plans
      ├ record_report_image()                   four_l_report.py:372  → report_image 表
      └ QCRouter / four_l_summary_service / push_wechat / B1 RAG      ← 报告解读专属

维 1:4L 后端文件清单 —— git ls-tree 命中 6 个
(four_l_report.py / four_l_orchestrator.py / four_l_summary_prompt.py /
four_l_summary_service.py / ocr_persistence_service.py / vision_ocr_service.py)

维 2:four_l_report.py 路由 8 条,上传入口 = @router.post("/4l-report") :269

维 3:orchestrator 的识别调用点 —— grep 'self.vllm.extract' 命中 2 处(:290 主链 / :408 重试链);
对照组 four_l_orchestrator 的调用方命中 2 文件(four_l_report.py / vllm_client.py)⇒ 查法有效


二、GPU1 27B 身份确认(这一维查出一个坑)

来源 VLM_SERVER_URL 说明
.env.example:20 http://100.125.72.34:8080 ⚠️ dev 旧机 · 注释还写 Qwen2.5-VL-7B · 已过期
deploy/env.production.template:20 http://100.68.51.72:8080 ✅ 生产 = GPU1

维 4:100.68.51.72 全仓命中 76 次(对照组 100.125.72.34 命中 27 次)⇒ 前者是现行主机
维 5:该端点跑什么 —— b3_ingest_service.py:96-98

# vllm-main vision 端点 (Phase 38 Qwen3.6-27B AWQ-INT4 · 多模态)
VLLM_MODEL = os.getenv("VLLM_VISION_MODEL", "cyankiwi/Qwen3.6-27B-AWQ-INT4")

维 6:brain_gateway.py:1421 注释实证 ——
「Phase 38 · Qwen3.6-27B zero-shot 已强 · 不挂 LoRA(替换 vllm-main · LoRA 架构不兼容)」

🔴 对照组说明:全仓搜模型名,Qwen2.5-VL-7B 命中 85 次、27B 型号只在 vllm-main 相关 3 文件出现。
一开始我据 .env.example 以为是 7B —— 是 dev 模板过期,以 deploy/env.production.template 为准。


三、识别调用的确切参数(照抄即可)

维 7:VLLMClient.extract(image_path, image_type) —— vllm_client.py:704
- 内部:_call_with_json_retry(messages, max_tokens=512, adapter="")
- 🔴 adapter="" = 走 base 27B zero-shot,不挂 LoRA
- 图片以 data_url 内联(_image_to_data_url)

维 8:还有一个前置 classify(同文件 :660 区)判图类型,max_tokens=256、同样 adapter=""
返回 {"type": "refraction|axial|oct|fundus|unknown", "confidence": 0-1, "description": "..."}

维 9:VLLMClient.__init__(base_url=None, timeout=60) ——
self.base_url = base_url or os.getenv("VLM_SERVER_URL", "http://127.0.0.1:8080")
⇒ 可独立 new,不依赖任何上下文


四、抽出的字段(14 个 · 与 C 端检查单对得上)

维 10:four_l_report.py:232-250 实测字段清单

va_od  va_os                      裸眼视力 OD/OS
iop_od iop_os                     眼压 OD/OS
axial_length_od axial_length_os   眼轴 OD/OS
k1_od k2_od k1_os k2_os           角膜曲率 K1/K2 OD/OS
ref_od_ds ref_od_dc               球镜/柱镜 OD
ref_os_ds ref_os_dc               球镜/柱镜 OS

维 11:必填校验 four_l_orchestrator.py:41
REQUIRED_OCR_FIELDS = ("ref_od_ds","ref_os_ds","axial_length_od","axial_length_os") —— 缺一则短路
维 12:OCR_FIELD_RANGES(:46)做合理性校验,防 base 模型瞎编值


五、写库路径(C 端可直接复用同一张表)

维 13:ocr_persistence_service.persist_ocr_to_vision_exam() → vision_exams + treatment_plans
维 14:图片归档 record_report_image()(four_l_report.py:372)→ report_image 表 · fire-and-forget
⇒ 与已拍板 D-A(检查数据只用 vision_exams 一张表) 完全一致,不用新建表


六、复用方案(最小改动 · 报告解读零改)

# C 端新建 service —— 唯一要写的东西
from app.services.vllm_client import VLLMClient

client = VLLMClient()                       # 默认读 VLM_SERVER_URL → GPU1 27B
kind   = await client.classify(path)        # 可选:自动判图类型
data   = await client.extract(path, kind)   # ← 与报告解读同一个方法、同一台 GPU、同一个模型
# 之后自己写 vision_exams(复用 ocr_persistence_service 的 map 逻辑)

为什么不会动到报告解读:
- extract() 是纯函数式 —— 输入图片路径 + 类型,输出 dict,不碰 DB、不碰 VLMQueue、不碰 VLMProgressBus
- 报告解读专属的东西(VLMQueue / VLMProgressBus / QCRouter / four_l_summary_service /
push_wechat_subscription / B1 RAG)全在 four_l_orchestrator 里,C 端根本不进那个类
- ⇒ 不改 four_l_report.py / four_l_orchestrator.py / four_l_summary_service.py /
four_l_summary_prompt.py / vllm_client.py(只调用,不修改)


七、三个必须写进技术方案的注意点

  1. 🔴 「其他(视功能、眼压等)」这一类 classify 不认
    现有 classify 只返 refraction / axial / oct / fundus / unknown。C 端第 9 段「其他」会落 unknown ——
    按已定口径它只存不识别,正好不冲突,但实施时别去改 classify 的枚举(改了就动到报告解读)。

  2. 🔴 配额不能误扣(已拍板 D-B:档案内识别不吃秒读额度)
    extract() 本身不含任何 quota 逻辑(four_l_report.py grep 不到 quota 引用),
    秒读的 6 次配额是本期新建模型、加在 4L 入口 ⇒ C 端调 extract 天然不扣次。别把扣减写进 extract。

  3. ⚠️ dev 与生产的 VLM 地址不一致
    .env.example 还指向旧机 100.125.72.34。本地联调请用生产模板的 100.68.51.72:8080,
    否则会打到一台跑 7B 的旧机上,识别效果对不上。


云机环境 · 本文全部为 origin/test 源码物理 grep 证据,非推断;未在真机跑过 e2e。