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

FeatureWhereKeyWhat comes back
Ask panelThe Ask a question button above the gridNoneA number or a small table, the formula, and the exact SQL
Describe a transformationThe box at the bottom of the Pipeline panel, then GoRequiredA full SELECT you review before it becomes a step
Generate from intentInside Filter, Add Column, Math, Update Values and Regex CaptureRequiredOne 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:

QuestionSQL it runs
Total revenue by regionSELECT "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 timeSELECT 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 NorthSELECT 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:

regiontotal_revenue
North234,238.97
West157,527.89
East127,918.02
South127,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
productrevenueunitsrevenue_per_unit
Sprocket43720.73355123.16
Doohickey36299.47194187.11
Thingamajig32459.23233139.31
Widget25910.5528291.88
Gizmo20780.53205101.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 →