AI 返回的分类和平台,怎么才能认对?直接把识别结果塞进表单是不行的——大模型会凭空造一个不存在的分类 ID、把”油烟机”写成”抽油烟机”、把订单平台猜成列表里没有的名字。本文记录三招兜底:候选注入、ID 优先匹配、平台白名单。
一、难点:两套数据字典
拾物清单里”分类”其实是两个独立的东西:
| 数据字典 | 表 | 结构 | 用途 |
|---|---|---|---|
| 业务分类 | goods_category |
平铺列表(系统预设 + 用户自建) | 决定物品归属哪个业务分组 |
| 图标分类 | goods_icon |
两级树(一级分组 → 二级图标) | 决定物品显示什么图标 |
AI 只要返回一个 categoryId,根本分不清它该填哪张表——两张表的 ID 空间互相独立,名称还可能撞车。所以 Prompt 里必须把两套候选分开、把规则写死:
1 | 1. categoryId/categoryName 是业务物品分类,只能从"业务分类候选"中选择。 |
二、候选注入:每次识别都带最新字典
候选列表不能硬编码在 Prompt 里——用户会自建分类、改分类名,硬编码很快就和数据库对不上。识别前动态加载当前用户的分类和图标树,拼进 Prompt:
1 | private String buildPrompt(List<GoodsCategory> categories, List<IconCategoryTreeVO> iconTree) { |
两个细节:
- ID 一律字符串化。项目用雪花算法生成 Long,而 JS 的 Number 安全整数上限是 2^53,模型和前端 JSON 中转都可能丢精度,字符串最稳。
- 限定输出结构。Prompt 里给出完整的 JSON 骨架,未知字段返回
null,要求”只返回 JSON,不要 Markdown”,解析端再兜一层 ```json 代码块剥离。
三、匹配策略:ID 优先,名称兜底,树形反查
模型返回的结果不能直接信,回填前要过一遍校验,优先级从高到低:
1 | // 1. 业务分类:先按 ID 在当前候选中找 |
图标树的匹配多一层父子约束:
1 | IconCategoryTreeVO first = findIconGroupById(dto.getFirstCategoryId(), iconTree); |
关键约束:二级图标必须落在选中的一级分类 children 里,不能接受”模型自己凑的一对父子”。名称兜底只在当前用户可见的候选树内做,绝不跨用户查表。
名称匹配再留两级宽容:先精确相等,再互相 contains 模糊匹配。同时维护两张别名表,兼容历史结果里的旧叫法:
1 | // 历史一级名称 → 当前名称 |
全部匹配不到就保持 null,宁可让用户手选,也不回填一个错误分类。
四、平台匹配:白名单 + 用户确认
平台字段先给模型一份固定候选(京东/淘宝/拼多多/抖音……),要求”无法判断返回 null”。回填时分两种情况:
- 命中现有平台列表:trim 后直接选中,不打扰用户;
- 识别出新平台(如一家没收录的小众商城):保留名称填入表单,同时弹轻量提示——“加入并选中”调
platformStore.addPlatform()写入我的平台,”仅填入”就只管本次表单。
边界都兜住:重名不重复创建;新增请求失败不撤销识别结果(表单照留,只提示失败);语音识别的平台提示延后到内容确认弹窗关闭后,避免两个弹窗叠加。
五、结果清洗:白名单校验
除分类外的枚举字段同样不信任模型,回填前过白名单:
1 | if (OCR_GOODS_STATUSES.contains(dto.getGoodsStatus())) { // in_use/idle/sold/damaged |
价格、数量这类数字字段用 try-parse,解析失败就当 null;avgType 缺省按天摊销。原则一致:模型输出是建议,校验通过才是数据。
写在最后
这套策略没有用什么 RAG 或向量检索,就是三件事:把候选喂准、把匹配做严、把清洗做全。对一个字段有限的表单识别场景,Prompt 内嵌候选 + 代码侧兜底校验已经足够;真要上更开放的实体识别,再考虑语义匹配不迟。
条评论