{
  "title": "在 AI 时代修复 B2B 商务搜索",
  "excerpt": "我审计了为四家 B2B 分销商提供搜索支持的 775,000 条产品记录。消费巨头已经公开了如何用 LLM 修复这类目录的方法，但贸易分销行业尚未拿起这本 playbook。这就是为暖通空调（HVAC）、管道和电气行业量身定制的版本，并附上了真实成本。",
  "content_html": "<p>B2B 商务搜索有一个不为人知的秘密：排名算法很少是问题的根源。</p>\n<p>当搜索结果不佳时，团队总会去拨那些有趣的杠杆：Boost、同义词、嵌入向量、重排序器，以及最近的 LLM 查询理解。我自己也拉过所有这些杠杆。但上周，我做了一件不那么光鲜的事：我把四个生产环境搜索索引中的每一条产品记录都导了出来——涵盖暖通空调（HVAC）、管道、工业用品和紧固件领域的四家 B2B 分销商，共计 775,051 条记录——并审计了数据本身。我启用了十二个并行运行的 AI Agent，每个负责一个维度：覆盖率、文本质量、零件编号、品牌、价格、规格、类目、媒体资源、重复项。</p>\n<p>审计结果对任何做过分销商目录的人来说都不足为奇，但对没做过的人来说则令人震惊。然而最让我印象深刻的一点是：解决方案早已公开。Amazon、DoorDash 和 Instacart 过去两年在公开的工程博客中详细阐述了它们如何利用 LLM 大规模清洗、标注和丰富杂乱的目录。贸易分销行业却大多没有注意到。这很奇怪，因为它们的目录更乱，它们的查询价值更高（承包商下单的是价值 4,000 美元的冷凝器，而不是 12 美元的午餐），而且它们的目录规模实际上让经济性变得更简单。</p>\n<p>因此，这篇文章就是专为贸易分销行业改编的 playbook：那些破坏 B2B 搜索的数据问题、如何今天下午就用编程 Agent 找出它们、如何用 LLM 修复它们（包括 UNSPSC 分类），以及整套方案的成本。先剧透最后一点：比你猜的便宜两个数量级。</p>\n<h2>这本 playbook 已经存在，只是不在我们行业里</h2>\n<p>这些平台型公司已经公开的部分内容包括：</p>\n<ul>\n<li>DoorDash 使用基于检索增强生成（RAG）的 LLM 来<a href=\"https://careersatdoordash.com/blog/how-doordash-leverages-llms-for-better-search-retrieval/\">构建其产品知识图谱并改进搜索检索</a>：在数百万商家提供的商品中自动提取品牌、提取属性并进行实体链接。</li>\n<li>Amazon 构建了 <a href=\"https://www.amazon.science/blog/building-commonsense-knowledge-graphs-to-aid-product-recommendation\">COSMO</a>，一个由 LLM 生成的知识图谱，将客户的真实意图（\"孕妇鞋\"）与产品本身（\"防滑鞋\"）连接起来。他们报告称在离线测试中推荐性能提升了高达 60%。</li>\n<li>Instacart 将 LLM 嵌入搜索技术栈以<a href=\"https://tech.instacart.com/supercharging-discovery-in-search-with-llms-556c585d4720\">生成发现内容</a>，并正在<a href=\"https://www.instacart.com/company/tech-innovation/building-the-intent-engine-how-instacart-is-revamping-query-understanding-with-llms\">围绕它们重建查询理解</a>——其中繁重的生成工作以离线批处理的方式完成，目的正是为了控制成本。</li>\n</ul>\n<p>共同的线索很容易被忽略：LLM 的胜利发生在目录侧，而且是离线的。这些公司在第一条查询到来之前就丰富了数据。发现系统分为离线侧——构建产物（目录、索引、嵌入向量）——和在线侧——基于这些产物进行检索和排序，而几乎所有已公开的 LLM 价值都落在离线侧。排序层的好坏，完全取决于它喂进去的数据。</p>\n<p>现在，把一本典型的 HVAC 或管道分销商目录放在它旁边对比。这本目录由 ERP 导出数据、供应商电子表格、采购集团数据流，以及某人十年前写的一个 CSV 解析器拼凑而成。产品的「标题」是为仓库拣货员写的发票简写。而打在上面的搜索流量几乎是意图密度最高的：一半是精确的零件编号，其余是行业黑话，比如 \"3/4 cxc 90 ell\"。说实话，我想不出比这更适合这本 playbook 的环境了。查询很有价值，数据可以修复，而且对于几十万 SKU 的目录，用 LLM 完整跑一遍的成本只要几百美元。不是几百万。是几百。</p>\n<p>以下是审计的架构，因为你会想要复现它：</p>\n<pre><code class=\"language-mermaid\">flowchart TD\n    D[\"原始目录导出&lt;br/&gt;（JSONL，每行一条记录）\"] --&gt; C1[\"按目录覆盖率的 Agent&lt;br/&gt;空值率、分布、死字段\"]\n    D --&gt; C2[\"维度 Agent&lt;br/&gt;文本 · 零件编号 · 品牌 · 价格&lt;br/&gt;规格 · 类目 · 媒体 · 重复项\"]\n    C1 --&gt; S[\"综合：按严重程度排序的发现&lt;br/&gt;附带示例记录\"]\n    C2 --&gt; S\n    S --&gt; F[\"修复流水线：规则 + LLM 丰富&lt;br/&gt;+ UNSPSC 分类 + 写入关卡\"]</code></pre>\n<p>在给出提示词之前，先说明一下格式。下面所有内容都基于一种通用导出：每行一个 JSON 对象，每个对象包含唯一的 <code>id</code> 和该记录的字段。Elasticsearch、OpenSearch、Solr、Algolia、Typesense、PIM、普通的数据库导出——都不重要。每个平台都能导出这种格式，而且这里的每个提示词都是针对它运行的。</p>\n<h2>类别 1：静默的流水线故障</h2>\n<p>我发现的最糟糕的问题，不是任何人写错的数据。而是流水线在导入过程中悄无声息地破坏的数据。</p>\n<p>这些目录在写入时通过脚本化的正则表达式从产品标题中提取尺寸和单位，从而构建主要搜索文本。对于长标题，正则表达式超出了引擎的复杂度上限，处理器失败，但记录仍然被索引了——带有一个没人看的错误字段，而且完全没有主搜索字段。</p>\n<img src=\"/assets/images/b2b-search-silent-failures.png\" alt=\"按分销商统计的、没有主搜索字段的已索引文档\" />\n<p>在整个集群中，有 52,570 条记录躺在索引里，但对主查询路径不可见。在最差的那本目录中，每七个产品就有一个。没有仪表盘捕捉到这个问题，因为没有任何东西报错。流水线日复一日、月复一月地返回成功。</p>\n<p>同一类别的另外两个发现。陈旧的镜像：一个字段保存真相（按账户的权限映射），第二个扁平化字段为其建立镜像以用于过滤。在一本目录中，镜像在 19.85% 的记录上发生了漂移——31,000 个活跃产品被错误地从按账户过滤的搜索中隐藏了。以及冻结的目录：一个索引已经十周没有刷新，而它的兄弟索引每天都在更新。运行时间被监控了。新鲜度却没有。</p>\n<p>修复方案很无聊，但这正是重点。对派生字段的覆盖率设置告警（派生字段为空而其来源字段不为空）。不要维护同一事实的两种表示；在写入时派生可过滤字段。像监控运行时间一样监控每个目录的数据新鲜度。</p>\n<p>以下是在你自己的目录中找出所有这些问题的提示词（使用 Claude Code 或 <code>codex exec</code>；操作方式在文末）：</p>\n<pre><code class=\"language-text\">以下是我的产品目录导出，以 JSONL 文件形式存放在 ./dump/ 中——每行一条 JSON 记录，每条记录包含唯一的 \"id\" 和产品字段。我的字段/模式定义（如有）在 schema.json 中。\n\n编写流式 Python 代码（不要一次性加载到内存）来发现静默的流水线故障：\n1. 带有流水线标记的任何 error/exception 字段的记录。\n2. 对于每个 DERIVED 字段（拼接字段、*_search、*_normalized 等变体）：派生字段为空但其明显来源字段不为空的记录。\n3. 看起来互为镜像的字段对（一个嵌套/富文本，一个扁平/可过滤）：量化它们不一致的频率。\n4. updated/indexed 时间戳的分布：目录中是否有某个切片在其余部分刷新时处于冻结状态？\n\n报告每项发现的数量、占目录百分比、5 个示例 id，以及受影响记录是否为活跃/可见。按严重程度排序。</code></pre>\n<h2>类别 2：占位符与哨兵值，或者说：撒谎的数据</h2>\n<p>空值检查是每个人都已有的数据质量工具。占位符却能绕过它们，因为占位符是有值的。只是这个值不是真的。</p>\n<p>我最喜欢的例子：在一本 31.3 万 SKU 的目录中，brand 字段有 741 个不同的值，填充率高达 99.99%。听起来很健康。但排名第一的值覆盖了所有记录的 88.8%，它是 \"Approved Vendor\"。一个 ERP 占位符。最受欢迎的真实品牌只覆盖了目录的 0.3%。因此，对于十分之九的目录来说，每个品牌分面、品牌加权以及品牌感知重写实际上都失效了，而分面 UI 还兴高采烈地把 \"Approved Vendor\" 当作第一大品牌筛选项。</p>\n<p>一旦你开始留意，这类问题无处不在：</p>\n<ul>\n<li>一本目录中 55% 的记录使用 <code>99999999.000000</code> 作为「无价格」哨兵值。该索引的中位数价格是九千九百万美元。</li>\n<li>64 条记录的产品名称字面量就是 <code>\"unknown\"</code>，于是它们在搜索 \"unknown\" 时获得了排名。</li>\n<li>1,516 个活跃产品的文本描述字段里写进了布尔值 <code>true</code>。引擎将其强制转换为字符串 \"true\"，使得搜索 \"true\" 就能找到这些产品。</li>\n<li>227 个 UPC 以 <code>\"6.71E+11\"</code> 的形式存储——Excel 的科学计数法，51 个毫不相关的产品共享同一个「标识符」——另外还有 15,121 个 UPC 丢失了前导零。Excel 再次发威。</li>\n<li>一本紧固件目录的 brand 字段里放的是产品线名称（\"C6L Lockbolts\"、\"Tool Parts\"），而真正的品牌就在旁边一个字段里。</li>\n</ul>\n<p>我从这个类别中学到的教训是：审计每个字段的 Top-N 值分布，而不是空值率。一个填充率 100% 的字段可能有 88% 是垃圾，而空值检查永远发现不了。然后在写入时对哨兵值进行黑名单拦截，将它们映射为真正的 null，并配上显式的 <code>has_price</code> / <code>has_real_image</code> 标志；在边界做类型断言；对标识符做结构性校验。把任何曾经经过电子表格的东西都视为可疑对象。</p>\n<pre><code class=\"language-text\">同样的 JSONL 导出。对我目录中的每个字段，计算 Top-20 值分布（完整遍历，流式处理）。\n\n标记：(a) 任何覆盖超过 10% 记录的单一值——占位符/哨兵候选，如 \"Approved Vendor\"、\"unknown\"、99999999、0.0；(b) 类型与该字段主导类型不一致的值（文本字段中的布尔值、字符串中的浮点数）；(c) 标识符字段（UPC/EAN/GTIN/part numbers）：校验校验和与长度，标记科学计数法、被剥离的前导零、嵌入的空白/Unicode，以及被多条记录共享的标识符；(d) 错放在 brand 字段中的类目或产品线值。\n\n对每个标记：数量、占目录百分比、5 个带 id 的原文示例，以及一条可拒绝它的建议写入规则。</code></pre>\n<h2>类别 3：贫瘠的内容</h2>\n<p>分销商的目录是 ERP 写的，不是商品经理写的。文本是发票简写：<code>PROPRESS 2-1/2X1 CXC RED CPLG</code>、<code>HC HX 18X8 W</code>。在一本目录中，三分之一的产品名称包含的词典词汇不到 60%。在另一本中，原始名称和描述字段 100% 为空——文本只存在于派生的搜索 blob 中，因此所有读取原始字段的东西（结果标题、嵌入输入、相关性判断提示词）读到的都是空，而没人知道。</p>\n<img src=\"/assets/images/b2b-search-field-coverage.png\" alt=\"四个生产环境 B2B 索引的字段覆盖率\" />\n<p>那张热力图是我这次审计中最喜欢的产物。这些字段中的每一个都存在于每个 schema 中。数字表示每个字段实际持有数据的比例。最让我震惊的是 ML-descriptions 那一行：schema 承诺在每条记录上都存在的丰富层，在 775,051 条记录中只有 18 条被填充。18 条。评分流水线中每个「如果存在丰富内容则使用」的分支都是静默的无操作。Schema 是一种愿景；只有覆盖率才是事实。</p>\n<p>这个类别正是已公开的 playbook 最直接适用的领域——DoorDash 的属性提取、Instacart 的离线生成——而下面的成本部分会给出具体价格。但在一个以零件编号为核心的业务中，有四条规则很重要，部分是从之前丰富化尝试的失败中学到的：</p>\n<ul>\n<li>扩展，不要替换。在原始发票字符串旁边生成客户可读的标题，并对两者都建立索引。柜台技术员的查询和房主的查询都应该能命中。</li>\n<li>永远不要把代码读成单词。那 18 条先驱记录？生成器把型号用单词拼了出来：\"RGF one hundred eighty\"。没有哪个承包商会这么输入。代码保持原文；扩展是补充，不是替代。</li>\n<li>顺手提取结构。同一次重写标题的过程可以输出 <code>{size, material, connection_type}</code> 用于分面。</li>\n<li>行业黑话是同义词问题，不是重写问题。<code>CXC</code>、<code>ELL</code>、<code>CPLG</code>、\"t-stat\" 属于分析器级别的同义词扩展，一次性配置即可。不要花钱把它们重写到五十万条记录里。</li>\n</ul>\n<pre><code class=\"language-text\">同样的导出。通过完整的流式遍历来评估面向客户的字段（name、short/long description）的文本质量：\n\n1. 有效标题覆盖率：拥有非空展示名称的 ACTIVE 记录占比。\n2. 可读性：全大写名称占比；词典词汇 token 低于 60% 的占比（缩写沙拉）；少于 10 个字符的名称；纯数字描述。\n3. 垃圾内容：HTML/CMS 标记、编码伪影、嵌入的运营备注（*** NOT A PHYSICAL ITEM ***）、CSV 列溢出。\n4. 重复：完全相同的 short/long description；被 20 个以上 SKU 共享的样板文本；精确长度聚类（254/255/500 字符）暗示上游 VARCHAR 限制。\n5. 丰富化现实检查：schema 承诺的每个 ML/enriched/embedding 字段的实际覆盖率。\n\n给出数字、每项 5 个原文示例、严重程度，以及哪些问题需要 LLM 丰富化、哪些需要流水线修复、哪些需要同义词层修复。</code></pre>\n<h2>类别 4：零件编号是神圣的</h2>\n<p>B2B 搜索流量中有一半是有人在输入零件编号。精确匹配是这里的全部关键，而它在悄无声息中失效。</p>\n<p>在一本目录中，99.99% 的记录所索引的零件编号是内部数字 SKU。制造商的编号——印在盒子上的那个——只存在于自由文本描述中。183,000 个产品无法通过客户实际会输入的编号被找到。</p>\n<p>另一本目录有一个变体扩展系统：去掉连字符、压缩段、索引变体，以便实现容错 PN 搜索。好主意。但生成器是组合式的（每个产品最多 80 个变体），在 11,826 条记录中，一个产品的生成变体恰好等于另一个产品的标准零件编号。紧固件是最残酷的情况，因为标点符号编码了物理尺寸：<code>702-1-5/32</code> 是 1-5/32 英寸的零件，而 <code>702-15/32</code> 是 15/32 英寸的零件，两者都归一化为 <code>7021532</code>。客户输入一个精确的零件编号，却在正确零件和其尺寸近亲之间得到抛硬币般的结果。</p>\n<p>同样属于这一类：竞争对手的交叉引用（「我有 Ferguson 的编号，你们的对应是什么？」）以未拆分的管道符分隔 blob 形式存储，如 <code>19MU82|Fastenal 0269214|Ferguson M48222407</code>，而所在字段被配置为不可搜索，因此一项核心的 B2B 销售行为根本无法运作。还有空白分叉——同一个零件编号被索引了三次（纯文本、NBSP 填充、尾部空格）——以及双解码 UTF-8 造成的乱码双胞胎。</p>\n<p>修复大部分问题的原则：精确匹配必须在结构上击败模糊匹配，而不是靠概率。一个保留标点的精确子句的得分必须严格高于任何归一化层级，绝不能采用变体可以与标准命中打平的扁平匹配。在索引前对照标准 PN 空间检查生成变体并丢弃冲突。在派生记录 ID 之前进行 Unicode 归一化。将交叉引用 blob 拆分为数组；这只是一行写入代码，却能解锁一整类查询。</p>\n<pre><code class=\"language-text\">同样的导出。我的用户通过零件编号搜索；审计精确匹配的完整性：\n\n1. 存在哪些 PN 类字段（part_number、mpn、alt/competitor/customer PNs、UPC），它们的覆盖率，以及哪些字段在持有真实数据的同时被配置为不可搜索。\n2. 唯一可搜索的 PN token 是内部 SKU 的记录（真正的制造商 PN 只出现在描述文本中——展示模式）。\n3. 如果存在 PN 变体/关键词扩展字段：找出每个等于另一条记录的 canonical PN 的生成变体（归一化：小写、去除标点）。展示冲突对——尤其是带分数的紧固件尺寸。\n4. 仅通过空白/不可见字符/大小写与另一条记录的 PN 不同的 PN（重复的身份分叉）。\n5. 以分隔 blob 而非数组形式存储的多值交叉引用字段。\n\n给出数量、示例，以及每个问题的修复所需的写入规则或查询构建器更改。</code></pre>\n<p>还有一些上面放不下但值得一提的事：一个同义词生成器把任何数字都变成了线规术语，于是 <code>REF#259286</code> 变成了 \"259286 awg\"，而 <code>#8-32</code> 的螺纹变成了 \"8 gauge wire\"（数千条记录现在会匹配到与它们毫无关系的电气查询）；一个电商平台的内部标志被索引为可搜索的产品规格，产生了 210 万对垃圾键值；以及 18 个 schema 字段在任何记录上的填充数都为零。扩展生成器需要上下文关卡。死 schema 是一种虚假承诺，迟早有人会基于它构建东西。</p>\n<h2>缺失的一层：UNSPSC，以及不知道自己是什么的产品</h2>\n<p>在我的审计中，一本目录有 83% 的记录缺失类目字段。属性键完全没有分类体系——在一本 15.6 万条记录的目录上有 7,068 个不同的规格键，其中 64% 用在少于十个产品上，还存在像 <code>horse_power</code> 与 <code>horsepower</code> 这样的漂移，把同一个属性拆到了不同分面中。而我反复想到的一个细节是：四本目录中有两本的 schema 里明明有 UNSPSC 字段，已映射好、准备就绪，填充数却恰好为零。有人知道分类很重要，建好了槽位，却从未填充。我敢打赌原因只有一个：手动将 30 万 SKU 分类到一套分类法中需要目录团队干一整年，因此它永远留在了路线图上。</p>\n<p>如果你不在 B2B 领域，UNSPSC 是联合国标准产品与服务代码，是采购系统使用的分类法。这是消费级搜索没有的部分：你客户的采购系统需要它。Punchout 目录、电子采购平台和支出分析工具都是围绕 UNSPSC 代码构建的。目录带有干净代码的分销商可以接入承包商或医院的采购系统。没有代码的分销商不过是一份带搜索框的 PDF 价目表。在内部，它也是类目分面、类目限定排名（「解析为线规的数字应该只提升电线类目」）、跨目录去重，以及类别 4 中每个扩展生成器的上下文关卡的驱动力。</p>\n<p>而现在它只是一个批处理作业。这与 DoorDash 描述用 LLM 解决的问题形状完全相同——针对受控词表的大规模标注——而且方法可以直接迁移：</p>\n<ul>\n<li>分层分类，不要扁平分类。UNSPSC 有四个层级（segment、family、class、commodity）。让模型先从约 450 个选项中选出 family，再在该 family 内选出 class。两个受约束的小选择胜过一个五万选一的抉择，而且你可以像 DoorDash 将其实体链接约束到自己的词表一样，把相关的分类法切片喂入上下文。</li>\n<li>先停在 class 层级。Class 代码已经能解锁分面和采购集成；commodity 级别的精度可以等到它物有所值的时候再推进。</li>\n<li>按置信度路由。小模型处理明确的案例，低置信度记录升级给更大的模型，持续存在分歧的进入人工队列，同时充当你的评估集。</li>\n</ul>\n<p>成本低到我几乎不好意思打出来。分类是一个短输出任务：每条记录大约 300 个输入 token，30 个输出 token。使用 Claude Haiku 4.5 的 Batch API 定价，每千个 SKU 大约 0.17 美元。我全部 77.5 万条记录的舰队分类下来大约只要 130 美元。那个因为需要一年人工工作而搁置在路线图上多年的任务，成本比你们讨论它的团队午餐还低。</p>\n<pre><code class=\"language-text\">你将 B2B 分销商的产品分类到 UNSPSC。附件：UNSPSC family 列表（level 2，约 450 条）作为参考数据。\n\n对于每条产品记录（title、description、brand、part number、任何现有 category text），输出 JSON：\n- unspsc_family: 4 位 family 代码——只能从附件列表中选择\n- family_confidence: high | low\n- rationale: 一个短短语（例如 \"copper press fitting -> pipe fittings\"）\n\n规则：根据产品的功能判断，而不是品牌。行业简写：CXC/FPT/MPT 是管道连接，ELL 是 elbow，CPLG 是 coupling，单独的分数+材料通常是 fitting 尺寸。如果记录不是实体产品（运费行、附加费、\"*** NOT A PHYSICAL ITEM ***\"），输出 NOT_A_PRODUCT。大胆使用 low 置信度——低置信度记录将用 class-level 列表进行第二遍处理。</code></pre>\n<h2>清理 playbook，以及真实的账单</h2>\n<p>修复流水线有三个层级，外加分类任务。诀窍是在正确的层级花钱，因为你几乎永远不需要为每条记录都调用一次 LLM。</p>\n<pre><code class=\"language-mermaid\">flowchart LR\n    A[\"Tier 0 - 规则&lt;br/&gt;确定性代码&lt;br/&gt;~$0\"] --&gt; B[\"Tier 1 - 对唯一值运行 LLM&lt;br/&gt;品牌 · 规格键 · 单位&lt;br/&gt;几十美元\"]\n    B --&gt; C[\"Tier 2 - 按记录运行 LLM&lt;br/&gt;标题 · 属性 · UNSPSC&lt;br/&gt;几百美元\"]\n    C --&gt; G[\"写入关卡&lt;br/&gt;每个修复都变成一个校验器\"]</code></pre>\n<p><strong>Tier 0 是确定性代码，基本上免费。</strong> 剥离 HTML 并解码实体。用显式标志将哨兵值转为真正的 null。修复 UPC（左填充、拒绝科学计数法、校验校验位）。对 ID 做 Unicode 归一化。拆分管道符分隔的交叉引用。丢弃与另一产品标准编号冲突的 PN 变体。删除死 schema。一个 Agent 在一轮会话中就能写出这些脚本，而这一层修复了我审计中发现的大约一半问题。在任何丰富化之前先做这一步，否则你会花钱请模型为那些已被流水线静默丢弃的产品 beautifully 重写标题。</p>\n<p><strong>Tier 1 让 LLM 运行在唯一值上，而不是记录上。</strong> 这是让词汇问题变得便宜的诀窍：我最差的那本目录有 31.3 万条记录，但只有 741 个不同的品牌字符串。品牌归一化是对 741 个字符串的一次作业——聚类大小写变体、将子品牌映射到母品牌、标记占位符——而不是 31.3 万次调用。规格键和单位后缀同理。每个词汇作业只要几美元，其输出是一张静态别名表，你的写入层可以永远确定性地应用它。整个层级在一批目录上花费 20-50 美元，大部分用于复核。</p>\n<pre><code class=\"language-text\">附件：我的 brand 和 manufacturer 字段的完整去重值分布（value、record_count）。生成一张归一化表：\n\ncanonical_brand, parent_manufacturer, [raw variants], confidence\n\n规则：聚类大小写/标点/空白变体；将子品牌映射到母制造商作为单独一列（不要合并）；将占位值标记为 NULL_SENTINEL；将错放在 brand 字段中的产品线或类目标记为 NOT_A_BRAND。对不确定的聚类进行标记，而不是猜测。输出 CSV，然后编写一个脚本在写入时应用它，并记录未匹配的新值。</code></pre>\n<p><strong>Tier 2 是按记录处理</strong>：为每个产品生成客户可读的标题、搜索扩展、结构化属性，以及找回的制造商零件编号。每个 SKU 的消耗很小——大约 400 个输入 token（记录加上共享指令，prompt caching 让这部分几乎免费）和 250 个输出 token。在你甚至还没选定模型之前，有两个杠杆：只丰富活跃记录（在我最差的目录中只有 14% 的记录是活跃的，直接砍掉 86%），以及使用供应商提供的 Batch API，因为丰富化没有延迟要求，而且 Anthropic 和 OpenAI 都对批处理提供 50% 的折扣。</p>\n<p>我为 2026 年你可能会用到的所有模型估算了同一项工作的价格——Claude、<a href=\"https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/\">GPT-5.6 的三个层级</a>（Sol、Terra、Luna）、Kimi K3、GLM-5.2、Qwen3.5，以及 <a href=\"https://deepseek.ai/pricing\">DeepSeek V4</a>。相同的工作量，供应商公布的费率，在有的地方应用了批处理折扣：</p>\n<img src=\"/assets/images/b2b-search-enrichment-cost-by-model.png\" alt=\"跨模型丰富化 77.5 万 SKU 的成本\" />\n<p>我不得不反复核对这些数字，因为它们看起来太离谱了。这个项目的最坏情况——使用前沿模型、每个 SKU 都处理、完全不做过滤——上限大约在 3,800 美元。地板价不到一百美元：DeepSeek V4-Flash 丰富化整个舰队的成本大约相当于一把不错的电钻。你实际会运行的版本是混合层级：用廉价模型（Haiku、Luna、Qwen Flash、DeepSeek）处理机械化的主体部分，用中端模型（Sonnet、Terra、GLM-5.2）处理小模型标记为低置信度的缩写沙拉长尾，再用前沿模型对 1-2% 的样本进行抽检作为质量关卡。这样下来，无论选哪家供应商，活跃 SKU 的花费都在几百美元左右。</p>\n<p>图表无法显示两点注意事项。廉价模型只有在输出能通过你的评估时才真的便宜——在全面投入前先做 200 条样本对比，因为一个搞砸了 5% 零件编号的廉价模型，比不犯错的前沿模型更贵。而且你的目录是竞争性数据：价格、交叉引用和客户零件编号都在这些记录里，因此在把 77.5 万条记录发往名单上最便宜的端点之前，务必检查每个供应商的数据保留和训练条款。对很多分销商来说，仅此一项检查就能决定某些供应商是否可用，而开源权重选项（DeepSeek、GLM、Qwen、Kimi）还有一个额外优势：如果数据完全不能离境，你可以自行托管它们。</p>\n<p>加上约 130 美元的 UNSPSC 分类（它在不同模型间的缩放方式相同——在廉价层级上降到零花钱级别）和词汇工作，整个目录改造的运行成本在几百到几千美元之间，而过去这相当于商品团队一年的工作量。这与 Instacart 描述的离线批处理经济学相同，只是在贸易目录的规模上，数字小到可以刷信用卡支付。</p>\n<p>它在我审计中的一条真实记录上看起来是这样的：</p>\n<pre><code class=\"language-text\">IN:  part_number: 1678619\n     short_desc:  PROPRESS 2-1/2X1 CXC RED CPLG 20685\n     brand:       (placeholder)\n\nOUT: display_title:      2-1/2\" x 1\" ProPress Copper Reducing Coupling\n     search_expansions:  [\"copper press fitting\", \"reducing coupling\",\n                          \"press x press coupling\"]\n     attributes:         {size_1: {value: 2.5, unit: in},\n                          size_2: {value: 1, unit: in},\n                          material: copper, connection: press}\n     extracted_mpn:      \"20685\"\n     unspsc_family:      4017 (pipe fittings)\n     confidence:         high</code></pre>\n<p>看看 <code>extracted_mpn</code> 这一行。同一次处理找回了此前埋在描述文本中的制造商零件编号——在我的一本目录中，这意味着 18.3 万个产品可以通过印在盒子上的编号被找到。仅此一项就值回了批处理作业的票价。</p>\n<pre><code class=\"language-text\">你为搜索丰富 B2B 分销商的产品记录。对于每条输入记录（raw ERP name、descriptions、brand、part number、category），输出 JSON：\n\n- display_title: 客户可读的，&lt;=80 字符，Title Case。扩展行业缩写（CXC -&gt; copper x copper connection，ELL -&gt; elbow，CPLG -&gt; coupling，RED -&gt; reducing）。保持每个零件编号、型号代码和尺寸与输入完全一致——永远不要把代码读成单词。\n- search_expansions: 2-5 个客户可能会输入但未出现在原始文本中的短语（plain-English 产品类型、常见贸易名称）。绝不编造来源中不存在的规格。\n- attributes: {name: {value, unit}}，仅从来源文本中提取。\n- extracted_mpn: 如果描述文本中存在制造商零件编号（通常在 brand 之后的 token）且 PN 字段中不存在，则输出，否则为 null。\n- confidence: high | low。每当你需要猜测时就使用 low；一个被送去复核的低置信度答案，胜过自信满满的幻觉。</code></pre>\n<p>两个生产环境注意事项：将共享指令放在缓存的 prompt prefix 中（缓存能在批处理折扣之上将输入侧成本削减高达 90%），并使用带 JSON schema 的结构化输出，这样你就永远不会为解析失败买单。</p>\n<p><strong>最后一层是人们最容易跳过、却最能产生复利的一层。</strong> 审计值一次清理。它生成的校验器值每一次未来的数据流。在修复会话结束时，加上：</p>\n<pre><code class=\"language-text\">把我们刚刚修复的每个问题都变成一条自动检查，让写入流水线大声报错：派生字段覆盖率断言、哨兵黑名单、文本字段的类型断言、标识符校验和验证、PN 变体冲突检查、ID 卫生规则、UNSPSC 覆盖率追踪，以及每个目录的数据新鲜度告警。将它们作为 CI 针对每个新数据流样本运行的测试输出。</code></pre>\n<p>跳过这一步，同样的 ERP 导出会在一个季度内重新生成同样的垃圾，你会为清理付两次钱。我知道这一点，因为我审计发现的两个 bug 显然以前被修复过，然后又回来了。</p>\n<h2>运行这些提示词</h2>\n<p>一个准备步骤：将你的目录导出为 JSONL，每行一个 JSON 对象，包含唯一的 <code>id</code> 和记录的字段。无论你用什么平台——Elasticsearch、OpenSearch、Solr、Algolia、Typesense、PIM，还是支撑这一切的数据库——直接让 Agent 写导出脚本：</p>\n<pre><code class=\"language-text\">编写一个脚本，将我的目录中的每条产品记录从 [描述：搜索索引名称 / 数据库表 / PIM 导出] 导出到 ./dump/records-{n}.jsonl，每 5 万条记录一个分块——每行一个 JSON 对象，包含唯一的 \"id\" 和所有字段——并将我的字段/模式定义（如果平台有的话）导出到 schema.json。验证导出数量与源数量一致。</code></pre>\n<p>使用 Claude Code 时，把任何类别的提示词丢进该目录下的一个会话中。它会自己编写并运行分析，完整遍历，绝不以采样为借口，并返回记录级别的凭据。当你说「现在修复它」时，同一个会话会生成写入校验器、回填脚本和批处理丰富化作业。将各个类别作为并行会话或子 Agent 运行；我就是这样跑的，整个审计只花了大约十二分钟的挂钟时间。使用 Codex 时，<code>codex exec</code> 可以用同样的提示词处理只读分析遍历；将写入侧的修复保留在你审查的会话中。</p>\n<p>三条我通过惨痛教训学到的规则：</p>\n<ol>\n<li>要求完整遍历和凭据。「分析这些数据」会招来采样和凭感觉。要求每项发现都有精确数量和五个示例记录 ID，这样每个结论都可核查。</li>\n<li>先规则，后丰富化。先修复真相，再添加描述，否则你会为那 88% 标记为 \"Approved Vendor\" 的记录幻觉出品牌。</li>\n<li>先采样，再评估，最后批处理。永远不要用一个未经验证的提示词直接发射 77.5 万条记录的批处理。200 条样本，一次评分遍历，然后再放大。评估框架只花一个 Agent 会话的成本，却能让整个支出规避风险。</li>\n</ol>\n<h2>贸易行业配得上平台级的搜索</h2>\n<p>Amazon、DoorDash 和 Instacart 公开它们的目录 LLM 工作并非出于慈善。它们公开是因为这些技术是通用的，而护城河在于执行。而且这些技术迁移到贸易分销行业的效果，比我能想到的几乎任何其他地方都好：目录小到完整跑一遍很便宜，数据烂到提升空间巨大，查询承载着真金白银，而且采购世界已经运行在一种分类法上，现在一个批处理作业只需大约 130 美元就能把它填充好。</p>\n<p>在我审计的四家分销商目录中浮现的几十个问题里，没有一个能从排序层看出来。但它们都在削弱排序层。喂养这些索引的供应链——ERP 导出、供应商电子表格、古老的 CSV 解析器——正是已公开的 playbook 所修复的东西。运行这套方案的分销商将拥有平台级的搜索能力，而它们的竞争对手仍然无法在目录中找到一个冷凝器盘管。</p>\n<p>「我的数据有什么问题吗？」这个问题过去要花费某人两周的时间，所以没人会问。现在它只需要一个提示词。去问吧。</p>",
  "source_hash": "sha256:729970b39c1b22c143ba55272659d6cc431b0f8889282089a4ea4eaa71f75000",
  "model": "moonshotai/kimi-k2.6",
  "generated_at": "2026-08-06T09:07:14.197233+00:00"
}