Ask a model yes/no, pick-one and score questions about your rows, with real probabilities and spending capped by default
Installing and Loading
INSTALL typed_ai FROM community;
LOAD typed_ai;
Example
-- Audit a chatbot: did each answer stick to its source? Needs TYPESAFE_API_KEY, or a local model profile.
CREATE SECRET jev (TYPE typed_ai, PROVIDER 'jev', API_KEY getenv('TYPESAFE_API_KEY'));
-- No key? A free local model works too (weaker answers):
-- CREATE SECRET local (TYPE typed_ai, PROVIDER 'openai', MODEL 'gemma2:2b',
-- URL 'http://localhost:11434/v1/chat/completions', USD_PER_MTOK_IN 0, USD_PER_MTOK_OUT 0);
CREATE TABLE bot_answers AS SELECT * FROM (VALUES
('Can I take photos?', 'Photography without flash is allowed in all galleries.', 'Yes, flash is fine everywhere.'),
('How heavy is the meteorite?', 'The Ashford meteorite weighs 412 kilograms.', 'It weighs about 412 kg.')
) t(question, source, answer);
CREATE TABLE topics AS SELECT * FROM (VALUES ('visiting rules'), ('science'), ('history')) t(name);
-- Price it first: no calls, no cost
SET typed_dry_run = true;
SELECT typed_prob(b, 'the answer is fully supported by the source') FROM bot_answers b;
SELECT dry_run_usd FROM typed_usage();
SET typed_dry_run = false;
-- The whole row goes in, so the model compares answer and source
SELECT answer, typed_prob(b, 'the answer is fully supported by the source') AS supported FROM bot_answers b;
-- Options can come from a table
SELECT question, typed_pick(question, 'what is the visitor asking about?', (SELECT list(name) FROM topics)) FROM bot_answers;
About typed_ai
typed_ai asks a model typed questions about rows and returns the model's own odds as probabilities.
It works with TypeSafe Jev and with any OpenAI-compatible server that returns
logprobs, including a local Ollama or vLLM.
| Function | Returns |
|---|---|
typed_is(input, statement [, threshold]) |
BOOLEAN, for WHERE |
typed_prob(input, statement) |
probability of yes, 0 to 1 |
typed_pick(input, question, options) |
the option the model rates highest; options can come from a table |
typed_score(input, question, levels) |
probability-weighted level |
typed_ask(input, question_json) |
the full answer as JSON |
typed_usage() |
spend, budget, requests, cache and breaker state |
Spending is capped from the first query: 1,000 rows per query and $1.00 per database until you raise
typed_max_rows or typed_budget_usd. typed_dry_run prices a query without calling anything. A job that
stops partway keeps every answer it paid for, so running it again costs only the missing rows.
Every value you judge is sent to the provider named in your profile (a DuckDB secret), over https unless that
server runs on your own machine; keep private data on a local model. HTTP goes through httpfs, loaded on first use.
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| typed_ask | scalar | NULL | NULL | |
| typed_is | scalar | NULL | NULL | |
| typed_pick | scalar | NULL | NULL | |
| typed_prob | scalar | NULL | NULL | |
| typed_score | scalar | NULL | NULL | |
| typed_usage | table | NULL | NULL |
Overloaded Functions
This extension does not add any function overloads.
Added Types
This extension does not add any types.
Added Settings
| name | description | input_type | scope | aliases |
|---|---|---|---|---|
| typed_batch_size | Rows per request, up to the provider's limit | BIGINT | GLOBAL | [] |
| typed_budget_usd | Most dollars this database may spend, counted since the extension loaded | DOUBLE | GLOBAL | [] |
| typed_cache_mb | Memory for saved answers | BIGINT | GLOBAL | [] |
| typed_concurrency | Requests in flight across the whole process | BIGINT | GLOBAL | [] |
| typed_dry_run | Price queries and call nothing; results are NULL | BOOLEAN | GLOBAL | [] |
| typed_fail_after | Failed requests in a row that stop calls for the rest of the query; 0 is off | BIGINT | GLOBAL | [] |
| typed_max_input_chars | Longest input value, in characters; longer values are refused, never cut | BIGINT | GLOBAL | [] |
| typed_max_retries | Extra attempts after a 408, 429, 5xx or dropped connection | BIGINT | GLOBAL | [] |
| typed_max_rows | Most rows one query may send to the model; cached rows are free | BIGINT | GLOBAL | [] |
| typed_on_stop | What a stopped job shows: 'error', 'null' or 'row' ({value, error} pairs) | VARCHAR | GLOBAL | [] |
| typed_profile | Name of the typed_ai secret to use; empty picks the only one, or Jev from TYPESAFE_API_KEY | VARCHAR | GLOBAL | [] |
| typed_timeout_s | Seconds per HTTP attempt | BIGINT | GLOBAL | [] |