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_* 五个文件一行不改 |
| 要不要新建模型/端点 | ❌ 不要。全部复用现有 |
前端 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)⇒ 查法有效
| 来源 | 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,不依赖任何上下文
维 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 模型瞎编值
维 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(只调用,不修改)
🔴 「其他(视功能、眼压等)」这一类 classify 不认
现有 classify 只返 refraction / axial / oct / fundus / unknown。C 端第 9 段「其他」会落 unknown ——
按已定口径它只存不识别,正好不冲突,但实施时别去改 classify 的枚举(改了就动到报告解读)。
🔴 配额不能误扣(已拍板 D-B:档案内识别不吃秒读额度)
extract() 本身不含任何 quota 逻辑(four_l_report.py grep 不到 quota 引用),
秒读的 6 次配额是本期新建模型、加在 4L 入口 ⇒ C 端调 extract 天然不扣次。别把扣减写进 extract。
⚠️ dev 与生产的 VLM 地址不一致
.env.example 还指向旧机 100.125.72.34。本地联调请用生产模板的 100.68.51.72:8080,
否则会打到一台跑 7B 的旧机上,识别效果对不上。
云机环境 · 本文全部为 origin/test 源码物理 grep 证据,非推断;未在真机跑过 e2e。