fix(server): 「一键匹配颜色和尺码」未拿到结论的规格值重问一次 #299

Open
opened 2026-09-16 15:27:55 +08:00 by ila · 1 comment
Owner

来源:#295 实施时记录的遗留项。2026-09-16 本地实测复现。

问题

规格匹配有三条路径,#295 只给采购侧两条(BatchSpecMatch、MatchArchiveSpecs)加了重试。虾皮商品详情页「一键匹配颜色和尺码」走的是第三条——shopeeproduct.suggestMappings——仍然一次失败即放弃。

实测(虾皮 1528,PDD 8514):

状态分布  unmatched=13  preserved=8  confirmed=1
黑色  unmatched -> 「AI 未给出可靠建议,请人工选择」

黑色 在 PDD 8514 里是唯一对应(黑色-冰块猫,9 个在售组合),属于 AI 抽风。同一模型在采购侧已证实非确定性:同一请求第一次答「未找到可靠的 PDD 规格」,第二次答「唯一匹配」。

与 #295 的差别

[必须] 这条路径是批量调用,不是逐值调用:

result, err := aiService.SuggestBatch(ctx, aimatching.SuggestRequest{
    Dimension: role, ShopeeTitle: shopee.Title, PDDTitle: pdd.Title,
    Sources: sources, Candidates: candidates,
})

一次问全部待定值,返回 result.Decisions[sourceID]。因此不能照搬 #295 的 resolveSpecMatch(那是逐值重试)。正确的形状是:只把没拿到结论的那些 source 组成第二次 SuggestBatch 再问一遍,已拿到结论的不重问。

「没拿到结论」有两种,都该重问:

  • !ok || decision.CandidateID == ""
  • 译回的 pddValue 不在候选集内(ai_suggest.go 里那段 defensive 检查)

另外 SuggestBatch 整体报错时当前直接 aiUnavailable(...) 返回,也值得重试一次。

方案

  1. 首次 SuggestBatch 之后,收集未拿到可靠结论的 sources,若非空则用同一 candidates 再问一次,只覆盖这些 sources 的结果。
  2. SuggestBatch 整体调用失败时重试一次。
  3. [必须] 重试上限为 1 次(总计 2 次调用)。真失败每次都会失败——PDD 确实没有该颜色、或 白色-冰块猫 / 白色-空白版 有歧义。#295 实测连续三轮全量匹配失败数稳定在 12,正是这两类,再重试只是浪费调用和采购员的时间。

非目标

  • [必须] 不改任何判据:候选集校验、candidateValues[pddValue] 的 defensive 检查、AutoConfirmMinConfidence 门槛全部保持原样。
  • 不改 maxAISuggestSources / maxAISuggestCandidates 上限。
  • 不改 deterministicColorMatch / deterministicSizeMatch(它们不调 AI,无需重试)。
  • 不改 preserved 分支——已确认映射不重问。
  • 不动 AutoMatchMappings 的事务与幂等重放逻辑。
  • 不改接口出入参结构与前端。

验收

  • 首次未拿到结论的值会被第二次批量询问,已拿到结论的不重问。
  • 重试的候选集与首次一致。
  • 总调用次数不超过 2 次,与待定值数量无关。
  • SuggestBatch 整体报错时重试一次,仍失败则按原有方式返回 aiUnavailable。
  • 置信度低于门槛的结果仍是 ai_suggested(不自动应用),行为不变。
  • 全部现有测试通过。

验证

go test ./app/goauto/shopeeproduct/... ./app/goauto/aimatching/...。

[必须] app/goauto/sybimport 存在 12 个先于本工单的失败用例(#285),前后失败集合必须一致,不得尝试修复它们。

文档影响

内部重试行为,入口、接口、错误码与可见文案均不变,无长期文档影响。

> 来源:#295 实施时记录的遗留项。2026-09-16 本地实测复现。 ## 问题 规格匹配有三条路径,#295 只给采购侧两条(`BatchSpecMatch`、`MatchArchiveSpecs`)加了重试。虾皮商品详情页「一键匹配颜色和尺码」走的是第三条——`shopeeproduct.suggestMappings`——仍然一次失败即放弃。 实测(虾皮 1528,PDD 8514): ``` 状态分布 unmatched=13 preserved=8 confirmed=1 黑色 unmatched -> 「AI 未给出可靠建议,请人工选择」 ``` `黑色` 在 PDD 8514 里是唯一对应(`黑色-冰块猫`,9 个在售组合),属于 AI 抽风。同一模型在采购侧已证实非确定性:同一请求第一次答「未找到可靠的 PDD 规格」,第二次答「唯一匹配」。 ## 与 #295 的差别 `[必须]` 这条路径是**批量**调用,不是逐值调用: ```go result, err := aiService.SuggestBatch(ctx, aimatching.SuggestRequest{ Dimension: role, ShopeeTitle: shopee.Title, PDDTitle: pdd.Title, Sources: sources, Candidates: candidates, }) ``` 一次问全部待定值,返回 `result.Decisions[sourceID]`。因此不能照搬 #295 的 `resolveSpecMatch`(那是逐值重试)。正确的形状是:**只把没拿到结论的那些 source 组成第二次 `SuggestBatch` 再问一遍**,已拿到结论的不重问。 「没拿到结论」有两种,都该重问: - `!ok || decision.CandidateID == ""` - 译回的 `pddValue` 不在候选集内(`ai_suggest.go` 里那段 defensive 检查) 另外 `SuggestBatch` 整体报错时当前直接 `aiUnavailable(...)` 返回,也值得重试一次。 ## 方案 1. 首次 `SuggestBatch` 之后,收集未拿到可靠结论的 sources,若非空则用同一 `candidates` 再问一次,只覆盖这些 sources 的结果。 2. `SuggestBatch` 整体调用失败时重试一次。 3. `[必须]` 重试上限为 1 次(总计 2 次调用)。真失败每次都会失败——PDD 确实没有该颜色、或 `白色-冰块猫` / `白色-空白版` 有歧义。#295 实测连续三轮全量匹配失败数稳定在 12,正是这两类,再重试只是浪费调用和采购员的时间。 ## 非目标 - `[必须]` 不改任何判据:候选集校验、`candidateValues[pddValue]` 的 defensive 检查、`AutoConfirmMinConfidence` 门槛全部保持原样。 - 不改 `maxAISuggestSources` / `maxAISuggestCandidates` 上限。 - 不改 `deterministicColorMatch` / `deterministicSizeMatch`(它们不调 AI,无需重试)。 - 不改 `preserved` 分支——已确认映射不重问。 - 不动 `AutoMatchMappings` 的事务与幂等重放逻辑。 - 不改接口出入参结构与前端。 ## 验收 - [ ] 首次未拿到结论的值会被第二次批量询问,已拿到结论的不重问。 - [ ] 重试的候选集与首次一致。 - [ ] 总调用次数不超过 2 次,与待定值数量无关。 - [ ] `SuggestBatch` 整体报错时重试一次,仍失败则按原有方式返回 `aiUnavailable`。 - [ ] 置信度低于门槛的结果仍是 `ai_suggested`(不自动应用),行为不变。 - [ ] 全部现有测试通过。 ## 验证 `go test ./app/goauto/shopeeproduct/... ./app/goauto/aimatching/...`。 `[必须]` `app/goauto/sybimport` 存在 12 个先于本工单的失败用例(#285),前后失败集合必须一致,不得尝试修复它们。 ## 文档影响 内部重试行为,入口、接口、错误码与可见文案均不变,无长期文档影响。
Author
Owner

实施

提交 9cdc9f3,改动 ai_suggest.go(+50 −4)与 ai_suggest_test.go(+256)。

由 grok-4.6 派单实施,我独立复核并重跑验证,未采信其自述结果。

核心改动

  • 收集首次 SuggestBatch 后没拿到可靠结论的 source(缺决策、空 CandidateID、或译回值不在候选集),用同一份 candidates 组第二次调用,只填补这些 source;已有结论的不重问。
  • SuggestBatch 整体报错时重试一次。
  • [必须] 两条重试路径互斥(suggestCalls < 2),总调用严格不超过 2 次,与待定值数量无关。
  • 新增 unreliableSuggestDecision 作为判定函数,未替换下游任何既有校验。

判据未动

候选集校验、candidateValues[pddValue] 的 defensive 检查、AutoConfirmMinConfidence 门槛、preserved 与确定性匹配分支全部保持原样。git diff 中删除的 4 行全部是被重构包裹的原句,无一行判据被删。

已验证

  • gofmt 无输出,go vet 干净,go build ./... 通过。
  • shopeeproduct / aimatching / purchase / task 四个套件全绿。
  • app/goauto/sybimport 仍为 12 条失败,与 #285 基线一致。
  • 新增 4 个用例:只重问未可靠的 source、整体报错重试一次、总调用不超过 2 次、unreliableSuggestDecision 判定。

未验证

未在本地真实点击「一键匹配颜色和尺码」复验 黑色 是否因重问而匹上。

遗留

decisions := result.Decisions 之后直接写入该 map。当前 SuggestBatch 每次新建 map,无实际影响;若将来改为返回缓存或共享 map 会污染调用方。属隐患非缺陷,未在本单处理。

状态

待验收。

## 实施 提交 `9cdc9f3`,改动 `ai_suggest.go`(+50 −4)与 `ai_suggest_test.go`(+256)。 由 **grok-4.6 派单实施**,我独立复核并重跑验证,未采信其自述结果。 ### 核心改动 - 收集首次 `SuggestBatch` 后没拿到可靠结论的 source(缺决策、空 `CandidateID`、或译回值不在候选集),用**同一份 candidates** 组第二次调用,只填补这些 source;已有结论的不重问。 - `SuggestBatch` 整体报错时重试一次。 - `[必须]` 两条重试路径互斥(`suggestCalls < 2`),总调用严格不超过 2 次,与待定值数量无关。 - 新增 `unreliableSuggestDecision` 作为判定函数,**未替换**下游任何既有校验。 ### 判据未动 候选集校验、`candidateValues[pddValue]` 的 defensive 检查、`AutoConfirmMinConfidence` 门槛、`preserved` 与确定性匹配分支全部保持原样。`git diff` 中删除的 4 行全部是被重构包裹的原句,无一行判据被删。 ## 已验证 - `gofmt` 无输出,`go vet` 干净,`go build ./...` 通过。 - `shopeeproduct` / `aimatching` / `purchase` / `task` 四个套件全绿。 - `app/goauto/sybimport` 仍为 **12** 条失败,与 #285 基线一致。 - 新增 4 个用例:只重问未可靠的 source、整体报错重试一次、总调用不超过 2 次、`unreliableSuggestDecision` 判定。 ## 未验证 未在本地真实点击「一键匹配颜色和尺码」复验 `黑色` 是否因重问而匹上。 ## 遗留 `decisions := result.Decisions` 之后直接写入该 map。当前 `SuggestBatch` 每次新建 map,无实际影响;若将来改为返回缓存或共享 map 会污染调用方。属隐患非缺陷,未在本单处理。 ## 状态 待验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#299