这是一份提交,面向 Hacktoberfest 开源 AI 挑战赛第 2 周:副业
由 @dronzer2code、@soumyadeepdey 和 @t-rexbytes 构建。
想象一下周四下午在一个儿童寄售店(这是一个虚构的例子,不是真实店铺)。一位家长在接收台放下一个袋子:一个玩具消防车、一个木制学习塔、一个睡眠机、一些幼儿睡衣。店员每件物品大约只有两分钟。那天早上,美国消费品安全委员会发布了当周的一批约十几起召回。接收台的人都没有读过这些通知。
以下是 9 月 17 日的一则通知中相关部分,涉及 Melissa & Doug 灯光和声音消防车(在 Target 销售约 84,000 件):
受影响的单位会有一个指针指向数字 25,另一个指针指向数字 4 或 5;或者有一个指针指向数字 26,另一个指针指向数字 3。
这些箭头其实是电池盒下方的两个日期拨盘。品牌和价格标签无法给出答案,你必须把玩具翻过来看。
对商店来说,这是一个法律问题。二手商品清理行动直接告诉旧货店和在线转售商,转售召回产品违反联邦法律。10 月 6 日,消费者报道,通过 News4Jax警告说,被禁止和召回的婴儿用品仍在 Craigslist、eBay 和 Facebook Marketplace 上列出。eBay 表示,其产品安全系统(AI 模型、关键词规则和人工审核)在 2025 年全球阻止了 2100万条潜在不安全的商品列表。此外,2026 年 Buchalter 分析指出,CPSC 已经发出了第 100,000 份下线通知,针对的是在线销售的被召回或禁止的产品。
我们为这个接收台构建了 RecallDesk。每周四它会读取最新的召回信息,核对店铺库存和 eBay 店铺,然后打印出一页:需要下架的商品以及需要翻转检查的商品。
型号编号藏在何处
在获取任何型号代码之前,我们收集了 CPSC 从 7 月 18 日到 10 月 15 日发布的所有召回信息:共 151 条,全部出现在周四,其中 48 条涉及儿童产品。将儿童召回信息追加到 2023 年(商店也会出售旧婴儿用品),总数达到 591 条。

随后,我们仅使用普通代码而未引入模型,询问标识号出现的位置。在 73/151 条通知中,型号、物品、款式或 SKU 编号仅出现在描述部分,从未出现在标题或产品名称中(儿童产品方面为 24/48)。通知 “Woodure Toddler Kitchen Step Stools Recalled” 仅提供品牌信息。您的踏板凳是否受影响,请往下看:“型号编号刻在底部踏板的底面。”

在二手市场方面,SerpApi 的 eBay 引擎检索了 237条召回信息(包括最近的 151 条以及 86 条较旧的儿童产品召回;共 235 次付费搜索,因为两条召回共用了一条缓存查询):得出 14,068 条结果,其中 12,562 条为唯一 listings。我们统计了标题中包含召回标识的数量,不区分大小写并忽略连字符。共有 45 条匹配,分布在 13 起召回中。就儿童召回而言,2/6,901,均来自同一品牌的潜水棒。
二手卖家最不太可能输入该标识:所有 listings 中有 17.1% 为二手商品(儿童召回方面为 20.2%),但在这 45 条精准匹配中只有 9% 符合。

这一数量塑造了产品。关键词过滤能标记出那 45 条,却清除不了剩余 12,517 条。因为标题里没有型号,就毫无意义。对多数同品牌商品,实用做法是去看标签,并注意标签所在位置。
我构建的
RecallDesk 是面向转售企业的每周召回服务。商店只需一次性导入库存(CSV 或粘贴),可选地填入 eBay 卖家名称。每周四东部时间下午 2:00,CPSC 发布召回信息后,它会把每条新召回转化为结构化规格(品牌、型号、日期码、销售时间窗),将商品和在售 listings 与可能适用的规格进行对比,应用商店之前的更正,并生成一张卡片:对于 match 显示 不要出售,对于 check 显示 检查标签(并给出查看位置),而 no_match 则不显示任何内容。
A trimmed excerpt of a real card for the demo shop, from a run with the fine-tuned model on Tinker:
=== RECALL CARD card_2026-10-15_little-loop Little Loop Kids Consignment (week of 2026-10-15) ===
6 new recalls this week | 60 items checked | model: qwen3.5-4b-recalldesk-lora
DO NOT SELL (8)
x Papablic Convertible Bedside Sleeper with Play Yard (model PAPA03001AI) -> CPSC 27027
why: Listing matches recalled brand, product type, and explicitly states the recalled model number.
remedy: Stop use, destroy product, email photos for refund
x Love To Dream Portable Sleep Machine with Night Light (model LTD-SM23) -> CPSC 26798
CHECK LABEL (7)
? Woodure Wooden Toddler Learning Tower -> CPSC 26658
look for: model no. WD1764, WD1357, WD1720; sold 2024-07 to 2026-06. The model number is
engraved on the underside of the bottom step.
? Sweet Cubby Kids Loungewear Set (shirt + shorts) -> CPSC 26752
look for: lot/date code Batch No.: 272779; sold 2025-06 to 2026-05. "Batch No.: 272779" and
the size are printed on the sewn-in neck label and on the seam label.
SHOP MEMORIES APPLIED (1)
* beeeda90-bb74-4681-b1fa-bfdacd9ef8e6: Shop correction: 'HARPPA Nordi Foldable Toddler Tower' is
NOT the recalled product for CPSC recall 26651. Ours is the 2022 one - consignor's receipt s...
model usage: 179 classify calls for 60 items | ... | est. $0.0348 at Tinker list price
workflow: check-little-loop-2026-10-17-e610 memory: backboard
那张卡片上的一行是错的,而且方向是保守的。Love To Dream 召回覆盖型号 LTD-SM23 带有 日期代码 7W15TN,因此我们的规则 3 表明该项目应为 CHECK LABEL,而非 DO NOT SELL。商店可能会下架其实没问题的睡眠机器。
仪表盘使用 Next.js 搭配 BoardUI 组件。大多数店主只需要可打印的卡片。其余部分用于设置以及回答“为什么会被标记?”
任意库存行会在与其比对的召回信息旁边打开。“不匹配?请纠正” 是商店教系统了解自身库存的方式。
召回页面把 CPSC 文本以及高亮的标识符放在提取的规格旁边:
eBay 监控面向在线转卖者:
谁会付费? 每日从陌生人处收取二手儿童商品的儿童寄售店。季节性寄售活动,周末内聚集数千件商品和志愿者工作人员。eBay 和 Poshmark 的转卖者盲目购买退货托盘。我们定价为 每店每月 15 美元 和 每场寄售活动 49 美元。对演示店的 60 件商品进行全复核花费了 179 次模型调用,成本为 0.035 美元。
这不是法律建议,也不是 cpsc.gov/Recalls 的替代品。它只能读取文字,无法看到照片中的标签,这就是 CHECK LABEL 存在的原因。仅限美国 CPSC 召回。
演示
一键部署完整栈(Agent 运行时加仪表盘),源自仓库的 Render Blueprint:
或者在本地运行。每个键均可选,缺失时会回退到标记的替代品。
git clone https://github.com/SoumyaEXE/recalldesk && cd recalldesk
cp .env.example .env # TINKER_API_KEY / SERPAPI_API_KEY / BACKBOARD_API_KEY, all optional
python -m uv venv --python 3.12 agent/.venv
python -m uv pip install --python agent/.venv/Scripts/python.exe -r agent/requirements-tinker.txt
agent/.venv/Scripts/python -m agent.dev --schedule # Temporal :7233 (UI :8233), worker, API :8000
agent/.venv/Scripts/python -m agent.run_check little-loop # run one shop's check and print its card
agent/.venv/Scripts/python -m agent.demo_crash # kill the worker mid-check, watch it recover
cd web && npm install && npm run dev # dashboard on :3000
在 macOS 或 Linux 上,解释器是 agent/.venv/bin/python。
代码
RecallDesk
在您上架之前,请先检查。 这是一项面向儿童寄售店和 eBay 转售者的每周 CPSC 召回检查。
转售被召回儿童产品在美国属于联邦违规,且被召回婴儿用品常见于二手平台。CPSC 每周四会发布大约十几起召回信息,而在自 2026-07-18 以来的 151 次召回中,有 73 次决定召回的型号仅出现在描述细则中。eBay 卖家几乎不会把它输入标题。
RecallDesk 会读取每条召回信息,将店铺库存和 eBay 列表进行对比,并为被召回的商品打印出 一张每周卡片(标注 禁止销售),对于只能由持有人判断的商品,则打印 检查标签(并标明标签的确切位置),随后直到下周四才会再次更新。
构建原则:大模型负责教学,小模型负责读取,代码负责决策。
…
共有四个目录。ml/ 目录负责数据收集、规格制定、eBay 搜索、标注、训练和评估。agent/ 目录包含 Temporal 工作流、API 和店铺内存。web/ 目录是仪表盘。shared/schema.py 是其他三部分共同遵循的唯一契约。这里的所有图表数据均来源于 ml/results/*.json、data/*/…stats.json、docs/charts/chart_data.json 或位于 agent/demo_logs/ 的转录文件。许可证: MIT。
我的构建过程

规则先行,模型后续
我们在选择模型之前就制定了决策规则。我们的三个判定在 ml/LABELING.md 中给出:
- match: 该商品即为召回产品。品牌和系列一致,且可见召回标识,或者召回涵盖该产品的每一个单位。
- check: 品牌和系列相同,但决定它的因素(型号、日期码、销售窗口、变体)不可见。
- no_match: 不同产品、被排除的型号,或仅为配件、零件或标称为“兼容”的物品。
七条有序规则用于判定。规则 7 起到了最大作用:无品牌且无召回标识的商品属于通用仿冒品,因此判定为 no_match,除非它提及只有召回产品才有的内容,例如授权角色。模型输出一个小型 JSON 对象,普通代码将其映射到卡片。
一个大型开放模型 Qwen3.5-397B-A17B 为全部 591 次召回编写了规格(耗时 100 秒,成本 2.21 美元),代码会删除公告中不存在的任何标识符。它还标记了 3,300 对(召回、列表),并一直将无品牌的仿冒品称为 “check”:首次遍历得到 1,134 次 check,在我们在提示词中加入规则 7 后得到 872 次。仍然太多,于是我们停止询问。一个简短的函数在老师违反规则时检查该规则并重写 训练目标:
def is_generic_lookalike(spec: dict, title: str) -> bool:
words = set(norm(title).split())
if brand_words(spec.get("brand", "")) & words: # names the brand -> not generic
return False
if id_hit(spec, title): # shows a recalled identifier -> not generic
return False
return len(distinctive_words(spec) & words) < 2 # fewer than 2 distinctive product-name words
它在 391 对数据上运行:训练集 357 对,测试集 34 对。所有更改均记录在 data/labeled/targets.jsonl 中。
397B 只教一次;4B 每周四工作
在 $2.20/千次检测 的情况下,397B 对每家店铺每周运行都太昂贵。于是我们用 Tinker 在其校正后的标签上对 Qwen3.5-4B(LoRA秩 32)进行了微调:共 3,007 对数据,提前固定 2 轮 epoch,批量 16 下 376 步,学习率 4.91e-4 线性衰减,仅在 JSON 上计算损失。我们的代码负责组装批次并调用 forward_backward 和 optim_step;Tinker 的 GPU 完成剩余工作,save_weights_for_sampler 返回周工作流采样所用的 tinker:// 路径。耗时约 38 分钟;保持外部损失从 0.724 降至 0.228。在约 400 token 的提示词下(教师约 1,060 token),学生的成本为 $0.19/千次检测。
Tinker 按官方价格计算:规格 $2.21,第一次标记运行被废弃 $6.80,第二次运行 $7.47,训练 $2.39,评估 $0.23,合计约 $19.10。SerpApi:使用了免费额度的 250 次查询中的 235 次。
不自我欺骗地评分
我们按 召回率 划分数据:217 对用于训练,20 对保留作为测试(其中 12 对是儿童数据),且召回率或列表未共享。每对数据在温度 0 下仅尝试一次;JSON 无效即视为错误。评估套件(bootstrap 置信区间、精确 McNemar)以及 Temporal 崩溃测试模式源自 @soumyadeepdey 之前的开源项目 TrailTruth(仓库)。
有一个注意点贯穿以下全部。 我们构建了一个盲标工具,但未及时完成人工标注,因此 293 组测试对的答案关键是 代码按规则 7 给出的教师判断,而非人工。这些得分展示了 4B 在未见过的召回上学习教师规则书的忠实程度,而非它在世界上的正确率。
| 在 293 组留出对上 | 准确率(95% 置信区间) | 误报(占 255) | 标记的风险对(占 38) | $/千次 |
|---|---|---|---|---|
| Qwen3.5-4B, 基础 | 0.512 (0.457–0.570) | 120 | 34 | $0.22 |
| Qwen3.5-4B, 基础 + 教师完整规则书 | 0.625 (0.570–0.679) | 106 | 36 | $0.37 |
| 标识符/关键词规则,无模型 | 0.864 (0.823–0.901) | 22 | 22 | $0 |
| 教师, Qwen3.5-397B-A17B | 0.884 (0.846–0.918) | 34 | 38 | $2.20 |
| Qwen3.5-4B + RecallDesk LoRA | 0.918 (0.884–0.949) | 12 | 26 | $0.19 |
教师的 38/38 全部正确是自动的,因为每个金标 check 都是其自身。它的 34 次误报正是规则 7 覆盖的那些配对。基础 4B 在 14 对上返回了无效 JSON,而其他模型均未出现这种情况。
微调使 4B 的得分从 0.512 提升至 0.918(提升 40.6 个百分点,95% CI 为 +33.8 至 +47.1,精确 McNemar 检验 p < 0.001)。相较于教师,差距(+3.4,p = 0.20)不显著:读取方式相同,成本仅约为十二分之一。对商店而言,误报列最为重要。基础模型将 255 条无害列表中的 120 条发送至 CHECK LABEL,这会掩盖真实的那些。LoRA 仅发送了 12 条。
代价:LoRA 标记了 38个风险配对中的26;其基础模型标记了34(仅在风险配对上,基础模型领先11比3,p = 0.057,不显著)。十二次漏检中有九次是一次召回,即 Sviyatp 池排水盖,根据 CPSC 的说明,“产品本身没有任何标签或产品标识”。这些列表是无品牌白色排水盖,规则 7 将其判定为 no_match,这正是学生所说的。我们的编码规则仅因为标题中共享 “dual suction outlet” 与 Sviyatp 包装上印刷的产品名称相同,才将它们保持为 “check”。另外两次漏检是无品牌婴儿玩具;其中一款是 14 只装的手指灯,对应的是召回的 28 只装和 30 只装。于是这是一次真实的权衡:通过在完全无标签的产品上信任规则 7,换来更少的误报。在 180 份儿童产品配对上,LoRA 得分 0.933,基础模型得分 0.428。
一个具体的误报:对于 TheKiddoSpace 被召回的 LED 悬浮球,eBay 返回了 “Rechargeable Hover Soccer Ball LED Lights Air Power Indoor Outdoor Toy Kids 3+”,其中没有任何品牌信息。基础模型返回 check,因为该列表“匹配品牌”。LoRA 返回 no_match:“外观通用的仿冒品:列表中既没有品牌也没有召回标识。”在 Tinker 托管的采样器上,每次请求的延迟为每个模型 2.3–2.8 秒,因此胜在成本而非速度。
查找列表(SerpApi)
每次调用使用 engine=ebay、ebay_domain=ebay.com、_ipg=50,并将教师的第一个查询作为 _nkw。响应会根据参数的哈希值进行缓存,因而不会重复付费;遇到 429 时会中止运行。商店自身的店铺使用 _sasl=<seller>&_saslop=1(该引擎没有 _ssn 参数)。卖家名称保留在原始缓存中;发布的数据使用哈希值。
每周四(Temporal)
recalldesk-weekly-sweep 是一个在每周四 14:00(America/New_York)运行的 Temporal 调度。它将在 10 月 22 日和 29 日的 18:00 UTC 触发,随后在时钟调整后于 11 月 5 日的 19:00 UTC 再次触发。每一步都是一个具有自身失败策略的活动:CPSC 会先回退获取,然后回退到已提交的快照;分类心跳会有六个并发调用,因此卡住的调用会被重试;格式错误的 JSON 会被重试,然后由规则回答并在卡片上添加备注;SerpApi 的 429 永不重试,且该步骤会记录 source=snapshot (SerpApi quota/429, not retried)。
死掉的工作进程会丢失工作吗?我们在演示店铺的 60 项(176 次比较)上启动了检查,在 12.4 秒时杀死了工作进程,此时已完成 19 项。随后三秒内没有任何运行,而 Temporal 仍显示工作流正在运行,有六个活动在进行中。一个新工作进程在 33.2 秒时重放了历史记录并发布了卡片。历史记录:60 项活动,每项只调度一次;在杀死之前已完成的 20 项被保留,进行中的 6 项获得了第二次尝试,没有任何项被完成两次。此次演练使用了规则分类器以及每项 1.2 秒的人工延迟,以确保杀死发生在运行中途。
我们随后在 Tinker 上使用真实的 LoRA 再次进行了同样的操作(工作流 check-little-loop-2026-10-17-1a16-crashdemo)。在 73.2 秒时,我们杀死了工作进程 PID 12764,此时已完成 60 项中的 18 项,且有 6 个调用在进行中。一个新工作进程在 76.5 秒时启动。卡片在 170.9 秒时发布。同样有 60 项活动,每项只调度一次:保留 18 项,重试 6 项,没有任何项被完成两次,历史记录事件共 344 条。下面的图表展示了此次 LoRA 运行;其后的 Temporal UI 截图来自演练。
仪表盘的运行页面将同一次运行标记为已恢复:
在 LoRA 工作进程中注入故障(假 CPSC 503、挂起的调用、损坏的 JSON),十项需要重试,三项由标记的回退回答,卡片对此进行了记录。自 10 月 8 日以来的手动召回清理从 CPSC 获得了 14 条活跃记录,并为每个店铺生成一个子工作流,耗时 83 秒生成了两张卡片。
记住店铺的更正(Backboard)
没有模型能知道这家店消防车表盘上显示为 24/11。持有它的员工可以。他们的 "Not a match? Correct this" 注释被存储为店铺自身 Backboard 助手的一个记忆(仅显式内存端点)。下次运行时,load_shop_memories 会根据含义而非确切文本检索它。该记忆与模型隔离,模型仍然回答 check;随后代码将判决改为 no_match,并在卡片上列出更正。在记录的运行中(LoRA 在 Tinker 上,Backboard 记忆为 063081d1-…),CHECK LABEL 从 7 项降至 6 项,DO NOT SELL 保持为 8 项。
覆盖操作需要匹配的召回编号,且与被记住项目词汇重叠度至少 60%,于是关于某塔楼的注释无法清除另一塔楼。同一次运行也应用了一个较旧的 HARPPA 塔楼更正(记忆为 beeeda90-…)。一个诚实的问题在于:长项目名称上的语义搜索表现不稳定。完整的 "Melissa & Doug Lights & Sounds Fire Truck Activity Board" 查询未返回自身更正,而较短品牌+名称查询返回了。于是每次运行现在仅使用品牌、名称和型号进行搜索,并且只列出一次店铺的记忆,将两者均通过相同的重叠守则。在记录的运行中,消防车更正通过列表生效。
它出问题的地方
- 一个 402 错误冻结了训练。 当我们的 Tinker 额度在运行中途用完时,待处理的请求未得到解决,循环一直在等待它却没有报错。现在每次等待都有 300 秒的超时,每个 epoch 结束时会保存权重。
- 老师不愿遵循规则 7。 两次提示词修改仅减少了违规次数,最终代码在训练目标上强制执行了该规则。
- Backboard 的免费额度仅覆盖内存。 我们本来计划通过 Backboard 聊天功能运行额外的开放权重基线,但未被纳入,因此这些行不在表中。
- 完整记录未产生任何内容。 其钩子按仓库安装,我们的协作会话在仓库文件夹外启动,因此不存在检查点,也没有进入该类别。
- 第一次标记运行在首次写入时崩溃 因为输出文件夹尚不存在,只需一行代码即可修复。
- 有些召回无法被任何人读取。 六则通知均以 “以下型号包含:” 结尾,且仅将列表放在 cpsc.gov 上,该站点拒绝自动请求。
为什么开放创新很重要?
小店利润空间承受不起每件商品每周一次的前沿模型费用。开放权重让我们为这一任务训练一个 4B 模型,而训练阶段是误报产生的环节;封闭的 API 只会让我们在昂贵的模型和嘈杂的提示词之间做选择。
此外,没有锁定效应:适配器导出为 PEFT 权重,店铺可在自己的 vLLM 或 llama.cpp 服务器上使用,只需通过一个环境变量选择,且无需密钥,RecallDesk 仍在其规则分类器上运行并相应地为卡片打标签。规格、划分、预测以及评估脚本都在仓库中,因此任何人都可以重新评分我们的 293 对数据,或添加我们未能获得的人工答案密钥。
隐私:RecallDesk 读取的所有内容都是公开的(CPSC 的开放 API,通过 SerpApi 获得的公开 eBay 搜索结果)。卖家用户名永不会离开被 git 忽略的原始缓存,演示店铺是虚构的。不过,默认情况下,标注、训练和推理在 Tinker 托管的 GPU 上运行;本地部署则意味着使用导出的适配器。
我的 Agent 会话
一次协调会话启动了五个并行的编码 Agent(机器学习与数据、Temporal 后端、仪表盘、图表和截屏、编写)。其第一个输出不是代码,而是 CONTRACT.md:涵盖了每种数据形状、每条 API 路由,以及由代码而非模型决定哪些内容能够到达卡片的规则。有了这个基准,仪表盘在训练仍在进行时便基于真实数据的固定装置构建完成,各部分在首次连接时即可契合。
我的第一位用户
目前尚未有付费店铺使用 RecallDesk,我们也不会把假想的情况当作客户来包装。我们希望从第一位儿童寄售店店主那里了解:如今进行召回检查需要多长时间;在没有演练的情况下,CHECK LABEL 是否清晰;他们在看到我们的方案之前会喊出什么价格;以及什么因素会让他们却步。我们怀疑是责任问题和数据录入,这会使得 POS 导入优先于任何模型工作。
以下是我们的报价,获胜者揭晓前一直有效:如果您在美国经营转售店铺或在 eBay 上进行倒卖,请将您的商品清单或卖家名称发送给我们,我们将在本周四通过 RecallDesk 处理并免费寄送卡片给您。我们将在获得许可后,把第一条真实回复添加到此节中。
奖项类别
- Tinker 的最佳用法: Qwen3.5-397B-A17B 负责编写规格和标签;Tinker 的训练 API 对 Qwen3.5-4B 进行微调(LoRA 排名 32),其采样器每周四提供服务。在 20 次未见过的召回中:准确率从 0.512 提升至 0.918(p < 0.001),误报从 120 次降至 12 次(共 255 次检测),每 1,000 次检测成本从 2.20 美元降至 0.19 美元,总计约 19.10 美元。注意事项(教师构建的答案要点,38 项风险中有 26 被标记)已在上文说明。
-
SerpApi 的最佳用法: 235 次基于 eBay 引擎的缓存搜索,覆盖 237 次召回,得到 12,562 条唯一商品列表,其中有 45 条成功找到产品;此外,通过
_sasl+_saslop=1进行每店每周扫描,且 429 秒从未重试。 - Temporal 的最佳用法: 一个感知夏令时的星期四调度表,每项活动的重试和回退策略,分类心跳,以及工作进程终止测试:共 60 项活动,其中 20 项被保留,6 项被重试,没有重复。
- Backboard 的最佳用法: 每家店铺的更正记忆,基于语义检索并由代码在召回编号和词汇重叠防护之后应用,使得员工的一行修改即可改变下周的卡片。
如果您经营儿童寄售店或季节性促销,或者在 eBay 上翻转退货,欢迎评论或给我们发消息。我们将在本周四免费为您的店铺运行 RecallDesk,并将卡片寄给您。对于其他人:曾经有哪次召回让您翻看商品以阅读标签,是什么?
由 @dronzer2code 和 @soumyadeepdey @t-rexbytes。













