{"slug": "duckdb-extension-typed-jev-answers-as-real-sql-types", "title": "DuckDB extension: typed Jev answers as real SQL types", "summary": "TypeSafe released a DuckDB extension for Jev, its model for typed answers, that lets users ask questions about every row of a table in SQL and receive a real SQL type back rather than free text. The extension exposes four functions — jev_choice, jev_score, jev_noul and jev_ask — whose criteria literal defines both the permitted answer set and the column type, returning ENUM, DOUBLE or STRUCT values, with invalid criteria failing at query planning rather than mid-execution. Requests are batched 16 rows at a time, identical requests are cached for the life of the process, and the extension is MIT licensed.", "body_md": "Ask a question about every row of a table, in SQL, and get a real SQL type back.\nA DuckDB extension over [Jev](https://typesafe.ai), TypeSafe's model for typed\nanswers instead of text.\n\nThe data you want to ask about already sits in a table or a Parquet file, and SQL is the query language everyone has. So ask the question where the data is, instead of pulling it out, wrapping an API in a script and writing the answer back.\n\nJev picks from a set you define instead of writing an answer you then check. The result is typed by construction, not by validation, and not generating is what makes it cheap enough to run on every row.\n\n```\nD CREATE SECRET (TYPE jev, API_KEY 'sk-...');\n```\n\n`ENDPOINT` and `MODEL` are optional. With no secret, queries fail when planned\nrather than part-way through.\n\nEach function takes the row's text, then a **criteria** literal. The criteria is\nboth the set of permitted answers and the column's type, so it must be constant.\n\n| Call | Criteria | Column | \n|---|---|---|\n| `jev_choice(text, MAP{option: meaning})` | what each option means | `ENUM` of those options | \n| `jev_score(text, [worst, ..., best])` | an ordered rubric | `DOUBLE` on that scale | \n| `jev_noul(text, MAP{'true': …, 'false': …})` | what yes and no mean | `DOUBLE` , probability of yes | \n| `jev_ask(text, {name: criteria, …})` | any mix | `STRUCT` , one field per question | \n\nThe descriptions are the only thing telling the model what an option means.\n\nOne request carries many questions, so ask them together. Each field takes its type\nfrom its criteria; choice and score carry a `<name>_confidence` beside them.\n\n```\nD WITH asked AS (\n      SELECT id, jev_ask(body, {\n          intent:   MAP{'refund': 'wants money back', 'bug': 'something broken',\n                        'praise': 'a compliment'},\n          severity: ['trivial', 'minor', 'normal', 'serious', 'critical'],\n          urgent:   MAP{'true': 'needs a reply today', 'false': 'can wait'}\n      }) AS a FROM tickets)\n  SELECT id, a.intent, round(a.severity, 1) AS severity, round(a.urgent, 2) AS urgent\n  FROM asked ORDER BY id;\n┌───────┬─────────────────────────────────┬──────────┬────────┐\n│  id   │             intent              │ severity │ urgent │\n│ int32 │ enum('refund', 'bug', 'praise') │  double  │ double │\n├───────┼─────────────────────────────────┼──────────┼────────┤\n│     1 │ refund                          │      1.7 │   0.52 │\n│     2 │ bug                             │      3.0 │   0.49 │\n│     3 │ praise                          │      0.6 │   0.46 │\n└───────┴─────────────────────────────────┴──────────┴────────┘\n```\n\nAn empty option map, a duplicate option, a one-level rubric, over 255 options or a non-constant criteria all fail when the query is planned, not on row 400,000.\n\nOne request per row, so treat these like a join against a paid service. Rows in a\nchunk go out sixteen at a time. Identical requests are cached for the life of the\nprocess. DuckDB evaluates a function once per place it appears, so without that the\nsame call in `WHERE` and `SELECT` bills twice per row. Rate limits, server errors and\ndropped connections retry with backoff; anything else fails at once.\n\n```\nD SELECT * FROM jev_usage();\n┌──────────┬────────────┬──────────────┬───────────────┐\n│ requests │ cache_hits │ input_tokens │ output_tokens │\n│  int64   │   int64    │    int64     │     int64     │\n├──────────┼────────────┼──────────────┼───────────────┤\n│        3 │          0 │         1163 │           208 │\n└──────────┴────────────┴──────────────┴───────────────┘\n```\n\n`SET jev_on_error = 'null'` loses the row instead of the query.\n\nMIT", "url": "https://wpnews.pro/news/duckdb-extension-typed-jev-answers-as-real-sql-types", "canonical_source": "https://github.com/colliber/duckdb-jev", "published_at": "2026-09-20 10:25:06+00:00", "updated_at": "2026-09-20 10:53:02.348164+00:00", "lang": "en", "topics": ["ai-tools", "structured-data", "developer-tools", "large-language-models"], "entities": ["DuckDB", "Jev", "TypeSafe"], "alternates": {"html": "https://wpnews.pro/news/duckdb-extension-typed-jev-answers-as-real-sql-types", "markdown": "https://wpnews.pro/news/duckdb-extension-typed-jev-answers-as-real-sql-types.md", "text": "https://wpnews.pro/news/duckdb-extension-typed-jev-answers-as-real-sql-types.txt", "jsonld": "https://wpnews.pro/news/duckdb-extension-typed-jev-answers-as-real-sql-types.jsonld"}}