Development Guide
This guide focuses on extension points: adapters, executors, subgraphs, chunking, and planner logic.
Extension guides
extensions/add-adapter.mdfor new datasource adapters.extensions/add-chunk-types.mdfor new schema chunk types.extensions/extend-planner.mdfor planner/AST updates.extensions/add-execution-backend.mdfor new executor services.
The registry
Adapters, LLM providers, embeddings providers, secret providers, stores, policy
checks, observers, hooks and scorers are all found by name through nl2sql.registry.Registry
(design). Each kind has an
entry point group, and the built-ins are declared in
packages/nl2sql/pyproject.toml in the same groups a plugin uses:
| Kind | Entry point group | Interface | Built-ins |
|---|---|---|---|
adapter |
nl2sql.adapters |
nl2sql_sdk.DatasourceAdapter (version 2) |
sqlite, postgres, mysql, mssql, duckdb |
llm |
nl2sql.llms |
nl2sql.llm.providers.LLMProvider (version 2: structured_output and create) |
openai, anthropic, openai_compatible, and its presets openrouter and ollama, made from PROVIDER_PRESETS |
embeddings |
nl2sql.embeddings |
nl2sql.embeddings.EmbeddingsProvider (version 2: create and describe) |
local, openai, openai_compatible |
secrets |
nl2sql.secrets |
nl2sql_sdk.SecretProvider |
env, aws, azure, hashicorp |
store |
nl2sql.stores |
nl2sql_sdk.stores (four interfaces, one per slot) |
chroma, sqlite, files, local |
policy_check |
nl2sql.policy_checks |
nl2sql_sdk.policy.PolicyCheck |
none |
observer |
nl2sql.observers |
nl2sql_sdk.events.Observer (version 1) |
log, audit, otel (the otel extra); see Observers |
hook |
nl2sql.hooks |
nl2sql.hooks.Hook (version 1, from release 0.7) |
none |
scorer |
nl2sql.scorers |
nl2sql.evaluation.scorers.Scorer (version 1, from release 0.7; see Scorers) |
result_match, outcome, retrieval_recall, faithfulness, routing |
Registry.default()lists the installed entry points by name and imports none of them.Registry.empty()holds nothing.add_adapter,add_llm,add_embeddings,add_secrets,add_store,add_policy_check,add_observer,add_hookandadd_scorer, and the decorators@registry.adapter(name)and so on, register in code on that registry only. A name already present raises unlessreplace=True.names(kind)lists the names;load(kind, name)imports one and checks itsinterface_version, finds itsdriver_modules(adapters) orrequires(LLM and embeddings providers) without importing them, and reports a name two installed packages declare. A failure is aRegistryErrorthat says how to fix it, and an unknown name suggests the closest one.
After editing an entry point in pyproject.toml, reinstall the package
(pip install -e packages/nl2sql): entry points are read from the installed
metadata, not from the source tree.
Subgraph extension overview
- Implement a
build_*_graph(ctx)function that returns a LangGraph runnable. - Add it as a node in
build_graph()and extendresolve_subgraph()with the capabilities the datasource must declare for it.
flowchart TD
Subgraph[build_*_graph] --> Graph[build_graph]
Graph --> Resolve[resolve_subgraph]
Resolve --> Router[build_scan_layer_router]
CLI console output
rich parses every string handed to Console.print, a Panel body, a Tree
label or a table cell as console markup. Text the CLI did not author — exception
messages, tracebacks, generated SQL, database errors, filesystem paths, LLM
output, query result rows — must therefore never reach those APIs unescaped:
- A closing-tag-shaped sequence (
[/{style}], which appears in rich's own source and so in tracebacks through it) raisesMarkupErrorand hides the real error. - A lowercase opening tag is consumed silently. T-SQL bracket-quoted
identifiers are the common case:
SELECT * FROM [dbo].[orders]renders asSELECT * FROM .unless the SQL is escaped.
Two conventions cover this:
- Interpolating external text into a markup string: wrap it in
rich.markup.escape, so the CLI's own styling still applies. - Passing external text as a whole cell, label or panel body: wrap it in
rich.text.Text, which is never parsed as markup.ConsolePresenter.print_sqldoes this for plain strings; callers wanting styling pass aTextthemselves.
Tracebacks are printed with console.print(..., markup=False).
Source references
- Registry:
packages/nl2sql/src/nl2sql/registry.py - Subgraph selection:
packages/nl2sql/src/nl2sql/pipeline/graph_utils.py - Adapter protocol:
packages/sdk/src/nl2sql_sdk/protocols.py - SQL executor:
packages/nl2sql/src/nl2sql/execution/executor/sql_executor.py