{"id":51002573,"url":"https://github.com/chenhaodev/med-agent-vision","last_synced_at":"2026-06-20T16:32:37.969Z","repository":{"id":364413544,"uuid":"1266288038","full_name":"chenhaodev/med-agent-vision","owner":"chenhaodev","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-12T22:43:47.000Z","size":262,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-13T00:22:16.748Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chenhaodev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T13:33:41.000Z","updated_at":"2026-06-12T22:43:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chenhaodev/med-agent-vision","commit_stats":null,"previous_names":["chenhaodev/med-agent-vision"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/chenhaodev/med-agent-vision","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fmed-agent-vision","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fmed-agent-vision/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fmed-agent-vision/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fmed-agent-vision/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chenhaodev","download_url":"https://codeload.github.com/chenhaodev/med-agent-vision/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenhaodev%2Fmed-agent-vision/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34578089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T16:32:36.643Z","updated_at":"2026-06-20T16:32:37.953Z","avatar_url":"https://github.com/chenhaodev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 居家健康图像分析管线 · med-agent-vision\n\n\u003e 给**手机随手拍的健康图像**（药盒、化验单……）做结构化解读，\n\u003e 但只在**值得花钱的地方**花钱：能在自己电脑上跑的 8B 小模型先看一遍，\n\u003e 看不清的字才放大重看，仍然拿不准的才上云端多模型会诊，剂量这种要命的字段额外查三道。\n\n`本地一线 = minicpm-v4.5（ollama）` · `云端会诊 = Qwen3-VL 系 / GLM-4.5V（siliconflow）` · `编排 = LangGraph` · `剂量两道防线` · `6 字段 × {值, 证据}`\n\n---\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEnglish TL;DR\u003c/b\u003e（for non-Chinese readers \u0026 LLM agents — data and docs are Chinese by design）\u003c/summary\u003e\n\n**What**: a home-health image-understanding pipeline. The first landed track (T1) reads a **pill-box\nphoto** into six grounded fields (`drug_name`, `dose{number,unit}`, `usage`, `expiry_date`,\n`batch_number`, `contraindications`), each carrying `{value, evidence}` and able to **abstain**\n(`UNCERTAIN`) rather than guess. The pipeline is a **router → local first-pass → confidence-gated\nzoom → selective cloud Mixture-of-Agents → arbiter** cascade: a local 8B model handles the easy 60–80%,\nhigh-resolution detail triggers a ZoomEye-style crop-and-reask loop, and only the genuinely uncertain\nor high-risk samples escalate to cloud MoA.\n\n**Why a cascade and not \"always MoA\"**: every MoA layer is an N× call multiplier, and\n[Self-MoA (arXiv:2502.00674)](https://arxiv.org/abs/2502.00674) shows that **mixing weaker proposers can\nlower aggregate quality**. So the MoA topology (Self vs Mixed) is treated as an *experiment to settle on\na real eval set*, not a default. In vision, the high-value diversity is **viewpoint** (full image / zoom\ncrop / OCR transcription), not just model heterogeneity — see 「MoA：怎么选、怎么融、怎么搭」 below.\n\n**The dosage obsession**: confusing 5 mg with 50 mg is a one-vote-veto error (whole item scores 0). Two\ndefenses guard it — `dose_norm.py` (deterministic rule normalization) and a `verify_dose` graph node\n(an existence probe that forces abstention when the model hallucinates a dose from the usage line).\n\n**Quick start**: `uv sync` → `cp .env.example .env` (set `SILICONFLOW_API_KEY`, optional for local\nsmoke) → `uv run pytest` (85 tests) → `uv run python scripts/smoke_t1.py` (end-to-end, local only).\n\n**For agents**: engineering context, conventions, gotchas, and the open TODO list live in\n[`CLAUDE.md`](./CLAUDE.md); the full design rationale is `docs/00–06`, empirical results `docs/07`,\nand a per-design-point ✅/🟡/⬜ status matrix in [`docs/08`](./docs/08-完成度核对.md).\n\n\u003c/details\u003e\n\n---\n\n## 一分钟看懂（无需算法或医学背景）\n\n**遇到的问题**：在家拍一张药盒照片，想让程序自动读出「药名、剂量、用法、有效期、批号、禁忌」六项。\n难点有两个——**一是小字**（剂量、批号、有效期天然是小字，手机原图降采样后必丢细节）；\n**二是最怕一本正经地编**（业内叫「幻觉」）：图里压根没印剂量，模型却从「一次1片」脑补出一个剂量来。\n而 `5mg` 和 `50mg` 是两种药，**读错剂量可能出人命**。\n\n**这个项目怎么做**：不追求「一个最强大模型一锤定音」，而是搭一条**会自己掂量成本的流水线**——\n\n1. **先用能在自己电脑上跑的 8B 小模型看一遍**（本地、免费），清晰的正面照到这一步就够了；\n2. **哪个字段看不清，就把那块区域裁出来放大再问一次**（还是本地小模型，秒级，免费）——\n   用「多看几眼」换「换个更大的模型」，这是 8B 约束下最划算的杠杆；\n3. **还拿不准、或者是高风险字段，才上云端多个模型「会诊」**（花钱，但只对少数难题花）；\n4. **剂量这一项额外查三道**：规则归一 + 存在性探测 + 一票否决判分——宁可它说「看不清」，绝不让它编。\n\n\u003e **一句话**：每一层只为上一层解决不了的样本付费。简单的停在本地，难的才升级，要命的字段查到底。\n\n---\n\n## 核心概念（先读这 6 个词）\n\n后面反复用到，先用大白话讲清楚（更全的解释见文末[名词速查](#附录名词速查)）：\n\n- **级联（cascade）**：流水线分层，**置信度高就早停**，不确定才进下一层。本地 → zoom → 云端 MoA → 终审，\n  逐层变贵。设计目标是让 60–80% 的简单流量停在本地（免费）。\n- **置信度门控 zoom**：本地一线输出时，**拿不准的字段自报 `UNCERTAIN`**；只对这些字段做「定位 bbox → 裁剪放大 → 单字段重问」，\n  每图最多 3 次（预算上限）。这是 [ZoomEye](https://arxiv.org/abs/2411.16044) 的免训练简化版——用推理时计算换模型规模。\n- **MoA（Mixture-of-Agents，多模型会诊）**：多个 proposer 各出一份草稿，一个更强的 **aggregator** 综合裁决。\n  本项目把它当成**升级层**，不是默认全开（理由见[专章](#moa怎么选怎么融怎么搭核心设计)）。\n- **弃权（abstention）**：任何字段看不清/图中不存在，值填 `UNCERTAIN` 并在 evidence 写明原因。\n  **宁可弃权，不可猜测**——判分时弃权只算「漏」(FN)、不罚「谎」(FP)。\n- **证据接地（evidence grounding）**：每个字段附「从图中哪个位置读到的」，既供 aggregator 核对，也供判分罚「无依据的自信」。\n- **剂量一票否决**：剂量数字或单位答错，**整题直接 0 分**（哪怕其余五项全对）。这是把「用药安全」写进 metric 的方式。\n\n---\n\n## 看它怎么跑\n\n一条命令端到端跑通本地全链路（无需云端 key）：\n\n```bash\nuv run python scripts/smoke_t1.py        # 合成药盒图 → route → local_infer → (zoom?) → 直出/MoA → verify_dose\n```\n\n```text\n[route]        scene=pillbox quality=ok\n[local_infer]  6 字段草稿；dose={\"number\":\"0.25\",\"unit\":\"g\"}  ✓ 无 UNCERTAIN\n[gate]         无弃权字段 → 本地直出 (0 次额外调用)\n[verify_dose]  evidence 含「规格」→ 信任，免探测\n→ score 1.0   latency ~47s   (本地 8B, 模型热加载后)\n```\n\n而当图里**根本没印剂量**时（合成集 safety-02），两道剂量防线如何兜底：\n\n```text\n[local_infer]  dose={\"number\":\"1\",\"unit\":\"片\"}  ← 从用法行「一次1片」幻觉出剂量\n[verify_dose]  evidence 不含规格关键词 → 触发存在性探测\n               探测：\"图中有独立剂量标注吗？\" → {\"present\": false}\n               → 强制弃权 dose={\"number\":\"UNCERTAIN\", ...,\n                  \"evidence\":\"存在性校验未通过: 图中未见独立剂量标注\"}\n→ 剂量否决率 1.0 → 0   (实测 docs/07 §4)\n```\n\n\u003e 这两段演示「级联早停」和「剂量兜底」两个核心机制。完整基线见[基线实测](#基线实测合成-7-题)。\n\n---\n\n## MoA：怎么选、怎么融、怎么搭（核心设计）\n\n\u003e 这是本项目**最花心思**的部分，也是从「文本 MoA」迁到「视觉 MoA」时最容易踩错的地方。\n\u003e 完整论证见 `docs/00 §3`、`docs/03 §4`、`docs/04`。\n\n### 1. 为什么不一上来就全量 MoA\n\n- **成本/时延**：MoA 每层是 N 倍调用。居家场景大多数输入是简单的（一张清晰正面照），\n  级联让 60–80% 流量停在本地，把 MoA 的钱省给真正的难题。\n- **Self-MoA 警告**：[Rethinking Mixture-of-Agents (arXiv:2502.00674)](https://arxiv.org/abs/2502.00674)\n  证明——当 proposer 之间质量差距大时，**混入弱模型会拉低聚合质量**，单一最强模型多次采样（Self-MoA）\n  常胜过异构混合（Mixed-MoA）。所以「异构混更好」不是默认真理，**必须用自建评测集实测再定拓扑**。\n  本项目把 Self vs Mixed 做成一个**可切换的实验旋钮**（`build_t1_graph(moa=\"mixed\"|\"self\")`），等真实集来裁决。\n\n### 2. 视觉 MoA 的「多样性」从哪来（按优先级）\n\n经典 MoA（[arXiv:2406.04692](https://openreview.net/forum?id=h0ZfDIrj7T)）的增益，来自**聚合器见到多样化的草稿**。\n但视觉任务里，多样性**不应该只靠换模型**——同一张图换个视角看，往往比换个模型更有用：\n\n| 优先级 | 多样性来源 | 在本项目的落地 |\n|:---:|------|------|\n| **1** | **输入多样性** | 全图 / zoom 裁剪放大区域 / 预处理图——**同一模型看不同视角**。zoom 产出的裁剪图直接复用为 MoA proposer 的输入（`zoom_images_b64` 顺着 state 流到 propose 节点） |\n| **2** | **角色多样性** | 同一图、不同提示词：**抄写员**（逐字转写，不理解不纠错）/ **提取员**（结构化六字段）/ **质疑者**（拿别人的草稿逐字段挑错） |\n| **3** | **模型多样性** | minicpm / Qwen3-VL-8B / GLM-4.5V——**仅当评测证明有增益时才保留**（呼应 Self-MoA 警告） |\n\n\u003e 关键洞察：**zoom 循环和 MoA 不是二选一，是流水线的上下游**——zoom 的中间产物（裁剪图）正好是 MoA 最廉价的「输入多样性」来源。\n\n### 3. 怎么融（aggregator 的裁决规则）\n\n聚合不是「投票取多数」，而是让一个**更强的会思考的模型**（`Qwen3-VL-30B-A3B-Thinking`）看着原图当裁判：\n\n```\n输入：原图（+ zoom 裁剪图）+ 三份材料（抄写稿 / 提取稿 / 异议清单）\n规则：① 各方一致        → 采纳\n      ② 有分歧          → 以原图为准，重新读取该字段\n      ③ 仍不确定        → 填 UNCERTAIN（继续弃权，不强行裁定）\n      ④ 高危字段分歧    → 额外输出 escalate=true，把球踢给终审\n```\n\nSelf-MoA 拓扑下，「三份材料」换成**同一个 30B 模型高温（temperature 0.7）独立采样的三份提取稿**，\n裁决规则相应改为「多数一致采纳 / 三份各异则重读」。两套裁决提示词都在 `prompts/t1.py`。\n\n### 4. 各角色用哪个模型（docs/00 §2 角色表）\n\n| 层 | 角色 | 模型 | 为什么是它 |\n|------|------|------|------|\n| L1 一线 | 本地全能 + OCR 初筛 | `minicpm-v4.5`（8B，本地） | OCRBench 超 GPT-4o-latest，支持 1.8M 像素任意宽高比，视觉 token 少 4 倍 |\n| L2 proposer | 第二意见 | `Qwen3-VL-8B-Instruct`（云） | 继承 235B 旗舰能力的低成本款 |\n| L2 proposer | 异构第三方 + 工具调用 | `GLM-4.5V`（云） | 同规模 SOTA 视觉理解，与 Qwen 系异源 |\n| L2 aggregator | 综合裁决 / Self-MoA 采样源 | `Qwen3-VL-30B-A3B-Thinking`（云） | MoE 激活 3B 性价比高，会思考，适合做裁判 |\n| L3 终审 | 高危字段仲裁 | `Qwen3-VL-32B-Thinking`（云） | 链路上最大可用的稠密 Thinking 模型 |\n\n\u003e ⚠️ **实测修正**：siliconflow 实际**没有** docs/00 设想的 GLM-4.6V 与 Qwen3-VL-235B，\n\u003e 已分别降级为 GLM-4.5V 与 32B-Thinking（`config.py` 有注释留痕）。设计设想要对照接口实况核验，这是一例。\n\n### 5. 为什么用 LangGraph 编排（而不是 CrewAI）\n\n`Router → Cascade → 升级` 是教科书式的**条件边 + 循环边 + Send 扇出**：置信度门控 zoom 是带预算的循环边，\nMoA 并行 proposer 是 Send 扇出到聚合节点，高危升级是条件边。CrewAI 的角色抽象上手快，但\n**做不到这种字段级精确路由**；DSPy 则被定位为**离线提示词编译器**（用评测集当训练信号），不进在线链路。\n四框架是分工，不是四选一——详见 `docs/04`。\n\n---\n\n## 它能测出什么（判分口径）\n\n判分函数 `scoring/field_f1.py` 把「用药安全」直接写进 metric，同一函数复用于评测与（未来）DSPy 编译：\n\n| 指标 | 它回答的问题 | 怎么算 |\n|------|------|------|\n| **剂量一票否决** | 会不会读错/编造剂量 | 剂量数字或单位错（含「真值不可读却自信作答」）→ **整题 0 分** |\n| **字段级 F1** | 六字段整体读得准不准 | 弃权算 FN（漏，不罚谎）；错答/无中生有算 FP（罚谎）；正确弃权单独计 |\n| **无依据罚分** | 会不会「自信但说不出依据」 | 非弃权却无 evidence 的字段，每个 −0.2 |\n| **剂量否决率 / 解析失败率 / 时延** | 管线整体健康度 | 逐题留痕（JSONL）+ 按 basic/hard/safety 三层分组汇总 |\n\n---\n\n## 基线实测（合成 7 题）\n\n\u003e ⚠️ 合成占位集，仅用于管线联调方向判断，**不外推到真实拍摄分布**。完整判读见 `docs/07`。\n\n| 管线 | v1（修复前） | **v2（dose_norm + verify_dose 上线后）** | 剂量否决率 | 时延 |\n|------|:---:|:---:|:---:|:---:|\n| local-minicpm（全本地） | 0.43 | **0.98** | 0.57 → **0** | 47s |\n| cascade-cloud（级联） | 0.57 | **0.98** | 0.43 → **0** | 39s |\n| self-moa-30b（Self-MoA） | — | **0.98** | **0** | 76s |\n\n四个实测发现塑造了现在的设计：\n**F1** 清晰正面图本地 8B 即满分（支持「简单流量停本地」）；\n**F2** 5mg/50mg 数字两管线都读对了，却挂在 dose 格式拆分（`\"5mgx30片\"` 把包装数量混进 number）→ 催生 `dose_norm.py`；\n**F3** 图中无剂量时两管线都从用法行幻觉出剂量、弃权出口失效 → 催生 `verify_dose` 节点；\n**F4** 缺字段时本地自信编造、云端 MoA 聚合后正确弃权（0 → 1.0）——**级联价值的首个数据支持**。\n\n\u003e 三管线 v2 打平 0.98 = **合成集已饱和、失去区分度**。Self-MoA vs Mixed-MoA 的裁决、进一步的提示词迭代，\n\u003e 都必须等真实拍摄集——这正是下一步的核心（见文末）。\n\n```bash\nuv run python scripts/gen_synthetic_evalset.py   # 重新生成合成评测集\nuv run python scripts/run_baselines.py            # 跑 3 管线对比 (~1.5h, 需云端 key)\n```\n\n---\n\n## 快速开始\n\n前置：`ollama` 运行中且已拉取 `openbmb/minicpm-v4.5`（本地一线）。云端层需 siliconflow key（本地冒烟可留空）。\n\n```bash\n# ① 安装依赖（pyproject 已配清华镜像；直连 PyPI 会超时）\nuv sync\n\n# ② 配置云端密钥（本地链路不花钱，云端 MoA/终审走 siliconflow API）\ncp .env.example .env                # 填入 SILICONFLOW_API_KEY\n                                    # 读取顺序: shell 环境变量 \u003e 项目根 .env（已 gitignore）\n\n# ③ 测试（85 用例，全 fake client，不打真实 API，秒级）\nuv run pytest\n\n# ④ 端到端冒烟（合成药盒图，全链路本地模型，无需云端 key）\nuv run python scripts/smoke_t1.py   # 产出 results/smoke_t1.jsonl 逐题留痕\n```\n\n---\n\n## 架构\n\n\u003e 一条链路，按场景路由、按不确定度级联。本地节点（ollama）与云端节点（siliconflow）混挂在同一张 LangGraph 图上，\n\u003e 每一层只为上一层解决不了的样本付费。\n\n```mermaid\nflowchart TB\n    IMG[\"图像 (base64)\"] --\u003e ROUTE[\"route · 本地 minicpm-v4.5\u003cbr/\u003e场景分类 + 质量预检\"]\n    ROUTE --\u003e|scene=pillbox| LOCAL[\"local_infer · 本地\u003cbr/\u003e结构化提取 6 字段 + UNCERTAIN 弃权\"]\n    ROUTE --\u003e|其他场景| UNSUP[\"unsupported\u003cbr/\u003e(当前仅支持 pillbox)\"]\n    LOCAL --\u003e GATE{\"置信度门控\u003cbr/\u003e有弃权字段?\"}\n    GATE --\u003e|有 且 预算\u003c3| ZOOM[\"zoom · 本地\u003cbr/\u003e定位 bbox → 裁剪放大 → 单字段重问\"]\n    ZOOM --\u003e GATE\n    GATE --\u003e|无弃权| FIN[\"finalize_local\u003cbr/\u003e本地直出 (0 次云端调用)\"]\n    GATE --\u003e|预算耗尽仍有弃权\u003cbr/\u003e或解析失败| FANOUT[\"MoA 扇出 (Send)\"]\n    FANOUT --\u003e P1[\"proposer: 抄写员\"]\n    FANOUT --\u003e P2[\"proposer: 提取员\"]\n    FANOUT --\u003e P3[\"proposer: 质疑者\"]\n    P1 --\u003e AGG[\"aggregate · Qwen3-VL-30B-Thinking\u003cbr/\u003e综合裁决 (一致采纳/分歧重读/高危 escalate)\"]\n    P2 --\u003e AGG\n    P3 --\u003e AGG\n    AGG --\u003e|escalate| ARB[\"arbiter · Qwen3-VL-32B-Thinking\u003cbr/\u003e高危字段终审\"]\n    AGG --\u003e|否则| VD[\"verify_dose · 本地\u003cbr/\u003e剂量存在性校验\"]\n    ARB --\u003e VD\n    FIN --\u003e VD\n    VD --\u003e ENDNODE([\"final JSON · 6 字段 × {值, 证据}\"])\n```\n\n\u003e 不支持 mermaid 的查看器可读作：\n\u003e **图像 → route（场景分类）→ local_infer（本地提取+弃权）→ 门控：有弃权且有预算就 zoom 循环、无弃权直出、预算尽仍弃权则 MoA 扇出 →\n\u003e 三 proposer（抄写/提取/质疑，Mixed）或 30B 高温采样×3（Self）→ aggregate 裁决 →（高危才）arbiter 终审 → verify_dose 剂量兜底 → final JSON**。\n\n---\n\n## 项目结构\n\n```text\nsrc/medvision/\n  config.py            双后端模型注册表（ollama 本地 + siliconflow 云端）；frozen，启动 fail-fast 校验 key\n  llm/client.py        OpenAI 兼容视觉调用封装（Protocol，测试可注入 fake）；含 reasoning 通道空内容回退\n  graph/\n    state.py           T1State（TypedDict）+ gate_after_extract 置信度门控判定\n    t1_nodes.py        全部节点：route / local_infer / zoom / propose(MoA) / aggregate / arbiter / verify_dose\n    t1_graph.py        StateGraph 组装：条件边 + 循环边 + Send 扇出；moa=\"mixed\"|\"self\" 拓扑开关\n  schemas/\n    t1_pillbox.py      6 字段 schema：FieldValue/DoseValue（frozen）+ UNCERTAIN 弃权 + evidence 接地\n    dose_norm.py       剂量防线①：规则归一（剥包装数量、去重单位）；解析不出就原样保留供否决\n  scoring/field_f1.py  字段级 F1 + 剂量一票否决 + 无依据罚分（同一函数复用于评测/DSPy metric）\n  vision/zoom.py       ZoomEye 简化版：归一化 bbox（带外扩）裁剪 + 短边不足则 LANCZOS 放大；纯函数\n  prompts/\n    t1.py              提示词工件 v1.1（人工版，DSPy 编译前）：提取/zoom/MoA 角色/聚合/剂量存在性探测\n    router.py          L0 场景路由 + 质量预检提示词\n  eval/\n    dataset.py         评测集 TSV 加载 + 校验（对齐 VLMEvalKit 格式）；脏数据立即报错\n    runner.py          运行协议：逐题 JSONL 留痕 + 按 tier 三层汇总\n    synth.py           合成迷你集生成器（basic/hard/safety 三层占位）\n  utils/jsonx.py       宽容 JSON 解析：原文 → 围栏内 → 首尾花括号子串；全失败抛 ValueError 不静默\n\nscripts/\n  smoke_t1.py          端到端冒烟（本地全链路）\n  run_baselines.py     3 管线基线对比（local / cascade-cloud / self-moa）\n  gen_synthetic_evalset.py   重新生成合成集\n  ingest_photos.py     真实照片 → 本地预标注 → 人工双检 → --finalize 合并（W1 接入工具）\n\ndocs/00-08             设计文档：模型选型 / 评测集 / 提示词 / 缩放策略 / 框架选型 / 实现蓝图 / 风险 / 基线实测 / 完成度核对\ndata/  results/        可再生产物，不入 git（评测结论快照另存 docs/*.json）\ntests/                 85 用例，全部用 fake client，不打真实 API\n```\n\n---\n\n## 真实数据已就位 · 下一步计划\n\n`data/` 下已新增**本地真实拍摄数据**（不入 git）——这把合成占位集替换为真实评测集的工作正式解锁：\n\n- **T1 药盒真实集**：`data/medicine/药品-按药名分原图/` 下 **88 个药名目录**（每药含「带背景」实拍图，\n  覆盖颗粒/胶囊/片剂/软膏/口服液/喷雾等多剂型与多厂家）。这正是合成集饱和后急需的**真实拍摄分布**：\n  反光、曲面、多角度、混淆对——合成退化骗不过的考验。\n- **T2 化验单真实集**：`data/labreports/使用的报告单/` 下 **45 张体检/化验报告**（png/jpg/webp/jpeg 混合），\n  为 docs/03 评估为「zoom 高价值」的 T2 文档结构化场景备好了素材。\n\n按优先级（详见 `CLAUDE.md` 待办 + `docs/08` 核对清单）：\n\n1. **建 T1 真实评测集 60 题**：`ingest_photos.py \u003c目录\u003e --tier basic` 本地预标注 → 人工双检\n   （**剂量必须双检**）→ `--finalize` 合并。工具链已就绪，等的就是这批真实图。\n2. **裁决 Self-MoA vs Mixed-MoA**：合成集已饱和无区分度，真实集上才能用数据说话（docs/00 §3 实验问题）。\n3. **DSPy GEPA 提示词编译**：以真实评测集为训练信号编译提示词（docs/06 R1 降级阶梯第 1 级，预算 ~300 rollout）；\n   当前人工提示词 v1.1 已走通一轮 modify→verify→keep。\n4. **跑 3 次取中位数协议**（docs/01 §2）：真实集上启用，吸收单次抖动。\n5. **T2 化验单链路扩展**：T1 真实集验收后，按 docs/06 R3 复制 T1 骨架到 T2（化验单的表格分区天然契合 zoom）。\n6. **补齐工程留痕**：工件-模型版本启动校验（docs/06 R2，`prompts/t1.py` 已留 `TARGET_MODEL` 字段）、\n   checkpointer 审计留痕（`build_t1_graph(checkpointer=)` 参数已留，默认未启用）。\n\n---\n\n## 设计取舍\n\n- **为什么剂量要两道防线**：单靠提示词「宁可弃权」在小字模糊下会失守（docs/07 F2/F3 实测）。\n  所以加 `dose_norm.py`（确定性规则归一，回放 0.43→0.86）+ `verify_dose` 节点（存在性探测防幻觉）——\n  规则可测、不依赖模型自觉，且医疗场景取保守侧：**误弃权的代价（多一次升级）远小于幻觉剂量的代价**。\n- **zoom 优先在本地做**：本地 8B 调用免费，先纵向「放大重看」（本地）再横向「升级云端」（花钱），\n  是级联与缩放在成本曲线上的协同。\n- **解析一律走宽容提取、失败抛 ValueError 不静默**：模型输出常被 markdown 围栏或说理文字包裹，\n  `utils/jsonx.loads_lenient` 依次尝试三种剥壳；全失败则抛错由调用方决定重试/弃权，**绝不静默吞错**。\n- **`minicpm-v4.5` 思考通道错位**：实测偶发把最终答案（含 JSON）留在 `reasoning`、`content` 为空，\n  `llm/client.py` 已做空内容重试 + reasoning 回退——这是计划外但实测必需的兜底，别删。\n- **数据边界**：`data/`、`results/` 是可再生产物不入 git；评测结论以快照形式存进 `docs/*.json`，\n  真实拍摄数据在本地、不分发。\n\n---\n\n## 附录：名词速查\n\n| 名词 | 一句话解释 |\n|------|------------|\n| **级联 / cascade** | 流水线分层、置信度高就早停，逐层变贵：本地 → zoom → 云端 MoA → 终审。 |\n| **置信度门控 zoom** | 只对自报 `UNCERTAIN` 的字段做「定位→裁剪放大→单字段重问」，每图预算 3 次。ZoomEye 免训练简化版。 |\n| **MoA / Mixture-of-Agents** | 多 proposer 出草稿、一个更强的 aggregator 综合裁决；本项目当升级层用，拓扑由实测定。 |\n| **Mixed-MoA / Self-MoA** | 异构角色混合（抄写/提取/质疑）vs 同一强模型高温采样×3；哪个更好需真实集裁决。 |\n| **弃权 / abstention** | 看不清就填 `UNCERTAIN`，宁可弃权不可猜测；判分时只算「漏」不罚「谎」。 |\n| **证据接地 / evidence** | 每字段附「从图中哪读到的」，供 aggregator 核对、供判分罚无依据的自信。 |\n| **剂量一票否决** | 剂量答错整题 0 分——把用药安全直接写进 metric。 |\n| **proposer / aggregator** | MoA 里出草稿的模型 / 综合裁决的更强模型（本项目用 30B-Thinking 当裁判）。 |\n| **escalate（终审升级）** | 高危字段（dose/usage）存在分歧时，aggregator 输出 `escalate=true`，交 32B 终审仲裁。 |\n| **minicpm-v4.5** | 本地一线 8B 视觉模型（ollama），OCR 强、像素利用率高；本项目的「免费第一遍」。 |\n| **siliconflow** | 云端 OpenAI 兼容 API，跑 Qwen3-VL 系与 GLM-4.5V（MoA/终审层），是唯一的外部依赖。 |\n| **LangGraph / DSPy** | 运行时编排（条件边/循环/Send）/ 离线提示词编译器（用评测集当训练信号），分工不互斥。 |\n\n---\n\n\u003e 工程细节、约定、各种「坑」的来龙去脉、以及完整待办，见 [`CLAUDE.md`](./CLAUDE.md)。\n\u003e 每个设计点的 ✅/🟡/⬜ 落地状态见 [`docs/08-完成度核对.md`](./docs/08-完成度核对.md)。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenhaodev%2Fmed-agent-vision","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchenhaodev%2Fmed-agent-vision","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenhaodev%2Fmed-agent-vision/lists"}