| from functools import lru_cache
|
| from typing import Literal
|
|
|
| from pydantic import Field
|
| from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
| ProviderName = Literal[
|
| "mistral",
|
| "github_models",
|
| "groq",
|
| "ollama",
|
| "openrouter",
|
| "perplexity",
|
| "gracekelly",
|
| "zen",
|
| "grok_cli",
|
| "claude_cli",
|
| "local_vllm",
|
| ]
|
|
|
|
|
| class Settings(BaseSettings):
|
| model_config = SettingsConfigDict(
|
| env_file=".env",
|
| env_file_encoding="utf-8",
|
| env_prefix="NL_SQL_",
|
| extra="ignore",
|
| case_sensitive=False,
|
| populate_by_name=True,
|
| )
|
|
|
| log_level: str = "INFO"
|
|
|
| default_provider: ProviderName = "mistral"
|
| frontier_provider: ProviderName = "groq"
|
| local_provider: ProviderName = "ollama"
|
|
|
| mistral_gen_model: str = "codestral-latest"
|
| mistral_nl_model: str = "mistral-large-latest"
|
| mistral_embed_model: str = "mistral-embed"
|
| mistral_base_url: str = "https://api.mistral.ai/v1"
|
|
|
| github_models_model: str = "openai/gpt-4o-mini"
|
| github_models_base_url: str = "https://models.github.ai/inference"
|
|
|
| groq_model: str = "llama-3.3-70b-versatile"
|
| groq_base_url: str = "https://api.groq.com/openai/v1"
|
|
|
| ollama_gen_model: str = "qwen2.5-coder:7b-instruct"
|
| ollama_base_url: str = "http://localhost:11434/v1"
|
| ollama_timeout_seconds: float = 180.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| openrouter_model: str = "qwen/qwen3-coder:free"
|
| openrouter_base_url: str = "https://openrouter.ai/api/v1"
|
|
|
|
|
|
|
|
|
|
|
|
|
| zen_model: str = "deepseek-v4-flash-free"
|
| zen_base_url: str = "https://opencode.ai/zen/v1"
|
| zen_timeout_seconds: float = 180.0
|
|
|
|
|
|
|
|
|
| grok_cli_path: str = "grok"
|
| grok_cli_model: str = "grok-composer-2.5-fast"
|
| grok_cli_timeout_seconds: float = 600.0
|
| grok_cli_max_turns: int = 4
|
| """Must be > 1. Grok spends its first turn announcing what it is about to do;
|
| `--max-turns 1` returns that preamble instead of the SQL (47.0% / 31% invalid
|
| on the first n=200 run). See llm/providers/grok_cli.py."""
|
| grok_cli_effort: str | None = None
|
| """`--reasoning-effort`. None leaves the CLI on its own default."""
|
|
|
|
|
|
|
|
|
| claude_cli_path: str = "claude"
|
| claude_cli_model: str = "claude-sonnet-5"
|
| claude_cli_timeout_seconds: float = 600.0
|
| claude_cli_max_turns: int = 4
|
| """Must be > 1: a denied tool call eats a turn and the CLI then answers
|
| `error_max_turns` instead of the SQL."""
|
| claude_cli_effort: str | None = None
|
| """`--effort` (low|medium|high|xhigh|max). None leaves the CLI on its own
|
| default. The spawned process does NOT inherit the effort of the session that
|
| launched it, so this is the only way to run a max-effort ablation."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| local_llm_base_url: str = "http://localhost:8000/v1"
|
| local_llm_model: str = "Qwen/Qwen2.5-Coder-7B-Instruct"
|
| local_llm_api_key: str = "dummy"
|
| local_llm_timeout_seconds: float = 180.0
|
|
|
|
|
|
|
|
|
|
|
| perplexity_browser_model: str = "claude-sonnet-4-6"
|
| perplexity_base_url: str = "http://127.0.0.1:8011"
|
|
|
|
|
|
|
|
|
|
|
|
|
| gracekelly_model: str = "claude-sonnet-5"
|
| gracekelly_base_url: str = "http://127.0.0.1:8011"
|
| gracekelly_timeout_seconds: float = 900.0
|
| """Browser path, so this is a human-scale wait, not an API round-trip.
|
| Measured on the NL_SQL generation prompt (schema + few-shot + rules, ~6k
|
| tokens) against Perplexity: 150-436s per call. The former 180s default
|
| timed out mid-benchmark and surfaced as `pipeline_exception`, which scores
|
| as a miss and silently understates the model. 900s leaves headroom."""
|
|
|
| mistral_api_key: str = Field(default="", validation_alias="MISTRAL_API_KEY")
|
| github_token: str = Field(default="", validation_alias="GITHUB_TOKEN")
|
| groq_api_key: str = Field(default="", validation_alias="GROQ_API_KEY")
|
| openrouter_api_key: str = Field(default="", validation_alias="OPENROUTER_API_KEY")
|
| zen_api_key: str = Field(default="", validation_alias="OPENCODE_API_KEY")
|
| """One or more OpenCode Zen keys, comma-separated. Free tiers meter per key,
|
| so extra keys are extra headroom: the provider rotates through them on 429."""
|
|
|
|
|
|
|
|
|
|
|
| api_key: str = Field(default="", validation_alias="NL_SQL_API_KEY")
|
|
|
|
|
|
|
|
|
|
|
| pg_dsn: str = Field(default="", validation_alias="NL_SQL_PG_DSN")
|
| pg_db_id: str = "pg_codebase_community"
|
| pg_description: str = "StackExchange-derived BIRD codebase_community, loaded into Postgres 16."
|
|
|
|
|
|
|
| llm_cache_dir: str = ".cache/llm"
|
| llm_cache_size_limit_gb: int = 4
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def get_settings() -> Settings:
|
| return Settings()
|
|
|