Typed decisions as SQL functions for SQLite and PostgreSQL
Typed decisions as SQL functions, for SQLite, PostgreSQL and MySQL. Ask a yes/no question,
pick from named options, or place a row on a rubric, and get calibrated probabilities
back – so a table of text can be classified, scored or filtered without leaving the
database.
A decision comes from whichever backend you load. A local checkpoint runs in-process
through laya.cpp and sends nothing anywhere;
an HTTP endpoint forwards to any service speaking the System One request shape. The SQL
is identical either way.
-- SQLite
.load ./dist/debug/decision_query
select id from tickets where noul(body, 'Does the customer request a refund?') > 0.5;
-- PostgreSQL
CREATE EXTENSION decision_query;
SET decision_query.model_dir = '/srv/models/laya';
SELECT id FROM tickets WHERE noul(body, 'Does the customer request a refund?') > 0.5;
-- MySQL, after `mysql -u root < mysql/install.sql`
SELECT dq_load('/srv/models/laya');
SELECT id FROM tickets WHERE noul(body, 'Does the customer request a refund?') > 0.5;
All three modules provide dq_load, dq_backend, dq_version, noul,
choice, score and decide(), which returns the full answers object as JSON.
| Directory | Contents |
|---|---|
engine/ |
Shared C++ engine: one resident checkpoint per process, serialized calls, lazy loading. decision_engine.hpp plus the decision-query-engine CMake target that builds laya.cpp and ggml as static position-independent archives. |
laya.cpp/ |
Git submodule with the native runtime, tokenizers and the laya-cli tool. |
sqlite/ |
SQLite loadable module, static library, Python wheel and tests. |
postgres/ |
PostgreSQL extension built from the pg_extension CMake template, with pg_regress tests. |
mysql/ |
MySQL loadable functions, install scripts and a test that runs them in a throwaway mysqld. |
tests/ |
Native Catch2 unit tests for the engine, the HTTP backend and the SQLite functions; no checkpoint needed. |
fuzz/ |
libFuzzer targets with seed corpora, run in CI by ClusterFuzzLite. |
cmake/ |
Warning, sanitizer and libFuzzer modules vendored from cpp-best-practices/cmake_template. |
The top-level CMakeLists.txt builds everything into one tree, so ggml and the laya
runtime compile once. Each module directory also configures on its own.
Requirements: a C++20 compiler, CMake 3.24+, ICU and nlohmann-json; the SQLite extension
headers for sqlite/; PostgreSQL server development files for postgres/; the MySQL
client development headers for mysql/; optionally the CUDA toolkit. On Debian-like systems:
sudo apt-get install cmake ninja-build libicu-dev nlohmann-json3-dev libsqlite3-dev \
postgresql-16 postgresql-server-dev-16
git clone --recurse-submodules https://github.com/r33drichards/decision-query.git
cd decision-query
make loadable static # SQLite: dist/debug/decision_query.so, libdecision_query.a, decision_query.h
make postgres # PostgreSQL: build/postgres/decision_query.so and decision_query.control
sudo make postgres-install # into the directories reported by pg_config
make mysql # MySQL: build/mysql/decision_query.so (needs libmysqlclient-dev)
sudo make mysql-install # into the plugin_dir reported by mysql_config
The CUDA backend is enabled automatically when CMake finds a CUDA compiler. Force a
choice with CMAKE_FLAGS='-DDQ_CUDA=OFF' make loadable or -DDQ_CUDA=ON,
adding -DCMAKE_CUDA_ARCHITECTURES=<arch> for your GPU as described in the laya.cpp README.
The PostgreSQL module is configured automatically when pg_config and the server headers
are found; -DDQ_POSTGRES=OFF skips it. Likewise the MySQL module is configured when
udf_registration_types.h is found; -DDQ_MYSQL=OFF skips it.
Download a pinned checkpoint into models/laya (requires pip install huggingface_hub):
make model # english
make model MODEL_VARIANT=multilingual # models/laya/multilingual
make model MODEL_VARIANT=typed-decisions
The english checkpoint lives at the model-store root; the other variants live in a
subdirectory named after the variant, matching the laya.cpp layout.
dq_load(dir, options) and the lazy-loading settings take a JSON object:
| Key | Default | Meaning |
|---|---|---|
variant |
english |
english, multilingual or typed-decisions; appended to dir when not english. |
cuda |
true when built with CUDA | Use the CUDA backend. |
metal |
true on Apple builds without CUDA | Use the Apple Metal backend. Faster, but see the accuracy note below. |
key |
$DQ_API_KEY |
Bearer token for an HTTP endpoint. Prefer the environment variable; a key written into SQL lands in your shell history. |
model |
jev-latest |
Model name sent to an HTTP endpoint. |
tensor_core |
false | Compensated Tensor Core FP32 projections (CUDA). |
flash |
false | Fused FP32 attention (CUDA). |
bf16 |
false | Native mixed BF16 (CUDA; implies flash). |
When no model is resident, the first inference call loads one from DQ_MODEL_DIR and
DQ_OPTIONS in the environment (PostgreSQL consults its decision_query.model_dir and
decision_query.options settings first). If nothing is found the call fails with
No decision backend loaded; call dq_load(dir_or_url) or set DQ_MODEL_DIR.
dq_load takes a checkpoint directory or the URL of a service speaking the
System One request shape. The SQL functions are identical either way.
select dq_load('./models/laya'); -- local, in-process
select dq_load('https://api.typesafe.ai/v1/systemone'); -- TypeSafe Jev
select dq_load('http://localhost:8000/v1/systemone',
json_object('model', 'reflex-4b')); -- a local server
Credentials resolve in this order: the key option, then key_file, then
$DQ_API_KEY_FILE, then $DQ_API_KEY.
Prefer a file. A key written into a SQL statement is a key in your shell history,
and on PostgreSQL it also reaches pg_stat_activity and the statement log.
select dq_load('https://api.typesafe.ai/v1/systemone',
json_object('key_file', '/etc/decision-query/api-key'));
On PostgreSQL, set the superuser-only decision_query.api_key_file GUC instead:
decision_query.api_key_file = '/etc/decision-query/api-key' # postgresql.conf
It names a path rather than holding the secret, so the value never appears in
pg_settings, SHOW ALL or a config dump. $DQ_API_KEY works there too but
is cluster-wide and cannot be scoped to a role.
https:// requires OpenSSL at build time. CMake reports which you have:
decision-query: HTTPS decision endpoints enabled, or a note that only http:// will work.
A remote backend sends the text you are asking about to that service. Local
checkpoints send nothing anywhere.
The two multi-option functions take different shapes, which is easy to get
wrong: choice takes an object of option to description, and score takes an
array of level descriptions, lowest first. A bare array passed to choice works
against a local checkpoint but is rejected by the System One HTTP shape, so the
object form is the portable one:
select choice('git push origin main', 'Which tool does this command use?',
json_object('git', 'the git version control tool',
'docker', 'the docker container tool'));
score places a row on an ordered rubric and returns a number in the range of
the levels you gave it:
select score('rm -rf /', 'How destructive is this?',
json_array('harmless', 'local damage', 'irreversible'));
Searching shell history in plain English walks
through cloning, building, loading the extension against an
atuin database, and asking questions like “does this command
contain an API key, and whose is it?”
make test-loadable # SQLite, no checkpoint needed
DQ_MODEL_DIR=models/laya make cli test-loadable # SQLite with checkpoint and CLI parity
DQ_MODEL_DIR=models/laya DQ_OPTIONS='{"cuda": false}' make postgres
sudo make postgres-install && make test-postgres # pg_regress, not as root
DQ_MODEL_DIR=models/laya make mysql test-mysql # throwaway mysqld; checkpoint optional
The native unit tests run under the sanitizers and valgrind as well, and the fuzz
targets need clang with its compiler-rt (libclang-rt-18-dev on Ubuntu):
make test-native # tests/ with Catch2
CMAKE_FLAGS='-DDQ_ENABLE_SANITIZER_ADDRESS=ON -DDQ_ENABLE_SANITIZER_UNDEFINED=ON' \
CC=clang CXX=clang++ make test-native # ASan + UBSan; also _THREAD, _MEMORY
make memcheck # the same tests under valgrind
make fuzz FUZZ_RUNTIME=300 # each fuzz target for five minutes
CI runs these in tests.yml with -DDQ_WARNINGS_AS_ERRORS=ON;
lint.yml runs clang-format and clang-tidy (.clang-tidy)
on the lines a pull request changes and cppcheck over the tree;
codeql.yml runs CodeQL; and the cflite_* workflows
fuzz each pull request for ten minutes and main for an hour a day.
decide() to evaluate severaljson_object('metal', 0) for numbers that satisfy the documented tolerance, atMIT, see LICENSE. laya.cpp, ggml and the Laya checkpoints retain their own licenses.