🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

照片策展助手 tanweiping1012-source/PhotoFilterAgent

DeepSeek Harness
tanweiping1012-source26 天前有提交已提供中文文档扫描:含敏感能力

跑在 DeepSeek Harness 上的照片策展 agent:本地 Vision 分类 + 连拍组比较 +…

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/9 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add tanweiping1012-source/PhotoFilterAgent
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么[目录]—
dsh 原生插件 · market
暂无时间戳·判据 →
装得上吗[实装验证]!
作者未把包发布到 npm,只能用源码安装
插件本身没问题,是分发方式不同
数据截至 2026/10/01·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 26 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查需源码安装

engines.node 要求 >=24,不满足 Node 22.19.0

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包@photo-filter-agent/dsh-photo-filter-v4(未发布到 npm,仅可源码安装)
✗Node 引擎要求 >=24 · 基线 Node 22.19 不满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/15 09:45:48

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件
查看扫描证据(共 10 处,展示前 10 条)
  • 执行外部命令来自配置/变量
    release/setup.mjs:30
    const result = spawnSync(command, args, { stdio: 'inherit' })
  • 写入本地文件
    release/setup.mjs:35
    mkdirSync(data, { recursive: true })
  • 读取本地文件
    release/setup.mjs:37
    if (!existsSync(python)) run(values.python || 'python3', ['-m', 'venv', join(data, 'venv')])
  • 读取本地文件
    release/setup.mjs:48
    if (!existsSync(join(directory, 'package.json'))) throw new Error('Install the plugin in this profile using dsh plugin first')
  • 读取本地文件
    release/setup.mjs:50
    const document = parseDocument(existsSync(path) ? readFileSync(path, 'utf8') : '[]', {
  • 读取本地文件
    release/setup.mjs:50
    const document = parseDocument(existsSync(path) ? readFileSync(path, 'utf8') : '[]', {
  • 读取本地文件
    release/setup.mjs:67
    if (existsSync(path)) copyFileSync(path, `${path}.backup-${Date.now()}`)
  • 写入本地文件
    release/setup.mjs:67
    if (existsSync(path)) copyFileSync(path, `${path}.backup-${Date.now()}`)
  • 写入本地文件
    release/setup.mjs:69
    writeFileSync(temp, document.toString(), { mode: 0o600 })
  • 写入本地文件
    release/setup.mjs:70
    renameSync(temp, path)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-tools@deepseek-ai/dsh-agent-preset-registry
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 本地Vision分类
  • 连拍组比较
  • 按需视觉打分

README

由 DeepSeek 最新模型翻译生成
PhotoFilterAgent 旅行回来几千上万张照片,帮你挑出值得留的那几十张。 它是一个跑在 DeepSeek Harness(DSH)上的智能体:你在网页里跟它说「从这个文件夹挑 20 张」,它在你的 Mac 上把照片过一遍,给你一份名单,你点头之后再把这些照片复制到一个新文件夹。原图只读,永远不移动、不删除、不改名。 - 想直接用 → 快速上手 - 想知道它挑得准不准 → 它能帮你到什么程度 - 想看懂或改代码 → 给开发者的说明 快速上手 你需要有 - 一台 Mac(看人脸和眼睛用的是苹果系统自带的 Vision 框架) - Xcode 命令行工具(xcode-select --install)、Node.js 22.19 以上或 24 以上、pnpm(npm install -g pnpm)、git、Python 3.9 以上 - 约 6GB 磁盘空间(Python 环境约 2GB,第一次运行会下载约 3.6GB 模型) - 一个 MiniMax 的 API Key(智能体用它聊天;打开视觉复核时也用它当裁判。
展开完整文档(README 剩余 1 节)
PhotoFilterAgent

旅行回来几千上万张照片,帮你挑出值得留的那几十张。

它是一个跑在 DeepSeek Harness(DSH)上的智能体:你在网页里跟它说「从这个文件夹挑 20 张」,它在你的 Mac 上把照片过一遍,给你一份名单,你点头之后再把这些照片复制到一个新文件夹。原图只读,永远不移动、不删除、不改名。

- 想直接用 → 快速上手
- 想知道它挑得准不准 → 它能帮你到什么程度
- 想看懂或改代码 → 给开发者的说明

快速上手

你需要有

- 一台 Mac(看人脸和眼睛用的是苹果系统自带的 Vision 框架)
- Xcode 命令行工具(xcode-select --install)、Node.js 22.19 以上或 24 以上、pnpm(npm install -g pnpm)、git、Python 3.9 以上
- 约 6GB 磁盘空间(Python 环境约 2GB,第一次运行会下载约 3.6GB 模型)
- 一个 MiniMax 的 API Key(智能体用它聊天;打开视觉复核时也用它当裁判。换别的模型见 dsh-v4/README.md)

装(一条命令)

git clone https://github.com/tanweiping1012-source/PhotoFilterAgent.git
cd PhotoFilterAgent
PHOTOS=~/Pictures/我的旅行照片 ./install.sh

PHOTOS 是允许智能体读取的照片根目录,它只能处理这个目录里的照片。install.sh 会装好 DSH(固定到本项目验证过的 0.2.0-rc.2,不用你另外下载)、编译本地分析程序、建 Python 环境,再把智能体的配置装进 ~/.dsh-photo-filter。每一步都先检查,已经做好的就跳过,可以放心重复运行。

跑

cd ~/deepseek-harness && DSH_HOME=~/.dsh-photo-filter pnpm dsh --profile photo-v4

(install.sh 结束时会打印这条命令,照着复制就行;装的时候用 HARNESS= 换过 DSH 目录的,路径以它打印的为准。)终端会打印一个带 token 的网址(dsh web: http://127.0.0.1:…/?token=…),用浏览器打开。第一次用先填 MiniMax 的 API Key:网页左下角「设置 → 模型 → minimax-cn → 编辑」,或者启动前 export MINIMAX_CN_API_KEY=你的key(两种都有时环境变量优先)。然后直接说:

请从 /Users/你/Pictures/我的旅行照片 里挑 20 张最好的照片

它会依次扫描目录、排序,把名单、这次花了几次模型调用、名单是怎么来的告诉你。之后可以接着问「为什么选了 p012」「换成有氛围的那种」「把这 20 张复制到 ~/Downloads/精选」(智能体会先给你一个确认码,按规则等你回复才复制。码对话模型也看得到,所以这一步靠它守规则,不是开关;复制只在你的电脑上进行,不发照片)。对话里照片只用 p001 这样的编号,真实文件名不进对话。

不想开网页,也可以在命令行跑一次性任务:

cd ~/deepseek-harness && DSH_HOME=~/.dsh-photo-filter pnpm dsh --profile photo-v4-headless "从 ~/Pictures/我的旅行照片 挑 20 张最好的照片"

哪里不对劲,先跑自检,它会指出哪一层没装好:

bash dsh-v4/doctor.sh

花多少钱、照片会不会发出去

- 默认配置下排序不发照片:三步全在你的电脑上算。花钱的只有对话本身(按你用的模型计费)。
- 让视觉大模型当裁判的「视觉复核」默认关。实测它没让结果变好:同一批 299 张照片,开着它跑 5 次,撞上主人精选 7、5、7、7、6 张,纯本地排序是 7 张,却每次多花约 120 次调用、10 分钟(见第二步与第三步的 A/B)。
- 想自己试,把 profile 里的 stage2Vlm 改成 true(见 dsh-v4/README.md)。打开后,第二步按组从大到小逐组打擂台,最多 60 局(约 120 次调用);每次发 512 像素的整幅小图和 448 像素的人脸特写,拍摄时间、地点、相机型号都剥掉,不发文件名和路径。跑完它会告诉你确切调用次数。
- 你在对话里要求「再精细一点」时,智能体可以用视觉模型再比几对照片。发图之前,网页会弹出审批,写明比几对、约几次调用、发哪几张,你点同意才发;这个问答由 DSH 直接问你,对话模型看不到也替你答不了。命令行模式没有审批界面,就一律不发。
- 不需要你先给它一批「我喜欢的照片」当例子。实测给了例子也没让结果变好,所以默认不要。

另有一种装法:把智能体打成 DSH 的标准插件包,装进你已有的 DSH 网页 profile(还没发布到 npm),见 release/README.md。

它能帮你到什么程度

我们拿一批真实的旅行照片做过测试:309 张人像,照片的主人自己从里面挑出了 20 张最喜欢的。然后让程序在不知道答案的情况下挑一遍(纯本地排序,同样的照片每次给同样的名单。智能体的默认配置跑的就是这一套;命令行排序器要带上 --engine,结果才完全相同。2026-10-01 用当前代码复测):

| 让程序挑 | 撞上你心头好的 | 如果闭眼乱挑 |
| 10 张 | 2 张(跟运气区分不开,p≈0.13) | 0.6 张 |
| 20 张 | 6 张 | 1.3 张 |
| 50 张 | 10 张 | 3.2 张 |

挑 20 张时它比乱挑好四倍多,挑 50 张时约三倍,这两档都排除了运气(p < 0.001);挑 10 张只多对一张多,跟运气区分不开。而且挑 20 张里也就 6 张是你真想要的。

所以它干的事是:把 309 张缩到 20 张,让你在这 20 张里挑。 你少翻 289 张,最后拍板的还是你。如果你想要能直接发朋友圈的成品,这个 agent 还在努力迭代。

整体思路:把一件事拆成三步

「帮我挑照片」听起来是一件事,其实是三件,而且难度天差地别:

| 阶段 | 详情 | 当前结果 |
| 第一步:过滤废片 | 扔掉明显不能要的 | workflow 程序做得好 |
| 第二步:局部最优 | 连拍六张留最好的一两张 | workflow(VLM 裁判可选,默认关) |
| 第三步:全局最优 | 从剩下的选出最值得留的,帮你直接挑选出朋友圈 | 千人千面,只能帮你缩小范围 |

越往后越主观,程序能帮的忙就越少。 至于第三步的成绩算高还是算低,现在没有参照:没有第二个人从同一批照片里挑过,两个人本来能重合多少,没人知道(见下面第二步的结论)。

第二步的 VLM 裁判默认关闭。第四轮实测:同一批 299 张照片,开着它跑 5 次撞上主人精选 7、5、7、7、6 张,纯本地排序是 7 张,没有一次更高(见下面第二步、第三步的 A/B)。

下面逐步讲每一步用了什么、怎么做的、测出来什么结果。

第一步 · 过滤废片

要解决什么

闭着眼的、糊掉的、低头看地的。这类照片不用你看第二眼。

原理

眼睛睁开的时候,眼睛轮廓是扁的椭圆;闭上的时候被压成一条线。
所以量一下眼睛轮廓的「高 ÷ 宽」,睁着的时候这个值明显大,闭着的时候明显小。

这个方法叫眼睛纵横比(Eye Aspect Ratio),是业界判断眨眼的标准做法,不需要任何联网的模型。

用了什么

苹果系统自带的 Vision 框架(你的 Mac 上本来就有,免费、离线):

| 用的功能 | 干什么 |
| VNDetectFaceLandmarksRequest | 找出眼睛、鼻子、嘴的轮廓点 |
| VNDetectFaceCaptureQualityRequest | 给「这张脸拍得怎么样」打个 0–1 的分 |
| VNDetectFaceRectanglesRequest(第 3 版) | 给出头的朝向角度 |

第三个必须显式指定第 3 版,默认版本不返回角度,会静默给出空值。

具体怎么做

① 眼部检测必须在高清人脸上做。

程序为了快,平时是拿缩到 1024 像素的小图分析的。但这批照片是「人站在雪山前」那种环境人像,人只占画面 8% —— 缩完之后脸只剩 74 像素,眼睛根本看不清。

而原图是 7728 像素宽的,同一张脸有 564 像素,绰绰有余。

所以流程改成:

在 1024px 小图上找到脸在哪(快) ↓ 按人脸大小反推需要多高的分辨率,从原图解出那一档 ↓ 把人脸连同周围 60% 的余量裁出来(要看得见眉毛和脸颊,只给眼眶检不出) ↓ 在这张约 320 像素的高清人脸上重做关键点

判断门槛也从「脸占画面多大比例」换成「脸实际有多少像素」(要求 ≥160)——后者才是关键点可靠性的真正决定因素,前者把分辨率写死了。

结果:能判断眼睛的照片从 32% 涨到 100%。

② 把「低头」和「闭眼」分开。

低头的时候眼睑会遮住眼球,高÷宽这个值同样会掉下来 —— 程序会把低头误判成闭眼。实测有一张照片主人自己选进精选的低头照就这么被扔掉了。

修法:读头的俯仰角。平视时约 5–7°,明显低头时约 15°。
超过 12° 就报「低头」,不报「闭眼」,也不否决。

(为什么低头不否决?因为实测把低头也当废片扔掉,会误伤照片主人的 2 张精选。
低头确实不太好看 —— 低头的照片只有 5% 被主人接受,全体是 27% ——但这个相关性还不够硬,不足以支持一票否决。)

这一步的验收标准不是准确率,是误杀率

两类错误的代价完全不对称:

少扔了(该删的没删)→ 你多翻几张,没什么损失错扔了(好照片被删)→ 你永远看不到它了,不可逆

所以设计目标是「在零误杀的前提下,能扔掉多少」。

评测结果

能判断眼睛的照片 32% → 100% 扔掉 52 / 309 张误杀(照片主人的 20 张精选) 0 张 ✅ 误杀(照片主人另外标注的可接受照片) 0 张 ✅

阈值 0.22 正好卡在临界点:放宽到 0.25 就开始误伤 5 张好照片。

第二步 · 局部最优(连拍里留最好的)

要解决什么

你按了六下快门,其实只需要一张。

原理

这六张几乎一模一样 —— 光线一样、构图一样、清晰度一样, 差别只在表情和眼神。差别维度少,反而好比较。

关键的设计选择是:用两两比较,不用各自打分。

因为「给一张照片打 7 分」这件事在我们的实测里非常不可靠:让视觉大模型给同一张照片打两次分,两次的差距,比不同照片之间的真实差距还大。
尺子的抖动超过了要测的东西。

而「这两张哪张更好」就稳得多 —— 有参照物的判断,比凭空打分可靠。

⚠️ 这个对比有一个已知的混淆,必须说明。
做那次打分实测时,发给模型的是 512px 整幅图,人脸中位数只有 45 像素(最小 21px),100% 低于本项目自己后来定的 96px「能看清脸」门槛。
在看不清眼睛的图上判「眼神好不好」,分数当然抖 —— 那更可能是信号不在图里,而不是「打分这种形式」本身的问题。
现在发的是 448px 人脸特写,但两两比较和打分从未在这个条件下做过头对头对比。
所以上面这句「比较比打分可靠」,目前只是一个合理猜想,不是已验证的结论。

用了什么

① 认出「哪些照片是一组的」:CLIP 图像特征

CLIP 是 OpenAI 的一个开源模型(用的是 ViT-L/14 这一档),它能把一张照片变成一串 768 个数字,内容相近的照片,这串数字也相近。

两张照片像不像,就看这两串数字的余弦相似度(一个 0~1 的数,越大越像)。

阈值不能写死。 相似度没有绝对刻度 —— 同一个阈值在不同批照片上表现完全不同。我们试过写死 0.86,结果把 309 张照片分成了 2 组。

改成自适应:算出这批照片两两相似度的分布,取 98 分位当阈值(也就是「最像的那 2% 才算一组」),并设一个 0.90 的下限。

聚类用贪心组长法:按文件名顺序遍历,每张照片要么加入某个已有的组,要么自己当新组的组长。不用单链接聚类,因为那会让连拍一路串成一大坨。

分组顺序故意不按分数排。否则换一个打分方式就换一套分组,两次结果没法比较 —— 实测同一份打分只改分组顺序,最终命中会在 3~5 张之间跳。

② 组内排序:一个专门评人脸的模型

用 topiq_nr-face(来自开源工具包 pyiqa),专门评「这张脸拍得怎么样」。

我们把六个通用美学模型都测了一遍,只有两个能超过随机水平:

| 模型 | 判别能力(0.5 = 等于抛硬币) |
| liqe | 0.449 |
| clipiqa+ | 0.473 |
| nima | 0.481 |
| topiq_iaa | 0.512 |
| musiq-ava | 0.548 |
| laion_aes | 0.583 ✅ |
| topiq_nr-face | 0.608 ✅ |

有人脸时用 topiq_nr-face,没人脸时退回 laion_aes。
两个融合起来反而更差(我们试过七次融合,没有一次让最终名单变好)。

具体怎么做:擂台赛

本地分最高的那张当擂主 ↓ 其余按分数从高到低依次挑战 ↓ 挑战者赢 → 换他当擂主继续应战平局 → 擂主不下台 ↓ 最后站着的那张当组内冠军

为什么是擂台赛不是全循环? 全循环要比 n×(n-1)/2 次,在这 309 张上是 1114 局;擂台赛只要 n-1 局,共 169 局。

为什么不只比前 3 名? 实测照片主人选中的那张,常常排在组内第 5、6、8 甚至第 14 位 —— 只比前几名会直接漏掉。(组内超过 8 张的会截断,因为超过 8 张的组很少,为它们多花一倍成本不值。)

平局为什么擂主不下台? 平局很常见,不该因为一次没分出高下就换人。
这让结果对判断噪声更稳。

VLM 用在哪一步

只用在这里 —— 擂台赛的每一局,判「这两张哪张更好」。 它不参与打分、不参与分组、不参与最终选片。

「裁判」就是决定这两张哪张好的那个角色,程序里做成了可替换的三种:

| 裁判 | 怎么判 | 花钱吗 |
| 本地分(默认) | 比 topiq_nr-face 的分数 | 不花 |
| VLM(可选,默认关) | 把图发给模型问哪张更好 | 每局 2 次调用 |
| 先知 | 直接查人工标注的答案 | 不花,只用于自测 |

发给 VLM 的必须是「整幅小图 + 高清人脸」两张

这是个容易忽略但很致命的细节。

发给模型的整幅小图最长边 512 像素。而这批是环境人像,人只占画面很小一块 —— 人脸在小图上只剩约 30 个像素,309 张里有 91% 不足 48 像素:

| 小图长边 | 人脸还剩多少像素 | 能判断表情吗 |
| 512px(原来发的) | 30 | ❌ 完全不能 |
| 1024px | 61 | ⚠️ 勉强 |
| 1536px | 91 | ⚠️ 勉强 |

而提示词却在要求模型判断「笑是不是到眼睛里、有没有僵硬」—— 它根本看不见,只能猜。

所以每张照片发两幅图:

整幅画面(512px) → 看构图、姿态、环境人脸特写(448px) → 看表情、眼神

人脸从原图裁(不是从小图放大),裁的时候连同周围一圈余量,要看得见眉毛和脸颊。这和第一步里本地检测眼睛用的是同一个道理,也是同一个坑:缩小之后脸就没了。

做成可换是为了能分开归因:

- 赛制本身对不对?→ 用「先知」跑一遍,应该接近满分
- 裁判判断力如何?→ 换本地分 / 视觉模型对比

混在一起时,一个坏结果没法归因 —— 分不清是赛制错了还是模型不行。

用视觉模型时的四条约束:

1. 只发最长边 512 像素的整幅小图 + 448 像素的人脸裁切(合计约 58KB),拍摄时间、地点、相机型号全部剥掉,不发文件名和路径
2. 正反各问一次(先给 A 后给 B 问一次,调换顺序再问一次)。
两次答案不一致记为「翻覆」,不定赢家,擂主不下台。
(当初这么设计是怕模型偏爱第一张;后来的标定排除了位置偏好,不一致的主因是模型答不稳,见文末补充一节)
3. 模型必须通过结构化输出回答,不接受自由文本;上场前先做一次能力探测,不通过就整轮停下,不允许静默换成别的模型
4. 打哪些局:所有至少两张的组,按组从大到小,逐组打满擂台(每组最多 8 张),打到下一组放不下 60 局为止,后面更小的组就不打了(约 120 次调用)。
不按「会不会进名单」挑组 —— 那种挑法的规则一条都没验证过

这一步做得怎么样 —— 一次 A/B 测试

先说一个前提:这个任务没有唯一答案。 连拍里那几张本来就差不多 ——照片主人隔一段时间自己再挑一遍,也只有 50% 跟上次一样。
所以程序选了另一张,不见得算错。

那怎么判断这一步值不值得做得更好?我们直接量最终交到你手上的照片。

这一步可以让视觉大模型当裁判(默认用不花钱的本地算法)。于是有个很自然的想法: 把「什么算好照片」写清楚给它,再给它看几组你自己挑过的例子,它是不是就能挑得更像你?

跑三遍完整流程来回答。同一批 299 张照片、同一个人的 20 张精选当答案,每次只多给模型一样东西:

| | 一份判据 | 你挑过的例子 |
| 第一遍 | ✗ | ✗ |
| 第二遍 | ✓ | ✗ |
| 第三遍 | ✓ | ✓ |

结果:三遍交出的 20 张,完全一样

| | 撞上主人精选 | |
| 闭着眼睛乱挑 | 1.3 / 20 | 6.5% |
| 不花钱的本地算法 | 7 / 20 | 35% |
| 第一遍(什么都不给) | 7 / 20 | 35% |
| 第二遍(给判据) | 7 / 20 | 35% |
| 第三遍(判据 + 例子) | 7 / 20 | 35% |

不只是张数一样 —— 三个文件夹里的照片,文件名相同,内容逐字节相同。 三遍一共花了约 490 次付费调用,你拿到手的照片一张都没变。

(这一轮把当例子用的那 10 张从候选池里拿掉了 —— 不能拿它见过答案的照片去考它 ——所以这里的基准是 7 张,跟后面第三步那张表里的 6 张口径不同。)

但模型的判断确实变了

翻开中间过程看,三遍的判决差得很明显:

| | 第一遍 | 第二遍 | 第三遍 |
| 每局给模型看几张图 | 4 张 | 4 张 | 24 张 |
| 比了多少局 / 花了多少次调用 | 60 / 120 | 60 / 120 | 60 / 120 |
| 判「前一张更好」 | 12 局 | 17 局 | 15 局 |
| 判「后一张更好」 | 8 局 | 4 局 | 6 局 |
| 判「两张都好」 | 3 局 | 2 局 | 6 局 |
| 判「两张都不够格」 | 0 局 | 1 局 | 1 局 |
| 正反问两遍,答案不一致(这局作废) | 37 局 | 36 局 | 32 局 |
| 认出图上编码的比例 | 98.3% | 98.3% | 100% |
| 比的是不是同一批照片对 | 60 局 | 逐对相同 ✓ | 逐对相同 ✓ |

加了判据之后,判「后一张更好」从 8 局掉到 4 局 —— 它的口味确实被影响了。

两行需要解释一下:

- 正反问两遍指的是同一对照片,换个顺序再问一次;两次说法不一致这一局就作废。
三遍里作废的都超过一半 —— 模型对这类题本来就答不稳,详见本文末尾那节一致性测试。
- 认出图上编码:每张图上烧了一个 4 位随机码,要求模型抄回来。抄对率 98~100%,说明它确实看到了图,不是在瞎猜。

那为什么照片没变

因为模型的意见到不了最终名单。

第二步只在同一组连拍内部比较,选出这一组的代表。
真正决定「哪些代表能进最终 20 张」的是第三步(下一节讲)—— 而那 20 张,来自 20 个不同的组:

只有 2 张,来自模型比过的组其余 18 张,模型从头到尾没看过

而那 2 张参与的每一局都赢了 —— 按规则,不输就不换人。

所以照片不变,是结构决定的,跟判据和例子写得好不好没关系。

2026-09-12 查出:这一轮的判决当时其实没有传到排序器(代码漏传了一个参数,已修)。
修好后用存档下来的实验组 2(判据 + 例子)的判决重放一遍(0 次付费调用;另两组的判决已经丢了),交出的 20 张一张没变,还是 7/20 ——结论成立,只是证据链比当时以为的短。详见附录。

结论

判据和例子到底有没有用,这一轮答不了。
模型的意见和最终名单之间没有通路 —— 我们量的是一段断开的电线。

这跟「判据和例子没用」是完全不同的两句话。

它也指出了下一步该改哪里:让模型优先去比那些真正决定名单的组,而不是像现在这样比最大的几组。这个改动不用多花钱,但截至 2026-09-30 还没改 ——改了等于让一个还没证明有用的裁判真正影响名单,要当作新实验先定判据再跑。

还有一件事这一轮也答不了:7/20 算好还是算差,现在没人知道。 答案是一个人的选择,两个人挑同一批照片本来重合度就不高 ——除非再找一个人从同一批照片里挑 20 张,否则没有参照系。

完整报告 → 第二步 A/B 实验报告

含全部原始数据、四次失败的执行记录、以及六条被我们自己推翻的判断。
跑之前写死的判据在 CRITERIA-RUBRIC-ANCHORS.md,跑完一个字没改。

第三步 · 全局最优(选出最值得发的)

要解决什么,以及为什么它最难

前两步问的是「这张照片好不好」—— 有客观答案。
第三步问的是「你想留哪张」—— 答案在你脑子里,照片本身没有这个信息。

同一批照片,你和你朋友挑出来的会是两批不同的照片,谁也没错。

原理:与其猜你的口味,不如匹配你的行为

我们比对了「照片主人自己挑的 20 张」和「程序挑的 20 张」的时间分布:

主人自己挑的 最挤的一个时间段里 5 张,覆盖了整趟行程 94% 的时间跨度程序原本挑的 最挤的一个时间段里 14 张 ← 全挤在十几分钟里

人是「这段行程的每一段都挑几张」,程序是「把最好的那一段整段端走」。

而挤在一段里等于自己砍掉了覆盖面 —— 打分信号本来就弱,只作用在照片池的一小部分上更浪费。

具体怎么做

把照片按拍摄顺序切成 10 段 ↓ 每段最多挑 ⌈目标张数 ÷ 10⌉ 张 ↓ 同一个连拍组最多进 2 张 ↓ 第二步选出的组内冠军优先,其次才看分数高低

最后那条很关键:组内名次是第一排序键,全局分数只是第二排序键。 这样每个组的冠军都排在所有组的亚军之前 —— 相当于又加了一层多样性保证。

(我们试过按「拍摄间隔突然变大」来自动切段,实测比固定切 10 段更差。
因为自动切出来的段大小差很多,一个只有 2 张的小场景和一个 40 张的大场景拿到同样的配额,小场景就被过度代表了。)

试过但没成功的:学你的口味

最自然的想法是让你标几张喜欢的,程序学。我们试了,失败。

标注 10~15 张时,融合之后最终名单反而更差,30 次随机划分里只赢了 7 次。

而且有个更根本的问题:普通用户不会先给你一批标注。 一个要求用户先做作业才能用的工具,不是工具。

所以这个功能默认关闭。你给的标注会被记录,但不参与排序 ——程序会如实告诉你「收到了但没用上,因为实测没有收益」,而不是粉饰成「正在学习你的口味」。

评测结果

| 让程序挑 | 撞上主人精选 | 乱挑的期望 | 好多少倍 |
| 10 张 | 2 张 | 0.6 | 不算(跟运气区分不开,p≈0.13) |
| 20 张 | 6 张 | 1.3 | 4.6 倍 |
| 50 张 | 10 张 | 3.2 | 3.1 倍 |

下一步该往哪走? 有个反直觉但数据支持的结论:别直接攻第三步。

第三步的机制一行没改、光靠改进前两步,挑 50 张就从 8 张涨到了 10 张。
前两步是第三步的输入 —— 废片扔得更干净、组内选得更准,第三步的候选池就更好。这比在信号本来就弱的第三层加机制有效得多。

试过:让视觉大模型复核第三步(默认关闭)

第三步挑完之后,可以让视觉大模型在每个时间段里比一次「已经入选的 vs 候补」:两张照片正着问一遍、反着问一遍,候补两次都赢才换人。

我们做了一次完整的 A/B:同一批 299 张照片,四种配置各跑 5 次,一共 20 次有效运行(连同中途出故障作废的,约 3300 次模型调用)。
第二步的视觉复核全程开着,四组只差这一步。同一批照片纯本地排序是 7 张:
| 配置 | 5 次的得分(撞上主人精选几张) | 第三步让精选变少 | 变多 | 不变 |
| 不开(对照) | 7 · 5 · 7 · 7 · 6 | — | — | — |
| 开,不给提示 | 7 · 6 · 6 · 5 · 7 | 1 次 | 1 次 | 3 次 |
| 开,给一份挑选标准 | 6 · 7 · 5 · 6 · 4 | 2 次 | 2 次 | 1 次 |
| 开,标准 + 8 张范例照片 | 8 · 5 · 7 · 7 · 7 | 1 次 | 2 次 | 2 次 |

「变少 / 变多 / 不变」比的是同一次运行里第三步之前和之后。对照组那一行也是第二步视觉复核开着的:它 5 次之间差 2 张,全部来自第二步裁判的不稳定。

实测有把精选换下去的情况。 开着的 15 次一共换了 36 次人:换上精选 12 次、换下精选 13 次,合起来精选少了 1 张。不开的那组自己五次之间就能差 2 张,开了之后的变化都没超出这个幅度 —— 测不出它有用,加标准、加范例也测不出区别。

为什么没用,是两个原因叠在一起:

- 局面不对称:本地排序在第三步已经挑得不差。裁判一共比了 150 局,其中 77 局两张都不是(或都是)主人精选,不影响得分;剩下的局里,57 局是「已选上的就是主人精选」,它判对了不加分、判错就扣分;只有 16 局是「精选还在候补」,判对才加分。
- 裁判不够准、也不够稳:它明确表态、而且恰好一张是主人精选的局里,判对 28/41,大约三分之二,比乱猜好;但扣分的机会是加分的 3.6 倍,三分之二刚好打平。它还有很多局拿不定主意 —— 同一对照片正反各问一遍,只有一半的时候两次答案一致。

还有一条结构上限:每个时间段最多 2 张、同一连拍组最多 2 张,在这批照片上任何第三步机制最多撞上 14 张,到不了 20。

所以它默认关闭。想自己试,开法见 dsh-v4/README.md;完整数据见 端到端 A/B 报告。

整条链路长什么样

你的照片文件夹 │ ├─ 建缓存:缩到 1024 像素,剥掉全部元数据 ├─ 算特征:CLIP ViT-L/14 → 每张 768 个数字 ├─ 算质量:topiq_nr-face(有脸)/ laion_aes(无脸) └─ 苹果 Vision:人脸质量、睁眼程度(高清人脸上测)、头的俯仰角 │ 第一步 闭眼的扔掉(低头的只标记,不扔) │ 第二步 按 CLIP 相似度分组 → 组内擂台赛 → 定出组内名次 │ 第三步 时间段配额 + 同组上限 2 + 组内名次优先 │ └→ 最终名单

命令行排序器全部在你的电脑上跑完,0 次付费调用,同样的照片永远给同样的结果。
第一次约 5 分钟(建缓存),之后每次 1.6 秒。

智能体默认跑的就是这一条。打开视觉复核后多一件事:第二步按组从大到小,逐组请视觉模型当裁判打擂台(最多 60 局、约 120 次调用,要十分钟左右)。模型的答案会变,所以打开后结果不再逐次相同。

只用命令行排序器

不需要 DSH、不需要 API Key、一张图都不发出去。没有对话,也没有视觉模型复核。

你需要有

一台 Mac(要用苹果自带的人脸识别)、Python 3.9 或更新、一个装照片的文件夹。

装

(cd engine && swift build -c release) # 本地分析引擎:看人脸、判断闭眼
cd ranker
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

第一次会下载几个模型(合计约 3.6GB),之后就不用了。

跑

python -m photofilter_rank.cli pick ~/Desktop/我的照片 --target 20 --engine ../engine/.build/release/photofilter

--engine 别省:不给它,闭眼的照片挡不掉,人脸质量也退回较弱的指标(同一批 309 张,挑 20 张从 6 张掉到 5 张),排序器会打出警告。带上它,结果和智能体默认配置逐张相同。

程序会打印一份名单。它不会动你的原图 —— 不移动、不删除、不改名、不覆盖。

先小规模试试:

python -m photofilter_rank.cli scan ~/Desktop/我的照片

你的照片会去哪里

命令行排序器:哪里都不去。 所有分析、打分、比较都在你自己的电脑上完成,不需要注册,不需要 API Key,不需要联网(除了第一次下模型)。

智能体: 默认配置下排序哪里都不去。会把照片发给视觉模型当裁判的有两种情况:你打开了视觉复核(stage2Vlm / stage3Vlm,这是开关);或者你在网页的审批里同意了「再精细比几对」—— 不点同意(或在命令行模式下),这个工具一张照片都不发。
发的是 512 像素的整幅小图和 448 像素的人脸特写,剥掉全部元数据,不发文件名和路径。
对话模型本身看不到照片,只看得到 p001 这样的编号。原图永远只读。

它做不到的

1. 风景照做不了。 我们单独测了 161 张风景照,六个业界通用的美学评分模型判别能力全在 0.46~0.55 之间(0.5 就是抛硬币)。为了确认这不是没调好,我们又用故意无关的判据做对照 —— 结果「照片里有没有文字」和「是不是黄金时刻的光线」得分一样高。这条路确实走不通。

2. 人像和风景混在一起的文件夹会出问题。 程序按整个文件夹的人脸比例决定用哪套标准,而真实用户的文件夹就是混的。
已知缺陷,还没修。

3. 只在一个人的照片上验证过。 换个人可能表现不一样。

4. 眼睛天生细长的人可能被误判成闭眼。 用的是固定阈值,理论上对不同眼型不公平。我们试过按每个人自己的基准判断,但测试集基本是同一个人,验证不了。目前没有证据证明它是公平的。

5. 让视觉大模型当裁判,至今测不出它有用。 第二步、第三步各做过 A/B,给判据、给范例都测不出区别。一个直接的原因是模型对同一问题答不稳:输入逐字节相同、重复问一遍有 62.8% 改口,详见下面「补充」一节。所以视觉复核默认关。

补充 · VLM 对比评判的一致性测试(2026-09-03)

在「第二步 · 局部最优」里,擂台赛每一局都要问视觉模型「这两张哪张更好」。
我们一直观察到一个现象:同一对照片对调位置问两遍,模型经常给出互相矛盾的答案。

这一轮专门做了一次标定,把可能的原因拆开。79 张照片 / 78 对 / 394 次调用。

结果

| 对调位置问两遍,一致 | 37 / 78 = 47.4% |
| 对调位置问两遍,不一致 | 41 / 78 = 52.6% |

不一致的原因,经对照测试逐条排除:

| 猜测 | 判定 | 怎么测的 |
| 模型偏好某个位置 | 排除 | 同一批照片排在前 72%、排在后 72%,被选中比例完全一样 |
| 模型没读到图(图太小、人脸太糊) | 排除 | 图上烧 4 位随机码要求抄回,310/312 抄对;原图 vs 重度模糊副本 10/10 全部选中清晰那张 |
| 模型没看图、硬编赢家 | 排除 | 同一张照片的两个副本(差异严格为零)问 36 次,34 次如实答「分不出」 |
| 模型对同一问题本身答不稳 | 确认 · 主因 | temperature=0、输入逐字节完全相同的重复调用,62.8% 改变答案 |

关键一条:什么都不改重复问一遍的不一致率(62.8%),高于对调位置的不一致率(52.6%)。
也就是说,观测到的全部不一致都能被「模型答不稳」解释完,不需要位置偏好这个假设。
跨进程复核 38% 与同进程 38% 一致,排除了会话状态残留。

这对本项目意味着什么

- 上面「发给 VLM 的必须是整幅小图 + 高清人脸」那套做法,这次拿到了正面证据:烧码 310/312 抄对、模糊对照 10/10,说明模型确实读到了小图上的细节。
- 擂台赛现在的规则是「正反问两遍,不一致记为翻覆、擂主不下台」。按这个不一致率,一半以上的局实际上由本地分定。这不是判据写得不好,是调用本身不稳定。
- 任何提示词 / 评分标准的 A/B 对比,都必须排在这个问题解决之后 ——在基准线搞清楚之前,小于噪声的差异测不出来(那一轮想测的效应不到 10 个百分点)。

完整报告 → 连拍场景下,VLM 的对比评判一致性测试

报告写给没参与过本项目的读者,含完整方法、逐条对照设计、原始数据口径与已知缺陷。
预登记判据见 INSTRUMENT-CHECK.md,跑之前就写死了。

本报告只测一致性(同一问题问两遍答案稳不稳),不评价模型判得对不对 —— 那是另一个话题。

想再了解一点

| 你想知道 | 去哪看 |
| 代码怎么组织、一次运行里发生了什么 | 给开发者的说明 |
| 智能体的配置、换模型、DSH 版本 | dsh-v4/README.md |
| 第三步视觉复核那次 A/B 的全部数据 | 端到端 A/B 报告 |
| 第二步那次 A/B 测试的全部数据 | 第二步 A/B 实验报告 |
| 这个工具是怎么一步步做出来的 | 版本演进 |
| 怎么判断一个选片程序好不好 | 测量方法 |
| 命令行的全部用法 | 排序器说明 |
| VLM 对比评判到底稳不稳 | 一致性测试报告 |
预览分享卡片长什么样 ↓tanweiping1012-source/PhotoFilterAgent
分享 · DeepSeek Harness Hub
tanweiping1012-source/PhotoFilterAgent

跑在 DeepSeek Harness 上的照片策展 agent:本地 Vision 分类 + 连拍组比较 +…

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部