Section D · AI enablement

AI Powering the Platform

The newest consumer of GridDP is an LLM answering questions in Slack — and it is the most dangerous one, because a confidently wrong number spreads faster and is trusted more than a blank dashboard. This chapter argues the unfashionable thesis: accuracy does not come from a bigger model. It comes from grounding the model on the semantic layer from chapter 08, feeding it catalog context, and wrapping it in evals and guardrails. The model is the easy part; the grounding is the platform's job.

AI: the newest data consumer — the promise and the peril

Every chapter so far has served a consumer with a known shape: a finance analyst wants a governed dashboard, the product wants a low-latency score, an ML model wants features. Now a new consumer arrives, and it does not fit the mold. Someone in the supply-ops channel types "what was H100 fill rate last week?" and expects a number back in seconds — no ticket, no analyst, no dashboard. This is the dream the Start Here consumer list flagged: the "user" is increasingly an LLM, and the value is enormous. Self-serve finally means everyone, not just people who write SQL.

That is the promise. The peril is sharper than for any human consumer. A dashboard that fails renders an error; a human analyst who is unsure says "let me check." An LLM does neither by default — it returns a fluent, plausible, confidently wrong number, with no signal that anything went wrong. And a wrong number in Slack is worse than no number, because it gets screenshotted, quoted in a standup, and pasted into a board doc before anyone reconciles it. The executive-trust crisis from ch. 08 — "but is this one right?" — does not go away with AI; it accelerates.

A confidently wrong number is a liability, not a feature

The failure mode of a search engine is a bad link, which you ignore. The failure mode of a generative answer is a false statement asserted as fact. If your NL interface answers "H100 fill rate was 81% last week" and the real figure is 68% because the model invented a join, you have not built a productivity tool — you have built a misinformation pipeline with your logo on it. The entire engineering problem of this chapter is making the system refuse to do that.

The naive answer — "just point an LLM at the warehouse and let it write SQL" — fails for exactly the reason ch. 08 spent a whole chapter on. The warehouse is full of raw tables with ambiguous columns, undocumented joins, and five plausible ways to compute every metric. A model handed that surface area does not become a careful analyst; it becomes the sixth team in the five-way "active GPU" fight, inventing a definition on the fly. Accuracy is not a model property. It is a platform property, and we build it the same way we built trust for humans: by removing the model's ability to be wrong, not by hoping it gets smarter.

Text-to-SQL: how it works, and why raw schemas break it

The core mechanism is text-to-SQL: give the model a natural-language question plus a description of the database, and have it emit a SQL query. In its naive form the "description" is the raw schema — table names, column names, maybe a few foreign keys — pulled from the warehouse catalog.

NAIVE TEXT-TO-SQL (raw schema) "what was H100 fill rate last week?" │ ▼ ┌───────────────────────────────┐ │ LLM + raw schema dump │ │ (table & column names only) │ └───────────────┬───────────────┘ ▼ ┌───────────────────────────────┐ │ generated SQL │ ← which "fill rate"? which join? │ SELECT ... FROM rental r │ which time column? which filter? │ JOIN gpu g ON ??? WHERE ... │ the model GUESSES every one └───────────────┬───────────────┘ ▼ a number ──▶ plausible, unverifiable, often WRONG

It demos beautifully on a clean three-table toy schema and collapses on a real platform. The reason is that every hard part of analytics lives in knowledge that is not in the schema. The column util does not say whether it means allocated time or actual compute. The path from rental to gpu does not say which of three join keys is correct. Nothing in the DDL encodes that "fill rate" means rented GPU-hours over listed GPU-hours and not over total hours. The model fills every gap with a confident guess, and crucially, it has no notion of correctness — it optimizes for plausible SQL, not for the right answer. Recall ch. 08's "five definitions of active GPU": a raw-schema model will reproduce that chaos query by query, never converging on the one the company agreed to.

Failure modeWhat the model doesGridDP example
Ambiguous columnsPicks a column by name, not meaningUses gpu_util_pct raw instead of the >5% "real compute" rule behind Utilization %
Wrong / invented joinsHallucinates a join key or pathJoins telemetry_sample to rental directly, skipping instance — mis-attributing every sample
Unknown business logicOmits filters it can't seeForgets rental_status = 'settled', so GMV silently includes cancelled rentals
Multiple metric definitionsInvents one of N plausible variantsComputes "active GPUs" as listed-on-market (16,800) when the agreed answer is telemetry+instance (13,250)
Event-time vs arrivalBuckets by load time, not event timeAggregates bids by ingest timestamp — the ch. 01 lateness bug, now automated
No notion of correctnessReturns fluent SQL regardless of truthAnswers with full confidence even when the join produced a fan-out and the number is doubled

The unifying point: these are not bugs a better model fixes. They are missing-context failures. The knowledge to answer correctly exists in the company — it just isn't in the schema the model was handed. The rest of this chapter is about handing the model the right context, and the single highest-leverage piece of context is the semantic layer.

Grounding on the semantic layer = the accuracy lever

Here is the central move. Instead of asking the model to write arbitrary SQL against raw tables, we ask it to pick a metric and some dimensions from the governed semantic layer — and let the layer compile the correct SQL, exactly as it does for every other consumer in ch. 08. The model never touches the join, the filter, or the aggregation. It chooses from a validated menu, and the menu guarantees the answer.

RAW-SCHEMA TEXT-TO-SQL │ SEMANTIC-LAYER-GROUNDED (fails) │ (works) │ question │ question │ │ │ ▼ │ ▼ LLM writes ANY SQL │ LLM picks metric + dimensions over 200 raw tables │ from the GOVERNED menu (ch. 08) │ guesses joins/filters │ │ {metric: fill_rate, ▼ │ ▼ dims:[gpu_model], grain: week} unverified SQL ─▶ wrong number │ SEMANTIC LAYER compiles │ │ the ONE correct SQL problem space: "write any SQL" │ ▼ (infinite, mostly wrong) │ governed SQL ─▶ correct number │ │ problem space: "pick from a menu" │ (small, all answers valid)

This is the whole trick, and it is a problem-space collapse. "Write any SQL over 200 tables" is an essentially unbounded space in which almost every point is subtly wrong. "Pick a metric from a list of nine and some dimensions from a known set" is a tiny, enumerable space in which every point is correct by construction, because the semantic layer authored the SQL, not the model. We have not made the model smarter. We have changed its job from authoring correctness (which it cannot do) to selecting from pre-validated correctness (which it does well).

NL question → semantic-layer query (the model's actual output)
// "what was H100 fill rate last week?"
// The LLM does NOT emit SQL. It emits a semantic-layer query:
{
  "metric": "fill_rate",          // chosen from the ch.08 metric catalog
  "dimensions": ["gpu_model"],    // chosen from declared dimensions
  "filters": [
    { "dimension": "gpu_model", "op": "=", "value": "H100" }
  ],
  "time": { "grain": "week", "range": "last_week" }
}

That structured request is handed to the same MetricFlow / Cube / Cortex Analyst engine every other consumer uses, which compiles it deterministically to the governed SQL — identical to the GMV compilation shown in ch. 08, applying the "rented ÷ listed GPU-hours" rule once, in one place. The model's output is validated before execution: is fill_rate a real metric? Is gpu_model a declared dimension? Is H100 a known value? If any check fails, the request is rejected and never becomes a wrong number.

This is WHY ch. 08 is load-bearing — the prerequisite, not a reference

Chapter 08 called the semantic layer "the hinge" and promised it would make AI accurate. This section is that promise being paid. Without the semantic layer, every technique below is mitigation on top of a fundamentally ungrounded system — you are sanding the edges of guesswork. With it, the AI inherits, for free, every metric definition the company already fought over and agreed to: the one "active GPU," the credits-netted GMV, the settled-only filter. The grounding is the semantic layer. If you skipped ch. 08, your NL interface will reproduce the five-way definition fight one query at a time, and no model upgrade will save it. Build the semantic layer first; the AI is the second consumer it pays off.

RAG over the catalog, docs, and lineage

Grounding on the semantic layer answers the metric questions cleanly, but two gaps remain. First, the model still has to map messy human language onto the right metric and dimension — "fill rate," "occupancy," "how full are we" all point at fill_rate, and "the big Hoppers" means gpu_model = 'H100'. Second, plenty of legitimate questions ("which tables feed the churn model?", "what does dlperf_probe mean?") are about the platform itself, not a metric. Both are solved by retrieval-augmented generation: before the model answers, fetch the relevant context from the catalog, docs, and lineage (the assets of ch. 09 and ch. 10) and put it in the prompt.

question ──▶ embed ──▶ semantic search over the knowledge base │ ┌───────────────┬──────────┼───────────┬────────────────┐ ▼ ▼ ▼ ▼ ▼ ┌──────────┐ ┌─────────────┐ ┌────────┐ ┌──────────┐ ┌──────────────┐ │ semantic │ │ table & │ │ column │ │ lineage │ │ prior vetted │ │ layer │ │ metric │ │ descr. │ │ (ch. 10) │ │ queries │ │ defs(08) │ │ docs (09) │ │ │ │ │ │ (few-shot) │ └────┬─────┘ └──────┬──────┘ └───┬────┘ └────┬─────┘ └──────┬───────┘ └───────────────┴───────────┴───────────┴──────────────┘ ▼ retrieved context injected into the prompt ▼ LLM disambiguates → correct metric/dimension pick

The retrieved context does the disambiguation work. Metric descriptions ("Fill rate = rented GPU-hours ÷ listed GPU-hours, per gpu_model") tell the model which metric matches the intent. Column semantics resolve synonyms and acronyms. Lineage answers structural questions and lets an agent reason about blast radius. And a small set of prior vetted queries — real questions paired with their correct semantic-layer requests — act as few-shot examples that pin the model's output format and teach it the house vocabulary.

RAG narrows the menu; it does not author SQL

Keep the division of labor clean. Retrieval's job is to help the model choose the right entry from the governed menu — it surfaces definitions and examples. It is never a license to generate raw SQL from a retrieved table description. The semantic layer still compiles the query. RAG makes the selection accurate; the semantic layer makes the execution correct. Conflating them — "the model read the table doc, so let it write the join" — quietly re-opens every failure mode from the table above.

Agents for data tasks — beyond question answering

NL-to-SQL is the headline, but the same grounding-plus-tools pattern powers a broader class of agents that do platform work, not just answer questions. An agent is a model given tools (run a query, read the catalog, open a PR) and a loop to use them. The high-value GridDP agents are unglamorous and internal:

  • Drafting dbt models — propose a staging or mart model from a source contract (ch. 01) and the modeling conventions of ch. 06. The agent drafts; a DPE reviews the PR.
  • Documenting tables — generate column descriptions and a table summary from schema + sample + lineage, to backfill the catalog of ch. 09. Cheap, high-coverage, and a human skims before merge.
  • Generating tests — propose dbt/data tests (not-null, uniqueness, the ledger-balances assertion from ch. 01) for a new model, extending the testing mindset of ch. 07.
  • Triaging data incidents — when a freshness or quality check fires (ch. 10), an agent reads the lineage, identifies the upstream culprit, and posts a first-pass diagnosis to the on-call channel.
The automate-vs-gate line is drawn at reversibility and blast radius

The decision rule is not "how smart is the agent" — it's "what does a wrong action cost, and can we undo it?" Read-only and proposal-only actions (answering a question, drafting a PR, suggesting docs) can run with light human review because a bad draft is discarded for free. Anything that mutates state or ships to a consumer — merging a model, altering a metric definition, writing to production — is gated behind a human approval, because the blast radius is real and the action is not trivially reversible. Human-in-the-loop is not a transitional crutch; it is the permanent boundary between "agent drafts" and "agent decides."

The accuracy stack — layered defense

No single technique makes an NL interface trustworthy; accuracy is a stack of defenses, each catching what the layer above missed. Like security, you assume any one layer can fail and design so a failure is caught, not shipped. From foundation to top:

THE ACCURACY STACK (each layer catches what slips past the one below) ┌──────────────────────────────────────────────────────────────┐ │ 5 · CONFIDENCE + ABSTENTION "I'm not sure" beats a wrong # │ ◀ last line ├──────────────────────────────────────────────────────────────┤ │ 4 · EXECUTION + SELF-CORRECTION run it; on error, repair │ ├──────────────────────────────────────────────────────────────┤ │ 3 · CONSTRAINED GENERATION / VALIDATION metric & dim must │ │ exist; reject before executing │ ├──────────────────────────────────────────────────────────────┤ │ 2 · RETRIEVAL OF CATALOG CONTEXT (RAG) docs, lineage, examples│ ├──────────────────────────────────────────────────────────────┤ │ 1 · GROUNDING ON THE SEMANTIC LAYER (ch. 08) the foundation │ ◀ the lever └──────────────────────────────────────────────────────────────┘ ▲ remove layer 1 and everything above is sanding guesswork
  1. Grounding on the semantic layer — the foundation. The model selects from governed metrics; the layer authors the SQL. Collapses the problem space.
  2. Retrieval of catalog context — RAG feeds definitions, column semantics, lineage, and vetted examples so the selection is right.
  3. Constrained generation / validation — the model's output must conform to the schema of valid metrics and dimensions; an unknown metric or dimension is rejected before any query runs. This is where most hallucinations die cheaply.
  4. Execution + self-correction — run the compiled query; if the engine returns an error (a type mismatch, an empty result where one is impossible), feed the error back and let the agent retry once or twice. Bounded loops only — a self-correction loop with no cap is a cost and latency hazard.
  5. Confidence + abstention — the last line. If the model can't map the question to a known metric, or retrieval comes back thin, or the question is genuinely out of scope, the correct output is "I'm not sure — here's what I can answer", not a guess. Designed abstention is the single feature that separates a trustworthy assistant from a misinformation pipeline.
Abstention is a feature you must build, not a default you get

Models do not abstain on their own — they are trained to be helpful, which means they will answer. You have to engineer the "I don't know": detect when no metric matches above a threshold, when a filter value isn't a known dimension member, when the question references a restricted domain. A platform whose AI never says "I'm not sure" is not confident — it is one that hasn't built the safety valve, and every out-of-distribution question becomes a confidently wrong answer.

Evaluating NL-to-SQL — the testing mindset, applied to AI

You cannot manage accuracy you do not measure, and "it looked right in the demo" is not measurement. The discipline is the same one ch. 07 brought to transformations: a curated eval set of question → known-correct-answer pairs, run as a regression test on every change to the prompt, the retrieval, or the model. The metric that matters is execution accuracy — does the result the system returns match the ground-truth result? — not whether the generated query string looks plausible, since two very different queries can return the same correct number and two similar ones can diverge.

THE EVAL LOOP curated eval set ┌──────────────────────┐ (question → correct answer) ───▶ │ run system on each Q │ ▲ └──────────┬───────────┘ │ add new questions ▼ │ from real misses compare result to ground truth │ │ │ ┌─────────┴──────────┐ │ ▼ ▼ │ exec-accuracy abstention rate │ (match? Y/N) (good vs over-cautious) │ │ │ │ └─────────┬──────────┘ │ ▼ │ track over time; BLOCK the change └────────────────────── if accuracy regresses on any commit

Two numbers are tracked together, because they trade off: execution accuracy (of the questions it answered, how many were right) and abstention rate (how often it correctly said "I'm not sure" versus refusing answerable questions). A system that answers everything with 80% accuracy is worse than one that answers 70% of questions at 99% accuracy and abstains on the rest — because the second one never lies. Every prompt tweak or model swap reruns the suite, and a regression blocks the change, exactly like a failing dbt test blocks a merge.

eval case — one row of the curated NL-to-SQL suite
- id: fill_rate_h100_lastweek
  question: "what was H100 fill rate last week?"
  expect:
    metric: fill_rate                 # must resolve to the catalog metric
    dimensions: [gpu_model]
    filters: [{ gpu_model: "H100" }]
    time: { grain: week, range: last_week }
  ground_truth_result: 0.683          # reconciled offline by an analyst
  tolerance: 0.005                     # exec-accuracy: |result - truth| <= tol
  must_not_abstain: true               # this IS answerable; abstaining = fail

- id: ceo_favorite_color
  question: "what is the CEO's favorite color?"
  expect: { abstain: true }            # out of scope → MUST abstain, not guess

The eval set is a living asset: every real-world miss becomes a new test case, so the system can never regress on a question it once got wrong. This is the same flywheel as data quality in ch. 10 — observe a failure, encode it as a check, prevent its recurrence — applied to the AI layer instead of the pipeline.

Guardrails — what the AI is allowed to touch

Accuracy is about returning the right answer; guardrails are about bounding what the system can do in the process. Even a perfectly grounded agent must run inside the access-control and privacy fence from ch. 11 — the AI is a consumer, and consumers get scoped permissions, not a master key.

GuardrailWhat it preventsHow it's enforced
Read-only roleAny write/DDL from a Q&A pathThe query path authenticates as a read-only warehouse role — no INSERT/UPDATE/DROP privilege exists to abuse
Cost & row limitsA runaway scan of the 150 M-row firehosePer-query byte/row/timeout caps; force the telemetry rollups (ch. 14), never raw samples
PII filteringLeaking KYC/account PII into a Slack answerRestricted columns are masked/tokenized at the source (ch. 11); the AI role simply cannot see them
Restricted-domain blockAnswering questions about trust-safety / fraud / finance internalsDomain allow-list per channel; questions touching gated domains route to abstention
Audit of AI queriesUntraceable "the bot said so" claimsEvery AI-issued query is logged with question, compiled SQL, result, and requester — same audit trail as any user
Prompt-injection defenseA malicious doc/row steering the agentTreat retrieved content as data, not instructions; never let it escalate the read-only role or domain scope

The unifying principle from ch. 11 holds: the AI gets least privilege. It authenticates as a specific, narrowly-scoped role; it can only reach the domains its channel is cleared for; and everything it does is audited identically to a human analyst. Grounding makes the answers correct; guardrails make sure that even a wrong or manipulated request can't do damage beyond returning a number.

Worked end-to-end: "what was H100 fill rate last week?"

Tie the whole stack together on the question that opened the chapter. A supply-ops engineer types it into Slack; here is every step, and why each is correct by construction rather than by luck.

"what was H100 fill rate last week?" │ ▼ ① RETRIEVAL (ch.09/10): surfaces metric def for `fill_rate`, │ synonym map ("the big Hoppers" → gpu_model=H100), 2 vetted examples ▼ ② GROUNDED PICK: model emits a semantic-layer query, NOT SQL: │ { metric: fill_rate, dims:[gpu_model], │ filters:[gpu_model=H100], time:{grain:week, range:last_week} } ▼ ③ VALIDATION: fill_rate ∈ metrics? ✓ gpu_model ∈ dims? ✓ H100 ∈ values? ✓ ▼ ④ COMPILE (ch.08 semantic layer → governed SQL): │ rented GPU-hours ÷ listed GPU-hours, settled only, by gpu_model ▼ ⑤ EXECUTE under read-only role + cost cap (ch.11/14) on fct_telemetry_hourly rollup ▼ ⑥ ANSWER with the DEFINITION shown, not just the number ▼ "H100 fill rate last week was 68.3% — rented GPU-hours ÷ listed GPU-hours on settled rentals (per the governed `fill_rate` metric). [view query]"

The compiled query is the governed one from the ch. 08 catalog — rented GPU-hours over listed GPU-hours, per gpu_model — running against the fct_telemetry_hourly rollup, not the raw firehose. Walk the contrast with the naive path:

  • The model never chose a join or a filter, so it could not skip instance or forget the settled-only rule — the failure modes from the table simply have no surface to occur on.
  • The answer is the one company-agreed fill rate, identical to what the supply-ops dashboard shows, because both compile through the same semantic-layer definition. There is no second number to disagree with.
  • The reply states its own definition and links the query — so the human can verify, and trust is earned rather than assumed.
  • Ask it something out of scope ("CEO's favorite color") or gated ("show me flagged-fraud accounts") and it abstains or refuses, because of the accuracy stack's top layer and the guardrails.
Correct-by-construction, not correct-by-luck

The naive system might have produced 68.3% on a good day — and 81% on a bad one, with no way to tell which you got. The grounded system produces 68.3% because there is no path by which it could produce anything else: the model's only freedom was selecting a metric and a dimension, both validated, and the SQL was authored by the same governed layer the whole company already trusts. That is the difference between a demo and a platform. The intelligence that made it accurate lives in the semantic layer, the catalog, and the evals — not in the model.

Takeaway

  • The AI is GridDP's newest and riskiest consumer: the promise is English questions answered in Slack; the peril is a confidently wrong number, which is worse than no number. "Just point an LLM at the warehouse" reproduces the five-way definition chaos of ch. 08, one query at a time.
  • Naive text-to-SQL on raw schemas fails on ambiguous columns, invented joins, unseen business logic, and multiple metric definitions — missing-context failures a bigger model does not fix.
  • Grounding on the semantic layer is the accuracy lever: the model picks metrics and dimensions from the governed menu and the layer compiles the one correct SQL, collapsing "write any SQL" into "pick from validated correctness." This is why ch. 08 is the prerequisite, not a footnote.
  • Around that foundation: RAG over the catalog/lineage for disambiguation, an accuracy stack (grounding → retrieval → validation → self-correction → abstention), execution-accuracy evals that block regressions, and ch. 11 guardrails (read-only, cost caps, PII filtering, audit) that bound what the agent can touch.
  • Accuracy is a platform property, engineered from grounding + governance + evals — not a model property bought with more parameters.

The grounded NL interface now leans hard on the telemetry rollups and the warehouse it queries under cost caps — which raises the question every prior chapter deferred: how does all of this stay fast and affordable at 150 M samples a day? Next: performance & scale.