Pipeline de enriquecimiento
Última actualización: 14 de abril de 2026 Audiencia: cualquiera depurando "¿qué hace exactamente Clione con mi producto?"
TL;DR
Clione transforma un producto en bruto (título, descripción, precio, vendor, imágenes) en una representación semántica multi-formato que:
- Permite que los LLMs (ChatGPT, Perplexity, Gemini) entiendan el producto con precisión
- Permite que los motores de búsqueda crawleen datos estructurados ricos (schema.org)
- Permite que la búsqueda vectorial recupere productos por intención (similitud semántica)
- Permite que los storefronts inyecten widgets de FAQ y señales JSON-LD automáticamente
Produce 6 categorías de artefactos a partir de una única llamada al LLM más una llamada al embedding:
| # | Artefacto | Dónde vive | Quién lo consume |
|---|---|---|---|
| 1 | Texto semántico (core_identity, reasoning) | Columna DB en product_embeddings | LLMs, búsqueda interna, dashboard |
| 2 | Quality scores (durability, quality_perception, value_for_money, price_positioning) | Columna DB synthetic_properties | Dashboard, scoring, ranking |
| 3 | Search keywords (array bilingüe) | Columna DB search_keywords | Búsqueda full-text, tag clouds |
| 4 | Vector embedding (array float de 1536 dims) | Columna pgvector | Búsqueda por similitud coseno |
| 5 | JSON-LD (schema.org/Product) | Generado on-the-fly en /products/:id.jsonld | Motores de búsqueda, crawlers de LLMs |
| 6 | Señales SEO meta (descripción, keywords, canonical) | Generado on-the-fly en /products/:id.meta | Herramientas SEO, storefronts headless |
Más a nivel de tenant: /.well-known/llms.txt (manifiesto del catálogo) y /.well-known/ai-offers.json (contexto de promociones).
Pipeline (de extremo a extremo)
┌─────────────────┐
│ Plataforma (Shopify, BigCommerce, etc.)
│ Envía producto en bruto:
│ título, descripción, precio, vendor, imágenes, categorías
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PASO 1 · Adapter normaliza la forma específica de la │
│ plataforma │
│ packages/platform-shopify/src/adapters/product.ts │
│ → RawProduct │
└────────┬────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PASO 2 · Enriquecimiento por LLM (Normalizer) │
│ packages/semantic-core/src/services/normalizer.ts │
│ Llama a llmClient.enrich() con el prompt en: │
│ apps/products-api/src/lib/promptTemplates.ts │
│ │
│ Modelo en producción: gpt-4o-mini │
│ Fallback en dev: Ollama (llama3.2:1b para el LLM, │
│ nomic-embed-text para el embedding) │
│ │
│ Devuelve: │
│ core_identity: "descripción semántica de 1-2 frases" │
│ reasoning: { recommended_for, decision_logic, │
│ objection_handler, seasonal_relevance } │
│ synthetic_properties: { durability_score, │
│ quality_perception, value_for_money, │
│ price_positioning, typical_competitors, │
│ search_keywords (bilingüe EN+ES) } │
└────────┬────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PASO 3 · Ensamblaje del texto del embedding │
│ packages/storage/src/embeddings/embedding-text.ts │
│ Concatena con separador " | ": │
│ core_identity + recommended_for + decision_logic │
│ + title + vendor + search_keywords │
└────────┬────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PASO 4 · Generación del vector embedding │
│ Producción: OpenAI text-embedding-3-small (1536 dims) │
│ Dev: Ollama nomic-embed-text (768 dims) │
│ Resultado: float[1536] L2-normalizado (listo para coseno)│
└────────┬────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PASO 5 · Persistencia + snapshot de versión │
│ packages/storage/src/vector/pgvector.ts │
│ Hace upsert en la tabla product_embeddings (una fila) │
│ Hace snapshot de la versión anterior en │
│ product_enrichment_versions (se conservan las 3 últimas)│
└─────────────────────────────────────────────────────────┘
Esquema de almacenamiento
product_embeddings
| Columna | Tipo | Contenido |
|---|---|---|
id | TEXT | ID de producto en la plataforma |
tenant_id | TEXT | aislamiento multi-tenant |
tenant_platform_id | TEXT | aislamiento por tienda |
platform | TEXT | shopify / bigcommerce / etc. |
core_identity | TEXT | descripción semántica generada por LLM |
reasoning | JSONB | { recommended_for, decision_logic, objection_handler, seasonal_relevance } |
synthetic_properties | JSONB | { durability_score, quality_perception, value_for_money, price_positioning, typical_competitors, search_keywords } |
technical_details | JSONB | { original_title, vendor, price, currency, images } |
metadata | JSONB | { transformed_at, llm_model, transformation_version, manually_edited?, last_manual_edit_at?, enrichment_pending? } |
search_keywords | TEXT[] | array desnormalizado para búsqueda FT |
embedding | vector(1536) | el vector real, normalizado para coseno |
product_enrichment_versions (versionado, últimas 3 conservadas por producto)
Snapshot de core_identity + reasoning + synthetic_properties + search_keywords + metadata más version, reason ("manual re-enrich" / "bulk re-enrich pending" / "rollback to vN" / "initial sync"), created_by, created_at. Lo usa la pestaña History + el endpoint de rollback.
Endpoints que exponen los artefactos
Por producto
| Endpoint | Content-Type | Propósito |
|---|---|---|
GET /api/{platform}/products/:id | application/json | Payload SemanticProduct completo (todo lo de arriba) |
GET /api/{platform}/products/:id.llm | application/json (X-Content-Format: llm-optimized) | JSON optimizado para LLM: identidad, reasoning, scores, market context, texto computado summary_for_ai |
GET /api/{platform}/products/:id.jsonld | application/ld+json | schema.org/Product con additionalProperty[] cargando los scores como PropertyValue |
GET /api/{platform}/products/:id.meta | application/json | Señales SEO: meta_description (160/300/full), URL canonical, keywords[] |
Nivel tenant / dominio
| Endpoint | Formato | Propósito |
|---|---|---|
GET /.well-known/llms.txt | text/plain (Markdown según spec llms.txt) | Manifiesto del catálogo: nombre de la tienda, número de productos, resumen de enriquecimiento, endpoints de API, guía de uso |
GET /.well-known/ai-offers.json | application/json | Contexto de promociones (gestionado por promotions-engine) |
Embed de storefront
embed.js es un loader de un solo fichero servido en /embed.js que:
- Auto-detecta tipo/ID de entidad a partir de patrones de URL + meta tags OpenGraph
- Recupera datos de FAQ vía
/api/v1/public/faq/render - Inyecta (a) acordeón HTML visible (b) schema JSON-LD
FAQPageinvisible
Validado por origin contra la lista blanca allowed_domains de la API key.
Quality Scores — limitaciones importantes
Lee esto antes de mostrarle scores a un cliente.
Los 5 campos de calidad (durability_score, quality_perception, value_for_money, price_positioning, typical_competitors) son hoy 100% inferidos por el LLM.
Qué usa el LLM
- Título
- Descripción
- Precio + moneda
- Vendor
- Categorías
enrichment_hintsdel Wizard si están rellenas
Qué NO usa el LLM
- Reseñas reales (Google / Trustpilot / internas)
- Tasa de devoluciones
- Datos de ventas / tasa de conversión
- Comparativas externas de catálogos
- Certificaciones de fabricante (ISO, MIL-STD, GOTS, etc.)
- Resultados de tests de ciclo de vida
- Bases de datos de sostenibilidad
Implicaciones
- Los scores son opinión sintética, no hechos verificados.
- El mismo producto puede dar scores distintos entre runs si la temperatura del modelo no es cero.
- Útiles relativamente: el LLM tiene sentido común (Hermès → lujo, Primark → budget), así que los scores ayudan a diferenciar dentro del mismo catálogo, no contra benchmarks externos.
typical_competitorstiene el mismo problema — son inferencias plausibles, no datos de mercado verificados.
Grounding — capas shippeadas
- Badge de disclaimer visual en cada bloque de Quality Scores, resuelto desde
metadata.scores_evidence_level: 🟢 Data-grounded · 🔵 AI + owner hints · 🟡 AI-inferred - Enrichment Wizard tiene una sección Evidence dedicada con 5 preguntas (
warranty_period,certifications,returns_rate,reviews_rating,manufacturing_origin). Rellenar 3+ sube el badge a verde; 1-2 a cyan. La lógica vive enlib/evidence-grounding.tsy aplica tanto a los endpoints de enrich de Shopify como de BigCommerce. - Typical Competitors proporcionados por el owner estructurados como
[{ brand, model, ref_price?, notes? }]vía el Edit tab del producto. Cuando hay al menos una fila guardada,metadata.competitors_source = 'owner'y el LLM deja de regenerar la lista en re-enrichment (lib/preserve-owner-data.ts).
Roadmap pendiente
Conectores de datos externos — webhooks de Returns rate, APIs de reviews Judge.me + Yotpo, GA conversion rate — y el schema de procedencia per-score (synthetic_properties.scores_provenance) con resolución compuesta. Diseño completo en docs/SCORES_GROUNDING_ROADMAP.md (interno).
Hasta que lleguen los conectores externos, asume que los scores reflejan la lectura del LLM del texto del catálogo afinada por la evidencia + competidores que pasaste vía el wizard — no es ground truth, pero es mejor que síntesis pura.
Aviso sobre cambio de modelo de embedding
La columna embedding está fijada como vector(1536) para coincidir con text-embedding-3-small. Cambiar a un modelo de embedding distinto con otra dimensión (p. ej. nomic-embed-text de Ollama, 768 dims) requiere re-indexar todo — habría que cambiar el tipo de columna y regenerar cada embedding existente. No es un toggle en runtime.
Cuándo re-enriquecer
| Escenario | Acción |
|---|---|
| Sincronización inicial desde la plataforma | Auto-enriquecido como parte del import |
| El título o la descripción del catálogo cambia en origen | El re-sync dispara re-enriquecimiento |
| El owner edita campos de IA manualmente en el dashboard | Se guarda con metadata.manually_edited = true. Los re-enriquecimientos futuros muestran una confirmación extra "OVERWRITE" para evitar perder el trabajo manual |
| El owner lanza el Enrichment Wizard con contexto nuevo | Re-enriquecimiento manual con las hints embebidas en el prompt |
| Upgrade del modelo LLM (p. ej. gpt-4o-mini → siguiente versión) | Re-enriquecimiento masivo recomendado; las versiones viejas se conservan durante 3 generaciones vía pestaña History |
El re-enriquecimiento es destructivo pero versionado: la salida anterior se snapshotea en product_enrichment_versions y se puede restaurar desde la pestaña History. Retención: las últimas 3 versiones por producto.
Por qué importa
Todo el diseño asume que el agent commerce es el próximo canal de descubrimiento. ChatGPT compra en Shopify, Perplexity recomienda, Gemini compara. Las tiendas cuyos catálogos se leen claramente para los LLMs ganan; las que no, son invisibles.
El trabajo de Clione es hacer que cada producto sea legible para esos agentes en cada formato que prefieran — sin que el merchant tenga que pensar en ello.
Por eso generamos 6 artefactos, no solo un vector.