10 月 22 号半夜刷 arXiv,看到这篇 nnInteractive Agent,标题写的是 3D 图像标注。我点进去本来是想看分割的,读到第三页发现不对——这压根不是一篇分割论文,是一篇 MCP 封装论文。
画外音:都 2026 年了,医学影像圈也开始干我们这行的活了。
先甩结论:0.81、0.84、0.85 这三个 Dice 数字,我一个都不打算当回事。真正让我坐直的是他们开源的那个 skill 组件——把“一个有经验的标注者会怎么放提示”写成文件塞给 agent 的那个东西。
它到底把什么交给了 agent
nnInteractive 这类交互式 3D 分割模型,你给一个点、一条涂鸦、一个框,它给你一坨体积掩膜。模型这一端早就够用了。麻烦的是链路里剩下的活:在三维里找到要分的东西、想清楚提示往哪放、跑完检查、发现多了切掉少了补一笔。这四件事全是人干,而且加起来比推理本身耗时多一个数量级,手动标过 CT 的都懂。
作者的做法很干脆:模型一个参数都不动,让通用 agent 把这四件事接过去。用户说人话提需求,agent 自己判断该分什么、定位、调工具、看结果、觉得不对就改、改完继续接反馈。
我照着论文描述还原了一下那个工具面,大概是这个形状(脑补的,不是原文代码):
@tool
def segment(volume_id: str, prompts: list[Prompt]) -> Mask: ...
@tool
def inspect(mask_id: str, slice_range: tuple[int, int]) -> list[Slice]: ...
@tool
def count_instances(volume_id: str, region: str) -> int: ...
我特意多看了两眼 inspect。agent 看不见三维,它只能通过切片去看自己的结果——这个接口设不设计,直接决定它有没有自查能力。很多人包一层分割器的时候根本不会想到这一层,掩膜一返回就完事,然后 agent 拿着一个 Dice 0.9 的东西,照样不知道剩下那 0.1 烂在哪。
更有意思的是这个工具面里没有“找”。
至少从论文里肝病灶那个例子看,“找”是它自己现写代码解决的。用户的请求是“数一下肝脏里有多少个病灶”,不是“把这个病灶分出来”——这两句话对模型的难度完全不是一回事。前者要求先把候选定位出来,再逐个分,还得处理粘在一起的情况。论文里说它会按要求把粘连的病灶拆开。
拆粘连这事,传统做法要么靠分水岭之类的后处理,要么靠人手动切一刀。agent 的做法是发现结果不对,写段代码,重新调工具。这就是它和专用模型的本质区别——它会为了完成任务临时给自己造工具。
还有一点容易被忽略:整条链路里人是随时能插话的。“这块多了”“不是这个肝叶”“再往左一点”,这些反馈 agent 得接得住,还得转成对提示的修改。这跟“一键分割”是两码事。一键分割的假设是模型一次就能对,这条链路的假设是第一次一定不对,靠迭代收敛。
那 0.04 的信息量
只给自然语言请求,Claude Code 和 Codex 分别拿到 0.81 和 0.84 的平均 Dice。把每个目标的位置直接告诉它们,两个都是 0.85,接近拿参考分割放提示的 oracle。
我一直盯着这 0.04 看。0.04 的 Dice 放在临床语境里什么都不是,在这篇论文里却是几乎全部的信息量。
它说明卡住 agent 的不是分割质量,是“知道东西在哪”。位置一给对,提示水平立刻摸到 oracle。分割器早就不缺能力了,缺的是那个能在三维里自己找路的人。
而这恰好是通用 agent 擅长、专用模型不擅长的部分。要让端到端模型学会自己找,得重新标一批数据重新训;agent 不用,写个循环就行。
这个判断有一半是我从“它会写代码、会自查”这条推出来的,论文没直接这么说。我先记着,等它被打脸。
对照组其实更有意思
同一批基准上还有 VoxTell,文本提示驱动的 3D 模型,正经干这行的。它在多数标准放射学目标上比 agent 更准。
我读到这段第一反应是“哦,那还是专用模型赢”。
再往下读就不是了。VoxTell 的稳健性很差:对其中一个目标直接返回空掩膜,在腿部肌肉、足部骨骼、小鼠、蚂蚁这些任务上失败。agent 在这些任务上还是准的。
关键不是谁分得更准,是两类东西的错法不一样。
专用模型的错是安静的——一张空掩膜,形状规整、格式正确、不报错,你要是不逐个看,它就这么混进你的数据集。agent 的错一般比较响:工具调用失败、结果肉眼可见不对、它自己回头改。我宁可要后者。
这几年我被“安静地错”坑的次数,比被“报错”坑的多得多。报错至少告诉你哪一行有问题。
真凶是那份 skill
论文提这个组件只用了一句话的篇幅:开源了 nninteractive-agent,用 MCP 把 nnInteractive 封装成工具,另外提供一个编码了有经验用户提示方法的 skill 组件。
全篇最容易被略过的一句,我认为也是唯一有长期价值的一句。
模型不是你的。Claude Code 下个月升级,Codex 下季度换版本,你今天调好的 prompt 明天可能就不好使。但“一个有经验的标注者会怎么放提示”是稳定的领域知识——成对结构先分一侧再镜像、血管树从主干起手不从末梢起手、拿到掩膜先看体积跟同部位历史中位数差多少、哪种形状一眼就知道要重跑。
这些东西写进文件,换谁来都能用。
## 提示放置(示例,按我自己的标注习惯编的)
- 成对结构(咬肌/翼肌这类)先分一侧,确认后再处理另一侧
- 血管树从主干起手,末梢起手会把整棵树带偏
- 掩膜体积和同部位历史中位数差三倍以上,直接重跑,别检查
这半年我干的活里,有差不多一半是在给工具写说明书。写多了会生出一种错觉,以为自己在做 prompt 工程——不是。我干的是把脑子里那些从来没写下来过的经验,逼成别人、或者别的 agent 能执行的形式。这活极其枯燥,而且经常写着写着才发现“哦,原来我平时是这么判断的”。
论文那份 skill 里到底写了什么,从摘要看不出来。这是我觉得最可惜的地方——真正值钱的东西,恰恰是最不好塞进摘要的。
泼一盆冷水
14 幅图像,51 个目标,覆盖 CT、MRI、PET 和显微 CT,人、小鼠、蚂蚁。基准设计得挺聪明,跨模态跨物种,摆明了是要测泛化。
但 51 个目标这个量级,平均 Dice 的方差我不敢信。血管树这种薄结构,差一两个体素就掉好几分;里面要是有几个特别难啃的目标,单个样本就能把平均值拉走零点几。
0.85 和 0.81 那点差距,在这个样本量下我甚至不确定是不是噪声。平均值掩盖的东西太多了,我更想看逐目标的分布。
这不是唱衰。是我这几年看过的所有“demo 上跑通”的东西,到了边界情况上都要再缴一遍智商税,无一例外。跨模态跨物种这个设计已经把最容易翻车的地方摆出来了,但 51 个目标里,蚂蚁的脑和小鼠的血管树各占几个,这决定了我该信多少。
还有一处小意见:摘要页上的代码链接是个占位符。想抄作业都抄不了。
我准备改的一件事
评这类 agent 工具,我以后先问一句:它的领域知识写在哪。
写在权重里的,我改不了,只能等下一版;写在文件里的,我今天就能改。这两者的差别,比模型本身强多少重要得多。
这篇我收藏了,收藏的不是 Dice,是那个 skill 组件的位置。等仓库链接不是占位符了,我想看看他们到底把哪些经验写了进去——那才是这篇论文里唯一我能直接拿来用的部分。
在那之前,我先去把自己那份标注笔记整理成文件。这活没人替我做。
