feat(agent): PDD 以图搜商品并接入现有采集链路 #277

Open
opened 2026-09-14 10:17:47 +08:00 by ila · 2 comments
Owner

所属 MVP:#275 | 来源:用户 2026-09-14 需求讨论(原话摘要见 #275)

目标

Agent 收到图搜类采集任务后,用 PDD App 的拍照搜索找到商品并打开,然后接入现有的分享链接识别与采集链路完成任务。

前置依赖

#276(相册权限)。契约依赖 #278。

前置门禁

实施前必须在真机确认参考项目记录的选择器仍然有效,尤其 contentDescription == "拍照搜索"。失效则本工单方案需重写并重新确认。

方案

  1. 图片准备:移植参考项目 PinduoduoImageSearchAssetStore 的做法——写入 MediaStore(IS_PENDING 先置 1 再置 0)、等待媒体库索引、校验 JPEG 头尾标记与 sha256、最小边不足 960 时放大、写入前清理自己的历史图片。
  2. 不认图,只保证自己最新:写完后确认自己是相册中最新的 jpeg,然后点击网格第一格。选图退化为固定位置点击,不需要任何图像识别。
  3. 进入图搜:从首页/搜索结果页点 拍照搜索 入口;支持从商品详情、规格弹层、订单确认页、图搜结果页、"再试一次"弹窗归位(参考项目状态机,上限 50 次恢复动作)。
  4. 打开第一个结果:结果页判据 搜图片同款 + 排序控件 ≥ 3;打开首个候选后接回现有 PddShareLinkResolution / 剪贴板路径取 goods_id,调用现有身份回填接口,之后完全走现有采集流程。
  5. 调度垫底:service/AgentStateStore.kt 的 TaskDispatchPolicy.decide() 在 else -> CHECK_COLLECTION 之前插入图搜分支,使优先级为 采购 > 采集 > 图搜。现有四条路径行为不得改变(需回归测试证明)。
  6. 失败文案:AgentForegroundService 现有错误码到人话的映射表补充:图搜入口打不开、没有搜到相似商品、价格差异过大已跳过、需要相册权限。
  7. 任务完成后删除本次写入的图片。

明确不做

  • 不用 OCR / VLM。
  • 不在手机上新增图搜发起按钮(批量发起在 Admin)。
  • 不新增 Agent tab、不新增历史页面。图搜任务本来就是采集任务,出现在现有「采集」tab。
  • 不实现"重新图搜"。「重新采集」对图搜任务的语义是重采同一个商品(符合既有规则:重置保留 URL、goods_id、规则和设备快照);搜错了应由采购员在 Admin 手动改关联。

设计证据

Agent 端无新页面。失败文案属于显示文案,执行最小界面检查。

验收

  • 真机跑通一个图搜任务:搜到商品 → 打开 → 识别身份 → 完成采集。
  • 从商品详情/规格弹层/订单确认页等脏现场启动,能归位到图搜入口。
  • 权限缺失、搜不到结果、进不去图搜入口三种失败各有可读提示。
  • 批量图搜执行期间,手动发起的采购任务能够插队优先执行。
  • 现有四条调度路径行为不变,有回归测试。
  • 任务结束后设备相册中无本功能残留图片。

验证

Android 单元测试(含调度回归、图片准备策略)+ .\scripts erify.ps1 -Component android + 真机执行。真机结论只在同一设备、同一 App 版本、同一规则快照下复用。

风险

  • 参考项目在"从结果页读候选卡片"一段真机未闭环(T-271:图片未加载完时 cards=0)。若本工单在同一处受阻,可能需要拆单。
  • PDD 版本更新会使选择器失效。
  • 不得触碰任何支付、下单相关控件;订单确认页只做返回归位。

文档影响

Agent 执行行为与错误码变化,需更新对应 Wiki 页面与 docs/08-agent-api-contract.md 中的错误码表。

> 所属 MVP:#275 | 来源:用户 2026-09-14 需求讨论(原话摘要见 #275) ## 目标 Agent 收到图搜类采集任务后,用 PDD App 的拍照搜索找到商品并打开,然后接入**现有的**分享链接识别与采集链路完成任务。 ## 前置依赖 #276(相册权限)。契约依赖 #278。 ## 前置门禁 实施前必须在真机确认参考项目记录的选择器仍然有效,尤其 `contentDescription == "拍照搜索"`。失效则本工单方案需重写并重新确认。 ## 方案 1. **图片准备**:移植参考项目 `PinduoduoImageSearchAssetStore` 的做法——写入 MediaStore(`IS_PENDING` 先置 1 再置 0)、等待媒体库索引、校验 JPEG 头尾标记与 sha256、最小边不足 960 时放大、写入前清理自己的历史图片。 2. **不认图,只保证自己最新**:写完后确认自己是相册中最新的 jpeg,然后点击网格第一格。选图退化为固定位置点击,不需要任何图像识别。 3. **进入图搜**:从首页/搜索结果页点 `拍照搜索` 入口;支持从商品详情、规格弹层、订单确认页、图搜结果页、"再试一次"弹窗归位(参考项目状态机,上限 50 次恢复动作)。 4. **打开第一个结果**:结果页判据 `搜图片同款` + 排序控件 ≥ 3;打开首个候选后**接回现有 `PddShareLinkResolution` / 剪贴板路径**取 goods_id,调用现有身份回填接口,之后完全走现有采集流程。 5. **调度垫底**:`service/AgentStateStore.kt` 的 `TaskDispatchPolicy.decide()` 在 `else -> CHECK_COLLECTION` 之前插入图搜分支,使优先级为 **采购 > 采集 > 图搜**。现有四条路径行为不得改变(需回归测试证明)。 6. **失败文案**:`AgentForegroundService` 现有错误码到人话的映射表补充:图搜入口打不开、没有搜到相似商品、价格差异过大已跳过、需要相册权限。 7. **任务完成后删除本次写入的图片。** ## 明确不做 - 不用 OCR / VLM。 - 不在手机上新增图搜发起按钮(批量发起在 Admin)。 - 不新增 Agent tab、不新增历史页面。图搜任务本来就是采集任务,出现在现有「采集」tab。 - 不实现"重新图搜"。「重新采集」对图搜任务的语义是**重采同一个商品**(符合既有规则:重置保留 URL、goods_id、规则和设备快照);搜错了应由采购员在 Admin 手动改关联。 ## 设计证据 Agent 端无新页面。失败文案属于显示文案,执行最小界面检查。 ## 验收 - [ ] 真机跑通一个图搜任务:搜到商品 → 打开 → 识别身份 → 完成采集。 - [ ] 从商品详情/规格弹层/订单确认页等脏现场启动,能归位到图搜入口。 - [ ] 权限缺失、搜不到结果、进不去图搜入口三种失败各有可读提示。 - [ ] 批量图搜执行期间,手动发起的采购任务能够插队优先执行。 - [ ] 现有四条调度路径行为不变,有回归测试。 - [ ] 任务结束后设备相册中无本功能残留图片。 ## 验证 Android 单元测试(含调度回归、图片准备策略)+ `.\scripts erify.ps1 -Component android` + 真机执行。真机结论只在同一设备、同一 App 版本、同一规则快照下复用。 ## 风险 - 参考项目在"从结果页读候选卡片"一段真机未闭环(T-271:图片未加载完时 `cards=0`)。若本工单在同一处受阻,可能需要拆单。 - PDD 版本更新会使选择器失效。 - 不得触碰任何支付、下单相关控件;订单确认页只做返回归位。 ## 文档影响 Agent 执行行为与错误码变化,需更新对应 Wiki 页面与 `docs/08-agent-api-contract.md` 中的错误码表。
Author
Owner

核对范围与方法(2026-09-15)

  • 被核对分支:release/283 @ c80d0c9;基线 main @ c14f5f5。
  • 该分支是打包分支,同时含 #272 #273 #274 #276 #278 #279 #280 #282 #283,本次只就 #275~#280 的验收项作结论。
  • 执行内容:go build ./...、go test ./...(server)、npm run build:prod(web)、逐条对照工单验收项读取 diff。
  • 未执行:真机验证、Android 构建与单测(见 #277 结论,Android 侧无可验证的实现)。

结论:未开始

git log --oneline main..release/283 | grep "(#277)"   ->  0 条

release/283 上 Android 侧改动合计 12 行,且全部属于 #276:

android/app/src/main/AndroidManifest.xml   |  2 ++
android/app/src/main/java/.../MainActivity.kt | 10 ++++++++++

本工单方案中的七项没有任何一项存在:相册写图与清理、图搜入口点击、选图、结果页判据、打开第一个候选、TaskDispatchPolicy 调度垫底、失败文案。

影响

这是本 MVP 的执行端。服务端已能创建任务、下发参考图元数据、在结果回传后自动建立关联(#278 #279 均已实现),但没有任何组件会去执行图搜,因此整条链路不可用,#275 无法进行集成验收。

仍未完成的前置

#275 记录的前置门禁(在图搜页与结果页各 dump 一次无障碍树,核对 拍照搜索 等常量)至今未执行。本工单方案依赖这批常量,门禁未过之前不应开始实施。

状态维持待实施。

## 核对范围与方法(2026-09-15) - 被核对分支:`release/283` @ `c80d0c9`;基线 `main` @ `c14f5f5`。 - 该分支是打包分支,同时含 #272 #273 #274 #276 #278 #279 #280 #282 #283,本次只就 #275~#280 的验收项作结论。 - 执行内容:`go build ./...`、`go test ./...`(server)、`npm run build:prod`(web)、逐条对照工单验收项读取 diff。 - **未执行**:真机验证、Android 构建与单测(见 #277 结论,Android 侧无可验证的实现)。 ## 结论:未开始 ``` git log --oneline main..release/283 | grep "(#277)" -> 0 条 ``` `release/283` 上 Android 侧改动合计 **12 行**,且全部属于 #276: ``` android/app/src/main/AndroidManifest.xml | 2 ++ android/app/src/main/java/.../MainActivity.kt | 10 ++++++++++ ``` 本工单方案中的七项没有任何一项存在:相册写图与清理、图搜入口点击、选图、结果页判据、打开第一个候选、`TaskDispatchPolicy` 调度垫底、失败文案。 ## 影响 这是本 MVP 的执行端。服务端已能创建任务、下发参考图元数据、在结果回传后自动建立关联(#278 #279 均已实现),但**没有任何组件会去执行图搜**,因此整条链路不可用,#275 无法进行集成验收。 ## 仍未完成的前置 #275 记录的前置门禁(在图搜页与结果页各 dump 一次无障碍树,核对 `拍照搜索` 等常量)至今未执行。本工单方案依赖这批常量,门禁未过之前不应开始实施。 状态维持待实施。
Author
Owner

上线信息(共用)

  • 合并提交:1d01186,已推送 be03972..1d01186 到 main。
  • 发布目录:/home/goauto/releases/20260915-1d01186,二进制 SHA-256 前缀 65e404c4721d592a(本地与服务器一致)。
  • systemctl is-active goauto.service = active,监听 127.0.0.1:8010。
  • 数据库迁移:新执行 2 个,跳过 45 个已应用。迁移前已做全库逻辑备份(66 张表,gzip 校验通过),路径在运维备份目录,不在此列出。
  • 迁移前后 collection_task 222 行、shopee_product 26901 行,逐一相符。

外部入口验证:

检查 结果
GET / HTTP 200
GET /api/admin/v1/collection-tasks {"code":401,"msg":"cookie token is empty"}
POST /api/admin/v1/collection-tasks/image-search/batch {"code":401,...}
不存在路由对照 HTTP 404

最后两行是关键对照:图搜接口返回 401 而非 404,证明新路由已注册。

回退方式:把 current 软链切回 20260914-be03972-r2 后重启。两条迁移均为加列与放宽约束,不阻碍回退到旧二进制。

实施

提交:9e0dcc2、a487409、def0a86、a16e692、5126568、c30b8c4、95be6e7、c42e32d。

已完成

  • automation/PinduoduoImageSearchAsset.kt:MediaStore 写图(IS_PENDING 1→0)、750ms 索引等待、JPEG 头尾标记与 sha256 校验、最小边不足 960 放大、写入前清理历史图片、最新一项判定跨图片与视频比较。
  • automation/PinduoduoImageSearchCriteria.kt:页面与网格判据纯函数,常量集中。
  • automation/PinduoduoImageSearchAutomation.kt:归位状态机、候选挑选(跳过广告)、媒体最新判定。
  • service/AgentForegroundService.kt:image_search 分支接入完整链路,复用既有 CurrentPageIdentityRunner 与 PddProductDetailCollector,finally 中删除本次写入的图片。
  • 失败码与人话文案:IMAGE_SEARCH_PERMISSION_REQUIRED / ASSET_INVALID / ASSET_NOT_LATEST / ENTRY_NOT_FOUND / NO_CANDIDATES。
  • ui/TaskHistoryFragment.kt:采集列表行显示图搜来源标记(#279 附带项),未重构既有布尔分叉。

调度垫底改由服务端实现

原实现给 TaskDispatchPolicy.decide() 加了 hasImageSearchWaiting 参数与 CHECK_IMAGE_SEARCH 分支,但调用点始终只传两个参数,该分支不可达,且注释声称的优先级与代码顺序相反。

[必须] 该排序在客户端无法实现:图搜任务是 source='image_search' 的普通采集任务,由同一个 /tasks/next 端点下发,Agent 调用时服务端已经选好了行。已移除死代码,改在 task.Service.Next 的两处 ORDER BY 实现,并保证来源降级排在 device_id 之后——指派设备是人的显式选择,必须继续优先。新增 image_search_priority_test.go 三个用例覆盖。

前置门禁:已完成

2026-09-15 在真机(1080×2354)dump 无障碍树,三处判据全部命中,详见 #275 的核对记录与 docs/08-agent-api-contract.md。

发现并已修复一处会点错的缺陷:个人中心 tab 的根节点同样带 content-desc='拍照搜索',覆盖全屏且不可点,而 visibleTexts() 会把 contentDescription 当文本收集。原实现只按文案判 HOME,导致个人中心被判成首页,几何兜底随后点到该页顶部最靠右的可点节点——实测那是「设置」按钮。已收紧为:HOME 必须由可点入口证明;几何兜底必须先看到顶部搜索栏证据。

验证

Android testDebugUnitTest 与 assembleDebug 通过;新增调度回归、图片准备策略、页面判据、候选挑选、媒体最新判定的单测。

未验证(重要)

  • 全部真机点击序列未执行:点相机入口、点相册第一格、结果页轮询、点候选卡片,只保证驱动它们的纯函数逻辑正确。
  • 重试弹窗分支(真机核对时识别成功,未触发该弹窗)。
  • 结果页「广告」文案是否确实落在候选卡片子树内(按 SnapshotNode.path 语义推断)。
  • 「用几何字段回查节点」是否总能唯一命中。
  • MediaStore.Files 改动后的跨 ROM 真实查询行为。

状态

代码已上线,端到端真机执行未做,保持待验收。下一步是安装 debug APK 到真机跑一次完整图搜任务。

## 上线信息(共用) - 合并提交:`1d01186`,已推送 `be03972..1d01186` 到 `main`。 - 发布目录:`/home/goauto/releases/20260915-1d01186`,二进制 SHA-256 前缀 `65e404c4721d592a`(本地与服务器一致)。 - `systemctl is-active goauto.service` = `active`,监听 `127.0.0.1:8010`。 - 数据库迁移:新执行 2 个,跳过 45 个已应用。迁移前已做全库逻辑备份(66 张表,gzip 校验通过),路径在运维备份目录,不在此列出。 - 迁移前后 `collection_task` 222 行、`shopee_product` 26901 行,逐一相符。 外部入口验证: | 检查 | 结果 | |---|---| | `GET /` | HTTP 200 | | `GET /api/admin/v1/collection-tasks` | `{"code":401,"msg":"cookie token is empty"}` | | `POST /api/admin/v1/collection-tasks/image-search/batch` | `{"code":401,...}` | | 不存在路由对照 | HTTP 404 | 最后两行是关键对照:图搜接口返回 401 而非 404,证明新路由已注册。 回退方式:把 `current` 软链切回 `20260914-be03972-r2` 后重启。两条迁移均为加列与放宽约束,不阻碍回退到旧二进制。 ## 实施 提交:`9e0dcc2`、`a487409`、`def0a86`、`a16e692`、`5126568`、`c30b8c4`、`95be6e7`、`c42e32d`。 ### 已完成 - `automation/PinduoduoImageSearchAsset.kt`:MediaStore 写图(`IS_PENDING` 1→0)、750ms 索引等待、JPEG 头尾标记与 sha256 校验、最小边不足 960 放大、写入前清理历史图片、**最新一项判定跨图片与视频比较**。 - `automation/PinduoduoImageSearchCriteria.kt`:页面与网格判据纯函数,常量集中。 - `automation/PinduoduoImageSearchAutomation.kt`:归位状态机、候选挑选(跳过广告)、媒体最新判定。 - `service/AgentForegroundService.kt`:`image_search` 分支接入完整链路,复用既有 `CurrentPageIdentityRunner` 与 `PddProductDetailCollector`,`finally` 中删除本次写入的图片。 - 失败码与人话文案:`IMAGE_SEARCH_PERMISSION_REQUIRED` / `ASSET_INVALID` / `ASSET_NOT_LATEST` / `ENTRY_NOT_FOUND` / `NO_CANDIDATES`。 - `ui/TaskHistoryFragment.kt`:采集列表行显示图搜来源标记(#279 附带项),未重构既有布尔分叉。 ### 调度垫底改由服务端实现 原实现给 `TaskDispatchPolicy.decide()` 加了 `hasImageSearchWaiting` 参数与 `CHECK_IMAGE_SEARCH` 分支,但调用点始终只传两个参数,该分支不可达,且注释声称的优先级与代码顺序相反。 `[必须]` 该排序在客户端无法实现:图搜任务是 `source='image_search'` 的普通采集任务,由同一个 `/tasks/next` 端点下发,Agent 调用时服务端已经选好了行。已移除死代码,改在 `task.Service.Next` 的两处 `ORDER BY` 实现,并保证来源降级排在 `device_id` **之后**——指派设备是人的显式选择,必须继续优先。新增 `image_search_priority_test.go` 三个用例覆盖。 ### 前置门禁:已完成 2026-09-15 在真机(1080×2354)dump 无障碍树,三处判据全部命中,详见 #275 的核对记录与 `docs/08-agent-api-contract.md`。 发现并已修复一处会点错的缺陷:个人中心 tab 的**根节点**同样带 `content-desc='拍照搜索'`,覆盖全屏且不可点,而 `visibleTexts()` 会把 `contentDescription` 当文本收集。原实现只按文案判 HOME,导致个人中心被判成首页,几何兜底随后点到该页顶部最靠右的可点节点——实测那是「设置」按钮。已收紧为:HOME 必须由**可点**入口证明;几何兜底必须先看到顶部搜索栏证据。 ## 验证 Android `testDebugUnitTest` 与 `assembleDebug` 通过;新增调度回归、图片准备策略、页面判据、候选挑选、媒体最新判定的单测。 ## 未验证(重要) - **全部真机点击序列未执行**:点相机入口、点相册第一格、结果页轮询、点候选卡片,只保证驱动它们的纯函数逻辑正确。 - 重试弹窗分支(真机核对时识别成功,未触发该弹窗)。 - 结果页「广告」文案是否确实落在候选卡片子树内(按 `SnapshotNode.path` 语义推断)。 - 「用几何字段回查节点」是否总能唯一命中。 - `MediaStore.Files` 改动后的跨 ROM 真实查询行为。 ## 状态 代码已上线,**端到端真机执行未做**,保持待验收。下一步是安装 debug APK 到真机跑一次完整图搜任务。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#277