Settings
The settings section of nl2sql.yaml holds the tuning knobs.
It is optional, and every field has a default, so most configs leave it out.
Each value is the engine's own: two engines in one process can run with
different settings.
# settings.yaml (every value shown is the default)
tenant_id: default_tenant
pipeline:
timeout_s: 60
plan_cache: true
strict_columns: true
today: null # pin the run date: 2013-12-31
retries: { max: 3, base_delay_s: 1.0, max_delay_s: 10.0, jitter_s: 0.5 }
max_llm_calls: 60 # model calls per question, parse retries included; null: no cap
max_cost_usd: null # dollars per question; null (the default): no cap
sanity_checks: true
retrieval:
full_snapshot_max_tables: 15
table_k: 8
planning_k: 12
decomposer_tables: 30
routing:
candidates: 3
max_datasources: 3
max_sub_queries: 6
floor_ratio: 0.5
results:
row_limit: 100000
fetch_batch_size: 10000
preview_rows: 20
traces:
mode: on_failure # off | on_failure | always
sample_rows: 50
max_field_chars: 20000
schema:
max_versions: 3
curated:
match_top_k: 5
match_floor: 0.6
feedback:
enabled: true
Unknown keys are errors, as everywhere in the config. The demo
(nl2sql init --demo) writes a settings.yaml with pipeline.timeout_s: 300
(a live run makes six model calls; one OpenAI run took 136 s) and
traces.mode: always (the playground's Debug drill-down reads every run's
trace).
In Python, the groups are Settings, PipelineSettings, RetrySettings,
RetrievalSettings, RoutingSettings, ResultSettings, TraceSettings,
SchemaSettings, CuratedSettings and FeedbackSettings in
nl2sql.config.
Where the engine's events, logs and metrics go is not a setting: it is the
observers section of
nl2sql.yaml. The 0.6 observability group (exporter, otlp_endpoint)
is gone; an otel observer takes its place. The schema group is the attribute schema_
(cfg.settings.schema_.max_versions), because schema would shadow a
pydantic method; the YAML key is schema.
tenant_id
The tenant id for requests that name none. Result files are kept per tenant.
pipeline
| Key | Default | Description |
|---|---|---|
timeout_s |
60 |
Timeout in seconds for one question. The caller is answered within about a second of the limit; the run's worker thread winds down in the background (see isolation). |
plan_cache |
true |
Reuse a plan that already passed validation and executed, for the same normalised sub-query intent, datasource and schema version on the same run date, instead of calling the planner LLM. The cached plan is validated again on every use, so RBAC and policy changes still apply. Stored in <storage.path>/state.db; nl2sql cache clear empties it. See Determinism → The plan cache. |
strict_columns |
true |
Treat a column the plan names but the retrieved schema does not have as a blocking, retryable COLUMN_NOT_FOUND error, so the planner is asked again instead of the plan reaching the database. false downgrades it to a warning that lets the plan through once retries are spent. |
today |
none | The date the planner and refiner are told it is, as an ISO date (2013-12-31). Unset, it is the real date in UTC. Pin it for tests and gold runs whose questions say "this year" or "the last 30 days". A trace records the date its run used, and nl2sql trace replay pins it. It is part of the plan cache key, so a plan made on another date is not reused. |
retries.max |
3 |
Retry attempts for the SQL agent's refinement loop. |
retries.base_delay_s |
1.0 |
Base delay between retries, in seconds. |
retries.max_delay_s |
10.0 |
Longest delay between retries. |
retries.jitter_s |
0.5 |
Most jitter added to a delay. |
max_llm_calls |
60 |
The most model calls one question may make, parse retries included; null turns the cap off. 60 covers the largest legitimate run: the resolver, the decomposer, six sub-queries each with a planner call and three refiner and planner retries, the answer, and parse retries. See Per-question caps. |
max_cost_usd |
none | The most one question may spend, in dollars; null (the default) is no cap. With a cap set, every model the llm section names must have a price, or the engine does not build. See Per-question caps. |
sanity_checks |
true |
The answer sanity checks after each sub-query runs (join fan-out, all-null columns, missing filter values). false turns them all off. Read from release 0.7's sanity checks (Wave A). |
Per-question caps
Both caps are checked before each model call, by the question's budget:
a call that would be the 61st, or that would start once the question has
spent max_cost_usd, is never sent. The calls already in flight finish, so a
question can pass the cost cap by the calls in flight: at most one per
running sub-query.
A question that reaches a cap ends with status: "stopped", never as a
planner error to retry: QueryResult.stop is {limit, value, spent}
(limit is max_llm_calls or max_cost_usd), the one error is
RUN_LIMIT_REACHED (not retryable) naming the limit and the setting, and the
answer says so: "This question reached its limit of 60 model calls
(settings.pipeline.max_llm_calls)." The run publishes run.limit_reached,
which the caller's stream carries and the audit log records.
nl2sql.limits.Budget(max_llm_calls=..., max_cost_usd=...) is the same
check for work that is not a question: make the model calls with
budget.callbacks(), and a call over a cap raises LimitReached (with the
RunStop in .stop) before it is sent.
retrieval
| Key | Default | Description |
|---|---|---|
full_snapshot_max_tables |
15 |
When a datasource's schema has at most this many tables, the schema retriever skips vector retrieval and passes the full schema snapshot (every table, every column) to the planner. 0 always uses vector retrieval. |
table_k |
8 |
Table entries the schema retriever's search keeps for a sub-query (and column entries, when no table matches). |
planning_k |
12 |
Column and relationship entries of those tables the planner is shown for a sub-query. |
decomposer_tables |
30 |
Table names the decomposer is shown per datasource; the rest are counted. |
table_k, planning_k and decomposer_tables replace the constants the
schema retriever and the resolver had before 0.7 (TABLE_K, PLANNING_K,
MAX_DECOMPOSER_TABLES).
routing
Cross-database routing: which datasources a question goes to, and how far it
may spread. The resolver reads candidates, max_datasources and
floor_ratio (Routing);
the decomposer reads max_sub_queries
(Wave A, Limits).
| Key | Default | Description |
|---|---|---|
candidates |
3 |
Datasources routing keeps for the answerability check, best by fused score first. |
max_datasources |
3 |
Datasources one question may use: of those the answerability check accepts, the resolver forwards at most this many, best first. |
max_sub_queries |
6 |
Sub-queries one question may have. A decomposition over it ends the run with QUESTION_TOO_BROAD. |
floor_ratio |
0.5 |
A candidate whose best index-entry similarity to the question is under this share of the most similar candidate's is dropped, between 0 and 1. It is on similarity, not on the fused score: with a handful of datasources every reciprocal-rank score is within a few percent of the best. 0 keeps every candidate up to candidates. |
results
| Key | Default | Description |
|---|---|---|
row_limit |
100000 |
The most rows one sub-query returns: a resource ceiling baked into the SQL as a LIMIT. A datasource's own limits.row_limit applies when it is lower (the demo datasources set 10000). A result that reaches it is flagged as truncated (QueryResult.truncated, sub_queries[].truncated) and the answer says it was cut. |
fetch_batch_size |
10000 |
Rows fetched from the database and written to the sub-query's result file at a time, for an adapter that streams (every built-in SQL adapter does). At most one batch is held in memory. See Artifact Store. |
preview_rows |
20 |
Result rows the answer model sees, with the total row count and the column names. The model writes about the result; it never copies it. See AnswerSynthesizerNode. |
traces
| Key | Default | Description |
|---|---|---|
mode |
on_failure |
When to write a run trace to <storage.path>/traces/: off, on_failure (errors, a retry, or an incomplete run) or always. See Debugging a Run. |
sample_rows |
50 |
Result rows kept in a trace. |
max_field_chars |
20000 |
Longest string kept in a node's recorded inputs and outputs; LLM prompts and responses are never cut. |
schema
| Key | Default | Description |
|---|---|---|
max_versions |
3 |
The newest schema snapshots kept per datasource in the state store, besides every version a build pointer names and the one a build just replaced. |
curated
| Key | Default | Description |
|---|---|---|
match_top_k |
5 |
Verified query candidates a question's search returns, one per query. |
match_floor |
0.6 |
Cosine similarity the best verified query candidate must reach before the model is asked to choose one. Below it the question is answered by the normal pipeline with no model call. See Curated Answers. |
Whether curated answers are on is not a setting: the
curated section is present or absent.
feedback
| Key | Default | Description |
|---|---|---|
enabled |
true |
Let the playground record a thumbs up or down, with an optional note, for a run. Stored in <storage.path>/state.db: question, role, status, SQL, the datasources it ran on, whether the answer was verified or generated, error codes, the model per node and the engine version, never result rows or sample values. nl2sql feedback clear empties it. See Feedback and Signals. |