Natural Language Query Layer
A natural-language query layer over pipeline data where no records ever reach the model, a 5-layer SQL guard enforces read-only access in code, not in policy.
- Python
- SQLite
- Internal LLM API
- NL→SQL
- 5-layer SQL guard
- columns in schema map
- 56
- SQL guard
- 5 layers
- records sent to model
- 0
Ops teams needed answers from pipeline data fast. Dashboards were too brittle and analyst tickets were too slow for a weekly forecasting cadence. I built an NL→SQL layer over scheduled local snapshots, with a privacy architecture where the LLM never sees a single record.
The problem
Ops teams needed answers from pipeline data fast: which accounts slipped this week, which segments are off-pace, which renewals are sitting without an owner. Getting those answers required either a pre-built dashboard tile or an analyst request.
Neither worked for a weekly forecasting cadence. Dashboards lagged the questions ops actually asked; analyst tickets queued behind quarter-close work.
Constraints I had to design around
Pipeline data is account-level revenue data. It cannot leave the trust boundary, full stop. That ruled out any architecture where the LLM sees records.
The system had to be safe by construction, not by prompt. 'Trust the model not to write' is not an enterprise-grade answer; the model has to be architecturally incapable of writing.
What I built
A natural language query layer on top of scheduled local snapshots of the pipeline data. A team member types a question in plain English. The system converts it to SQL and returns a structured answer with the row count, the SQL it ran, and an inline preview.
The flow is five fixed stages: plain-English question → schema context layer (56-column domain map, no live data) → enterprise-approved internal LLM → 5-layer SQL guard (SELECT-only, validated before run) → plain-English answer returned to the user.
Snapshots refresh on a schedule local to the user. The query interface looks like a chat, but the runtime is a constrained SQL engine. There is no live connection to the source system at any point.
The non-negotiable, no records reach the model
The LLM receives only a 56-column schema map and the natural language question. It returns a SQL string. That string runs locally against the snapshot. Live data never moves, and the model never sees a single value from a record.
A 5-layer SQL guard validates the model's output before it executes: parse the AST, reject any non-SELECT verb, reject schema-mutating clauses, reject access to columns outside the whitelisted set, and enforce a row-count ceiling. The system cannot write under any circumstances, enforced in code, not in policy.
The trade-off I accepted
The snapshot approach was a deliberate trade-off. Running queries against the live system creates operational risk, both contention and the chance that a bad query touches more than it should. Running them against a scheduled local extract creates a lag in data freshness that ops teams accepted in exchange for speed and safety.
That trade-off is the case study, not the NL→SQL part. The NL→SQL part is the easy half.
"Most NL→SQL demos read live data and trust the model to behave. The hard part was inverting that, make the model architecturally incapable of touching the records, then earn the speed back with smart snapshotting. The next step is wiring the same guard pattern into a reusable platform service so other Adobe teams don't rebuild it."
Builds on the same thread
- PRODUCTION
Pipeline Automation
Took a year-old, ~800-field manual revenue-ops task per run across three regions to a reviewed automation cleared by Legal, IT and enterprise security review, 87% time reduction, in production globally.
- PROTOTYPE
Agentic Operations Loop
A planning agent that reasons over pipeline state, proposes write actions, and hands off to a human at every consequential step, human review is not configurable out.