X上的电商 AI技术解读:Shopify 怎么判断“六个颜色是不是同一件商品”,附一个能在本机跑的变体聚类小实验
素材来源:X 用户 Brydon Parker(@parker_brydon,简介写的是 Shopify 的数据与 AI 方向)2026-10-27 20:09(北京时间 UTC+8)发布的长帖,原帖:https://x.com/parker_brydon/status/2115053146149986519 。他在同一串回复里给出了原文链接,是 Shopify Engineering 博客上 Mariya Mansurova 写的《Clustering billions of products for agentic commerce with Catalog API》:https://shopify.engineering/catalog-clustering 。另外参考了 @ShopifyEng 2026-10-21 00:04(北京时间)介绍第二篇(跨店聚类)的帖子:https://x.com/ShopifyEng/status/2112575624371933477 ,以及原帖下面两条有信息量的回复(链接见第三节)。
信息读取日期:2026-11-03(Asia/Shanghai)。下文对原帖和原文的转述是笔者用中文重写的意思,不是逐句翻译;帖子里个别细节和原文写法不一致的地方,以 Shopify 原文为准,文中会标出来。第四节的小实验是笔者自己写、在本机跑的,和 Shopify 的实现无关。
本文标注:原创(分享解读,已注明出处)。
一、原帖在讲什么
做过多颜色商品的人都知道,同一件 T 恤有两种上架方式:一种是一个商品链接下面挂黑、白、雾蓝几个颜色规格;另一种是每个颜色单独发一个链接。两种都没错,在自己店里也都能正常卖。
问题出在“平台要把全网商品统一起来给 AI 购物助手用”的时候。Shopify 有一个叫 Catalog 的统一商品层,通过 Catalog API 给开发者和购物助手查询。要让助手回答“哪家的这款外套最便宜”,系统得先知道:A 店的一个链接加六个颜色规格,和 B 店的六个单独链接,其实是同一款东西。Shopify 的做法是把它们归到同一个通用商品标识(原文叫 UPI,Universal Product Identifier)下面。
Brydon Parker 的长帖就是把 Shopify 原文里“店内聚类”这部分拆成了一份笔记。核心是三件事:怎么定义“同一款”,怎么让大模型在几百万家店上稳定地做这个判断,以及出错时优先保哪一头。

二、拆开看:几个值得记下来的设计
1. 两种错误,先保精确。 原帖把失败分成两类。精确失败是把不同商品并到一起,例子是同款运动鞋的男款和女款被合并,买家让助手买女款,收到的是男款。召回失败是漏掉了该在一组里的变体,例子是一件外套有六个颜色,偏偏你想要的那个颜色没被归进去。Shopify 选的是精确优先,原文的说法是先定一条精确率的硬线,在这条线之内尽量提高召回。理由很实在:漏掉一个颜色,这个商品还能单独被搜到;买错了东西,买家是不会原谅的。
2. “核心价值”这个判断标准。 这是我觉得整篇里最有用的一句话:问模型“买家主要是为了什么买它”。某个属性变了、答案不变,那就是变体;答案变了,它就是商品身份的一部分,要拆成不同的 UPI。原文举的对照例子很好记:蛋白粉的巧克力味和香草味是同一个商品的不同口味,因为买的是营养;油漆的两种颜色(原文是 Midnight Blue 和 Sage Green)应该是两个商品,因为买油漆买的就是颜色。也就是说,颜色是不是变体,要看类目。
3. 规则先行,大模型只处理模糊的部分。 原帖说大多数店铺靠规则就能处理。原文补充得更具体:有一个叫 singleton detector 的预过滤步骤,会分析店铺主题模板里商品之间是怎么关联的(metafield 引用、标签分组、集合),如果一家店的商品本来就是各自独立的,就直接给每个商品一个 UPI,不进大模型。原文说只有一小部分店铺真正需要大模型聚类。
4. 预分块。 大店有几千到几百万个商品,塞不进一次提示。做法是先把每个商品做成向量,找最近的 100 个邻居,再用平均链接(UPGMA,组间距离取成员两两距离的平均)从单个商品开始往上合并,距离超过 0.25 或者一块满 200 个商品就停。这样每块商品既足够相近、能让模型看出这家店的命名习惯,又不至于全是重复品。这里有个小出入:原帖只写了用 FAISS 找邻居,原文写的是用 FAISS 里的 HNSW 做近似最近邻检索,并且图里没连上的两组会加一个略高于阈值的惩罚距离,防止聚类沿着图“跳着”并过去。
5. 两阶段:先提出,再挑错。 第一阶段,每一块连同店铺信息(店名、域名、品牌)和每个商品的标题、URL 等字段交给大模型,返回严格的 JSON,给每个商品 ID 填一个 brand 和一个 model,同一店里 brand:model 相同的就是一组,ID 记成 shop_id:brand:model(原帖这里写成了 UCP,应该是笔误,原文是 UPI)。第二阶段把一个候选组的全部商品再交给模型,让它输出一段理由和一个 outliers 数组,列出不该在这组里的商品,默认倾向是“放在一起,除非有明确证据说明核心价值不同”。原文的解释是:从零开始分组是创造性的工作,检查一个现成的分组只是挑错,挑错更简单也更可靠。
6. 结构化输出解决“模型漏商品”。 这部分原帖没展开,但原文花了不少篇幅,我觉得对做开发的人最有参考价值。早期让模型自由输出,会漏掉商品、合并 ID、编出数据里没有的品牌、返回坏掉的 JSON。后来改成严格的 JSON Schema,而且是每一块按商品 ID 动态生成 Schema:输入里有几个商品 ID,输出就有几个必填字段,模型不可能漏。第二阶段的 outliers 数组用枚举约束,只能填这一组里真实存在的 ID。原文还提到,Schema 里字段的顺序会影响质量:要求模型先输出 patterns(这家店的命名规律),再输出每个商品的结果,效果更好。
7. 怎么评估。 人工标注的分层样本,加上用人工标注校准过的大模型评审,持续跟踪精确率和召回率。
三、评论区里两条有用的补充
原帖下有个问题问得很到位:@ankita080393 问聚类用的是商品文本还是购买行为,作者回复说不用购买行为,只用商品元数据和语义相似度(https://x.com/parker_brydon/status/2115219841200120107 )。这一点对卖家很关键:你填的标题、规格名、品牌字段,就是系统判断的全部依据。
另一条是 @accelerate2021(Caleb Scott)的长回复(https://x.com/accelerate2021/status/2115131005682004243 ),他做的是跨线下门店匹配同款商品。他提到几条经验:条码一离开单店就成了主要的关联依据,但条码会被重新发放,也有店铺在条码栏里填自己的编码,所以条码匹配上了还要再核对品牌和规格;即使巧克力味和香草味属于同一个 UPI,库存也必须落在变体这一级,因为一家店可能只有其中一种。还有一位 @zandemha 问商家能不能看到自己的商品被归到了哪个 UPI,否则分错了只会以退货的形式暴露出来,作者回复说暂时不确定,会去确认。这个问题目前在帖子里没有答案。
@ShopifyEng 10 月 21 日发的帖子介绍的是第二篇,讲跨店聚类:不同商家的标题、描述、拍图方式都不一样,要在数十亿条商品里配对。笔者读了第二篇的开头,大意是先用向量检索找候选对,再用三级打分模型(从便宜的特征模型,到同时读两条商品文本的交叉编码器,再到看图片的模型)逐级判断,最后在图上合并成簇。这部分和本文主题关系稍远,就不展开了。
四、本机小实验:文本像,不等于是同一款
为了直观感受“颜色是不是变体取决于类目”,笔者用 Python 标准库写了一个很小的实验:12 条虚构的商品标题,按字符二元组做向量,算余弦距离,再用平均链接合并,阈值先用原文同样的 0.25。这只是演示思路,特征完全不同于 Shopify 用的向量,阈值也不可直接类比。
import itertools, math, re
from collections import Counter
titles = [
"纯棉圆领短袖T恤 男款 黑色", "纯棉圆领短袖T恤 男款 白色", "纯棉圆领短袖T恤 男款 雾蓝",
"纯棉圆领短袖T恤 女款 黑色", "纯棉圆领短袖T恤女款 白色 宽松", "【新色】纯棉圆领短袖T恤 男款 墨绿",
"陶瓷马克杯 350ml 奶白", "陶瓷马克杯 350ml 雾蓝", "陶瓷马克杯 450ml 奶白",
"水性墙面漆 1L 雾霾蓝", "水性墙面漆 1L 鼠尾草绿", "水性墙面漆 1L 奶油白",
]
COLORS = ["黑色", "白色", "雾蓝", "墨绿", "奶白", "雾霾蓝", "鼠尾草绿", "奶油白"]
norm = lambda t: re.sub(r"【.*?】|\s+", "", t) # 去掉营销前缀和空格
vec = lambda t: Counter(norm(t)[i:i+2] for i in range(len(norm(t)) - 1))
def cos_dist(a, b):
dot = sum(a[k] * b[k] for k in a)
return 1 - dot / (math.sqrt(sum(v*v for v in a.values())) * math.sqrt(sum(v*v for v in b.values())))
def upgma(D, n, th):
clusters = {i: [i] for i in range(n)}
while True:
best = None
for a, b in itertools.combinations(clusters, 2):
d = sum(D[i][j] for i in clusters[a] for j in clusters[b]) / (len(clusters[a]) * len(clusters[b]))
if best is None or d < best[0]:
best = (d, a, b)
if best is None or best[0] > th:
break
clusters[best[1]] += clusters.pop(best[2])
return list(clusters.values())
跑了四种情况(完整脚本和输出放在本机 gh24/cluster/ 下,这里只贴结果):

--- A 原始标题,阈值 0.25,7 组
男款 黑/白/雾蓝/墨绿 | 女款 黑/白 | 马克杯 350ml 奶白/雾蓝 | 马克杯 450ml | 三种墙面漆各自一组
--- A 原始标题,阈值 0.45,3 组
男款女款 T 恤全部并成一组 | 三个马克杯一组 | 三种颜色的墙面漆一组
--- B 先去掉颜色词,阈值 0.25,4 组
男款一组 | 女款一组 | 马克杯 350/450ml 一组 | 墙面漆三个颜色并成一组
--- C 去颜色词 + 墙面漆类目保留颜色,阈值 0.25,6 组
男款一组 | 女款一组 | 马克杯一组 | 三种墙面漆各自一组
单次聚类在本机(Python 3.13.5)约 0.1 毫秒,规模太小,耗时没有参考意义。值得看的是结果的变化:
- 阈值 0.25 的原始结果看上去最“对”,但这是碰巧,换一批标题就不一定了;
- 阈值放宽到 0.45,男款和女款并到了一起,这就是原文说的精确失败,墙面漆的三个颜色也并了;
- 把颜色词去掉再比(相当于告诉算法“颜色是变体”),T 恤和马克杯都对了,墙面漆却错了;
- 只有按类目区分“颜色是不是身份”,结果才全部合理。
这正好对应原文的观点:靠调相似度阈值解决不了这个问题,得先回答“买家买的是什么”。在大模型出现之前,这件事只能靠类目规则表来做;Shopify 现在是把这个判断写进提示词,让模型自己推断,再用第二阶段挑错。
五、对国内卖家和开发者意味着什么
在淘宝、京东、拼多多、抖店开店的卖家,平台的商品结构是固定的,你没法决定平台怎么归并同款。但原帖里“只看元数据和语义相似度”这一点照样适用:平台的搜索、比价、同款识别,读的都是你填的标题、规格名和属性。几件能马上做的事:
- 同一款的不同颜色,尽量放在一个链接的颜色规格里,而不是为了多占几个搜索位拆成很多链接;拆开发,评价和销量也会被分散,原帖下也有人提到他审计过的一家店就是这样,评价分散在好几个链接里;
- 颜色规格名写规范,不要一会儿“雾蓝”、一会儿“雾霾蓝”、一会儿“灰蓝色”,营销名可以有,但同一家店里要统一;
- 男女款、儿童款、不同容量这种会改变“买的是什么”的属性,要写在标题和属性里,不要只出现在图片上;
- 标题前缀里的“新色”“爆款”这类词对识别同款没有帮助,实验里笔者是先把它去掉再比的。
做商品中台、ERP 或独立站的开发者,原文里有几处可以直接借鉴:
- 先用规则处理确定的部分(店铺自己定义的款号、条码、规格结构),只把模糊的商品交给模型;
- 让模型输出结构化结果时,按输入的商品 ID 动态生成 Schema,outliers 这类字段用枚举限定在真实 ID 里,可以省掉大量“模型漏了一个商品”的补救逻辑;
- 先提出、再挑错的两阶段做法,对“合并同款”“去重 SKU”这类任务很通用;
- 评估集要按类目分层,墙面漆和 T 恤对颜色的处理完全相反,混在一起评估会掩盖问题。
六、我的看法
我比较认同这篇内容的地方,是它把“AI 购物助手”这个很大的话题落到了一个具体、很多人天天遇到的问题上:多颜色、多规格的商品到底算不算同一款。它给出的判断标准也不玄,“买家买的是什么”这一句话,比一张复杂的类目规则表更容易推广到没见过的类目。
也有两点要说明。一是原文给的是方法和设计取舍,没有公开整体的精确率、召回率数字,只提到某一处改动在某个评估集上让召回提高了 8%,所以效果到底怎样,外部无法验证。二是 @zandemha 提的那个问题很现实:如果商家看不到自己被归到了哪一组,分错了就只能从退货里倒推。国内平台的同款识别同样不透明,对卖家来说,能控制的只有把自己的商品信息写准确。
对国内卖家,我觉得最值得做的一步是把店里所有颜色规格名统一一遍,再检查男女款、容量这类“身份属性”有没有写进标题和属性。工作量不大,不管平台以后怎么识别同款,这一步都不会白做。
原帖链接:https://x.com/parker_brydon/status/2115053146149986519
更多推荐

所有评论(0)