Skip to content

LLM API

Source: packages/api/src/nl2sql_api/routes/llm.py

Endpoints

PUT /api/v1/settings/llm

Replaces the engine's whole llm section in the running process, through engine.llm.replace (see LLM API → llm.replace).

Admin only: see Admin routes.

Request body: the llm section as JSON, the same shape as llm.yaml (see LLM configuration).

{
  "provider": "openai",
  "model": "gpt-5.4",
  "api_key": "${env:OPENAI_API_KEY}",
  "agents": {"ast_planner": {"model": "gpt-5.5"}}
}

Execution flow: - The body is checked exactly as llm.yaml is: strict keys with "did you mean", the section's validators, and the providers resolved through the engine's registry. A ${provider:key} reference is kept in a secret field (api_key) and resolved by the engine's secret manager, so the body can name a variable or a secret rather than carry a key. A reference anywhere else is an error: the server's environment is never read into the config. - Every agent's API key must resolve. No model is called. - The new clients are built, then swapped in with engine.config.llm as one step. Questions already running finish on the old LLM. - The change is logged (one INFO line on nl2sql.llm) and audited, naming the caller's roles, via: "api", and each agent's old and new provider and model. Nothing is written to disk: a restart reads llm.yaml again.

Response (200): the change, never a key.

{
  "by": {"user_id": null, "roles": ["admin"], "via": "api"},
  "changes": {
    "default": {"old": "openai/gpt-5.4", "new": "anthropic/claude-sonnet-5-5"},
    "ast_planner": {"old": "openai/gpt-5.4", "new": "anthropic/claude-opus-5-5"}
  }
}

Errors: - No role -> HTTP 401; a role not in policies.admin_roles, or a remote client with no role source configured -> HTTP 403. - A section that is not valid, a provider that is not installed, or a key that does not resolve -> HTTP 422, whose detail is the config error's text. Nothing changes.

HTTP 422
{"detail": "LLM has 1 problem:\n\n  LLM → llm.agents.ast_planer\n    Unknown key. Did you mean 'ast_planner'?"}

GET /api/v1/llm

Response: {"llms": {...}}, engine.llm.list(): each agent by its name in the llm section (default first) with its provider, model, temperature and inherited (True when it sets nothing of its own). Never a key. No role needed.

GET /api/v1/llm/{agent}

Response: one agent's entry, engine.llm.get(agent).

Errors: - An unknown agent -> HTTP 404, naming the agents.