Search Shortcut cmd + k | ctrl + k

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.

Maintainer(s): hfmsio

Compatibility Matrix

This table covers all DuckDB releases that have not reached end-of-life, including LTS releases, and shows the 2 most recent patch releases of each. Older patch releases are not listed, but the extension may be available for them as well.

Platform 1.4.4 1.4.5 1.5.5 1.5.6 2.0.0
Linux AMD64
Linux ARM64
macOS AMD64
macOS ARM64
Windows AMD64
Windows ARM64
WebAssembly

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.64
-- wedding ring wrapped in a napkin            0.59
-- laptop with a school sticker                0.53
-- box of live crickets labelled lizard food   0.36
-- broken umbrella                             0.36
-- half-eaten sandwich                         0.27
-- cello in a hard case                        0.26

-- 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, or be pictures
typed_score(value, question, levels) a rating on your scale, e.g. 0 for the lowest level up to 3
typed_box(photo, target) where something is in a photo, and how sure (Jev only)
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, a whole row, or (new in 0.1.1, Jev only, in preview) a photo.

Safe by default:

  • At most 1,000 rows per query and $1.00 in total, until you raise typed_max_rows or typed_budget_usd.
  • SET typed_dry_run = true shows 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

Photos (new in 0.1.1, Jev only)

With Jev, a PNG or JPEG file is sent as a picture, so read_blob turns a folder of photos into rows. Each side must be 280 to 1568 pixels; shrink big phone photos first with magick mogrify -resize '1568x1568>' *.jpg. A 1024 x 768 photo costs about $0.0001. Photo support is a Jev preview, so its limits may change. Other providers stop with a clear message instead of sending a photo. The community store has this version for DuckDB 2.0 and later; older DuckDB gets 0.1.0, which cannot send photos.

CREATE TABLE found AS SELECT parse_filename(filename, true) AS item, content FROM read_blob('found/*.jpg');

-- Ask about the photo
SELECT item, typed_pick(content, 'which shelf?', ['medical', 'electronics', 'valuables', 'animals',
                                                  'food and rubbish', 'general']) AS shelf FROM found;
-- hearing_aid      medical
-- leather_wallet   valuables

-- Photo and text together: does the desk note match the photo?
SELECT item, typed_prob({photo: content, note: note}, 'the note correctly describes the photo') AS matches
FROM found JOIN desk USING (item);
-- teddy_bear   (note: teddy bear in a red jumper)   0.96
-- wristwatch   (note: silver digital watch)         0.01   it is a gold watch

-- Options as pictures: which lost-item report, each one the owner's photo?
SELECT item, typed_pick(content, 'which lost-item report is the item in this photo?',
         (SELECT map_concat(map(list(report), list(photo)), MAP {'no report matches': NULL::BLOB})
          FROM reports)) AS report FROM found;
-- found_bottle_blue   bottle_blue
-- sandwich            no report matches

-- Where is it? Edges are fractions of the photo's width and height
SELECT typed_box(content, 'the winding crown') FROM found WHERE item = 'wristwatch';
-- {'left': 0.1, 'top': 0.73, 'right': 0.22, 'bottom': 0.88, 'confidence': 0.89}

Added Functions

function_name function_type description comment examples
typed_ask scalar The full answer as JSON, with the odds for every option NULL [typed_ask(note, '{"v": 1, "type": "pick", "question": "how urgent?", "options": ["now", "later"]}')]
typed_box scalar Where something is in a photo: box edges from 0 to 1, and confidence (Jev only) NULL [typed_box(content, 'the coffee mug')]
typed_is scalar True when the AI thinks the statement is true (cutoff 0.5 unless given) NULL [typed_is(review, 'the customer is angry')]
typed_pick scalar The option that fits best; options are names, or a MAP of name to picture (Jev only) NULL [typed_pick(item, 'which shelf?', ['food', 'tools', 'other'])]
typed_prob scalar How likely the statement is true, from 0 to 1 NULL [typed_prob(item, 'the owner needs this back today')]
typed_score scalar A rating: 0 for the first level, 1 for the next, and so on NULL [typed_score(answer, 'how helpful is it?', ['not', 'a bit', 'very'])]
typed_usage table What has been spent, the limit, and how many answers were reused NULL [FROM typed_usage()]

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_value
temp_directory Set the directory to which temporary files are written. VARCHAR GLOBAL [] post.db.tmp
typed_batch_size Rows per request, up to the provider's limit BIGINT GLOBAL [] 10
typed_budget_usd Most dollars this database may spend, counted since the extension loaded DOUBLE GLOBAL [] 1.0
typed_cache_mb Memory, in MB, for saved answers BIGINT GLOBAL [] 256
typed_concurrency Most requests sent at the same time BIGINT GLOBAL [] 4
typed_dry_run Price queries and call nothing; results are NULL BOOLEAN GLOBAL [] false
typed_fail_after Stop asking for the rest of the query after this many failed requests in a row; 0 never stops BIGINT GLOBAL [] 3
typed_max_input_chars Longest input value, in characters; longer values are refused, never cut BIGINT GLOBAL [] 20000
typed_max_retries Extra tries when the AI service is busy, down or drops the connection BIGINT GLOBAL [] 3
typed_max_rows Most rows one query may send to the model; cached rows are free BIGINT GLOBAL [] 1000
typed_on_stop What a stopped job shows: 'error' fails the query, 'null' leaves answers empty, 'row' adds the reason to each row VARCHAR GLOBAL [] error
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 to wait for each try BIGINT GLOBAL [] 30
© 2026 DuckDB Foundation, Amsterdam NL
DuckDB Home Code of Conduct Trademark Use Blog