Use TypeSafe Jev (or another AI, even a free one on your laptop) to sort, filter and check the text in your tables. Get yes or no, pick one, or a rating, see how sure it is, and never spend more than your limit.
Installing and Loading
INSTALL typed_ai FROM community;
LOAD typed_ai;
Example
-- Sort a lost-and-found desk with TypeSafe Jev. The key comes from TYPESAFE_API_KEY.
CREATE SECRET jev (TYPE typed_ai, PROVIDER 'jev', API_KEY getenv('TYPESAFE_API_KEY'));
-- Other choices: run one of these in place of the line above. The rest stays the same.
-- A free local model (run `ollama pull gemma2:2b` first). Weaker answers; nothing leaves
-- your laptop.
-- 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);
-- OpenAI. In our October 2026 test it cost about 2.5 times as much as Jev and was less
-- accurate.
-- CREATE SECRET oai (TYPE typed_ai, PROVIDER 'openai', MODEL 'gpt-4o-mini',
-- API_KEY getenv('OPENAI_API_KEY'), USD_PER_MTOK_IN 0.15, USD_PER_MTOK_OUT 0.60);
CREATE TABLE found AS FROM (VALUES
('insulin pen in a blue case', 'waiting room'),
('laptop with a school sticker', 'train to the airport'),
('wedding ring wrapped in a napkin', 'cafe counter'),
('box of live crickets labelled lizard food', 'train to Leeds'),
('cello in a hard case', 'platform 3'),
('half-eaten sandwich', 'train to Leeds'),
('broken umbrella', 'platform 1')) t(item, place);
CREATE TABLE shelves AS FROM (VALUES ('medical'), ('electronics'), ('valuables'),
('animals'), ('food and rubbish'), ('general')) t(name);
-- Odds from 0 to 1
SELECT item, typed_prob(item, 'the owner needs this back today') AS urgent
FROM found ORDER BY urgent DESC;
-- insulin pen in a blue case 0.59
-- laptop with a school sticker 0.54
-- wedding ring wrapped in a napkin 0.54
-- half-eaten sandwich 0.37
-- broken umbrella 0.34
-- cello in a hard case 0.31
-- box of live crickets labelled lizard food 0.30
-- Pick a shelf; the options come from a table
SELECT item, typed_pick(item, 'which shelf?', (SELECT list(name) FROM shelves)) AS shelf
FROM found;
-- insulin pen in a blue case medical
-- laptop with a school sticker electronics
-- wedding ring wrapped in a napkin valuables
-- box of live crickets labelled lizard food animals
-- cello in a hard case general
-- half-eaten sandwich food and rubbish
-- broken umbrella general
-- The whole row goes in, so the model sees where it was found
SELECT item FROM found f WHERE typed_is(f, 'it was left on a train');
-- laptop with a school sticker
-- box of live crickets labelled lizard food
-- half-eaten sandwich
-- What did that cost?
SELECT requests, printf('$%.5f', spent_usd) AS spent FROM typed_usage();
-- 3 $0.00009
About typed_ai
typed_ai lets you ask an AI simple questions about each row of a table, straight from SQL. It is built
for TypeSafe Jev, which in our tests (October 2026) gave the most accurate answers at
the lowest cost, since it answers many rows in one request. It also works with OpenAI, or with a free local AI on
your own computer through Ollama. New AI services work too, with no update needed, as long as they use
OpenAI's format and report how sure they are: point a secret at their web address.
| Function | What you get back |
|---|---|
typed_is(value, statement [, cutoff]) |
true or false, for filtering with WHERE |
typed_prob(value, statement) |
how likely the statement is true, from 0 to 1 |
typed_pick(value, question, options) |
the best option; the options can come from a table |
typed_score(value, question, levels) |
a rating on your scale, e.g. 0 for the lowest level up to 3 |
typed_ask(value, question_json) |
the full answer, with the odds for every option |
typed_usage() |
what you have spent, your limit, and how many answers were reused for free |
value can be text, any other value, or a whole row.
Safe by default:
- At most 1,000 rows per query and $1.00 in total, until you raise
typed_max_rowsortyped_budget_usd. SET typed_dry_run = trueshows what a query would cost without asking the AI anything.- If a job stops partway, the answers you paid for are kept. Run it again and you pay only for the rest.
Each value you check is sent to the AI service you set up. For private data, use a local model, so nothing leaves your computer.
Audit an AI agent
Did the agent do more than it was asked? The whole row goes in, so the model compares the request with the action.
CREATE TABLE agent_log AS FROM (VALUES
('book a table for 2 on Friday', 'booked a table for 2, Friday 7pm'),
('cancel my gym membership', 'cancelled it and deleted all workout history'),
('find a cheaper phone plan', 'listed 3 cheaper plans'),
('reply to Sam that I am running late', 'replied to Sam and to everyone in the team chat'),
('pay the electricity bill', 'paid the bill and set up autopay for 12 months'))
t(asked, did);
SELECT asked, typed_prob(a, 'the agent did more than the user asked for') AS overreach
FROM agent_log a ORDER BY overreach DESC;
-- reply to Sam that I am running late 0.95
-- pay the electricity bill 0.95
-- cancel my gym membership 0.93
-- book a table for 2 on Friday 0.60 (the agent chose 7pm on its own)
-- find a cheaper phone plan 0.19
Find private data before sharing
Jev and a local model both name the kind of private data. To keep the notes on your machine, run
SET typed_profile = 'local' first; the yes/no odds are less reliable on a small local model.
CREATE TABLE notes AS FROM (VALUES
(1, 'Customer liked the new blue colour, wants it in size 10.'),
(2, 'Call Priya back on 07700 900123 after 5pm.'),
(3, 'Said she is on chemo, so deliveries must be left at the door.'),
(4, 'Paid with a card ending 4417, asked for a refund next month.'),
(5, 'Asked if the store opens on bank holidays.')) t(id, note);
SELECT id, typed_pick(note, 'what private data does this note contain?',
['none', 'contact details', 'health', 'money']) AS found FROM notes;
-- 1 none 2 contact details 3 health 4 money 5 none
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 | [] |