fix(ops): 图搜与规格匹配的失败原因未被记录,排查只能靠手工重放 #294

Open
opened 2026-09-16 11:33:53 +08:00 by ila · 1 comment
Owner

来源:2026-09-16 排查图搜与规格匹配失败时,两次都因为缺少记录而无法定位,只能靠手工重放接口和按时长反推。

问题一:BatchSpecMatch 的逐条结果被丢弃

if _, err := purchase.NewService(service.DB).BatchSpecMatch(ctx, ...); err != nil {
    log.Printf("image search spec match failed for syb products %v: %v", ...)
}

每条明细的 skipped / pending / failed 原因都在 response.Items 里,这里用 _ 丢掉;err 只在整体出错时非空。

实测后果:2026-09-16 图搜后 23 条明细全部匹配失败(AI 服务 503),日志里一个字都没有。真实原因是手工重放接口才拿到的:{"status":"failed","reason":"AI 匹配服务暂时不可用,请稍后重试"}。后来换 AI 服务商重跑,12 条 failed、7 条 skipped,同样无任何记录。

问题二:Agent 的原始失败说明被统一文案覆盖

AgentForegroundService.kt:423 在上报前把错误码换成给采购员看的文案:

"IMAGE_SEARCH_ENTRY_NOT_FOUND" -> "打不开拍照搜索入口,请检查 PDD App 版本或手动确认后重试。"

而该错误码在 Agent 内部有两个来源,含义完全不同:

  • "找不到唯一可点的拍照搜索入口"(立即返回)
  • "归位到图搜入口并选中参考图失败,请人工检查"(预算耗尽)

两者在库里长得一模一样。排查任务 156 时只能靠「失败耗时 15 秒 ≈ 50 × 300ms」反推是后者,没有直接证据。

方案

  1. 服务端:图搜后的匹配把 autoConfirmedCount / pendingCount / failedCount / skippedCount 记入日志,并带上若干条具体原因,便于定位是 AI 不可用、规格对不上还是已有映射。
  2. Agent:上报时保留内部原始 reason 作为诊断字段,用户可见文案不变。

[必须] 两处都不得记录凭据、账号、订单或个人数据;只记录错误码、计数与规格相关的结构化原因。

非目标

  • 不改任何判据、不改错误码取值、不改采购员看到的文案。
  • 不新增日志表,不保存控件树或截图。

验收

  • 图搜后的匹配全部失败时,日志能看出计数与代表性原因。
  • IMAGE_SEARCH_ENTRY_NOT_FOUND 的两个来源在记录中可区分。
  • 采购员可见文案与现在完全一致。
  • 日志中不出现凭据、账号、订单或个人数据。

验证

go test ./app/goauto/task/...;Android 单元测试;本地构造一次全失败的匹配并检查日志。

文档影响

错误码语义与可见文案均不变;如 docs/08-agent-api-contract.md 需补充诊断字段,按实际改动同步。

> 来源:2026-09-16 排查图搜与规格匹配失败时,两次都因为缺少记录而无法定位,只能靠手工重放接口和按时长反推。 ## 问题一:`BatchSpecMatch` 的逐条结果被丢弃 ```go if _, err := purchase.NewService(service.DB).BatchSpecMatch(ctx, ...); err != nil { log.Printf("image search spec match failed for syb products %v: %v", ...) } ``` 每条明细的 skipped / pending / failed 原因都在 `response.Items` 里,这里用 `_` 丢掉;`err` 只在整体出错时非空。 实测后果:2026-09-16 图搜后 23 条明细**全部匹配失败**(AI 服务 503),日志里一个字都没有。真实原因是手工重放接口才拿到的:`{"status":"failed","reason":"AI 匹配服务暂时不可用,请稍后重试"}`。后来换 AI 服务商重跑,12 条 failed、7 条 skipped,同样无任何记录。 ## 问题二:Agent 的原始失败说明被统一文案覆盖 `AgentForegroundService.kt:423` 在上报前把错误码换成给采购员看的文案: ```kotlin "IMAGE_SEARCH_ENTRY_NOT_FOUND" -> "打不开拍照搜索入口,请检查 PDD App 版本或手动确认后重试。" ``` 而该错误码在 Agent 内部有两个来源,含义完全不同: - `"找不到唯一可点的拍照搜索入口"`(立即返回) - `"归位到图搜入口并选中参考图失败,请人工检查"`(预算耗尽) 两者在库里长得一模一样。排查任务 156 时只能靠「失败耗时 15 秒 ≈ 50 × 300ms」反推是后者,没有直接证据。 ## 方案 1. 服务端:图搜后的匹配把 `autoConfirmedCount` / `pendingCount` / `failedCount` / `skippedCount` 记入日志,并带上若干条具体原因,便于定位是 AI 不可用、规格对不上还是已有映射。 2. Agent:上报时保留内部原始 reason 作为诊断字段,用户可见文案不变。 `[必须]` 两处都不得记录凭据、账号、订单或个人数据;只记录错误码、计数与规格相关的结构化原因。 ## 非目标 - 不改任何判据、不改错误码取值、不改采购员看到的文案。 - 不新增日志表,不保存控件树或截图。 ## 验收 - [ ] 图搜后的匹配全部失败时,日志能看出计数与代表性原因。 - [ ] `IMAGE_SEARCH_ENTRY_NOT_FOUND` 的两个来源在记录中可区分。 - [ ] 采购员可见文案与现在完全一致。 - [ ] 日志中不出现凭据、账号、订单或个人数据。 ## 验证 `go test ./app/goauto/task/...`;Android 单元测试;本地构造一次全失败的匹配并检查日志。 ## 文档影响 错误码语义与可见文案均不变;如 `docs/08-agent-api-contract.md` 需补充诊断字段,按实际改动同步。
Author
Owner

实施

提交 b2b8c0d。

服务端 matchSpecsAfterImageSearch:BatchSpecMatch 的返回值此前用 _ 丢弃,改为记录 confirmed/pending/failed/skipped 四个计数,并附前 3 条阻塞原因(batchSpecMatchDigest)。新增的 MatchArchiveSpecs 同样记录计数。

Agent AgentForegroundService:新增 imageSearchFailureMessage(code, internal),在用户文案后括注内部原因,写法与既有的候选点击失败一致。IMAGE_SEARCH_ENTRY_NOT_FOUND 的两个来源(「找不到唯一可点的拍照搜索入口」/「归位到图搜入口并选中参考图失败」)不再被抹平。

[必须] 两处只记录错误码、计数与规格层面的原因,不含凭据、账号、订单和个人数据。采购员可见文案的前半段与现在完全一致。

已验证

服务端全部受影响套件通过;Android 全量单测 372 通过、0 失败。

未验证

未构造一次全失败的匹配来实地检查日志输出。

状态

待验收。

## 实施 提交 `b2b8c0d`。 **服务端** `matchSpecsAfterImageSearch`:`BatchSpecMatch` 的返回值此前用 `_` 丢弃,改为记录 `confirmed/pending/failed/skipped` 四个计数,并附前 3 条阻塞原因(`batchSpecMatchDigest`)。新增的 `MatchArchiveSpecs` 同样记录计数。 **Agent** `AgentForegroundService`:新增 `imageSearchFailureMessage(code, internal)`,在用户文案后括注内部原因,写法与既有的候选点击失败一致。`IMAGE_SEARCH_ENTRY_NOT_FOUND` 的两个来源(「找不到唯一可点的拍照搜索入口」/「归位到图搜入口并选中参考图失败」)不再被抹平。 `[必须]` 两处只记录错误码、计数与规格层面的原因,不含凭据、账号、订单和个人数据。采购员可见文案的前半段与现在完全一致。 ## 已验证 服务端全部受影响套件通过;Android 全量单测 372 通过、0 失败。 ## 未验证 未构造一次全失败的匹配来实地检查日志输出。 ## 状态 待验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#294