{
  "title": "AI 시대의 B2B 커머스 검색 수정하기",
  "excerpt": "네 개의 B2B 유통업체 검색을 지원하는 775,000개의 제품 레코드를 감사했습니다. 소비자 중심 대기업들은 이미 LLM으로 이런 카탈로그를 수정하는 방법을 공개했지만, 산업용 유통업계는 아직 그 플레이북을 채택하지 않았습니다. HVAC, 배관, 전기 분야에 맞게 조정하고 실제 비용까지 포함한 플레이북을 소개합니다.",
  "content_html": "<p>B2B 커머스 검색에는 더러운 비밀이 있습니다. 랭킹 알고리즘이 문제인 경우는 거의 없다는 것입니다.</p>\n<p>검색 결과가 좋지 않을 때, 팀들은 재미있는 레버를 찾습니다. 부스트, 동의어, 임베딩, 리랭커, 그리고 최근에는 LLM 쿼리 이해까지요. 저 역시 그 모든 레버를 당겨봤습니다. 하지만 지난주에는 매력 없는 일을 했습니다. 네 개의 프로덕션 검색 인덱스에서 모든 제품 레코드를 덤프했습니다. HVAC, 배관, 산업용품, 패스너 분야의 네 B2B 유통업체에 걸쳐 775,051개의 레코드였습니다. 그리고 데이터 자체를 감사했습니다. 저는 병렬로 실행되는 12개의 AI 에이전트를 사용했고, 각 에이전트는 하나의 차원을 담당했습니다. 커버리지, 텍스트 품질, 부품 번호, 브랜드, 가격, 사양, 카테고리, 미디어, 중복 항목이었습니다.</p>\n<p>결과는 유통업체 카탈로그 작업을 해본 사람에게는 흔한 것이었고, 해보지 않은 사람에게는 충격적인 것이었습니다. 하지만 제 마음에 남은 부분은 이것입니다. 해결책은 이미 공개되어 있다는 것입니다. Amazon, DoorDash, Instacart는 공개 엔지니어링 블로그에서 LLM을 사용해 지저분한 카탈로그를 대규모로 정리, 라벨링, 보강하는 방법을 2년에 걸쳐 정확히 기록해 왔습니다. 산업용 유통업계는 대부분 이를 눈치채지 못했습니다. 이상한 일입니다. 왜냐하면 그들의 카탈로그가 더 지저분하고, 쿼리의 가치가 더 높으며(12달러짜리 점심이 아니라 4,000달러짜리 응축기를 주문하는 계약자), 카탈로그 크기 덕분에 경제성도 더 좋기 때문입니다.</p>\n<p>이 글이 바로 그 플레이북을 산업용 유통에 맞게 조정한 것입니다. B2B 검색을 망가뜨리는 데이터 문제, 오늘 오후에 코딩 에이전트로 이를 찾는 방법, UNSPSC 분류를 포함해 LLM으로 수정하는 방법, 그리고 전체 비용이 얼마인지를 다룹니다. 마지막 항목에 대한 스포일러: 짐작하는 것보다 두 자릿수 낮을 것입니다.</p>\n<h2>플레이북은 이미 존재한다, 단지 우리 업계에는 없을 뿐</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 내보내기, 공급업체 스프레드시트, 구매 그룹 피드, 그리고 누군가 10년 전에 작성한 CSV 파서로 꿰매어져 있습니다. 제품 \"제목\"은 창고 피커를 위해 작성된 송장 약어입니다. 그리고 이를 때리는 검색 트래픽은 검색 트래픽이 될 수 있는 만큼 의도가 밀집되어 있습니다. 절반은 정확한 부품 번호이고, 나머지는 \"3/4 cxc 90 ell\" 같은 산업 용어입니다. 솔직히, 이 플레이북을 위한 더 좋은 환경을 상상할 수 없습니다. 쿼리는 가치 있고, 데이터는 수정 가능하며, 수십만 개의 SKU 규모에서 카탈로그 전체에 대한 LLM 패스는 수백 달러가 듭니다. 수백만이 아니라 수백 달러입니다.</p>\n<p>감사 형태는 다음과 같습니다. 여러분도 재현하고 싶을 것이기 때문입니다:</p>\n<pre><code class=\"mermaid\">flowchart TD\n    D[\"Raw catalog dump&lt;br/&gt;(JSONL, one record per line)\"] --&gt; C1[\"Per-catalog coverage agents&lt;br/&gt;null rates, distributions, dead fields\"]\n    D --&gt; C2[\"Dimension agents&lt;br/&gt;text · part numbers · brands · pricing&lt;br/&gt;specs · categories · media · duplicates\"]\n    C1 --&gt; S[\"Synthesis: findings ranked by&lt;br/&gt;severity, with example records\"]\n    C2 --&gt; S\n    S --&gt; F[\"Fix pipeline: rules + LLM enrichment&lt;br/&gt;+ UNSPSC classification + ingest gates\"]\n</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<p><img src=\"/assets/images/b2b-search-silent-failures.png\" alt=\"기본 검색 필드 없이 인덱싱된 문서, 유통업체별\"></p>\n<p>전체 인덱스에서 52,570개의 레코드가 메인 쿼리 경로에 보이지 않는 상태로 인덱스에 방치되어 있었습니다. 가장 심한 카탈로그에서는 일곱 제품 중 하나였습니다. 아무것도 실패하지 않았기 때문에 어떤 대시보드도 이를 잡지 못했습니다. 파이프라인은 몇 달 동안 매일, 하루 종일 성공을 반환했습니다.</p>\n<p>같은 계열의 추가 발견 사항 두 가지. 첫째, 오래된 미러: 한 필드가 진실을 담고 있고(계정별 권한 맵), 두 번째 평탄화된 필드가 필터링을 위해 이를 미러링합니다. 한 카탈로그에서 미러가 19.85%의 레코드에서 드리프트가 발생했습니다. 31,000개의 활성 제품이 계정 필터링된 검색에서 잘못 숨겨진 것입니다. 둘째, 멈춘 카탈로그: 한 인덱스가 형제 인덱스들이 매일 업데이트되는 동안 10주 동안 새로고침되지 않았습니다. 가동 시간은 모니터링되었습니다. 신선도는 그렇지 않았습니다.</p>\n<p>해결책은 지루하고, 그것이 핵심입니다. 파생 필드 커버리지에 대해 알림을 설정하십시오 (소스 필드가 비어 있지 않은데 파생 필드가 비어 있는 경우). 하나의 사실에 대한 두 가지 표현을 유지하지 마십시오. 쓰기 시점에 필터링 가능한 필드를 파생하십시오. 가동 시간을 모니터링하는 것과 같은 방식으로 카탈로그별 데이터 신선도를 감시하십시오.</p>\n<p>다음은 자신의 카탈로그에서 이 모든 것을 찾기 위한 프롬프트입니다 (Claude Code 또는 <code>codex exec</code>; 작동 방식은 마지막에):</p>\n<pre><code class=\"text\">Here is a dump of my product catalog as JSONL files in ./dump/ - one JSON\nrecord per line, each with a unique \"id\" and the product fields. My field/\nschema definition (if any) is in schema.json.\n\nWrite streaming Python (don't load it all in memory) to find silent pipeline\nfailures:\n1. Records carrying any error/exception field a pipeline stamped on them.\n2. For every DERIVED field (concatenations, *_search, *_normalized variants):\n   records where the derived field is empty but its obvious source field\n   is not.\n3. Pairs of fields that look like mirrors of each other (one nested/rich,\n   one flat/filterable): quantify how often they disagree.\n4. The distribution of updated/indexed timestamps: is any slice of the\n   catalog frozen while the rest refreshes?\n\nReport each finding with counts, % of catalog, 5 example ids, and whether the\naffected records are active/visible. Rank by severity.\n</code></pre>\n<h2>카테고리 2: 자리 표시자와 센티넬, 또는: 거짓말하는 데이터</h2>\n<p>Null 검사는 모두가 이미 가지고 있는 데이터 품질 도구입니다. 자리 표시자는 이를 무력화시킵니다. 자리 표시자는 채워져 있기 때문입니다. 단지 사실이 아닐 뿐입니다.</p>\n<p>제가 가장 좋아하는 예: 313K SKU 카탈로그에서 브랜드 필드는 741개의 고유 값과 99.99%의 채우기율을 가지고 있었습니다. 건강해 보입니다. 모든 레코드의 88.8%에서 최상위 값은 \"Approved Vendor\"였습니다. ERP 자리 표시자입니다. 가장 인기 있는 실제 브랜드는 카탈로그의 0.3%를 차지했습니다. 따라서 모든 브랜드 패싯, 브랜드 부스트, 브랜드 인식 재작성은 카탈로그의 10분의 9에 대해 사실상 죽어 있었고, 패싯 UI는 \"Approved Vendor\"를 1위 브랜드 필터로 명랑하게 제공하고 있었습니다.</p>\n<p>이런 것들을 찾기 시작하면 어디에나 있습니다:</p>\n<ul>\n<li>한 카탈로그의 55%에서 <code>99999999.000000</code>이라는 \"가격 없음\" 센티넬. 해당 인덱스의 중위 가격이 9,900만 달러였습니다.</li>\n<li>64개 레코드에서 제품 이름이 문자 그대로 <code>\"unknown\"</code>이었고, 이후 \"unknown\" 쿼리에 대해 랭킹되었습니다.</li>\n<li>1,516개의 라이브 제품에서 텍스트 설명 필드에 boolean <code>true</code>가 작성되었습니다. 엔진이 이를 문자열 \"true\"로 강제 변환하여, \"true\"를 검색하면 해당 제품을 찾을 수 있게 만들었습니다.</li>\n<li>227개의 UPC가 <code>\"6.71E+11\"</code>로 저장되었습니다. Excel의 과학적 표기법으로, 51개의 무관한 제품이 하나의 \"식별자\"를 공유했습니다. 게다가 15,121개의 추가 UPC에서 선행 0이 누락되었습니다. Excel이 또 한번 피해를 입혔습니다.</li>\n<li>패스너 카탈로그의 브랜드 필드가 제품 라인 이름(\"C6L Lockbolts\", \"Tool Parts\")을 담고 있었고, 실제 브랜드는 한 필드 옆에 있었습니다.</li>\n</ul>\n<p>이 카테고리에서 얻은 교훈: 필드별 상위 N개 값 분포를 감사하십시오, null 비율이 아닙니다. 100% 채워진 필드가 88% 쓰레기일 수 있으며, 어떤 null 검사도 이를 알려주지 않을 것입니다. 그런 다음 수집 시점에 센티넬을 블록리스트에 추가하고, 명시적인 <code>has_price</code> / <code>has_real_image</code> 플래그로 실제 null에 매핑하고, 경계에서 타입을 단얱하고, 식별자를 구조적으로 검증하십시오. 스프레드시트를 통과한 적이 있는 모든 것을 의심하십시오.</p>\n<pre><code class=\"text\">Same JSONL dump. For every field in my catalog, compute the top-20 value\ndistribution (full pass, streaming).\n\nFlag: (a) any single value covering &gt;10% of records - placeholder/sentinel\ncandidates like \"Approved Vendor\", \"unknown\", 99999999, 0.0; (b) values whose\ntype differs from the field's dominant type (booleans in text fields, floats\namong strings); (c) identifier fields (UPC/EAN/GTIN/part numbers): validate\nchecksum and length, flag scientific notation, stripped leading zeros,\nembedded whitespace/unicode, and identifiers shared by multiple records;\n(d) category-like or product-line values sitting in brand fields.\n\nFor each flag: count, % of catalog, 5 verbatim examples with ids, and a\none-line proposed ingest rule that would have rejected it.\n</code></pre>\n<h2>카테고리 3: 빈약한 콘텐츠</h2>\n<p>유통업체 카탈로그는 머천다이저가 아닌 ERP가 작성합니다. 텍스트는 송장 약어입니다: <code>PROPRESS 2-1/2X1 CXC RED CPLG</code>, <code>HC HX 18X8 W</code>. 한 카탈로그에서는 제품 이름의 3분의 1이 60% 미만의 사전 단어를 가지고 있었습니다. 다른 카탈로그에서는 원시 이름 및 설명 필드가 100% null이었습니다. 텍스트는 파생된 검색 블롭 내부에만 존재했으므로, 원시 필드를 읽는 모든 것(결과 제목, 임베딩 입력, 관련성 판단 프롬프트)은 아무것도 읽지 않고 있었고 아무도 몰랐습니다.</p>\n<p><img src=\"/assets/images/b2b-search-field-coverage.png\" alt=\"네 개의 프로덕션 B2B 인덱스의 필드 커버리지\"></p>\n<p>그 히트맵은 감사에서 제가 가장 좋아하는 아티팩트입니다. 그 필드들은 모든 스키마에 존재합니다. 숫자는 각 필드가 실제로 데이터를 얼마나 담고 있는지를 보여줍니다. 제 마음을 끄는 행은 ML-descriptions입니다: 모든 레코드에 대해 스키마가 약속한 보강 계층은 775,051개 중 18개의 문서에서만 채워져 있었습니다. 18개입니다. 스코어링 파이프라인의 모든 \"보강이 있으면 사용\" 분기는 조용한 no-op이었습니다. 스키마는 열망입니다. 커버리지만이 사실입니다.</p>\n<p>이 카테고리는 공개된 플레이북이 가장 직접적으로 적용되는 곳입니다. 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\"은 분석기 수준의 동의어 확장에 속하며, 한 번 수정하면 됩니다. 50만 개의 레코드로 재작성하기 위해 비용을 지불하지 마십시오.</li>\n</ul>\n<pre><code class=\"text\">Same dump. Assess text quality of the customer-facing fields (name, short/long\ndescription) with a full streaming pass:\n\n1. Effective-title coverage: % of ACTIVE records with a non-empty display name.\n2. Readability: % ALL-CAPS names; % with under 60% dictionary-word tokens\n   (abbreviation salad); names under 10 chars; digits-only descriptions.\n3. Junk: HTML/CMS markup, encoding artifacts, embedded operational notes\n   (*** NOT A PHYSICAL ITEM ***), CSV column spillover.\n4. Duplication: identical short/long descriptions; boilerplate shared by 20+\n   SKUs; exact-length clusters (254/255/500 chars) indicating upstream\n   VARCHAR caps.\n5. Enrichment reality check: for every ML/enriched/embedding field the schema\n   promises, its actual coverage.\n\nNumbers, 5 verbatim examples each, severity, and which issues need an LLM\nenrichment pass vs. a pipeline fix vs. a synonym-layer fix.\n</code></pre>\n<h2>카테고리 4: 부품 번호는 신성하다</h2>\n<p>B2B 검색 트래픽의 절반은 누군가 부품 번호를 입력하는 것입니다. 정확한 매치는 그곳에서 전부이며, 조용한 방식으로 실패합니다.</p>\n<p>한 카탈로그에서 인덱싱된 부품 번호는 99.99%의 레코드에서 내부 숫자 SKU였습니다. 상자에 인쇄된 제조사 번호는 자유 텍스트 설명 내부에만 존재했습니다. 183,000개의 제품이 고객이 실제로 입력할 번호로 도달할 수 없었습니다.</p>\n<p>또 다른 카탈로그에는 변형 확장 시스템이 있었습니다. 대시를 제거하고, 세그먼트를 축소하고, 변형을 인덱싱하여 관대한 부품 번호 검색이 작동하게 합니다. 좋은 생각입니다. 하지만 생성기는 조합적이었고(제품당 최대 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 번호를 가지고 있는데, 당신 것은 무엇인가요?\")가 <code>19MU82|Fastenal 0269214|Ferguson M48222407</code>과 같이 검색 불가능하도록 구성된 필드에 분할되지 않은 파이프로 구분된 블롭으로 저장되어, 핵심 B2B 판매 동작이 작동하지 않습니다. 그리고 공백 포크 - 동일한 부품 번호가 세 번 인덱싱됨(일반, NBSP 패딩, 후행 공백) - 더블 디코딩된 UTF-8의 모지바케 트윈도 있습니다.</p>\n<p>이것의 대부분을 수정하는 원칙: 정확한 매치는 확률적으로가 아니라 구조적으로 퍼지를 이겨야 합니다. 구두점을 보존하는 정확한 절이 정규화된 계층 위에 엄격하게 점수 매겨지며, 변형이 정규 히트와 동점이 될 수 있는 평면 매치는 결코 없어야 합니다. 인덱싱 전에 생성된 변형을 정규 부품 번호 공간과 대조하고 충돌을 제거하십시오. 레코드 ID를 파생하기 전에 유니코드 정규화를 수행하십시오. 교차 참조 블롭을 배열로 분할하십시오. 이것은 전체 쿼리 클래스를 잠금 해제하는 단 한 줄의 수집 코드입니다.</p>\n<pre><code class=\"text\">Same dump. My users search by part number; audit exact-match integrity:\n\n1. Which PN-ish fields exist (part_number, mpn, alt/competitor/customer PNs,\n   UPC), their coverage, and which are configured unsearchable while holding\n   real data.\n2. Records whose only searchable PN token is an internal SKU (real\n   manufacturer PN appears only inside description text - show the pattern).\n3. If there's a PN-variant/keyword expansion field: find every generated\n   variant that equals a DIFFERENT record's canonical PN (normalize:\n   lowercase, strip punctuation). Show colliding pairs - especially\n   fraction-bearing fastener sizes.\n4. PNs differing only by whitespace/invisible chars/case from another\n   record's PN (duplicate identity forks).\n5. Multi-value cross-reference fields stored as delimited blobs instead of\n   arrays.\n\nCounts, examples, and for each issue the ingest rule or query-builder change\nthat fixes it.\n</code></pre>\n<p>위에 맞지 않지만 한 줄 정도 언급할 가치가 있는 몇 가지: 모든 숫자를 와이어 게이지 용어로 변환하는 동의어 생성기. <code>REF#259286</code>이 \"259286 awg\"가 되었고, <code>#8-32</code> 나사산이 \"8 gauge wire\"가 되었습니다 (수천 개의 레코드가 자신과 무관한 전기 쿼리에 매치됨). 전자상거래 플랫폼의 내부 플래그가 검색 가능한 제품 사양으로 인덱싱되어 210만 개의 쓰레기 키-값 쌍이 생겼습니다. 그리고 18개의 스키마 필드가 어느 레코드에서도 채워지지 않았습니다. 확장 생성기에는 컨텍스트 게이트가 필요합니다. 죽은 스키마는 누군가가 결국 구축하게 될 거짓 약속입니다.</p>\n<h2>누락된 계층: UNSPSC, 그리고 자신이 무엇인지 모르는 제품들</h2>\n<p>다섯 번째 문제가 있습니다. 이는 산업용 유통이 마켓플레이스에 가장 뒤처져 있는 부분이기 때문에 자체 섹션이 필요합니다. 분류입니다.</p>\n<p>제 감사에서 카테고리 필드는 한 카탈로그의 83%에서 누락되어 있었습니다. 속성 키에는 전혀 분류 체계가 없었습니다. 156K 레코드 카탈로그에서 7,068개의 고유 사양 키가 있었고, 그중 64%가 10개 미만의 제품에서만 사용되었으며, <code>horse_power</code>와 <code>horsepower</code> 같은 드리프트가 동일한 속성을 패싯 간에 분할하고 있었습니다. 그리고 제가 계속 돌아가는 세부 사항: 네 카탈로그 중 두 개는 UNSPSC 필드가 스키마에 있었고, 매핑되고 준비되어 있었지만, 정확히 0개의 레코드에 채워져 있었습니다. 누군가 분류가 중요하다는 것을 알고, 자리를 만들었지만, 결코 채우지 않은 것입니다. 이유에 대해 돈을 걸겠습니다: 30만 개의 SKU를 손으로 분류 체계에 넣는 것은 카탈로그 팀의 1년 치 작업이기 때문에, 영원히 로드맵에 남아 있었습니다.</p>\n<p>B2B 외부에 계시다면, UNSPSC는 UN 표준 제품 및 서비스 코드로, 조달 시스템이 사용하는 분류 체계입니다. 이것이 소비자 검색에 없는 부분입니다. 고객의 구매 시스템이 이를 요구합니다. Punchout 카탈로그, 전자 조달 플랫폼, 지출 분석 도구는 UNSPSC 코드를 중심으로 구축됩니다. 깨끗한 코드를 가진 유통업체의 카탈로그는 계약자나 병원의 구매 시스템에 연결될 수 있습니다. 없는 것은 검색 상자가 있는 PDF 가격표일 뿐입니다. 내부적으로도 이는 카테고리 패싯, 카테고리 범위 랭킹(\"와이어 게이지로 파싱되는 숫자는 와이어만 부스트해야 함\"), 교차 카탈로그 중복 제거, 카테고리 4의 모든 확장 생성기에 대한 컨텍스트 게이트를 구동합니다.</p>\n<p>그리고 이제 이는 배치 작업입니다. 이것은 DoorDash가 LLM으로 해결한다고 설명하는 것과 동일한 형태의 문제입니다. 통제된 어휘에 대한 대용량 라벨링이며, 접근 방식이 직접적으로 이전됩니다:</p>\n<ul>\n<li>평면이 아닌 계층적으로 분류하십시오. UNSPSC에는 4개의 수준(segment, family, class, commodity)이 있습니다. 모델이 ~450개 옵션에서 family를 선택하게 한 다음, 해당 family 내에서 class를 선택하게 하십시오. 두 번의 작은 제약된 선택이 50,000-way 선택 하나보다 낫고, DoorDash가 엔티티 연결을 어휘로 제한하는 것과 같은 방식으로 관련 분류 체계 조각을 컨텍스트에 제공할 수 있습니다.</li>\n<li>먼저 class 수준에서 멈추십시오. class 코드만으로도 패싯과 조달 통합이 가능해집니다. commodity 수준의 정밀도는 그 가치를 증명할 수 있는 곳에서 나중에 올 수 있습니다.</li>\n<li>신뢰도로 라우팅하십시오. 작은 모델이 명확한 사례를 처리하고, 저신뢰도 레코드는 더 큰 모델로 에스컬레이션되며, 지속적인 불일치는 평가 세트 역할도 하는 사람 대기열로 갑니다.</li>\n</ul>\n<p>비용은 입력하기가 거의 부끄러울 정도입니다. 분류는 짧은 출력 작업입니다. 레코드당 대략 300개의 입력 토큰과 30개의 출력 토큰입니다. Claude Haiku 4.5의 Batch API 가격으로 천 SKU당 약 17센트입니다. 전체 775K 레코드 플릿을 분류하는 데 약 $130이 들 것입니다. 1년 치 수동 작업이었기 때문에 로드맵에 수년간 방치되었던 것이, 논의할 팀 점심 비용보다 적게 듭니다.</p>\n<pre><code class=\"text\">You classify B2B distributor products into UNSPSC. Attached: the UNSPSC\nfamily list (level 2, ~450 entries) as reference data.\n\nFor each product record (title, description, brand, part number, any existing\ncategory text), output JSON:\n- unspsc_family: the 4-digit family code - choose ONLY from the attached list\n- family_confidence: high | low\n- rationale: one short phrase (e.g. \"copper press fitting -> pipe fittings\")\n\nRules: judge from the product's function, not its brand. Trade shorthand:\nCXC/FPT/MPT are pipe connections, ELL is elbow, CPLG is coupling, a bare\nfraction+material is usually a fitting size. If the record is not a physical\nproduct (freight lines, upcharges, \"*** NOT A PHYSICAL ITEM ***\"), output\nNOT_A_PRODUCT. Use low confidence liberally - low-confidence records get a\nsecond pass with the class-level list.\n</code></pre>\n<h2>실제 청구서와 함께하는 정리 플레이북</h2>\n<p>수정 파이프라인에는 3개의 계층과 분류 작업이 있습니다. 핵심은 올바른 계층에 지출하는 것입니다. 레코드당 LLM 호출이 거의 필요 없기 때문입니다.</p>\n<pre><code class=\"mermaid\">flowchart LR\n    A[\"Tier 0 - Rules&lt;br/&gt;deterministic code&lt;br/&gt;~$0\"] --&gt; B[\"Tier 1 - LLM on unique values&lt;br/&gt;brands · spec keys · units&lt;br/&gt;tens of dollars\"]\n    B --&gt; C[\"Tier 2 - LLM per record&lt;br/&gt;titles · attributes · UNSPSC&lt;br/&gt;hundreds of dollars\"]\n    C --&gt; G[\"Ingest gates&lt;br/&gt;every fix becomes a validator\"]\n</code></pre>\n<p><strong>Tier 0은 결정론적 코드이며 기본적으로 무료입니다.</strong> HTML을 제거하고 엔티티를 디코딩하십시오. 센티넬을 명시적 플래그로 실제 null로 변환하십시오. UPC를 복구하십시오(왼쪽 패딩, 과학적 표기법 거부, 체크 디지트 검증). ID를 유니코드 정규화하십시오. 파이프로 구분된 교차 참조를 분할하십시오. 다른 제품의 정규 번호와 충돌하는 부품 번호 변형을 제거하십시오. 죽은 스키마를 삭제하십시오. 에이전트가 세션에서 이 스크립트를 작성하며, 이 계층은 제 감사가 발견한 모든 것의 대략 절반을 수정했습니다. 보강 전에 이것을 하십시오. 그렇지 않으면 파이프라인이 조용히 삭제한 제품의 제목을 아름답게 재작성하기 위해 모델에게 비용을 지불하게 될 것입니다.</p>\n<p><strong>Tier 1은 레코드가 아닌 고유 값에 대해 LLM을 실행합니다.</strong> 이것이 어휘 문제를 저렴하게 만드는 트릭입니다. 제 최악의 카탈로그는 313K 레코드를 가지고 있었지만 고유한 브랜드 문자열은 741개뿐이었습니다. 브랜드 정규화는 313K 호출이 아닌 741개 문자열에 대한 하나의 작업입니다. 대소문자 변형을 클러스터링하고, 하위 브랜드를 상위 브랜드에 매핑하고, 자리 표시자를 플래그합니다. 사양 키와 단위 접미사에도 동일한 방법을 적용합니다. 각 어휘 작업은 한 자릿수 달러이며, 그 출력은 수집 시 영원히 결정론적으로 적용되는 정적 별칭 테이블입니다. 전체 계층을 $20-50로 책정하십시오. 주로 검토 패스에 소비됩니다.</p>\n<pre><code class=\"text\">Attached: the full distinct-value distribution of my brand and manufacturer\nfields (value, record_count). Produce a normalization table:\n\ncanonical_brand, parent_manufacturer, [raw variants], confidence\n\nRules: cluster case/punctuation/whitespace variants; map sub-brands to parent\nmanufacturers as a separate column (don't merge); mark placeholder values as\nNULL_SENTINEL; mark product lines or categories misfiled as brands as\nNOT_A_BRAND. Flag uncertain clusters rather than guessing. Output CSV, then\nwrite a script that applies it at ingest and logs unmatched new values.\n</code></pre>\n<p><strong>Tier 2는 레코드당 패스입니다</strong>: 모든 제품에 대해 고객이 읽을 수 있는 제목, 검색 확장, 구조화된 속성, 복구된 제조사 부품 번호. SKU당 작습니다. 약 400개의 입력 토큰(레코드, 프롬프트 캐싱으로 거의 무료가 되는 공유 지침)과 250개의 출력 토큰입니다. 모델을 선택하기 전에 두 가지 레버가 있습니다. 활성 항목만 보강합니다(제 최악의 카탈로그에서는 레코드의 14%만 활성이었으므로 즉시 86% 절감). 그리고 제공자가 있는 경우 배치 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<p><img src=\"/assets/images/b2b-search-enrichment-cost-by-model.png\" alt=\"모델별 775K SKU 보강 비용\"></p>\n<p>이 숫자들이 잘못된 것처럼 느껴져서 다시 확인해야 했습니다. 이 프로젝트의 최악의 경우 - 프론티어 모델, 모든 단일 SKU, 필터링 없음 - 최대 약 $3,800입니다. 하한선은 100달러 미만입니다. DeepSeek V4-Flash는 괜찮은 드릴 가격 정도로 전체 플릿을 보강할 것입니다. 실제로 실행할 버전은 계층을 혼합합니다. 기계적 다수를 위한 예산 모델(Haiku, Luna, Qwen Flash, DeepSeek), 작은 모델이 저신뢰도로 플래그한 약어 투성이 꼬리를 위한 중간 계층 모델(Sonnet, Terra, GLM-5.2), 그리고 품질 게이트로 1-2% 샘플을 스폿 체크하는 프론티어 모델. 선택하는 벤더와 관계없이 활성 SKU에 대해 몇백 달러에 도달합니다.</p>\n<p>차트가 보여줄 수 없는 두 가지 주의 사항. 저렴한 모델은 출력이 평가를 통과하는 경우에만 저렴합니다. 커밋하기 전에 200개 샘플 비교를 실행하십시오. 부품 번호의 5%를 망가뜨리는 예산 모델은 그렇지 않은 프론티어 모델보다 비용이 더 많이 듭니다. 그리고 카탈로그는 경쟁 데이터입니다. 가격, 교차 참조, 고객 부품 번호가 레코드에 있으므로, 775K 레코드를 목록에서 가장 저렴한 엔드포인트로 보내기 전에 각 제공자의 데이터 보존 및 학습 약관을 확인하십시오. 많은 유통업체에게 그 확인만으로 일부 벤더를 포함하거나 제외시킵니다. 오픈 웨이트 옵션(DeepSeek, GLM, Qwen, Kimi)은 데이터가 전혀 떠날 수 없는 경우 자체 호스팅할 수 있다는 추가 속성을 가지고 있습니다.</p>\n<p>~$130 UNSPSC 패스(모델 간 동일한 방식으로 확장됨 - 예산 계층에서는 주머니 잔돈으로 떨어짐)와 어휘 작업을 추가하면, 전체 카탈로그 변환이 머천다이징 팀의 1년 치 작업이었던 것에 대해 몇백 달러에서 몇천 달러 사이로 실행됩니다. 이는 Instacart가 설명하는 것과 동일한 오프라인 배치 경제학이지만, 숫자가 신용카드로 결제할 수 있을 만큼 작아지는 산업용 카탈로그 규모에서입니다.</p>\n<p>제 감사의 실제 레코드에서 어떻게 보이는지:</p>\n<pre><code class=\"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\n</code></pre>\n<p><code>extracted_mpn</code> 라인을 보십시오. 동일한 패스가 이전에 설명 텍스트에 묻혀 있던 제조사 부품 번호를 복구합니다. 제 카탈로그 중 하나에서는 183K 제품이 상자에 인쇄된 번호로 검색 가능해집니다. 이것만으로 배치 작업 비용을 충당할 수 있습니다.</p>\n<pre><code class=\"text\">You enrich B2B distributor product records for search. For each input record\n(raw ERP name, descriptions, brand, part number, category) produce JSON:\n\n- display_title: customer-readable, &lt;=80 chars, Title Case. Expand trade\n  abbreviations (CXC -&gt; copper x copper connection, ELL -&gt; elbow, CPLG -&gt;\n  coupling, RED -&gt; reducing). Keep every part number, model code, and\n  dimension VERBATIM as typed - never spell codes out in words.\n- search_expansions: 2-5 phrases a customer might type that don't appear in\n  the raw text (plain-English product type, common trade names). Never invent\n  specs absent from the source.\n- attributes: {name: {value, unit}} extracted ONLY from the source text.\n- extracted_mpn: manufacturer part number if present in the description text\n  (usually the token after the brand) and absent from the PN fields, else null.\n- confidence: high | low. Use low whenever you had to guess; a low answer\n  routed to review beats a confident hallucination.\n</code></pre>\n<p>두 가지 프로덕션 노트: 공유 지침을 캐시된 프롬프트 접두사에 넣으십시오(캐싱은 배치 할인에 더해 입력 측면을 최대 90%까지 줄입니다). 그리고 JSON 스키마와 함께 구조화된 출력을 사용하여 파싱 실패 비용을 지불하지 않도록 하십시오.</p>\n<p><strong>마지막 계층은 사람들이 건너뛰는 것이며, 복리 효과를 내는 것입니다.</strong> 감사는 한 번의 정리 가치가 있습니다. 그것이 생성하는 검증자는 모든 미래 피드에 가치가 있습니다. 수정 세션을 다음과 같이 종료하십시오:</p>\n<pre><code class=\"text\">Turn every issue we just fixed into an automated check that fails the ingest\npipeline loudly: derived-field coverage assertions, sentinel blocklists, type\nassertions on text fields, identifier checksum validation, PN-variant\ncollision checks, id hygiene rules, UNSPSC coverage tracking, and a data-\nfreshness alarm per catalog. Emit them as tests CI runs against a sample of\nevery new feed.\n</code></pre>\n<p>이를 건너뛰면 동일한 ERP 내보내기가 한 분기 내에 동일한 쓰레기를 재생성할 것이며, 정리 비용을 두 번 지불하게 될 것입니다. 제 감사가 발견한 버그 중 두 개는 분명히 이전에 수정되었었다가 다시 나타난 것들이었기 때문에 압니다.</p>\n<h2>프롬프트 실행하기</h2>\n<p>하나의 설정 단계: 카탈로그를 JSONL로 덤프하십시오. 고유한 <code>id</code>와 레코드 필드가 있는 한 줄에 하나의 JSON 객체. 플랫폼이 무엇이든 - Elasticsearch, OpenSearch, Solr, Algolia, Typesense, PIM, 그 뒤의 데이터베이스 - 에이전트에게 내보내기 작성을 요청하면 됩니다:</p>\n<pre><code class=\"text\">Write a script that exports every product record from my catalog [describe:\nsearch index name / database table / PIM export] to ./dump/records-{n}.jsonl\nin 50k-record chunks - one JSON object per line with a unique \"id\" plus all\nfields - and my field/schema definition (if the platform has one) to\nschema.json. Verify the exported count matches the source count.\n</code></pre>\n<p>Claude Code를 사용하면 해당 디렉토리의 세션에 카테고리 프롬프트를 드롭하십시오. 에이전트가 분석을 직접 작성하고 실행하며, 샘플링 변명 없이 전체 패스를 수행하고, 레코드 수준의 영수증과 함께 돌아옵니다. \"이제 수정해\"라고 말하면, 동일한 세션이 수집 검증자, 백필 스크립트, 배치 보강 작업을 생성합니다. 카테고리를 병렬 세션 또는 하위 에이전트로 실행하십시오. 저도 그렇게 했으며, 전체 감사는 실제 시간으로 약 12분이 걸렸습니다. Codex를 사용하면 <code>codex exec</code>가 동일한 프롬프트를 사용하여 읽기 전용 분석 패스를 처리합니다. 쓰기 측 수정은 검토하는 세션에 보관하십시오.</p>\n<p>어려운 방식으로 배운 세 가지 규칙:</p>\n<ol>\n<li>전체 패스와 영수증을 요구하십시오. \"이 데이터를 분석해\"는 샘플링과 감각에 초대합니다. 각 발견에 대해 정확한 카운트와 5개의 예제 레코드 ID를 요청하면, 모든 주장이 검증 가능해집니다.</li>\n<li>보강 전에 규칙을 적용하십시오. 산문을 추가하기 전에 진실을 수정하십시오. 그렇지 않으면 \"Approved Vendor\"로 라벨링된 88%의 레코드에 대해 브랜드를 환각하게 될 것입니다.</li>\n<li>샘플링, 평가, 그런 다음 배치. 검증되지 않은 프롬프트에서 775K 레코드 배치를 발사하지 마십시오. 200개 샘플, 스코어링 패스, 그런 다음 확장. 평가 하네스는 하나의 에이전트 세션 비용이 들며, 전체 지출의 위험을 줄입니다.</li>\n</ol>\n<h2>산업용 거래는 마켓플레이스 수준의 검색을 받을 자격이 있습니다</h2>\n<p>Amazon, DoorDash, Instacart는 자선을 위해 카탈로그-LLM 작업을 공개한 것이 아닙니다. 기술이 범용적이고 해자가 실행에 있기 때문에 공개한 것입니다. 그리고 그 기술은 제가 생각할 수 있는 거의 모든 곳보다 산업용 유통에 더 잘 이전됩니다. 카탈로그는 전체 패스가 저렴할 만큼 작고, 데이터는 개선 여력이 엄청날 만큼 나쁘며, 쿼리는 실제 돈을 담고 있고, 조달 세계는 이미 배치 작업이 약 $130에 채울 수 있는 분류 체계로 실행됩니다.</p>\n<p>제 감사가 네 개의 유통업체 카탈로그에서 수십 개의 문제를 발견했지만, 랭킹 계층에서 보이는 것은 하나도 없었습니다. 모두가 그것을 저하시켰습니다. 그 인덱스를 먹이는 공급망 - ERP 내보내기, 공급업체 스프레드시트, 오래된 CSV 파서 -이 바로 공개된 플레이북이 수정하는 것입니다. 이를 실행하는 유통업체는 경쟁자가 여전히 응축기 코일을 찾을 수 없는 카탈로그에서 마켓플레이스 수준의 검색을 갖게 될 것입니다.</p>\n<p>\"내 데이터에 문제가 있나?\"라는 질문은 예전에는 누군가의 2주 시간이 들었기 때문에 아무도 묻지 않았습니다. 이제는 하나의 프롬프트 비용이 듭니다. 물어보십시오.</p>",
  "source_hash": "sha256:729970b39c1b22c143ba55272659d6cc431b0f8889282089a4ea4eaa71f75000",
  "model": "z-ai/glm-5.2",
  "generated_at": "2026-08-06T09:52:00.962617+00:00"
}