Every LLM-generated catalog claim gets a confidence score before it reaches a customer.
- Get link
- X
- Other Apps
Every LLM-generated catalog claim gets a confidence score before it reaches a customer.
An autonomous fact-checking layer that validates LLM-generated product descriptions, attributes, and personalization insights against a structured knowledge graph — catching bad pricing, mismatched attributes, and broken personalization before publication.
LLMs write fluent catalog copy. Fluent isn't the same as correct.
Once LLMs started generating product descriptions, attributes, and even personalization insights at catalog scale, a new failure mode showed up that traditional QA wasn't built for: content that reads perfectly, passes spell-check, sounds on-brand — and is simply wrong. Not wrong in an obvious way. Wrong in a way that only shows up once it's live: a material tag that doesn't match the product's actual category, a price that's plausible-sounding but three standard deviations off the category norm, a "recommended for" tag built on a pattern that doesn't hold.
The problem isn't that the LLM is unreliable in some abstract sense — it's that nothing was checking its work against ground truth. Traditional catalog QA assumes a human typed the entry and made a typo. It isn't built to catch a model confidently inventing an attribute combination that's internally consistent but factually disconnected from the product it describes. That gap is what this pipeline exists to close.
Silent price drift
An LLM-suggested price that "sounds right" for the copy but ignores the category's real distribution — reaching checkout before anyone notices.
Mismatched specs
A material, size, or compatibility claim that doesn't hold against the taxonomy — eroding customer trust and driving returns.
Confident but ungrounded
A "recommended for" or affinity claim generated from a plausible-sounding pattern rather than actual customer-behavior data.
The naive fix — have a human review every LLM output — doesn't scale to catalog volume, and a purely rule-based QA layer only catches what someone already thought to write a rule for. What's needed is something that sits between the two: automated enough to run at catalog scale, but grounded in the same structured knowledge (taxonomy, pricing history, entity relationships) a careful human reviewer would actually check against, and honest about its own uncertainty so people only get pulled in where it matters.
That's the motivation for grounding every claim in a knowledge graph rather than trusting the LLM's self-reported confidence or a second LLM's opinion of the first. A knowledge graph encodes the retailer's actual taxonomy, historical pricing, and entity relationships — it doesn't hallucinate, and it can be queried deterministically. Pairing that with graph embeddings covers the cases no one thought to write an explicit rule for. The result is a confidence score that's traceable: every flag can be explained back to a specific rule or a specific structural inconsistency, which is what makes the review team trust it enough to actually act on it.
Pipeline architecture
Every claim is normalized, matched against the knowledge graph from two angles — rule-based and embedding-based — then scored and routed.
The architecture is deliberately built around one idea: no single validation method catches everything. Explicit rules (SPARQL) are precise but only as good as the rules someone remembered to write. Embeddings catch the unwritten patterns but can't explain themselves as clearly. Running both in parallel and fusing their output is what makes the confidence score both comprehensive and explainable — comprehensive because embeddings cover the long tail, explainable because rule failures can be named.
Schema matching & entity resolution
Maps free-text LLM attributes to canonical taxonomy fields and resolves referenced entities to KG nodes using deterministic + embedding-based similarity.
Dual validation
SPARQL rules catch explicit taxonomy/pricing violations; graph embeddings catch statistically implausible combinations no rule explicitly forbids.
Confidence fusion & routing
Weighted signals produce one interpretable score per claim, routed to auto-publish, human review, or hard block based on tuned thresholds.
How entities get identified, recognized, and matched to the graph
Before any rule can run, the pipeline has to turn a claim's free-text fields into the exact (head, relation, tail) shape the knowledge graph uses. That split into two very different problems — one is deterministic, the other isn't.
Relation identification — deterministic
The LLM claim is generated as structured output against a fixed schema, not free prose, so each field name maps 1:1 to a predefined KG relation via a maintained lookup: material → schema:hasMaterial, recommended_for → schema:hasAffinitySegment, price → schema:hasListPrice. No fuzzy matching needed — the relation is fixed at generation time by which field the value showed up in.
Entity identification — not deterministic
The value in that field ("merino wool", "hosiery", "outdoor enthusiasts") is free text and has to be resolved to one specific node — the tail — already sitting in the taxonomy. This is standard entity linking: recognize the mention, generate candidates, disambiguate, and either bind the winner or flag the claim as unresolved.
Concretely, entity linking runs in four steps:
| Step | What happens |
|---|---|
| 1 · Normalize | Lowercase, strip punctuation and pluralization, expand known abbreviations — "Merino Wool Socks" → "merino wool". |
| 2 · Candidate generation (blocking) | Rather than compare against every node in the graph, restrict candidates to nodes typed for the expected class — if the relation is hasMaterial, only consider KG nodes of type schema:Material — then retrieve the top-k nearest by sentence-transformers embedding similarity between the mention and each candidate's label/aliases, with a rapidfuzz string-similarity pass as a cheap pre-filter. |
| 3 · Disambiguation & scoring | Combine embedding similarity, string similarity, and the type constraint into one linking score per candidate (a weighted sum, tuned on the labeled ground-truth set). The highest-scoring candidate wins if it clears a resolution threshold. |
| 4 · Outcome | Resolved — score clears the threshold with a clear margin over the runner-up; bound straight into the SPARQL query and the embedding scorer as the tail. Ambiguous — top candidates are too close together; routed to review. Unresolved — nothing clears the threshold; treated as an automatic soft-fail, since a claim referencing an entity that doesn't exist anywhere in the taxonomy is itself a signal something's off. |
Claim field material: "merino wool", relation fixed as schema:hasMaterial from the field name. Candidate generation restricts to schema:Material nodes and returns:
| Candidate node | Embedding sim. | String sim. | Linking score |
|---|---|---|---|
| Merino_Wool | 0.96 | 0.91 | 0.94 — resolved |
| Wool | 0.78 | 0.55 | 0.71 |
| Cashmere | 0.41 | 0.20 | 0.34 |
0.94 clears the 0.85 resolution threshold with a wide margin over the runner-up (0.71), so Merino_Wool is bound as the tail — the same node the SPARQL rule below and the embedding scorer both check against. Had "merino wool" instead come back as, say, 0.58 against every candidate, the claim would be flagged as referencing an unresolved material before any rule even runs.
How the SPARQL rules actually get built
Each rule is written as a SPARQL ASK query — a query that returns true/false rather than a result set — encoding one specific business constraint against the knowledge graph. The claim's fields (category, material, price, etc.) are bound into the query as parameters at request time, using the relation and the resolved entity from the linking step above, and the query answers "does this claim's data fit what the graph says is valid?"
# Rule PRC-014 — material must be a recognized option for the claim's category
ASK {
?category a schema:ProductCategory ;
schema:categoryId "hosiery" .
?category schema:validMaterial ?material .
FILTER(?material = "merino_wool")
}
# Bound at request time from the incoming claim: category → "hosiery", material → "merino_wool"
# Returns false → rule fails → contributes a "soft fail" signal to the confidence score
Building the rule library is a business-logic authoring exercise as much as an engineering one: a domain expert (merchandising, pricing ops) describes a constraint in plain language — "ceramics should never list below $20" — and it gets translated into a graph pattern against the taxonomy and pricing nodes already in the KG. Each rule is versioned as its own .sparql file, tagged with a severity weight (hard block vs. soft flag), and — critically — back-tested against the labeled ground-truth set before it's allowed into the production rule library, so a badly-written rule can't silently start flagging good claims. At request time, the rule engine (built on rdflib / SPARQLWrapper) loops over the relevant rules for a claim's type, executes each against the live KG endpoint, and aggregates the pass/fail results with their severity weights into the rule component of the confidence score.
How the graph embeddings are trained and used
Embeddings exist to catch the cases no one wrote a rule for — statistically implausible combinations that are structurally valid but don't resemble anything the KG has seen before. Getting there is a four-step process:
1. Extract triples
The KG is flattened into (head, relation, tail) triples — e.g. (SKU_4021, hasMaterial, Merino_Wool), (Merino_Wool, partOf, Category_Hosiery) — the raw training data for the embedding model.
2. Choose the model
TransE for fast, interpretable relations (good default); ComplEx when relations aren't symmetric (e.g. "cheaper_than"); an R-GCN/GNN variant if node features beyond graph structure should matter.
3. Train with negative sampling
For every true triple, corrupt the head or tail to create a false one (e.g. swap the material to something implausible), then train with a margin-ranking loss so true triples score higher than corrupted ones.
4. Evaluate & score
Held-out true triples are ranked with metrics like Mean Reciprocal Rank and Hits@10. In production, a new claim's implied triple is scored the same way — ‖h + r − t‖ for TransE — and compared against the score distribution for valid triples in that category to produce a plausibility score.
The core idea worth unpacking is TransE, the default model: it represents every entity and relation as a vector in the same low-dimensional space (typically 100–200 dimensions in production; illustrated here in 3 for readability), trained so that for a true triple, adding the head vector to the relation vector lands close to the tail vector — h + r ≈ t. A false or implausible triple won't land close to any real tail, so the distance ‖h + r − t‖ is small for things the graph considers true and large for things it doesn't.
Say an LLM claim reads: "Cast iron skillet — dishwasher safe, oven safe to 500°F." Schema matching and entity resolution turn this into two candidate triples to score: (SKU_71029, hasMaterial, Cast_Iron) and (SKU_71029, hasCareInstruction, Dishwasher_Safe).
| Triple | h + r (illustrative) | Nearest tail | ‖h+r−t‖ | Read |
|---|---|---|---|---|
| hasMaterial → Cast_Iron | [0.17, −0.14, 0.79] | Cast_Iron [0.15, −0.13, 0.77] | 0.03 | Typical for true triples in this category (~0.02–0.06) — plausible. |
| hasCareInstruction → Dishwasher_Safe | [0.53, −0.42, 0.66] | Dishwasher_Safe [0.10, 0.51, −0.30] | 1.24 | Far outside the range for true hasCareInstruction triples in cookware (~0.10–0.20) — implausible. |
The raw distance is then converted into something the confidence model can actually use: it's compared against the distribution of ‖h+r−t‖ scores for every known-true hasCareInstruction triple restricted to the cookware category. A distance sitting at, say, the 97th percentile of that distribution triggers a "soft fail" contribution to the confidence score — functionally similar to a SPARQL rule failing, except no one ever had to write a rule saying "cast iron generally isn't dishwasher-safe." That's the coverage embeddings add: the model learned it from the graph's structure, not from an explicit constraint.
A bit more on the mechanics behind each step:
- Negative sampling ratio — for every true triple, several corrupted versions are generated (commonly 1 true : 5–10 corrupted), swapping either the head or the tail for a random entity of the same type. This keeps the negatives "hard" — a corrupted
hasMaterialtriple still points to some other material, not a nonsense entity, which forces the model to learn real distinctions rather than trivial ones. - Margin-ranking loss — training pushes the true triple's score at least a fixed margin below every corrupted triple's score (lower distance = more plausible for TransE), rather than driving the distance to zero, which keeps the embedding space well-structured instead of collapsing.
- Cohort-relative thresholds — a "large" distance means something different for a common relation like
hasMaterialversus a sparser one likehasCareInstruction, so thresholds are set per relation-and-category cohort against the training distribution, not as one global cutoff.
A rule only exists if someone thought to write it — it can't catch a novel bad combination it was never told about. An embedding model generalizes to novel combinations but can't tell you which business constraint was violated, only that the combination looks statistically off. Fusing both gives you rule-level explainability where rules exist, and embedding-level coverage everywhere else — which is also why the confidence score is built as a weighted combination rather than picking one method and discarding the other.
Retraining cadence: the embedding model is retrained on a schedule (weekly/monthly, via Vertex AI Pipelines) as the KG grows and new categories/products are added — a stale embedding space is the most common cause of a rising false-positive rate over time.
Deployment on Google Cloud
Serverless where possible, managed graph/ML services where it reduces ops burden — sized to scale from batch catalog sweeps to real-time inline validation.
Two very different traffic patterns have to be served by the same system: a nightly (or continuous) sweep across the whole catalog, and a low-latency inline check when a merchandiser or automated feed publishes a single new listing. Rather than build two separate systems, the architecture uses one event stream (Pub/Sub) that feeds both a batch path (Dataflow) and a synchronous path (Cloud Run), so the validation logic itself — the rules, the embeddings, the scoring model — stays identical regardless of which door the claim came through. That consistency is what prevents the classic split-brain problem of "it passed the nightly sweep but failed the real-time check" or vice versa.
Service selection notes
| Concern | GCP service | Why |
|---|---|---|
| Knowledge graph store | Spanner Graph (or Neo4j AuraDB / self-managed on GKE) | Native graph queries; Spanner Graph if you want a fully-managed GCP-native option, Neo4j if SPARQL/RDF tooling maturity matters more than managed ops. |
| Event trigger | Pub/Sub | Decouples "new LLM content produced" from "validation runs" — supports both the real-time and batch paths off one event stream. |
| Batch validation | Dataflow (Apache Beam) | Autoscaled batch/streaming for nightly catalog sweeps and KG re-sync jobs. |
| Sync validation API | Cloud Run | Serverless container hosting for FastAPI; scales to zero, fits bursty inline-gating traffic. |
| Embedding / scoring models | Vertex AI Prediction + Pipelines | Managed model serving and retraining pipelines with built-in versioning and monitoring. |
| Review-queue state | Firestore | Low-latency document store for the human-in-the-loop workflow UI. |
| Reporting warehouse | BigQuery | Feeds Power BI directly via its native BigQuery connector; also backs ad-hoc KPI analysis. |
| CI/CD | Cloud Build + Artifact Registry + Terraform | Independent versioned deploys for the API, SPARQL rule library, and ML models. |
| Security | IAM + VPC Service Controls | Service-to-service auth for internal-only endpoints; perimeter around KG and model data. |
Because pricing anomaly detection, embedding inference, and SPARQL rule checks have very different resource profiles — one is CPU-bound and bursty, one benefits from GPU/accelerator batching, one is a graph traversal. Separating them into lanes means each can scale independently and fail independently: a slow embedding retrain job in Vertex AI Pipelines should never be able to stall the sync validation endpoint a merchandiser is waiting on.
Project structure
Kept in two clear zones: experimentation (notebooks, model iteration, offline eval — fast and disposable) and deployment (versioned services, infra-as-code, CI/CD — stable and reviewed).
This split exists because the two zones have fundamentally different tolerances for risk and iteration speed. A data scientist trying five embedding architectures in an afternoon should never be blocked by a PR review — and a SPARQL rule that gates whether a $2,000 product publishes correctly should never merge without one. Keeping them physically separate (not just logically) makes that boundary impossible to accidentally cross: nothing in experimentation/ has a deploy path. It has to be deliberately promoted, reviewed, and versioned into src/ first.
Experimental zone — experimentation/
Fast-moving, disposable work. Nothing under this root has a deploy path — every file here exists to answer a question, not to ship.
| Path | Purpose |
|---|---|
| experimentation/ | Root of the fast-iteration zone; loosely pinned dependencies, no CI/CD gate, no deploy path. |
| notebooks/ | Exploratory analysis — the first place a new question gets investigated. |
| notebooks/kg_audit.ipynb | Phase 0 knowledge-graph coverage and quality audit that scopes what the whole pipeline can validate. |
| notebooks/ground_truth_labeling.ipynb | Interactive labeling workflow for building the ~500–1000 example ground-truth set. |
| notebooks/error_analysis.ipynb | Post-hoc review of scoring-model misses, used to steer the next iteration. |
| kg_audit/ | Scripted, repeatable Phase 0 coverage and quality checks against the knowledge graph. |
| model_training/embeddings/ | TransE / ComplEx / GNN architecture experiments for the graph-embedding model. |
| model_training/confidence_scoring/ | Feature engineering and model iteration for the confidence-fusion model. |
| model_training/pricing_anomaly/ | IsolationForest / distribution-based experiments for the pricing anomaly detector. |
| eval/ground_truth_v1.jsonl | Versioned labeled evaluation set used to score every candidate model. |
| eval/metrics_report.ipynb | Precision, recall, and false-positive-rate analysis against ground truth. |
| sandbox_api/ | Throwaway FastAPI app for demoing scores to stakeholders before any build-out begins. |
| requirements-dev.txt | Loosely pinned dependencies scoped to this zone only — never promoted as-is. |
| README.md | Running notes on current experiments, dead ends, and open questions. |
Deployment zone — src/, infra/
Versioned, reviewed, and CI/CD-gated. Everything here arrived through a promotion from the experimental zone — nothing is written here first.
| Path | Purpose |
|---|---|
| src/schema_matching/ | Maps free-text LLM attributes to canonical taxonomy fields. |
| src/entity_resolution/ | Resolves claim-referenced entities to knowledge-graph nodes. |
| src/sparql_rules/rules/ | Versioned .sparql rule files — reviewed and merged like application code. |
| src/sparql_rules/engine.py | Executes the rule library against the knowledge graph at request time. |
| src/embeddings/model.py | Production graph-embedding model definition, promoted from model_training/embeddings/. |
| src/embeddings/inference.py | Serving-time embedding inference used by the sync and batch paths. |
| src/confidence_scoring/model.py | Production confidence-fusion model combining rule and embedding signals. |
| src/confidence_scoring/thresholds.yaml | Tuned auto-publish / review-queue / hard-block routing thresholds. |
| src/pricing_anomaly/ | Production pricing anomaly detector — category-level distribution checks. |
| src/api/main.py | FastAPI application entrypoint. |
| src/api/routers/validate.py | Synchronous claim-validation endpoint used by the inline gating path. |
| src/api/routers/claims.py | Review-queue claim CRUD endpoints backing the human-in-the-loop console. |
| src/api/schemas/ | Pydantic request/response models shared across routers. |
| src/pipelines/ | Dataflow / Apache Beam batch jobs for nightly catalog sweeps. |
| src/config/ | Environment-specific service configuration. |
| infra/modules/ | Reusable Terraform modules — one per GCP service (Cloud Run, Pub/Sub, BigQuery, Vertex AI). |
| infra/envs/dev · staging · prod | Environment-specific Terraform variable sets for each deploy target. |
| dashboards/ | Power BI definitions and BigQuery reporting views. |
| tests/ | Unit, integration, and data-quality test suites. |
| .github/workflows/ or cloudbuild.yaml | CI/CD pipeline definitions gating every promotion into this zone. |
| Dockerfile · README.md | Container build definition and service-level documentation. |
Model artifacts and rule versions promote from experimentation/ into src/ only through a reviewed PR — nothing in the experimental zone deploys directly.
Review queue interface
Claims that land in the mid-confidence band route here. Reviewer decisions feed back into the training set for the scoring model.
The interface exists to solve a specific trust problem: a confidence score by itself is a black box, and reviewers won't act quickly — or correctly — on a number they can't interrogate. So every flagged claim surfaces why it was flagged, in the same language a domain expert would use: which rule failed, which comparison looked implausible. That's also what makes the review step valuable beyond just gatekeeping this one claim — every approve/reject decision becomes a labeled example that improves the next version of the confidence model, which is the mechanism that lets the false-positive rate keep dropping after launch instead of plateauing.
- Material "merino_wool" not in top-5 KG neighbors for this category
- SPARQL rule PRC-014 (material/price band) — soft fail
- Price 3.5ฯ below category distribution
- SPARQL rule PRC-002 (price floor by category) — hard fail
- All checks passed — routed here only for spot-audit sampling
Confidence breakdown
Session KPIs
Static mockup for scoping the build — the production version wires Approve / Reject / Edit to the Firestore-backed review-queue service and writes the decision back as a labeled training example.
KPI dashboards
Shipped in Power BI against a BigQuery reporting layer; definitions iterated with business stakeholders to keep the false-positive rate trending down without masking real errors.
Dashboards here aren't a vanity layer bolted on at the end — they're the mechanism that keeps the whole system honest after launch. A model that flags too much gets ignored by reviewers; a model that flags too little lets errors through silently. Both failure modes look fine in isolation and only show up in trend data over weeks, which is exactly what these three views are built to surface: what kind of claims are actually flowing through, whether the pricing-error reduction promised in Phase 0 is materializing, and whether the false-positive rate is actually trending down as the model retrains on reviewer feedback — or quietly plateauing, which would be the signal to revisit thresholds with stakeholders.
Claims validated by type
Pricing errors reaching production
False-positive rate trend
Stack & libraries
| Layer | Tooling | Purpose |
|---|---|---|
| Knowledge graph | Neo4j / Spanner Graph, rdflib, SPARQLWrapper | Graph storage + SPARQL rule execution |
| Schema matching | sentence-transformers, rapidfuzz, pandas | Semantic + string similarity for attribute mapping |
| Entity resolution | dedupe / Splink, sentence-transformers | Blocking + candidate scoring against KG entities |
| Graph embeddings | PyTorch, PyTorch Geometric, DGL-KE (TransE / ComplEx) | Learn structural plausibility beyond explicit rules |
| Confidence scoring | scikit-learn, XGBoost, SHAP | Signal fusion + explainability for reviewer trust |
| Pricing anomaly detection | scikit-learn (IsolationForest), statsmodels | Category-level price distribution + outlier checks |
| Service layer | FastAPI, Pydantic v2, Uvicorn/Gunicorn | Sync + batch validation endpoints |
| Async/batch | Apache Beam (Dataflow runner), Celery/Cloud Tasks | Catalog sweeps, background job orchestration |
| MLOps | Vertex AI Pipelines, MLflow (experiment tracking), Vertex Model Registry | Versioned retraining + deployment of embeddings/scorer |
| Infra as code | Terraform, Cloud Build, Artifact Registry | Reproducible environments, CI/CD |
| Storage & analytics | BigQuery, Cloud Storage, Firestore | Reporting warehouse, artifact storage, review-queue state |
| Dashboards | Power BI (BigQuery connector), optionally Looker Studio | KPI tracking, stakeholder-facing reporting |
| Testing & quality | pytest, great_expectations, locust (load testing) | Unit/integration tests, data quality checks, API load tests |
| Observability | Cloud Monitoring, Cloud Logging, OpenTelemetry | Latency, drift, and business-outcome monitoring |
Phased delivery plan
The phasing follows the risk, not the calendar. Foundation comes first because everything downstream — how good the schema matching can be, how meaningful the confidence scores are — is capped by how complete the knowledge graph actually is; skipping that audit just moves the discovery of gaps to a much more expensive point later. Production is deliberately the longest single stretch of validation (shadow mode before enforcement) because the cost of a wrongly-blocked listing or a wrongly-published price error is asymmetric — it's cheaper to run in parallel a while longer than to roll back trust once it's lost.
Foundation
KG audit, canonical schema, architecture decisions, ground-truth labeling (~500–1000 examples).
Core engine
Schema matching, entity resolution, SPARQL rule library, graph embeddings, confidence model, pricing anomaly detector.
Integration
FastAPI service, review-queue workflow, Power BI dashboards, KPI definitions signed off with stakeholders.
Production
Shadow mode, CI/CD + MLOps, monitoring, staged category-by-category rollout with rollback criteria.
Key risks & mitigations
None of these risks are hypothetical edge cases — they're the predictable failure modes of any system that sits between an LLM and a live catalog. Naming them here isn't pessimism; it's what makes the mitigations (shadow mode, versioned rules, drift monitoring) look like deliberate design choices rather than reactive patches added after something broke in production.
| Risk | Mitigation |
|---|---|
| KG incompleteness limits coverage | Phase 0 audit sets realistic scope; expand KG incrementally alongside rollout |
| High false-positive rate erodes trust | Shadow mode before enforcement; interpretable scoring first; tight KPI feedback loop |
| Latency on real-time validation | Precompute embeddings, cache entity resolutions, offer async batch path |
| Rule/model drift as catalog evolves | Versioned rule library + model registry, scheduled review cadence, drift monitoring |
Blueprint v1 — adjust phase boundaries as the Phase 0 knowledge-graph audit clarifies real scope.
- Get link
- X
- Other Apps
Comments
Post a Comment