Call LLMs from SQL to classify, summarize, extract typed fields, embed and search text, or query your tables, with local models or 35+ hosted APIs
Installing and Loading
INSTALL ai FROM community;
LOAD ai;
Example
-- 1. Choose where the model runs.
-- Local: any model server on your machine, so data never leaves it. This example uses
-- Ollama with its default model (`ollama pull llama3.2`); llama.cpp, LM Studio and vLLM work too.
SET duckdb_ai_provider = 'ollama';
-- Hosted: put the key in the environment (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, ...)
-- and pick the provider instead, for example: SET duckdb_ai_provider = 'anthropic';
-- 2. Run model functions over a table like any other SQL function.
CREATE TABLE tickets AS SELECT * FROM (VALUES
(1, 'I was charged twice for the same invoice. Please refund one of them.'),
(2, 'The dashboard has been down for every user since 9am.')
) t(id, body);
-- A label, a yes/no check and a summary for every row
SELECT id,
ai_classify(body, MAP {'billing': 'Charges, invoices and refunds',
'outage': 'Something is down for users',
'other': 'Anything else'}) AS category,
ai_filter(body, 'asks for a refund') AS wants_refund,
ai_summarize(body) AS summary
FROM tickets;
-- Typed fields from a JSON Schema, returned as a STRUCT
SELECT id, r.product_area, r.urgency
FROM (SELECT id, ai_extract_record(body, '{"type": "object",
"properties": {"product_area": {"type": "string"}, "urgency": {"type": "integer"}},
"required": ["product_area", "urgency"]}') AS r
FROM tickets);
-- 3. See what it cost: calls, tokens, failures and estimated spend (NULL for local models)
SELECT sum(calls) AS calls, sum(total_tokens) AS tokens, sum(failures) AS failures,
sum(estimated_cost_usd) AS estimated_usd
FROM ai_usage_summary();
About ai
The ai extension brings language models into DuckDB SQL. Point a function at a column and it calls
the model once per row, several rows at a time, and returns an ordinary DuckDB value (VARCHAR,
BOOLEAN, DOUBLE, a typed STRUCT, a LIST or an embedding) that you can filter, join, aggregate
and save like any other column.
What you can do
- Label and rewrite text:
ai_classify,ai_classify_labels,ai_sentiment,ai_summarize,ai_translate,ai_fix_grammar,ai_redact,ai_complete. - Get typed answers instead of free text:
ai_extract_recordreturns aSTRUCTfrom a JSON Schema (pydantic schemas work),ai_filteraBOOLEAN,ai_scoreaDOUBLEfrom 0 to 1, andai_complete_jsonvalidated JSON. - Ask several typed questions per row with decision models:
ai_decidereturns probabilities and confidences from TypeSafe Jev, Cloudflare Clef, Perplexity or a local decision model. - Search by meaning:
ai_embed,ai_similarityandai_rerank, withai_generate_chunksandai_prep_searchto split long documents. - Summarize groups with the
ai_aggandai_summarize_aggaggregates. - Ask questions about your data:
ai_sqlwrites a validated read-onlySELECTfrom your schema, andai_query_dataruns it.
Any model, local or hosted
Local model servers keep data on your machine: Ollama, llama.cpp, LM Studio, vLLM or any OpenAI-compatible endpoint. Hosted APIs include OpenAI, Anthropic, Google Gemini and Vertex AI, Azure OpenAI, Amazon Bedrock, Mistral, DeepSeek, Groq, Together, OpenRouter, Databricks and Snowflake Cortex, among more than 35 providers. Switching is one setting; the SQL stays the same. Replies are read the way small models write them, so a label in markdown or followed by an explanation still counts.
Safe to run on real tables
- API keys come only from environment variables or
TYPE duckdb_aisecrets, never from SQL arguments. Loading the extension makes no network calls, andallowed_hostslimits where data can be sent. ai_completion_request_jsonshows a request without sending it.ai_usage()andai_usage_summary()track calls, tokens, failures and estimated cost.- Per-row error handling (
on_error := 'null',ai_try_complete), retries with backoff, rate and concurrency limits, and an opt-in response cache keep long batch runs predictable.
Learn more
- Documentation: https://leonardovida.github.io/duckdb-ai/docs
- Function reference: https://leonardovida.github.io/duckdb-ai/docs/functions
- Cookbooks for ticket triage, semantic search, resumable batch runs and cost tracking: https://leonardovida.github.io/duckdb-ai/docs/cookbooks
- For agents, the full docs as one text file: https://leonardovida.github.io/duckdb-ai/llms-full.txt
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| ai_agg | aggregate | Aggregate: sends all text in a group to the model with one instruction and returns one answer. | NULL | [SELECT product, ai_agg(review, 'List the top three complaints') FROM reviews GROUP BY product;] |
| ai_build_classifier | aggregate | Aggregate (experimental): trains a cheap embedding classifier from model-labeled samples for ai_classify_optimized. | NULL | [SELECT ai_build_classifier(review, ['positive', 'negative'], optimization := 'minimize_cost') FROM reviews;] |
| ai_classify | scalar | Picks exactly one label for each row from the labels you list. | NULL | [SELECT ai_classify(body, ['billing', 'outage', 'other']) FROM tickets;, SELECT ai_classify(body, 'positive, negative, mixed') FROM reviews;] |
| ai_classify_labels | scalar | Picks every label that applies (zero or more) and returns them as a VARCHAR[]. | NULL | [SELECT ai_classify_labels(review, ['shipping', 'pricing', 'quality']) FROM reviews;] |
| ai_classify_optimized | scalar | Classifies with a saved embedding classifier and asks a language model only for uncertain rows. | NULL | [SELECT ai_classify_optimized(review, classifier) FROM reviews, saved_classifier;] |
| ai_classify_result | scalar | Multi-label classification that returns STRUCT(value, error, metadata) instead of failing the query. | NULL | [SELECT ai_classify_result(review, ['shipping', 'pricing', 'quality']) FROM reviews;] |
| ai_clear_cache | table | Table function: clears the in-memory response and generated SQL caches. | NULL | [SELECT * FROM ai_clear_cache();] |
| ai_clear_usage | table | Table function: clears recorded usage events and counters. | NULL | [SELECT * FROM ai_clear_usage();] |
| ai_complete | scalar | Sends a prompt to a language model and returns the response text. | NULL | [SELECT ai_complete('Describe DuckDB in one sentence.');, SELECT ai_complete('Say hello', provider := 'ollama', model := 'qwen3.8:27b');] |
| ai_complete_json | scalar | Asks a model for JSON and returns it after checking it is a valid object or array. | NULL | [SELECT ai_complete_json('Return a JSON object with the keys name and kind for DuckDB');] |
| ai_complete_record | table | Table function: asks a model for one JSON object and returns its fields as typed columns from a JSON Schema. | NULL | [SELECT * FROM ai_complete_record('Describe DuckDB', '{"type": "object", "properties": {"name": {"type": "string"}, "year": {"type": "integer"}}}');] |
| ai_completion_request_json | scalar | Shows the JSON request a completion would send, without calling the provider. Useful for debugging. | NULL | [SELECT ai_completion_request_json('Say hello', provider := 'openai', model := 'gpt-5.6-luna');] |
| ai_count_tokens | scalar | Estimates the number of tokens in the text, locally. | NULL | [SELECT sum(ai_count_tokens(body)) FROM tickets;] |
| ai_decide | scalar | Answers typed questions with a decision model and returns a STRUCT of choices, scores and yes/no probabilities. | NULL | [SELECT ai_decide(body, {team: MAP {'billing': 'Payments', 'technical': 'Errors and outages'}}, provider := 'ollama') FROM tickets;, SELECT ai_decide(body, {urgent: MAP {'true': 'Needs action now', 'false': 'Can wait'}}, provider := 'cloudflare', model := 'clef-flash') FROM tickets;] |
| ai_deprovision_endpoint | table | Table function: starts removing a model serving endpoint. | NULL | [SELECT * FROM ai_deprovision_endpoint('support_model');] |
| ai_embed | scalar | Turns text into an embedding vector (DOUBLE[]) for semantic search. | NULL | [SELECT ai_embed(body) FROM tickets;, SELECT ai_embed('wrong charge', provider := 'ollama', model := 'nomic-embed-text');] |
| ai_embedding_request_json | scalar | Shows the JSON request an embedding would send, without calling the provider. | NULL | [SELECT ai_embedding_request_json('duck', provider := 'openai', model := 'text-embedding-3-small');] |
| ai_endpoint_status | table | Table function: returns the status of an endpoint provisioning operation. | NULL | [SELECT * FROM ai_endpoint_status('operation-id');] |
| ai_explain_sql | table | Table function: explains in plain English what a read-only SELECT does. | NULL | [SELECT * FROM ai_explain_sql('SELECT region, sum(amount) FROM sales GROUP BY ALL');] |
| ai_extract | scalar | Extracts the information you describe from the text and returns it as text. | NULL | [SELECT ai_extract('Anna is 31 and lives in Rome', 'the age of the person');] |
| ai_extract_record | scalar | Extracts typed fields from each row's text and returns a STRUCT shaped by a JSON Schema. | NULL | [SELECT ai_extract_record(body, '{"type": "object", "properties": {"product": {"type": "string"}, "urgency": {"type": "integer"}}}') FROM tickets;] |
| ai_filter | scalar | Returns true when the text matches a condition written in plain English. | NULL | [SELECT * FROM reviews WHERE ai_filter(review, 'mentions a shipping problem');] |
| ai_fix_grammar | scalar | Corrects grammar, spelling and punctuation. | NULL | [SELECT ai_fix_grammar('thes is a tst');] |
| ai_fix_sql | table | Table function: rewrites a broken query into a working read-only SELECT, or fixes one line with mode := 'line'. | NULL | [SELECT * FROM ai_fix_sql('SELEC 42');, SELECT * FROM ai_fix_sql('SELECT amout FROM sales', error := 'column amout not found', fix_attempts := 2);] |
| ai_generate_chunks | table | Table function: splits text into fixed-size or recursive chunks for embedding. | NULL | [SELECT * FROM ai_generate_chunks('First paragraph. Second paragraph.', chunk_size := 20);] |
| ai_is_read_only_sql | scalar | Returns true when the SQL is exactly one read-only SELECT statement. | NULL | [SELECT ai_is_read_only_sql('SELECT 42');] |
| ai_jev | scalar | TypeSafe Jev form of ai_decide: typed decisions with up to 32 rows per request. | NULL | [SELECT ai_jev(body, {team: MAP {'billing': 'Payments', 'other': 'Other'}}) FROM tickets;] |
| ai_model_prices | table | Table function: the built-in per-model token prices used for cost estimates. | NULL | [SELECT * FROM ai_model_prices() WHERE provider = 'openai';] |
| ai_models | table | Table function: lists model profiles created with CREATE EXTERNAL MODEL. | NULL | [SELECT * FROM ai_models();] |
| ai_parse_document | table | Table function: parses a PDF or other file (BLOB) through a remote document parser. | NULL | [SELECT * FROM ai_parse_document(read_blob('document.pdf').content, 'application/pdf', 'documents');] |
| ai_prep_search | table | Table function: builds RAG-ready chunks with titles, headings and page numbers attached for embedding. | NULL | [SELECT * FROM ai_prep_search('# Billing' || chr(10) || 'Invoices are sent monthly.', title := 'Guide');] |
| ai_provider_base_url | scalar | Returns the default API base URL for a provider. | NULL | [SELECT ai_provider_base_url('openai');] |
| ai_provider_call | scalar | Sends a provider-native JSON request and returns the full JSON response, for tools, reasoning and streaming. | NULL | [SELECT ai_provider_call('{"model": "gpt-5.6-luna", "messages": [{"role": "user", "content": "Hello"}]}', provider := 'openai');] |
| ai_provider_protocol | scalar | Returns the wire protocol the extension uses for a provider. | NULL | [SELECT ai_provider_protocol('anthropic');] |
| ai_provision_endpoint | table | Table function: dry-runs provisioning of a model serving endpoint, or submits it with dry_run := false. | NULL | [SELECT * FROM ai_provision_endpoint('support_model');] |
| ai_query_cache_stats | table | Table function: shows size, hits, misses and evictions of the generated SQL cache. | NULL | [SELECT * FROM ai_query_cache_stats();] |
| ai_query_data | table | Table function: answers a question about your tables by generating a read-only SELECT and running it. | NULL | [SELECT * FROM ai_query_data('total sales by region', include_tables := ['main.sales']);] |
| ai_recommended_batch_size | scalar | Suggests how many rows to send per batch to stay under a provider's token and request rate limits. | NULL | [SELECT ai_recommended_batch_size(200, 100, 100000);] |
| ai_redact | scalar | Masks personal data, credentials, secrets and payment numbers in the text. | NULL | [SELECT ai_redact('Contact [email protected] or +1 555 0100');] |
| ai_rerank | scalar | Asks a language model how relevant a candidate text is to a query, from 0 to 1. | NULL | [SELECT title FROM docs ORDER BY ai_rerank('how to export to Parquet', body) DESC LIMIT 5;] |
| ai_schema_prompt | table | Table function: describes your tables and columns as text to give a model context. | NULL | [SELECT summary FROM ai_schema_prompt(include_tables := ['main.sales']);] |
| ai_score | scalar | Rates how well the text meets a criterion, from 0 to 1. | NULL | [SELECT ai_score(body, 'reports a production outage') FROM tickets;] |
| ai_secrets | table | Table function: lists duckdb_ai secrets with API keys redacted. | NULL | [SELECT * FROM ai_secrets();] |
| ai_sentiment | scalar | Labels the sentiment of the text as positive, neutral or negative. | NULL | [SELECT ai_sentiment(review) FROM reviews;] |
| ai_similarity | scalar | Embeds two texts and returns their cosine similarity, from -1 to 1. | NULL | [SELECT ai_similarity('refund request', 'I want my money back');] |
| ai_sql | scalar | Writes one read-only DuckDB SELECT that answers a question about your tables and returns it as text. | NULL | [SELECT ai_sql('total sales by region');, SELECT ai_sql('total sales by region', include_tables := ['main.sales'], fix_attempts := 2);] |
| ai_summarize | scalar | Summarizes the text. | NULL | [SELECT ai_summarize(body) FROM tickets;] |
| ai_summarize_agg | aggregate | Aggregate: summarizes all text values in a group. | NULL | [SELECT product, ai_summarize_agg(review) FROM reviews GROUP BY product;] |
| ai_translate | scalar | Translates the text into the target language. | NULL | [SELECT ai_translate(body, 'English') FROM tickets;] |
| ai_try_complete | scalar | Like ai_complete, but returns STRUCT(response, error) so one failed row does not stop the query. | NULL | [SELECT r.response, r.error FROM (SELECT ai_try_complete(body) AS r FROM tickets);] |
| ai_usage | table | Table function: lists recent model calls with status, latency, tokens and estimated cost. | NULL | [SELECT function_name, status, total_tokens, estimated_cost_usd FROM ai_usage();] |
| ai_usage_summary | table | Table function: totals calls, retries, failures, cache hits, tokens and cost per query. | NULL | [SELECT * FROM ai_usage_summary();] |
| ai_usage_totals | table | Table function: lifetime provider counters that are kept even after old events are dropped. | NULL | [SELECT * FROM ai_usage_totals();] |
| ai_validate_read_only_sql | scalar | Returns the normalized SQL, or raises an error unless it is exactly one read-only SELECT. | NULL | [SELECT ai_validate_read_only_sql('SELECT 42');] |
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 |
|---|---|---|---|---|
| duckdb_ai_aggregate_model | Default model for ai_agg and ai_summarize_agg | VARCHAR | GLOBAL | [] |
| duckdb_ai_allowed_hosts | Comma-separated hosts that provider and log requests may reach; empty allows all hosts | VARCHAR | GLOBAL | [] |
| duckdb_ai_base_url | Provider endpoint override, for example a gateway or self-hosted server URL | VARCHAR | GLOBAL | [] |
| duckdb_ai_cache | Reuse successful responses for identical requests, in memory for this DuckDB instance | BOOLEAN | GLOBAL | [] |
| duckdb_ai_cache_max_entries | Maximum number of cached responses (0 to 1000000); 0 stores nothing, -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_cache_ttl_seconds | Maximum age of cached responses in seconds (0 to 31536000); 0 never expires, -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_completion_model | Default model for ai_complete, ai_complete_json, ai_complete_record, ai_rerank and ai_score | VARCHAR | GLOBAL | [] |
| duckdb_ai_connect_timeout_seconds | Connection timeout for each provider request in seconds (1 to 31536000); -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_embedding_model | Default embedding model for ai_embed, ai_similarity and the classifier functions | VARCHAR | GLOBAL | [] |
| duckdb_ai_input_token_price_per_million | Input token price in USD per million tokens for estimated_cost_usd; -1 disables manual pricing | DOUBLE | GLOBAL | [] |
| duckdb_ai_log_endpoint | HTTP endpoint that receives usage logs; prompt text is excluded unless log_include_text is set | VARCHAR | GLOBAL | [] |
| duckdb_ai_log_format | Usage log payload format: generic_json or otlp_json | VARCHAR | GLOBAL | [] |
| duckdb_ai_log_include_text | Include prompt and response text in usage logs (off by default) | BOOLEAN | GLOBAL | [] |
| duckdb_ai_log_sample_rate | Fraction of usage events sent to the log endpoint (0 to 1); -1 uses the default | DOUBLE | GLOBAL | [] |
| duckdb_ai_log_strict | Fail the SQL query when a usage log cannot be delivered | BOOLEAN | GLOBAL | [] |
| duckdb_ai_log_tags | Tags added to every usage log, for example 'app=triage,env=prod' | VARCHAR | GLOBAL | [] |
| duckdb_ai_max_concurrent_requests | Maximum provider requests in flight at once (0 to 64); 0 removes the limit, -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_min_request_interval_ms | Minimum milliseconds between the starts of provider requests (0 to 60000); -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_model | Default model for every ai_* function unless model := or a function-family model setting is set | VARCHAR | GLOBAL | [] |
| duckdb_ai_on_error | Failed provider calls: fail stops the query, null returns NULL, capture also keeps the error where the function returns one | VARCHAR | GLOBAL | [] |
| duckdb_ai_output_token_price_per_million | Output token price in USD per million tokens for estimated_cost_usd; -1 disables manual pricing | DOUBLE | GLOBAL | [] |
| duckdb_ai_prompt_cache | Send provider-side prompt caching hints (OpenAI, Anthropic, xAI) for repeated prompt prefixes | BOOLEAN | GLOBAL | [] |
| duckdb_ai_provider | Default provider for ai_* functions, for example 'ollama', 'openai' or 'anthropic' | VARCHAR | GLOBAL | [] |
| duckdb_ai_response_format | Default completion response format: text, json_object or json_schema | VARCHAR | GLOBAL | [] |
| duckdb_ai_response_schema | Default JSON Schema for structured completion responses | VARCHAR | GLOBAL | [] |
| duckdb_ai_retry_backoff_ms | Base wait between retries in milliseconds (0 to 60000), with jitter; -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_retry_count | Retries after network errors and retryable statuses such as 429 and 5xx (0 to 10); -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_sql_assistant_model | Default model for ai_sql, ai_query_data, ai_explain_sql and ai_fix_sql | VARCHAR | GLOBAL | [] |
| duckdb_ai_task_model | Default model for text tasks such as ai_classify, ai_summarize, ai_translate and ai_filter | VARCHAR | GLOBAL | [] |
| duckdb_ai_timeout_seconds | Timeout for each provider request in seconds; 0 uses the extension default | BIGINT | GLOBAL | [] |
| duckdb_ai_token_limit_per_minute | Cap on estimated provider tokens per rolling minute (0 to 10000000000); 0 removes the cap, -1 uses the default | BIGINT | GLOBAL | [] |
| duckdb_ai_use_builtin_model_prices | Estimate cost from the built-in ai_model_prices() catalog when no manual price is set | BOOLEAN | GLOBAL | [] |