Natural-language → SQL adapter for SQLRite. Pure-Rust LLM transport (Anthropic-first; OpenAI / Ollama bindings on the roadmap), no async runtime, no third-party LLM SDK — built directly on ureq + serde_json. Phase 7g.1 of the project.
Given a &str schema dump (a sequence of CREATE TABLE statements) plus a &str question, returns the generated SQL plus a one-sentence explanation:
use sqlrite_ask::{ask_with_schema, AskConfig};
let schema = "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);";
let cfg = AskConfig::from_env()?; // reads SQLRITE_LLM_API_KEY etc.
let resp = ask_with_schema(schema, "How many users are over 30?", &cfg)?;
println!("SQL: {}", resp.sql);
println!("Explanation: {}", resp.explanation);
println!("Cache hit: {} input tokens", resp.usage.cache_read_input_tokens);The crate is pure — it doesn't know about Connection, Database, or any engine type. The schema-dump-aware integration with the engine lives in the sqlrite-engine crate's ask feature instead. See the Most users want section below for which entry point to reach for.
This crate is the LLM transport layer. Most callers don't need to use it directly — the engine's ask feature wraps it in an ergonomic Connection::ask extension trait that introspects the schema for you:
[dependencies]
sqlrite-engine = "0.2"
# `sqlrite-engine`'s `ask` feature is on by default, which pulls
# `sqlrite-ask` transitively. You don't usually need to depend on
# this crate directly.use sqlrite::{Connection, ConnectionAskExt};
use sqlrite::ask::AskConfig;
let conn = Connection::open("foo.sqlrite")?;
let cfg = AskConfig::from_env()?;
let resp = conn.ask("How many users are over 30?", &cfg)?;Reach for sqlrite-ask directly only if:
- You want to build the schema dump yourself (custom introspection, schema generation, schema fragments) and call
ask_with_schemaover a&str. - You're targeting a runtime where
sqlrite-enginewon't compile (e.g. wasm32, where the WASM SDK usessqlrite-askwithdefault-features = falseto skip theureqHTTP transport). - You want to plug a custom
Providerimpl in viaask_with_schema_and_providerfor testing / proxying / non-Anthropic backends.
Three layers of precedence: per-call argument > AskConfig::from_env() > defaults. Env vars:
| Variable | Default | Purpose |
|---|---|---|
SQLRITE_LLM_PROVIDER |
anthropic |
Provider |
SQLRITE_LLM_API_KEY |
(none) | Provider API key (required for any LLM call) |
SQLRITE_LLM_MODEL |
claude-sonnet-4-6 |
Model ID |
SQLRITE_LLM_MAX_TOKENS |
1024 |
Per-call max output tokens |
SQLRITE_LLM_CACHE_TTL |
5m |
Anthropic prompt-cache TTL on the schema dump (5m, 1h, off) |
AskConfig's Debug impl deliberately omits the API key value — printing the config in logs / debuggers won't leak the secret.
Full reference for the env vars + the per-surface usage notes lives in the project's docs/ask.md.
default = ["http"]— pullsureq+rustlsfor the HTTP transport.http— the Anthropic provider that POSTs toapi.anthropic.com/v1/messages.
For wasm32 builds, depend on this crate with default-features = false to keep just the prompt-construction + response-parsing helpers (AskConfig, AskResponse, parse_response, ask_with_schema_and_provider<P: Provider>) without dragging in the HTTP transport. The WASM SDK does this — see the WASM SDK README for the JS-callback shape it uses instead.
- Hand-rolled JSON request/response shapes in
serde_json— there is no official Anthropic Rust SDK and rolling our own matches the project's "build it ourselves" theme. ~120 LOC of types vs ~400 LOC + a tokio runtime via a third-party SDK. - Sync
ureqover asyncreqwest— perask()call we make exactly one POST. The sync surface is the right fit and avoids pulling tokio into every embedder. (rejectedreqwest::blockingbecause it pulls tokio in even on the blocking path.) - Schema dump is byte-stable — alphabetically sorted, deterministic column ordering — so the prompt's
cache_control: ephemeralblock reliably hits Anthropic's prompt cache on repeat asks. - Tolerant response parsing — accepts strict JSON, fenced JSON (
```json … ```), and JSON-with-leading-prose. Real LLM output drifts even with strict instructions; the parser is forgiving so the caller doesn't have to be. - No engine dep —
sqlrite-ask0.1.18 had asqlrite-enginepath-dep that created a cargo cycle (sqlrite-engine[bin] → sqlrite-ask → sqlrite-engine[lib]). Dropped in 0.1.19; the engine integration moved intosqlrite-engine's newaskfeature. See the v0.1.19 dep-direction flip retrospective indocs/roadmap.md.
- The
sqlrite-mcpModel Context Protocol server exposesaskas a tool any MCP client (Claude Code, Cursor,mcp-inspector) can call. Phase 7g.8. - Per-language SDKs (
sqlriteon PyPI,@joaoh82/sqlriteon npm,github.com/joaoh82/rust_sqlite/sdk/go,@joaoh82/sqlrite-wasmon npm) all expose theask()family in idiomatic shapes — seedocs/ask.mdfor the per-SDK reference.
MIT. Same as the rest of the project.