feat(t211): return SKU-matched image search candidates

This commit is contained in:
QiuSW
2026-07-27 18:35:59 +08:00
parent 667a64f6c2
commit edfee2c35e
26 changed files with 1838 additions and 153 deletions
+5 -3
View File
@@ -146,11 +146,13 @@ provider Key 从旧设置迁移到 Android Keystore 包装的加密存储;迁
浏览范围。
- 模型不能返回点击坐标、状态迁移或“允许提交订单”等执行授权。
T-104 按候选 ordinal 串行评估,每个候选最多一次结构化调用、整批最多 5 次;首个
T-104/T-211 按候选 ordinal 串行评估,每个候选最多一次结构化调用、整批最多 5 次;首个
网络失败或无效输出停止后续调用。模型响应只允许
`schema_version/candidate_index/decision/score/matched/missing_or_uncertain/
rejection_reasons/confidence`,其中 `decision` 仅允许 `REVIEW/REJECT/
MANUAL_REQUIRED`。ordinal、证据 SHA-256、建议候选、provider provenance、
rejection_reasons/confidence/hard_constraint_results`,其中 `decision` 仅允许
`REVIEW/REJECT/MANUAL_REQUIRED`。`hard_constraint_results` 必须逐项原样回显本地给出的
颜色和尺码期望值,并返回 `MATCH/MISMATCH/UNKNOWN` 与证据说明;缺项、重复、改写期望值
或 `UNKNOWN` 均不能进入自动 Top 5。ordinal、证据 SHA-256、建议候选、provider provenance、
`manual_review_required=true` 和 `order_submitted=false` 由本地代码确定。任务没有
预算时,模型声称价格或预算匹配会被视为无效输出。
+30 -11
View File
@@ -474,7 +474,9 @@ App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 终态”顺
### `POST /api/v1/tasks/{task_id}/candidates`
批量保存当前 execution 实际检查的最多 5 个候选,必须带 `Idempotency-Key`:
批量保存当前 execution 实际检查后通过策略的 `0..5` 个候选,必须带
`Idempotency-Key`。正式参考图检索使用固定审计值 `PDD_IMAGE_SEARCH`,不把标题或
SKU 伪装成图片检索词:
```json
{
@@ -482,18 +484,18 @@ App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 终态”顺
"claim_generation": 1,
"task_content_sha256": "64-char-lowercase-hex",
"execution_mode": "AI_ASSISTED",
"search_query": "黑色 20L 双肩包",
"search_query": "PDD_IMAGE_SEARCH",
"provenance": {
"provider_id": "device-configured-provider",
"model": "device-configured-model",
"prompt_version": "candidate-evaluation-v1",
"schema_version": 1
"prompt_version": "candidate-evaluation-v2",
"schema_version": 2
},
"candidates": [
{
"ordinal": 1,
"title": "页面可见标题",
"sku_text": "黑色 20L",
"sku_text": "BLACK-L",
"price": "189.00",
"product_url": "https://mobile.yangkeduo.com/goods.html?goods_id=example",
"image_url": "https://example.invalid/short-lived-image",
@@ -501,10 +503,24 @@ App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 终态”顺
"evaluation": {
"decision": "REVIEW",
"score": 0.82,
"matched": ["颜色接近"],
"missing_or_uncertain": ["容量需人工确认"],
"matched": ["颜色和尺码均有可见证据"],
"missing_or_uncertain": [],
"rejection_reasons": [],
"confidence": 0.78
"confidence": 0.78,
"hard_constraints": [
{
"kind": "COLOR",
"expected": "BLACK",
"status": "MATCH",
"evidence": "候选页面显示黑色"
},
{
"kind": "SIZE",
"expected": "L",
"status": "MATCH",
"evidence": "候选页面显示 L 码"
}
]
}
}
],
@@ -516,9 +532,12 @@ App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 终态”顺
}
```
`MANUAL_FIRST` 时 `provenance` 和 `evaluation` 为空,但候选观察、搜索词和人工结果仍
可提交。后端校验 ordinal 连续唯一、最多 5 个和任务内容哈希;不请求 `product_url`
或 `image_url`,主要证据必须是已鉴权 asset。
`MANUAL_FIRST` 时 `provenance` 和 `evaluation` 为空,但候选观察、搜索审计值和人工
结果仍可提交。`AI_ASSISTED` 的非空 `evaluation` 必须同时包含唯一的 `COLOR` 和
`SIZE`,且全部为 `MATCH`;schema v2 还要求分数和置信度均不低于 `0.75`,非空批次的
推荐必须指向排序后的第 1 项。没有候选满足条件时提交空 `candidates` 和空
`recommendation`,不得用弱匹配或未知项凑数。后端校验 ordinal 连续唯一、最多 5 个和
任务内容哈希;不请求 `product_url` 或 `image_url`,主要证据必须是已鉴权 asset。
### `POST /api/v1/tasks/{task_id}/complete`
+24 -11
View File
@@ -5,10 +5,9 @@
## 当前快照
- 日期:2026-07-27
- 阶段:T-207 App 本地 VLM、候选、证据和结果回传完成;T-209 登录失败保留输入完成;
T-210 拼多多 8.17 结果页兼容完成,下一步 T-208
- 阶段:T-211 参考图召回、SKU 颜色尺码过滤与 Top 5 回传完成,下一步 T-208
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-207、T-209、
T-210 均已纳入 Git 历史
T-210、T-211 均已纳入 Git 历史
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
- Android:固定 `main@c8a6d7f03422eb01744b01f3ee77bf7757741f7e`;MIT 许可证已保留
@@ -18,10 +17,10 @@
- 本机 Android 工具:JDK 17.0.13、Command-line Tools 22.0、SDK 34、
Build Tools 34.0.0、Platform Tools/ADB 37.0.0;用户级 SDK 环境变量已设置
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
- 测试:T-207 运行 `:app:testDebugUnitTest` 和 `:app:assembleDebug` 通过;Debug APK
已安装并在 PKG110 启动到任务登录页,无 crash/ANR
- 后端测试:T-207 运行 `GOTOOLCHAIN=local go test -count=1 ./...`、全包 race 和
`go vet ./...` 通过;临时 SQLite migration 已验证 `status -> up -> down -> up`
- 测试:T-211 运行 `:app:testDebugUnitTest` 和 `:app:assembleDebug` 通过,20 个 suite
共 108 个测试、0 失败;最新 Debug APK 已安装到 PKG110
- 后端测试:T-211 运行 `GOTOOLCHAIN=local go test -count=1 ./...` 和
`go vet ./...` 通过;T-207 的全包 race 与 migration 验证继续有效
- 原型:4 个管理 Web 页面和 7 个 Android 页面均可离线独立打开;Playwright
以 1440×900、390×844、360×800 验证 36 个页面/视口组合,无页面横向溢出、
脚本错误或外部请求,Android 可见交互控件均不小于 44px
@@ -47,12 +46,17 @@
失败记录均按 execution 存储;Android 使用加密 outbox 按事件、截图、候选和终态
顺序回传,授权到期补报单独审计,所有终态固定 `order_submitted=false`。管理任务
详情展示模型/候选/人工理由/事件/证据摘要,不保存 VLM Key、完整 endpoint 或原始响应。
- T-211 图片检索:后台任务固定使用经 SHA-256 校验的参考 JPEG,经一次性 MediaStore
图片进入拼多多拍照搜索;App 本地从 SKU 唯一提取颜色和尺码,schema v2 逐项返回
`MATCH/MISMATCH/UNKNOWN`。只有两项均匹配且分数/置信度不低于 `0.75` 的候选按
分数、置信度和曝光顺序回传 `0..5` 项,弱匹配和未知项不凑数。
- Workflow:纯 Kotlin runner 已支持步骤 timeout、最多 3 次 retry、安全阻塞、
用户停止和单 runner 并发拒绝;T-102 已接入搜索加有界候选采集五步
- TaskSource:严格 CLI 已生成并验证真实私有 ProbeTask;默认 APK 不含私有 fixture
- VLM:需求提取与候选评估均使用严格 schema、0.75 阈值、受控证据源、安全端点和
单次调用边界;候选最多 5 个并按 ordinal 串行评估,本地产生建议并停在人工确认,
SKU/数量由本地原值回填,预算保持空,订单提交状态固定为 false
SKU/数量由本地原值回填,颜色/尺码硬约束由本地生成,预算保持空,订单提交状态固定
为 false
- 后置数据闭环:已登记 T-208,在第一版 T-206/T-207 跑通后分离保存候选观测、
模型预测、确定性推荐和人工标签;人工接受/拒绝使用结构化理由,20 条试验依赖它
- VLM 部署决策:手机本地调用已配置的 OpenAI 兼容 provider;管理后端只负责身份、
@@ -63,8 +67,9 @@
RUNNING 不自动重新分配
- 测试设备:OnePlus PKG110,Android 16/API 36;肉包 `1.4.2 (7)`;拼多多
`8.17.0 (81700)`
- 设备就绪:肉包采购无障碍已启用并连接;拼多多首页、搜索输入、固定词结果页、
双列候选卡、详情截图和返回均已通过 8.17.0 真机验证
- 设备就绪:拼多多首页、文字/图片搜索、双列候选卡、详情截图和返回均已通过 8.17.0
真机验证;最终 APK 重装/force-stop 后 ColorOS 已关闭肉包采购无障碍,当前需采购员
在系统无障碍设置中重新启用,不通过 ADB 绕过系统授权
- 拼多多 8.17 兼容:结果页可识别新版显示查询词的文本搜索栏和“推荐、手机、女装”等
分类栏,也保留旧版“综合、销量、价格、筛选”识别;长任务搜索词的拼多多省略显示须
至少两个按序片段对应原词才通过。每次候选探针会重写本次搜索词,不复用已有结果页。
@@ -101,6 +106,7 @@
| `docs/tasks/T-207.md` | DONE | App 本地 VLM、Key 加密、候选、事件、截图和结果回传 |
| `docs/tasks/T-209.md` | DONE | 登录失败保留采购员和设备输入以便直接重试 |
| `docs/tasks/T-210.md` | DONE | 兼容拼多多 8.17 搜索结果页与长词省略显示 |
| `docs/tasks/T-211.md` | DONE | 参考图召回、SKU 颜色尺码硬匹配与 0..5 候选回传 |
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
@@ -111,7 +117,7 @@
## 任务摘要
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-207、T-209、T-210。
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-207、T-209、T-210、T-211。
- 正在进行:无。
- 下一个可领取任务:T-208 候选决策数据与人工理由闭环。
- 后置任务:T-208 候选决策数据与人工理由闭环;不得跳过 T-206/T-207 提前实现。
@@ -149,6 +155,13 @@ go run ./cmd/api
`order_submitted` 始终为 false;日志敏感词、Base64、原始响应和 schema/prompt
命中均为 0。该结果不代表真实模型的商品匹配质量。
2026-07-27 完成 T-211 真机 smoke:PKG110 上从后台任务下载并校验参考 JPEG,写入
一次性匿名 MediaStore 图片后进入拼多多拍照搜索并选择该图。App 完成 4 个有界候选
详情和证据采集并以 `PDD_IMAGE_SEARCH` 回传后台;不足 5 个未凑数,临时图库图片在
流程结束后删除。验证期间修复相机自动结果页竞态和 MediaStore volume URI 差异;
测试任务随后取消,全程未进入购物车、订单或支付。真实 VLM provider 尚未参与本次
smoke,当前结论只覆盖图片召回、严格契约和回传链路。
同日完成 T-201 后端 smoke:本机迁移执行 `status -> up -> up -> down -> up`,
第二次 `up` 应用数为 0;API 真实进程的 `GET /healthz` 返回 `200` 和稳定 JSON,
未知路由返回 `404`,错误方法返回 `405`,无默认 CORS,stdout/stderr 均为空。
+31 -8
View File
@@ -5,7 +5,7 @@ phase: 2
deps:
- T-207
- T-210
status: TODO
status: DONE
created: 2026-07-27
context_ref: 30841f8
work_branch: null
@@ -13,14 +13,21 @@ write_paths:
- docs/tasks/T-211.md
- docs/02-requirements.md
- docs/04-architecture.md
- docs/api.md
- docs/current-state.md
- android-buyer/app/src/main/AndroidManifest.xml
- android-buyer/app/src/main/java/com/roubao/autopilot/MainActivity.kt
- android-buyer/app/src/main/java/com/roubao/autopilot/accessibility/**
- android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/**
- android-buyer/app/src/main/java/com/roubao/autopilot/procurement/**
- android-buyer/app/src/main/java/com/roubao/autopilot/ui/screens/SearchProbeScreen.kt
- android-buyer/app/src/main/java/com/roubao/autopilot/vlm/**
- android-buyer/app/src/test/java/com/roubao/autopilot/pinduoduo/**
- android-buyer/app/src/test/java/com/roubao/autopilot/procurement/**
- android-buyer/app/src/test/java/com/roubao/autopilot/vlm/**
- backend-api/internal/usecase/execution_result_service.go
- backend-api/internal/usecase/execution_result_service_test.go
- backend-api/internal/transport/httpapi/device_handlers_test.go
---
## 问题 / 背景
@@ -55,14 +62,14 @@ write_paths:
## 验收要点
- [ ] 单元测试证明任务参考图是图片搜索的唯一输入,图片无效时不会启动拼多多。
- [ ] 单元测试覆盖颜色/尺码规范化、匹配/不匹配/未知、缺少硬约束和不足 5 个不凑数。
- [ ] 候选评估 prompt/schema 明确颜色和尺码硬约束;模型不能把未知规格判为通过。
- [ ] 后端收到的 AI 候选最多 5 个、ordinal 连续,包含逐项评估与推荐理由。
- [ ] Android 单元测试和 Debug 构建通过。
- [ ] PKG110、Android 16、拼多多 8.17.0 真机 smoke 能选择本任务参考图、进入图片
- [x] 单元测试证明任务参考图是图片搜索的唯一输入,图片无效时不会启动拼多多。
- [x] 单元测试覆盖颜色/尺码规范化、匹配/不匹配/未知、缺少硬约束和不足 5 个不凑数。
- [x] 候选评估 prompt/schema 明确颜色和尺码硬约束;模型不能把未知规格判为通过。
- [x] 后端收到的 AI 候选最多 5 个、ordinal 连续,包含逐项评估与推荐理由。
- [x] Android 单元测试和 Debug 构建通过。
- [x] PKG110、Android 16、拼多多 8.17.0 真机 smoke 能选择本任务参考图、进入图片
搜索结果并采集候选;记录 App/拼多多版本。
- [ ] 真机确认未进入购物车、订单确认、提交订单或支付页面;未知页、验证码、风控和
- [x] 真机确认未进入购物车、订单确认、提交订单或支付页面;未知页、验证码、风控和
登录页仍安全停止。
## 边界
@@ -76,3 +83,19 @@ write_paths:
- 2026-07-27:根据新需求登记任务;复用 T-207 候选批次接口,先实现 Android 图片
召回和 SKU 硬约束,不新增后端 AI 服务。
- 2026-07-27:任务登记提交 `667a64f` 后开始实现。
- 2026-07-27:实现任务参考 JPEG 严格校验、MediaStore 一次性图片、拼多多拍照搜索/
最近图片选择/图片结果页识别,并在流程结束或停止后删除临时图片。真机首次验证发现
相机结果页竞态和 `external_primary`/`external` URI 表示差异,修复后均增加回归覆盖。
- 2026-07-27:新增 SKU 颜色/尺码确定性提取、候选 schema/prompt v2、逐项
`MATCH/MISMATCH/UNKNOWN` 校验和 Top 5 策略;只回传分数、置信度和两个硬约束均通过
的 `0..5` 个候选,重新生成连续 ordinal。后端 schema v2 拒绝弱匹配、未知/缺失硬
约束和非首项推荐,schema v1 保持兼容。
- 2026-07-27:`.\gradlew.bat :app:testDebugUnitTest :app:assembleDebug --no-daemon`
通过,20 个 suite 共 108 个测试、0 失败;`go test -count=1 ./...` 和
`go vet ./...` 通过。
- 2026-07-27:PKG110(Android 16/API 36)、肉包 `1.4.2 (7)`、拼多多
`8.17.0 (81700)` 真机以后台测试任务完成参考图选择、图片结果页识别、4 个有界候选
详情和证据回传;后台审计值为 `PDD_IMAGE_SEARCH`,不足 5 个未凑数。测试任务随后
取消,临时图库图片已删除,全程未进入购物车、订单或支付。该 smoke 验证检索和回传
链路,不代表真实 VLM provider 的匹配质量。