Using AI Features
Two ways to ask in plain English. One needs no key and shows its working. The other talks to a model you pay for, with a prompt you can read on this page.
Which one you want
| Feature | Where | Key | What comes back |
|---|---|---|---|
| Ask panel | The Ask a question button above the grid | None | A number or a small table, the formula, and the exact SQL |
| Describe a transformation | The box at the bottom of the Pipeline panel, then Go | Required | A full SELECT you review before it becomes a step |
| Generate from intent | Inside Filter, Add Column, Math, Update Values and Regex Capture | Required | One condition, expression or pattern dropped into the field |
The Ask panel is a parser, not a model. It matches your words to column names and known values and compiles a fixed set of question shapes to SQL. A question it cannot map is refused, with the nearest one it can answer offered instead. The two AI features send a prompt to the provider you configured and take back whatever the model returns.
Ask, on the sales template
sales.csv has 180 rows: date, region, product, channel, units, unit_price, revenue. Load it, click Ask a question, and six starters built from the columns appear, What is the total revenue? and Which region has the highest revenue? among them. Five questions of my own and the statement each one compiles to:
| Question | SQL it runs |
|---|---|
| Total revenue by region | SELECT "region" AS "region", SUM("revenue") AS "total_revenue" FROM ask_data GROUP BY "region" ORDER BY "total_revenue" DESC NULLS LAST |
| Which product has the highest revenue? | SELECT "product" AS "product", SUM("revenue") AS "total_revenue" FROM ask_data GROUP BY "product" ORDER BY "total_revenue" DESC NULLS LAST LIMIT 1 |
| revenue over time | SELECT STRFTIME(DATE_TRUNC('month', "date"), '%Y-%m') AS "period", SUM("revenue") AS "total_revenue" FROM ask_data WHERE "date" IS NOT NULL GROUP BY 1 ORDER BY 1 |
| total revenue in the North | SELECT SUM("revenue") AS "total_revenue" FROM ask_data WHERE LOWER(CAST("region" AS VARCHAR)) = 'north' |
| How many rows by channel? | SELECT "channel" AS "channel", COUNT(*) AS "rows" FROM ask_data GROUP BY "channel" ORDER BY "rows" DESC NULLS LAST |
ask_data is a view over whatever your pipeline currently outputs, so a filter you applied earlier is respected. The first question comes back as this table, shown here to the cent:
| region | total_revenue |
|---|---|
| North | 234,238.97 |
| West | 157,527.89 |
| East | 127,918.02 |
| South | 127,641.32 |
Text comparisons ignore case on purpose: north finds North. Column names carry weight, though. A money column literally called total confuses "highest total", where the word reads as an instruction and the parser falls back to counting rows; rename it amount first. Under every answer, The SQL behind this figure expands to the statement, and a button opens it in a SQL Query step for editing.
Setting up a model
Open Settings (the gear icon). Pick an AI Provider: Anthropic Claude, OpenAI, Google Gemini, xAI Grok, Mistral, DeepSeek, Groq, OpenRouter, Kimi (Moonshot), or Ollama / local server for a model on your own machine, which needs no key at all. Paste the key, click Test, then Save. Keys are kept per provider in your browser and sent only to that provider. There is no proxy: the request goes from your tab straight to the provider's API, and with Ollama nothing leaves localhost.
What the prompt contains
For Describe a transformation, the system prompt is assembled in the browser and holds:
- the table name, plus the alias
data, which always means the input to this step; - every column with its DuckDB type, one per line, such as
"revenue" (DOUBLE), plus the row count and each column's null and distinct counts; - each earlier pipeline step's description and SQL, and a note that the current source is a view called
pipeline_output; - the rules: DuckDB syntax, double-quote column names, exactly one SELECT (CTEs allowed), never DROP, DELETE, UPDATE, INSERT, ALTER or TRUNCATE, add columns with
SELECT *, expr AS name.
By default that is as far as it goes: names, types, row count, and null/distinct counts, none of which is cell content. Turn on Also share up to five example values per column in Settings and the prompt adds, per column, up to five real values, the full list of values for a low-cardinality column (say, a status field with five states), and the min/max for numeric and date columns. That helps when the model needs to know a status is spelled "shipped" rather than "Shipped". Either way, full rows never leave your browser, and the whole schema block is capped at about 2,800 characters so a wide table can't balloon the prompt. The in-panel generators send less again: the column list and a one-line job, such as "return only the raw boolean expression". The regex one adds a warning that DuckDB uses RE2, so no lookaheads and no backreferences.
One worked prompt
With sales.csv loaded and no steps yet, I typed: revenue per unit by product for the first quarter of 2024, highest revenue first. A correct answer keeps to every rule above:
SELECT "product", ROUND(SUM("revenue"), 2) AS "revenue", SUM("units") AS "units",
ROUND(SUM("revenue") / SUM("units"), 2) AS "revenue_per_unit"
FROM "sales"
WHERE "date" >= DATE '2024-01-01' AND "date" < DATE '2024-04-01'
GROUP BY "product"
ORDER BY "revenue" DESC
| product | revenue | units | revenue_per_unit |
|---|---|---|---|
| Sprocket | 43720.73 | 355 | 123.16 |
| Doohickey | 36299.47 | 194 | 187.11 |
| Thingamajig | 32459.23 | 233 | 139.31 |
| Widget | 25910.55 | 282 | 91.88 |
| Gizmo | 20780.53 | 205 | 101.37 |
Models differ, so I check two things every time: the date filter is a half-open range like the one above rather than BETWEEN ending on March 31, and money is rounded after the SUM, never before it. The generated SQL lands in an editor under the box. Fix it there, then Apply; the app checks that it is a single SELECT before running it.
When it fails
Invalid API key. Check your key in Settings. is a 401 from the provider. Rate limited. Try again in 20s. passes on its retry-after header. When a generated expression fails validation, the error is fed back to the model for up to three more tries before you see Generation failed after 4 attempts.
Open sales.csv and ask it a question →