Files
iomgaa 35e77777e3 docs: 硬性约束第 2 条改为影像专家先独立判断再听临床信息;新增 paper/ 与论文问题句、主张句
AGENTS.md 第 2 条的改写来源:负责人 2026-10-16 定「第一轮之后、影像专家提出需要才给临床信息」。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:25:42 -04:00

155 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md
本文件是给所有编码 Agent(Claude Code、Codex 等)的工作说明。`CLAUDE.md` 只引用本文件,内容以这里为准。
## 项目是什么
CADx-4 研究胰腺囊性肿瘤的鉴别诊断:给同一个病人的三路资料(临床信息、CEUS 超声造影视频与超声报告、MR 报告)各配一个只看自己那一路的 AI 专家,模拟多学科会诊,判断病灶属于四类胰腺囊性肿瘤中的哪一类:IPMN(导管内乳头状黏液性肿瘤)、SPN(实性假乳头状肿瘤)、MCN(黏液性囊性肿瘤)、SCN(浆液性囊性肿瘤)。前三类有恶变可能或本身是低度恶性,通常要手术或密切随访;SCN 几乎都是良性,多数不用手术。分错直接决定病人是手术、随访还是不用管。金标准是术后或穿刺取得的病理结论。最终产出是一篇医学 AI 交叉论文,重点在临床问题、与人类医生的对比和可解释性,不在方法复杂度。
几个词先解释:**MR** 是磁共振检查,本项目只有它的文字报告;**CEUS** 是往静脉打造影剂后连续录下的超声视频;**ROI**(感兴趣区)是视频里圈出的病灶区域;**TIC**(时间-强度曲线)是 ROI 平均亮度随时间的变化,造影剂进来变亮、洗出变暗,曲线形状就是灌注的量化。本仓库的术语表随模块迁移逐步建在 `research-wiki/reference/术语表.md`;建成前查旧项目的 `reference/CADx-4-CAD/research-wiki/reference/glossary.md`。
本仓库是对旧项目的**重构**。旧项目在远程服务器上按工程产品搭建(HTTP 服务、数据库、任务队列),后来发现目前不需要做成产品,于是在这里按研究原型重写,只保留有研究价值的算法和知识。旧项目的完整副本在 `reference/CADx-4-CAD/`(只读,见下文)。重构的来由和取舍见 `research-wiki/design/0001-从产品重构为研究原型.md`。
和临床合作方之间的议题、会议纪要、交付清单和临床口径在 Linear(团队用的议题跟踪系统,临床方也在上面)上,不在本仓库。
## 新会话先读什么
1. `WORK_LOG.md`:当前目标、做到哪一步、下一步、运行中的任务。
2. `research-wiki/README.md`:文档体系与各目录的用途。
3. `research-wiki/explanation/结构.md`,以及与当前任务相关的其他 `explanation/` 文档。
## 事实来源
文档之间有冲突时按下表定谁说了算,找负责人确认,不自行折中成第三种说法。
| 问题 | 看哪里 |
| --- | --- |
| 当前进度、下一步 | `WORK_LOG.md` |
| 代码分哪些模块、谁能依赖谁、各模块预期规模 | `research-wiki/explanation/结构.md` |
| 各环节在做什么、输入输出 | `research-wiki/explanation/` 的其他文档 |
| 还没定的问题 | `research-wiki/待定问题/`(定了就删,结论进 `design/`) |
| 某个决策为什么这样定 | `research-wiki/design/NNNN-*.md`(写完冻结) |
| 命令怎么敲、环境怎么配 | `research-wiki/guides/` |
| 医学词、参数的原始口径、临床方给的字段定义 | `research-wiki/reference/`(每条自带出处) |
| 交付资料的格式和口径(列名、编码、时间的含义) | 最新的交付包,目前是 `data/交付包_20260908_40例/`。旧样本(如 `data/1.2-TEST/`)和旧程序的口径只当线索。来源:负责人 2026-10-16「数据改过很多版,所以你们需要对齐“40例交付包”那个」 |
| 配置值、字段定义 | 代码里的定义与校验;文档只解释含义,不抄数值 |
| 临床口径、双方约定、待交付清单 | Linear(临床方维护的那几份 document 是权威,仓库里的抄本以它为准) |
| 旧项目做了什么、踩过什么坑 | `reference/CADx-4-CAD/`(只当线索,见下文) |
## reference/ 怎么用
`reference/CADx-4-CAD/` 是旧项目的副本,含完整 git 历史(366 个提交)和当时没进 git 的 `debugs/` 脚本。
- 只读。不在里面改文件、不在里面跑会写盘的命令。
- **算法代码可以迁移**:`app/domain/ceus_roi/`(除 `service.py`)、`app/providers/video_decoder.py` 和 `debugs/` 里的三个 `.py` 脚本是负责人自己的成果,迁移时去掉数据库、HTTP、任务队列等产品层依赖,按本文件的标准改写,并补上测试。读交付包的逻辑(`app/providers/` 里的列白名单、病例号规范化、剔除报告结论段)只借规则,按普通函数重写。产品层代码(`app/api/`、`persistence/`、`workers/`、`migrations/` 等)不搬。
- 旧文档和报告里的数字(40 例跑通 34 例、参数取值等)只是线索。要写进本仓库的文档或结论,先在原始产物或本仓库的真实运行里核到。
- 旧 `research-wiki/design/0001–0049` 讲的是当时为什么那样定。其中与研究有关的结论(优先读哪几份见本仓库 `research-wiki/design/0001` 第 8 条的表)在本仓库重新写成 explanation 或 design。可以注明「出自旧 design 00NN」交代来历,但现行规则以本仓库的文档为准。
`reference-pdf/` 放文献原文,命名 `年份-第一作者-短名.pdf`,索引在 `reference-pdf/INDEX.md`。两者都不进 git。
## 硬性约束
1. **答案不进 AI 专家能看到的任何东西。** 病理金标准、工作簿里的诊断列、按规则算出诊断倾向的公式列、医生的结构化判读列(**标注工作簿**是临床方另交的一份表,医生在里面逐例标了病灶位置、大小、囊壁、增强模式等三十来列,它只用于评价专家抽取得准不准)、报告的「印象 / 结论」段、真实病例号、Excel 行序,一律不能进入提示词、工具返回或专家可读的文件。**行序也算**:原始表格按诊断分组录入,行号本身就带着答案。这一条错了不会报错,只会让所有「系统诊断得准不准」的结论变成「系统能不能读到答案」。任何会削弱这条的改动,先停下来问。来源:旧项目 CLAUDE.md §1.2、旧 design 0026。
2. **影像专家先独立判断,再听临床信息。** 资料库里每一路只放自己那一路资料:性别和年龄只在临床那一路,超声和 MR 两路的资料里没有,即使医院把这几列也复制进了报告工作簿。会诊时,超声和 MR 两位专家先只凭本路资料各自给出判断;第一轮之后,它们需要时可以向临床专家提问,临床专家按问作答,可以答性别、年龄。先独立这一步不能省:判断有没有分歧要靠两路影像证据本身,先听到同一份人口学信息会把两位专家往同一个方向拉,分歧就看不出来了。各路能看见什么见 `research-wiki/explanation/结构.md` 第四部分。来源:旧项目 CLAUDE.md §1.2、旧 design 0026;临床专家只提供信息出自旧项目 HAI-62 的 2026-08-02 会议纪要;「第一轮之后、影像专家提出需要才给临床信息」与本条改写是负责人 2026-10-16 定的。
3. **患者资料不进 git、文档和提交说明。** 原始资料只在 `data/`,运行产物只在 `runs/`,两者都不进 git。文档里可以写结构和规模,不写报告原文、姓名、病例号和诊断。把病例资料发给外部模型服务之前,先确认负责人已同意该服务。超声帧上烧录的信息栏(拼音姓名、检查号、性别年龄、医院与设备参数)不做遮蔽:临床方判定拼音和不唯一的检查号不构成身份泄露(出自旧 design 0041)。
4. **密钥不进 git、文档、日志和运行快照。** 只放环境变量或本地 `.env`;仓库只提交不含真实值的 `.env.example`。
5. **写进论文和结论的数字能追到一次真跑的原始记录**(`runs/` 下的运行目录)。不把预期、估算、旧项目报告或别人论文里的数字当成本仓库的结果;没有就写「没有」。文献里的数字核到 `reference-pdf/` 的原文。
6. **错误不静默吞掉。** 不吞异常,不用默认值把问题盖过去,缺关键配置直接报错。细则见「写代码的标准」。
7. **花钱的批量运行先说预估成本。** 调模型 API 跑全部病例这类动作,开跑前在回复里给出预估;量级明显超过以往的,先问。
## 怎么协作
- **用简体中文回复,先给一句话结论。**
- **先讲后写。** 新环节或新设计先在终端讲:旧项目怎么做的、问题在哪、打算怎么做、为什么。负责人认可后再写文件或代码。需要负责人决定时,一次列出全部问题、选项和你的倾向。
- **能自己定的直接定**,按设计判断或文献先例给方案,不把问题抛回去。
- **范围要窄,改得要深。** 只动这次要改的那一块;范围内要改到读起来像一开始就这么设计的,被取代的旧内容同一次删掉。范围外看到问题,记下来告诉负责人。
- **讨论会话与执行会话分开。** 和负责人讨论方向的会话写任务书(`research-wiki/任务书/NN-*.md`),由负责人另开会话执行、讨论会话验收。小改动不必写任务书。
- **子 Agent 交证据不交判断**:带 `文件:行号`、命令原始输出或产物路径;prompt 里不带邮箱等个人信息和患者资料;要用浏览器只能用无头模式。收回的报告先抽查两三条原文,其中一条挑带评价性修饰的句子;子 Agent 说「通过」不算数,要自己核对实际改动和测试产物。
- **审查**:改动完成后派一个新鲜上下文的子 Agent,只给 diff、验收标准和相关文档,不给实现时的推理。下文「账本级」的代码另派一个全新上下文的子 Agent 再审一轮;Codex 可用时再加一轮对抗审查(`/codex:rescue --fresh --wait`)。审查意见只采纳影响正确性或明确需求的项,采纳前问:日常使用中会不会真撞上。
- **汇报**时分开写已验证的事实、推断和未验证的部分。
- **新加的规则注明来源**:负责人哪句话、哪份 design 文档,还是 Agent 自己提的。本文件的改动来源写在提交说明里。
- 推送、建 PR、往 Linear 写东西这类外部可见的动作先问;Linear 上有临床方在看。
## 写代码的标准
### 两档标准
- **账本级**(像记账一样只追加、可核对;出错会悄悄污染结论的部分):答案隔离(各路资料怎么读、怎么分给专家)、金标准的读取、评分与统计、写进论文的定量结果。要求:
- 运行产物只追加,已有的运行目录不删、不覆盖,要删先问;
- 每条规则有独立预期值的测试,预期值来自手算、独立脚本或人工核对过的样例,不从实现里抄公式。
- **其余部分**:视频解码、ROI 追踪、绘图、提示词拼装、脚本。写错了马上能看出来,按研究原型的标准写:用最直接的写法,遇到罕见情况就停下来把原因写清楚。
### 可复现
随机种子、模型的精确版本号(以服务商返回为准)、提示词版本、git 提交号和全部配置,写进每次运行的快照。复现不了的结果等于没有结果。每次运行一个新目录,同一个输出目录同时只允许一个进程写。
### 不设默认值
配置的每个字段都必填,函数参数不写默认值;可以没有的值显式写 null,不靠省略。缺了就报错。默认值会掩盖错误:字段名拼错、文档改了代码没跟上,流水线照样跑完,没人知道用的是哪个值。路径、模型名、服务地址同样不写死在代码里。
### 罕见情况怎么办
「可能会出现」不是写分支的理由。按顺序选:
1. 改数据的表示或格式要求,让它不可能出现。
2. 做不到,就在系统边界检查,不满足就报错停下。边界指交付的资料、模型的输出、负责人的配置。模块之间的调用不算边界,不互相防御。交付资料里只落在某一个病例上的问题是例外,见「错误怎么处理」。
一个抽象在出现第二个真实调用点之前不引入。不考虑向后兼容:不写兼容层,读到旧格式直接报错;发现更好的设计就按它重写,同步改文档和测试。
### 遇到问题先找原因
出了问题先查清为什么,再到原因所在的地方改。重试、跳过、补默认值、放宽解析、捕获异常后接着跑,只是让症状消失。动手前问:这个改动是让问题不再发生,还是让问题发生了也没人看见?
### 错误怎么处理
- **外部错误**(模型接口超时、网络断开):只在调用外部服务的那一处做有限次重试,仍失败就把这一步记为失败并写明状态。
- **内部错误**(资料不满足规则、不该有的状态组合):立刻停下,报错写清是哪条规则、哪个病例的哪个文件。模型交不出合格输出按内部错误处理。
- **交付资料里只落在某一个病例上的问题**(这一例的编号认不出、缺文件、取值不合规则、和别的表对不上等):跳过这一整例,接着处理下一例,并在标准答案库的跳过清单里写明这一例没过的是哪项检查、哪个文件哪一列。对一个病例,遇到第一项不过的检查就不再往下查,所以清单上每例只有一项,后面的检查没做过,不代表没问题。整张表读不了(工作表名或列名不对、文件缺失、配置不全)仍然立刻停下。跳过不是把问题藏起来:跳过清单交给医生核对,评价时要报告跳过了几例、各因为什么。来源:负责人 2026-10-16「都是遇到整个病例跳过,让医生核对去吧」。
中间层不写 try/except,除非它能把错误完整处理掉且上层不需要知道。不写 `except Exception: pass` 及其变体。
### 提示词
- 提示词引导模型怎么想,不设必填栏目:逼模型填栏目,它会为了填而编。只有程序确实要读的值才做成输出字段。
- 提示词是 `prompts/` 下的版本化文件,不散落在代码字符串里;给模型的提示词用英文。
### 测试
- 真实模型链路要用真跑证明,假模型测试证明不了;需要显卡或花钱的测试用 pytest 标记 `gpu`、`live` 区分,不进 `make check`。
- 修缺陷时附一条能复现原问题的测试。
- 不为方便测试给产品代码加参数、开关或分支;不靠删断言、改预期值或只保留成功的运行让测试通过。
### 规模与注释
- 每个模块的预期规模写在结构文档里;超过预期一倍时停下检查:是需求本身复杂,还是专门路径在变多。
- 注释只写这段代码现在做什么、为什么这样做;修改过程写进提交说明和 `WORK_LOG.md`。
## 文档
- **代码标识符用英文;注释、docstring、文档和回复用简体中文。**
- 读者是没参与过讨论的医学 AI 方向硕士生,也包括负责人:懂 Python 和深度学习,不认识 IPMN,也不知道我们讨论过什么。医学词、缩写、代号首次出现处就地解释;每个决策写清为什么。
- 同一个配置值只在一处定义,别处引用。
- `explanation/`、`guides/`、`reference/` 是常青文档:改了就重写那一段,不在后面追加「某日又改了」。
- 决策的来历写进 `research-wiki/design/NNNN-简短主题.md`,按顺序编号,写完冻结;决策变了新写一份,开头写明取代哪份,旧的开头加一行指向新的。
- `WORK_LOG.md` 只记当前状态:目标、已完成(附证据)、未完成、关键决定(附负责人原话)、下一步。一个阶段完成后压缩成几行。没有真实证据的「可能情况」只在汇报里提一句,不写进 `WORK_LOG.md`。
- **新写或大改一份文档**(新文档、重写整节、改动超过约 100 行),过一轮「硕士生阅读」:新鲜上下文的子 Agent 扮演这样的读者,只读这份文档,先列出最可能读不下去或要回头重读的段落、再复述要点,然后报哪句读不懂、哪个医学词没解释、哪个决策缺理由、哪个值两处不一致。不要让它描述自己的思考过程。格式和措辞建议不采纳。
## 提交规范
提交信息用英文前缀加中文正文,前缀按 Conventional Commits:`feat:`、`fix:`、`docs:`、`test:`、`refactor:`、`chore:` 等,可加作用域,例如 `feat(ceus): 暗区掩膜改用奇偶规则`。
## 本机环境的坑
- `grep`、`find`、`ps`、`diff` 可能被 shell 包装函数改写,静默少输出。搜索用 `/usr/bin/grep`、`/usr/bin/find`;查进程用 `pgrep -af`;比较文件用 `cmp` 或 `/usr/bin/diff`;看 git 差异用 `git --no-pager diff`。派出去的子 Agent 的 prompt 里也写上这一条。
- 超过约一分钟的命令放进 tmux 跑,日志写到文件。
- 日常开发和实验在本机(一张 RTX 4070 Ti SUPER 16GB)。需要用远程服务器(旧项目所在的 4090 服务器)时,ssh 和 rsync 一律走 `wangwenhan@202.38.247.58`,不走 Tailscale 地址 `100.83.164.94`(同一台机器,后者实测约 150KB/s,前者约 100MB/s)。它的显卡是共用的,命令必须显式设 `CUDA_VISIBLE_DEVICES`。
## 开发命令
- `make sync`:用 uv 同步项目环境和依赖。
- `make fmt`:用 ruff 自动修正并格式化。
- `make test`:运行不花钱、不需要显卡的测试。
- `make check`:依次检查 lint、格式、类型、导入边界并运行测试;失败即停止。
环境怎么搭见 `research-wiki/guides/开发环境.md`。