Klarix Book a call
Case study 01 of 03 · Semantic AI benchmark

A semantic layer makes an AI analyst consistent. Does it make it right?

I built a benchmark to answer that properly: one frozen ecommerce dataset with five planted data problems, ground truth computed independently, and an LLM agent answering the same 20 business questions twice, once against raw BigQuery tables and once through a governed Cube semantic layer. This first part runs entirely on Google Cloud.

BigQuery EU Cube Core v1.7.46 Gemini 3.8 Flash on Vertex AI EU Claude Opus 5 judge theLook eCommerce

58% vs 43%

of answers passed through the governed layer vs raw tables, same Gemini agent, 60 runs each

14/15 vs 9/15

straightforward lookups answered correctly, the questions leaders assume are safe

22/22

layer correctness checks within 0.5% of ground truth, run without any LLM involved

$10

to run the full 60-answer evaluation on Gemini through Cube ($7 on raw tables)

My role: solo build, covering benchmark design, star schema, semantic layer, agent and evaluation harness.

Semantic Layer vs Raw Tables on BigQuery: 58% of AI answers passed through the Cube layer, 43% on raw tables

The question

Every AI analyst answers confidently. Not every answer means what the question meant.

Teams are connecting LLMs to their warehouses and asking things like "what was revenue last quarter?". The model will always produce a number. The risk is that it produces one just as confidently when "revenue" is ambiguous, when test accounts sit in the orders table, or when a product category was renamed halfway through the year.

The usual advice is to put a semantic layer in front of the model. That is partly right: a semantic layer makes answers consistent, because every question goes through the same definitions. It does not make them correct by itself. The layer is only as correct as the judgment encoded into it, and I wanted to measure that gap instead of asserting it.

How the benchmark works

Build data with known answers, corrupt a copy the way real warehouses get corrupted, then see who still gets it right.

  1. Step 1 · Freeze

    One snapshot, never re-pulled

    Seven theLook tables pulled once to Parquet and hashed. The public dataset regenerates upstream, so re-pulling would silently change the right answers.

  2. Step 2 · Two worlds

    What happened vs what the warehouse sees

    A true world (the business as it was) and an observed world with planted problems injected. Same seed, byte-identical output on every rebuild.

  3. Step 3 · Independent truth

    The system under test never grades itself

    Correct answers are computed with DuckDB, straight from the true world, by separate code. Never through BigQuery, never through Cube.

  4. Step 4 · Warehouse

    Raw, staging, star schema, marts

    The observed world is loaded into BigQuery and modeled as a Kimball star schema with plain SQL views. 16 SQL assertions cover keys, orphans and revenue reconciliation.

  5. Step 5 · Two backends, one contract

    The LLM never writes SQL

    The agent asks for metrics and dimensions by name. A naive backend compiles that to the SQL a hurried analyst would write on raw tables; Cube compiles it against the governed model.

  6. Step 6 · Score

    20 questions, 3 phrasings each

    Each answer is checked numerically against truth, for consistency across phrasings, for asking when it should ask, and by one fixed Claude judge working from a per-question rubric.

The planted problems

Five traps, each one I have seen in a real company's data.

Four are measurement problems: the business did not change, the data did. One is a real business signal hidden in the noise. A good analyst flags the first kind and explains the second.

ID What is wrong in the data How the governed layer handles it
P1 "Revenue" is ambiguous. Cancelled and returned items make gross and net revenue differ substantially. Deliberately left open: both metrics exist with explicit definitions, to test whether the agent notices the split or quietly picks one.
P2 A category is renamed mid-window. Sweaters becomes Knitwear in October 2025; old sales seem to collapse and new ones appear from nowhere. A hand-maintained mapping assigns both names to one category family, so the rename is invisible downstream.
P3 Consent tracking loss. From one date, a share of new users and sessions lose their traffic source, so every channel appears to decline. Missing values become an explicit "Unattributed" bucket. The true source is unrecoverable; the layer can only make the gap visible.
P4 Internal and test accounts. 1,000 test users with many tiny orders inflate order counts and repeat-purchase rate. A rule-based internal flag, excluded from every governed metric by default. The raw backend structurally cannot exclude them.
P5 A high-return acquisition cohort. Users from one channel who signed up in a three-month period return far more items. Real, but invisible at channel level. Nothing to fix; it is real. A cohort mart makes it findable at the right grain. The agent still has to look there.

Results

The layer helps most where people assume they do not need help.

Share of individual answers that passed every check, by question type (each bar is 3 to 5 questions times 3 phrasings). Under the strict rule, where a question only counts if all three phrasings pass and agree, the scores are 40% on raw tables, 45% through Cube with Gemini, and 30% through Cube with Claude.

Raw BigQuery tables · Gemini
Cube semantic layer · Gemini
Cube semantic layer · Claude (control)

Straightforward

Plain lookups: orders last month, top brands

60%
93%
80%

Definition

Ambiguous terms: revenue, AOV, conversion rate

33%
50%
42%

Trap

A planted artifact makes the naive answer wrong

67%
60%
53%

Ambiguous

Underspecified: the right move is to ask or state assumptions

33%
33%
11%

Insight

Finding the real high-return cohort

0%
33%
11%

The honest reading

The governed layer lifted plain lookups from 60% to 93% and made the hidden cohort findable at all. It did not fix ambiguity, and it barely moved definition questions, where the agent still has to choose between two correct metrics. Consistency comes from the layer; judgment does not.

Three answers side by side

Same agent, same question. The difference is what the agent could know.

Q01 · Internal accounts

"How many orders did we have last month?"

Truth: 4,633

Raw tables · 5,898 (27% high)

The agent noticed a real anomaly, a thin last day of the month, and blamed the gap on that. It had no way to see 1,000 test accounts. Confidently wrong.

Cube · 4,495 (3% low)

Not exact either, but its stated definition says test and internal accounts are excluded, because the exclusion is built into the governed view. An error you can explain.

Q18 · Margin by channel

"Which acquisition channel is most profitable after returns?"

Truth: all five channels within 0.3 points (51.7% to 52.0%)

Raw tables · invented a winner

Reported Organic at about 30% margin against 46 to 47% elsewhere, then explained the gap with a product-mix story. The gap does not exist.

Cube · matched truth

Said the channels are virtually identical on margin rate and separated that from absolute dollars, where Search leads on volume alone.

Q10 · Category rename

"How has Knitwear performed over the last 12 months?"

Trap: Knitwear was called Sweaters until October 2025

Raw tables · right 1 time in 3

Three phrasings of the same question produced different conclusions. A leader asking in their own words would get whichever one they happened to trigger.

Cube · right 3 times in 3

The category family mapping carries the rename, so the trend is continuous no matter how the question is worded.

Where the layer did not help

The failures are the more useful half of the result.

Hiding an artifact is not explaining it

Asked which category declined most in October 2025, the raw backend passed 2 of 3 times and Cube 0 of 3. The Cube agent spotted the rename and still named Sweaters as the biggest decliner instead of giving a defensible real answer.

Available structure is not used structure

The cohort mart was there, yet on "which cohort has the worst return behavior?" the agent said it could not analyse signup cohorts and missed the one returning about 40% of items against a 12% baseline.

A semantic layer does not ask questions

"How are we doing?" and "What is our margin?" scored the same on both Gemini backends. Clarifying behaviour lives in the agent and its prompt, not in metric definitions.

The control run is partly a budget story

Claude on the same Cube layer hit the 8-tool-call limit before answering in 21 of 60 runs. Its lower score says as much about tuning the tool budget per model as about the model itself.

Engineering findings

What I would want to know before building this on a client's stack.

Cube Core cannot join two fact tables through a shared date dimension

Order-item metrics and order-level metrics both needed one "order date". In v1.7.46 that forced an unsupported join on every query. I routed the single catalog name to whichever physical member each metric needs, so the agent still sees one clean name.

"Last month" is a contract decision, not a model inference

Telling the model today was 31 August made it read "last month" as July. Naming the completed month explicitly fixed it. Date semantics belong in the prompt and the contract, written down once.

Agent evals caught a bug the unit tests missed

The test-account generator piled fake orders into the latest months (683 extra in one month). Unit tests checked averages and passed; a real question about last month exposed it. Evals on business questions are a test layer, not only a scoreboard.

Small type issues fail silently

BigQuery DATE vs TIMESTAMP in Cube time dimensions, and unqualified columns inside expressions, both break only when a particular join appears. A fixed set of 22 layer checks with no LLM involved is what made them cheap to find.

Cost and latency per full evaluation (20 questions × 3 phrasings)
Run Tokens in Tokens out Cost Median latency
Raw tables · Gemini 8.19M 88K $7.08 55 s
Cube · Gemini 11.84M 78K $10.03 55 s
Cube · Claude (control) 2.00M 199K $14.97 41 s

If you are putting AI on your data

Write the judgment down before the model touches it.

Decide what "revenue", "customer" and "active" mean, which accounts are excluded, and how renamed categories roll up. That written judgment is the semantic layer; the tool is secondary.

Test the agent with your own trap questions against an independent truth before anyone relies on it. Demos on clean questions tell you very little.

Expect the layer to make answers consistent and explainable. Expect ambiguity and real insight to still need a person, or an agent designed to ask.

If you are evaluating an AI analyst or a semantic layer for your team, I am glad to walk through what this harness would look like on your data.

Privacy

The agent receives schema metadata and aggregated query results only, never raw rows. Gemini runs on Vertex AI in an EU region inside the project's own Google Cloud boundary. The data is Google's public, synthetic theLook dataset; no real customer data is involved at any point.

Next in the series

02: the same benchmark on dbt Core and MetricFlow, with Claude as the agent. 03: Power BI with the model defined in TMDL. Same data, same questions, same judge, so the stacks can be compared fairly.