← 返回博客

博客

借来的眼睛

一个暖橙色的人闭着眼睛坐在桌前,抬手举着一只借来的蓝色眼睛,用它端详面前并排摆着的两张图片,画面上方写着「借来的眼睛」几个字

通过」不等于「验证过」:一次测试先要有能力失败,它的通过才算数。

一个闭环,缺了一环

我想在 DeepSeek Harness 上做出「产品设计」这项能力。这个 harness 就是上一篇写过的那个「一切皆插件」的智能体运行时,下面简称 DSH。这项能力是个闭环:从设计稿出发写实现,再自己判断实现得像不像,不像就修,修完再判,直到收敛。

这个闭环里最要命的一环是判定。没有可信的判定,循环要么永不停止,要么在错的地方停下。

而 DeepSeek 是纯文本模型。它写得了代码,却既看不见设计稿,也看不见自己写出来的页面。判定这一环,它做不了。

于是我做了个工具:图片不进模型上下文,模型需要看的时候调 deepseek_vision,由一个真正的视觉模型代看,把文字带回来。

装上这只眼睛,只花了一小部分时间。剩下的时间全用在回答一个问题:借来的这只眼,够不够格当裁判。这个过程里我栽了三次:三次都以为自己验证了什么,其实什么都没验。

管子是通的,只有阀门关着

动手前先读了 DSH 的源码,发现一件有意思的事:图片管道其实是通的。图片传得进来,认得出来,转得过去,整条链路齐全,缺的只有一道准入检查:图片进来时,DSH 会去查当前选中的模型有没有声明支持图片,没有就直接拒绝。管子铺好了,只是阀门关着。

这就带出一个三选二的困境,而且困境是 DSH 的架构自己规定的:图片能不能进来,只取决于当前选中的模型有没有声明支持图片,这道检查只认这一处声明,别的什么都不看。所以「粘贴图片」要走通,路只有两条:要么改这道检查本身,那就动了本体;要么造一个声明支持图片的新模型选项,那选择器里就多了一项。两处都不想动,就只能放弃粘贴,做一个传路径的工具。零改本体、粘贴可用、不多一个模型选项,这三样最多同时拿两样:

做法零改本体粘贴可用不多一个模型选项
改本体的准入检查
注册一个新模型选项
只做工具,传路径或 URL

社区里已经有人走了第二条路:注册一个声明支持图片的新模型选项,图片先由视觉模型描述成文字,再转给 DeepSeek。零改本体,很干净。我还是选了第一条。

为什么我选了改本体

先说清楚一件容易被混淆的事:「让模型自己决定问什么」并不是改本体买来的。第二条路一样能做到,它完全可以不描述图片,改成塞一行文字指针,再由插件注册工具。

改本体真正买到的,是两个产品判断。

一、用户不用切模型。 DSH 的模型选择器列的是你手上有哪些账号和密钥;「你有哪些能力」是另一张单子。往里塞一条「DeepSeek + Vision」,是把能力项混进账号列表。用户的直觉是「我用一个 key 换一个模型」,而不是「我装一个插件就多一个模型选项」。这是个范畴错误。

二、这个本体必将被改。 DSH 目前拿 DeepSeek 读图是直接拒绝的,明明链路都写好了,这在产品上说不过去。既然它迟早要改,我这三处补丁也算不上加新东西,只是把已经写好但没开放的能力提前接上。

改完还有个意外的红利:所有已配置的纯文本模型一次全覆盖。我拿一个跟 DeepSeek 毫无关系、同样只支持文本的模型实测,7 个读数点全对。

还有一层要说破:我知道这是个短期方案。能力就躺在本体的代码里,官方开放是必然,到那天这套补丁就该被取代。代价我也认:升级会冲掉补丁,上游改动那三处位置补丁就会失配,卸载必须显式还原。我用备份、一键回滚和每天自动巡检兜住,但这是笔要一直付的维护税。至于明知会被取代为什么还做,留到最后说。

真正的问题:这只眼睛够不够格当裁判

装上眼睛,只是让模型能看见。而「能看见」和「可用于判定」是两回事,一个模型可能读得出图上的字,却:

  • 看得见但不主动看:开放式提问下漏掉难档缺陷
  • 看得见但会编:把设计稿和它自己配对,仍报出「差异」
  • 看得见但不稳:同一张图三次判定三个结论,循环因此震荡
  • 看得见但说不清:「颜色不一致」这种结论没法据此改代码

所以我造了一套评测:落地页、仪表盘、列表、表单共 4 组素材,每组是「设计稿 + 忠实实现 + 注入缺陷版」,合计 23 处已知缺陷,难 9 / 中 8 / 易 6。四条通过线一一对应上面四种失效,全过才算这只眼可以进生产判定循环。

然后我开始跑,三次翻车都出在接下来这几轮里。

第一次什么都没验:两轮跑在空白图上

前两轮的结论很漂亮,也很错。

当时的记录是:三个视觉模型在整页拼图上一致失败,自称看不见图,却照常输出完整分析,还编出了「#3498db」这种精确到色号的蓝色。而真实设计稿是珊瑚橙加暖白,一个蓝色像素都没有。我据此写下结论:整页拼图不可用,必须分区送检。

第三轮加了断言,才发现根因:

拼图脚本加载本地图片的方式有个坑。浏览器出于安全策略,把那种方式下的本地图片静默拦截了;而「页面加载完成」的信号照样正常返回,因为被拦截的请求根本不计入等待。产出的「对比图」,就是两个空白框加两行英文标注,页面内容一个像素都没有。

于是当时那些记录全部要重读:

当时的记录实际发生的
「模型自称看不见图,却照常输出分析」图确实是空白的,模型报告属实
「编造不存在的色值」空白图上被追问,它只能猜
「整页 0/6,必须分区送检」从未真正测过整页

其中一个模型三轮一致回答「两个面板均显示为空白,仅有破损图片图标,因此无法判断」。

它诊断对了。是我的评测记录把它的诚实记成了幻觉。

这件事让我改了两个习惯:拼图一律把图片直接嵌进页面,并在生成后断言图片确实有宽度;评测集里常驻一张已知空白图当负对照,哪天它没被报成空白,就说明又有东西悄悄坏了。

我把作废的那两轮原文留在了仓库里,顶上标一行「结论已作废」。因为归因错误的过程本身也是结论:「素材会静默损坏」这条陷阱就是从这儿来的。

第二次什么都没验:一次命中就记成能力

第二轮我记了一个命中。一处难档缺陷,主标题字重 800 对 500,模型答「左边更粗」,方向正确,判定命中。

第三轮把它扩到 15 次,只对了 8 次。

53% 就是掷硬币。

更阴险的是错误的分布方式:它按轮次聚集,一整轮全对,下一整轮全反。所以单看任何一轮,它都像是稳定的。

从此写进规范:二选一的方向类判定,必须跑满 3 次才计分。

同一批实验里还有个类似的坑。我原本用字重 800 改 600 做缺陷注入,后来发现在常用字体下,600、700、800、900 渲染出来是同一个粗细。像素上完全无效的注入,模型当然「漏检」。改成 800 对 500,并且注入之后必须先做像素级断言,确认缺陷真的存在。

第三次什么都没验:在已经装好的环境里测安装

前两次是我掉进坑里。这次反过来,抓人的是我。

这个插件是我让 Claude 做的。安装脚本写完,它自己跑了端到端验证,报上来一路绿灯,一副做完收工的架势。

我看了一眼就把它拦下了:「你这个测试不对,要环境是纯净的、刚下载下来的 DSH。

它跑的那个环境,补丁早就打过了,配置也早就写好了。脚本从头到尾打印的都是「已打过,跳过」「已存在,跳过」。看着全绿,其实一行安装逻辑都没真的跑过。

换到真正干净的环境重跑,当场抓出两个真 bug。

这条后来进了每天的自动巡检:安装脚本先空跑一遍,要是所有落点都说「已打过」,直接算失败,因为那种全绿什么都没验证。


三件事,同一个教训:

通过」不等于「验证过」。要先证明这次测试有能力失败。

空白图上,测试发现不了模型的真实水平;单次方向题里,它分不清实力和运气;已装好的环境里,它连一条代码路径都没碰到。


最有用的一条发现:提问方式比模型选型更重要

这是整轮实验里最反直觉、也最有实用价值的结论。

先解释两个指标:「零差异对照」是拿设计稿和忠实实现配对,理想答案是「没有差异」,答出差异就是幻觉;「难档召回」是最难的那批缺陷能不能被抓出来。

同一个模型、同一批图,只改问法:

提问形式零差异对照的幻觉难档缺陷召回
「你自己找差异」0/300/2
「这个方面有区别吗」0/300/2,判定题它默认答否
「哪个更大」0/361/2,留白类 3 次全答反
各自是多少0/122/2

召回从 0/2 走到 2/2,幻觉全程为 0,换的只是问法。

注意第一行那个漂亮的 0 幻觉,有一部分是「懒」换来的:大量回答「一致」,说得少自然错得少。所以召回和幻觉必须一起看,单看任何一项都会得出相反的结论。

这条发现有个直接的架构后果:它把「自动描述」这条路堵死了。

第二条路是图片一进来就先描述成文字。但描述发生在问题诞生之前,它不知道下游要验证什么,只能问出第一行那种「你自己找差异」,而那一行的成绩是 0/2。

对视觉模型来说,一张图被看到什么细节程度,取决于你问它什么;而下游所有基于视觉的判定,都配套在这个细节程度上。对一个要养一堆下游能力的底座来说,这件事太重要了。

正因为问法不同、效果就不同,我才把这件事做成「工具 + 提问纪律」的组合:提问权交给模型,它看到一行文字指针,需要什么信息,就自己组织问题去问;而「怎么问才可信」的纪律,写在技能说明里,不写死在工具里。这套组合是评测逼出来的必要设计,实测也比固定模板更准。

顺带说一句缓存。有人会说:图描述一次缓存起来,省钱。但要分清缓存的是什么。缓存「看到了什么」,对下游的理解是负债,甚至干脆是幻觉的来源:为问题 A 生成的描述被问题 B 复用,越攒越偏。缓存「问了什么、得到什么」才是资产。这两件事经常被混为一谈。

其他几条实测读数

读数方向可信,量级不可信。 字重真值 800 和 500,读出来是 800 和 700;间距真值 80 和 25,读出来是 72 和 38。被测的那张图,总是被往参照图的方向拉。所以用它判断「有没有差异、往哪个方向」,别把它当尺子。实现侧的精确值直接从代码里拿就行;视觉模型不可替代的只有一处:设计稿那一侧只有像素,没有代码可查。

图像消耗的 token 按张算,不按大小算。 实测某家模型,一张大图和一张小图的输入 token 完全相同:图像占一个固定预算,大图只是被采样得更粗。推论:换更大上下文的模型,解决不了「看不清」的问题。另一家则是按尺寸计费。两种机制都存在,别预设是哪一种。

并排本身就是信息。 把对比图拆成两张单独送检,成本翻倍,效果更差。有一组素材,并排时能认出来,拆开后三次全错:单看一张裁剪图,模型把一个房子图标读成了「一本书」。并排提供了对比的锚,拆开就丢了。

关掉思维链,输出省 113 倍。 一次读数,开着思维链会产生 1582 个 token 的推理过程,关掉只要 14 个,读数结果完全相同。

模型会不信工具,然后自己绕路。 早期版本里,模型拿到识图结果后不采信,转头自己写脚本去数像素。把「不要绕路」这条约束从报错提示挪进工具说明和技能正文之后,同一个提问重放:14 步、24 万 token、181 秒,变成 2 步、2.3 万 token、16 秒,绕路的脚本调用从 9 次归零。同一条约束,写在哪儿比写了什么更要紧。

我知道这套评测还差什么

写到这儿,如果我不说下面这段,前面那些「三次什么都没验」就成了表演。

  • 23 处缺陷的真值齐全,但跑分工具目前只覆盖其中一组的 6 处,另外三组的描述还没补齐,跑到会报错。
  • 横评表里有一行模型没有留下原始输出,只剩汇总数字,无法逐格复核。
  • 「零差异对照的幻觉率」只在一组素材上跑过,因为另一组的「忠实实现」本身就不忠实:模型报的「差异」大多为真,测不出幻觉率。
  • 前面那张「只改问法」的四行表,是当时实测的记录,但原始输出没有留存,仓库里复核不到那些分母。有据可查的是另一组独立对照:同一模型、同一批缺陷,定向提问 12/12 全中,开放式提问漏检——结论方向一致,具体数字请打这个折扣。

除了上面这些缺口,其余每个数字的未编辑原始模型输出,都收进了仓库。理由很简单:这个项目的主张是「认真研究了裁判何时可信」,而别人无法复核,主张就是空的

它什么时候该退休

DeepSeek 官方把读图能力开放的那天。

届时不需要改任何东西:工具会查到调用方本身就能看图,自我拒绝并让位,图片直接走原生通路。三处补丁一键还原即可。这就是前面那笔账的一半答案:补丁层生来就是要被取代的。

另一半是,要退休的只是补丁那一层。DSH 是个开放的底座,谁家的模型都能接进来。而靠纯文本能力一路往 AGI 走的模型,一直都有,以后也少不了:推理很强,没有眼睛。门禁开放解决的是「能不能传图」,解决不了「看不看得见」。一个纯文本模型收到图片字节,还是瞎的。只要这样的模型还在,「借一只眼睛,并且搞清楚这只眼睛什么时候可信」这件事就不会过期。

补丁是消耗品,用完就换;工具和评测,才是我想留下来的东西。


项目开源在 dsh-design-qa,评测素材、跑分脚本和原始模型输出都在 eval/ 下。