Files
cmautobuy/docs/task/255-admin运行时规格规则ai解析.md
T

112 lines
7.1 KiB
Markdown
Raw Normal View History

2026-08-17 17:21:43 +08:00
# 255 Admin:实现采购运行时真机规格规则与 AI 解析
- 类型:需求
- 父级大工单:#96
- 所属 MVP / 版本:#253 采购运行时 AI 规格纠偏闭环
- 状态:已完成,待用户验收
- 日期:2026-08-17
- Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/255
## 背景与目标
Client 在真实采购时能够读取 PDD App 当前颜色下的真实可购买尺码,但数据库里的规格可能
来自蝦皮回填,和真机文字不同。原有 `MatchSybSpecWithAI` 只读取数据库候选,不能保证
返回真机可点击的规格。
本任务实现 #254 已冻结的一次性规格解析命令:Admin 核对任务和真机候选快照,优先用
确定性规则寻找唯一等价候选,规则无法唯一判断时再调用受控 AI,并把第一次结论幂等、
可审计地返回 Client。解析不得修改任务、PDD 主数据或真实采购安全门禁。
## 最终方案
- 新增 `POST /api/v1/client/tasks/{task_id}/spec-resolution`。Handler 限制请求体为 64 KiB,
读取原始字节用于请求哈希,并把 Service 错误转换为 Client 契约定义的稳定错误码。
- Service 逐项校验 schema、字段长度、1~100 条连续候选、候选原文和 `color/size`,再按
Client 契约的 UTF-8 长度前缀算法重算候选快照哈希和确定性幂等键。
- 在写候选观察前核对任务存在、类型为采购、版本、PDD 商品、领取时的完整动态规格,
并确认当前 Client 曾领取该任务。任务已取消、重派或结束不作为拒绝条件。
- 规则忽略大小写、空白和安全的展示分隔符,处理常见繁简差异,支持
`kg / 公斤 / 千克 / 斤` 单值和正向区间等价。只有一个等价候选时返回 matched;多个
等价或多个重量区间同时重叠时直接 uncertain,不调用模型。加减号和范围符号保留,
避免把 `M+ / M-` 或不同重量范围折叠成同一规格。
- 抽出 SYB 批量 AI 和运行时 AI 共用的候选白名单核心,统一执行运行快照、严格响应结构、
候选编号、冲突/缺失维度和置信度阈值门禁。运行时模型只得到任务目标和请求内短候选,
不能生成规格、真实 PDD 选项键或点击坐标。
- 使用“短事务写 pending 候选观察 → 事务外规则/AI → 短事务完成一次决策并保存幂等响应”。
同一进程内相同幂等身份串行;数据库唯一键和只允许 `pending` 完成一次的条件更新负责
最终收敛。中断留下的 pending 由相同请求接管,不另建记录。
- `pdd_products.skus_json.spec_source=shopee_backfill` 只追加到非敏感决策原因作为诊断,
不主动触发 AI,也不阻断本来可以由规则唯一确定的候选。沿用 v26 表,不新增字段或迁移。
- 最终决策只写 `purchase_spec_resolutions` 和 `idempotency_keys`,另刷新 Client 活动时间;
不更新任务状态、版本、payload、订单总价上限、结果数据或 PDD `skus_json`。
- 不记录完整模型请求/响应、API Key、原始控件树、截图、订单、收货信息、Cookie 或 Token。
模型错误中的当前密钥原文会在进入审计原因前替换为 `[REDACTED]`。
和建单方案相比,最终实现进一步收紧了两个边界:多个重量候选同时重叠时不交给 AI 猜;
`+/-/范围` 符号不作为普通展示符号删除。由于 #254 的 v26 审计模型没有独立诊断来源列,
`shopee_backfill` 使用决策原因留痕,没有为诊断信息增加数据库迁移。
## 改了哪些
- `admin/handler/api/client_api.go`
- 注册一次性规格解析路由,增加请求大小限制和稳定 HTTP 错误映射。
- `admin/handler/api/client_api_test.go`
- 验证超过 64 KiB 的请求在进入数据库前返回 `INVALID_BODY`。
- `admin/main.go`
- 把既有 AI 密钥存储和端点策略注入 Client API。
- `admin/repository/task.go`
- 任务身份查询补充版本和原始 PDD 动态规格,供运行时核对。
- `admin/service/ai_specmatch_client.go`
- 增加 SYB 与运行时共用的模型候选白名单、阈值和冲突门禁核心。
- `admin/service/ai_specmatch.go`
- 原 SYB AI 编排改用共用核心,保留原有人工映射优先和保存前复查流程。
- `admin/service/purchase_spec_resolution.go`
- 实现请求/哈希校验、任务授权、规则、AI、诊断、并发门禁、短事务和幂等响应。
- `admin/service/purchase_spec_resolution_test.go`
- 覆盖规则边界、任务越权、AI 门禁、密钥脱敏、并发重放、主数据不变和诊断来源。
- `docs/admin/01-requirements.md`
- 记录采购运行时真机规格解析的产品和安全边界。
- `docs/admin/02-architecture.md`
- 记录 Client API、共用 AI 核心和两阶段短事务编排。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 未领取、非采购、版本、商品或任务规格不一致时拒绝且不写解析记录 | 通过 |
| 完全等价、繁简等价和唯一公斤/斤单值及区间由规则匹配,不调用模型 | 通过 |
| 多个等价、多个区间重叠或模型报告信息缺失时不自动选择 | 通过 |
| AI 只能选择当前候选;伪造编号、低置信度、冲突和错误不能返回 matched | 通过 |
| 同一幂等请求重试和并发得到一致响应、一个最终记录和一次有效模型调用 | 通过(隔离 SQLite 与 race 测试) |
| `shopee_backfill` 只记录诊断来源,不触发或拒绝解析 | 通过 |
| 不修改任务状态、payload、价格上限、结果数据和 PDD 主规格 | 通过 |
| 日志、数据库和响应不保存 API Key、原始 XML、订单或个人数据 | 通过 |
| 原 SYB AI 共用白名单核心后保持人工优先和原门禁 | 通过(全量 Go 回归;MySQL 场景见未验证项) |
| Go 1.23.0 格式、build、test 和 vet | 通过 |
| MySQL 8.4 `_test` 服务编排集成测试 | 未验证 |
## 测试
- 执行的命令:
- `$env:GOTOOLCHAIN='go1.23.0'; gofmt -l .`
- `$env:GOTOOLCHAIN='go1.23.0'; go test ./... -count=1`
- `$env:GOTOOLCHAIN='go1.23.0'; go vet ./...`
- `$env:GOTOOLCHAIN='go1.23.0'; go build ./...`
- `$env:GOTOOLCHAIN='go1.23.0'; go test -race ./service -run 'Test(MatchPurchaseSpec|ParsePurchaseSpec|ResolvePurchaseSpec)' -count=1`
- 结果:Go 1.23.0 全量测试、vet、构建和本任务 race 测试通过。运行时服务编排测试默认在
临时隔离 SQLite 数据库执行;设置 `CMAUTOBUY_MYSQL_TEST=1` 时会自动改用项目的隔离
MySQL 8 `_test` 数据库。
- **没验证到的部分**:本机 MySQL 8.4 服务没有当前可用的授权测试账号,因此未执行
`CMAUTOBUY_MYSQL_TEST=1` 的 #255 服务集成用例;没有用生产库替代测试库。依赖工单 #254
已验证 v26 MySQL 8.4 表、约束、唯一键和 Repository。本工单也未做 Client 真机联调,
Client 的采集和续跑属于后续 #256~#258。
## 遗留问题
- 配置可创建临时数据库的本地 MySQL 8 `_test` 账号后,应补跑:
`$env:CMAUTOBUY_MYSQL_TEST='1'; go test ./service -run 'TestResolvePurchaseSpec' -count=1 -v`。
## 相关提交
- `de36cfd` 实现采购运行时规格规则与 AI 解析