Skip to content

Commit 8adcbd5

Browse files
committed
fix(local): honor configured SQL provider in UI
1 parent 795fdc4 commit 8adcbd5

4 files changed

Lines changed: 119 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,14 @@ uv run streamlit run app/streamlit_app.py
8080
# or: make ui
8181
```
8282

83-
`MISTRAL_API_KEY` is required for embeddings (`mistral-embed`) even if SQL generation uses `GITHUB_TOKEN`, `GROQ_API_KEY`, or Ollama. Supply your own Mistral key; external API calls are subject to your provider quotas (not claimed free here).
83+
`MISTRAL_API_KEY` is required for embeddings (`mistral-embed`). Supply your own Mistral key;
84+
external API calls are subject to your provider quotas (not claimed free here).
85+
86+
Choose SQL generation independently in `.env` with
87+
`NL_SQL_DEFAULT_PROVIDER=mistral|github_models|groq|ollama`. Hosted alternatives
88+
use your matching `GITHUB_TOKEN` or `GROQ_API_KEY`; Ollama needs no generation
89+
key but must be running with `NL_SQL_OLLAMA_GEN_MODEL` pulled. This setting
90+
changes SQL/explanation generation only—query embeddings still use Mistral.
8491

8592
Postgres is optional (local/CI). The public HF Space is SQLite-only.
8693

‎app/bootstrap.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
from pathlib import Path
65
from typing import Any
76

87
import chromadb
@@ -15,6 +14,7 @@
1514
from nl_sql.llm.providers import build_provider
1615
from nl_sql.llm.providers.base import EmbeddingProvider, LLMProvider
1716
from nl_sql.llm.providers.mistral import MistralProvider
17+
from nl_sql.paths import under_root
1818
from nl_sql.schema_index.indexer import SchemaIndex
1919

2020

@@ -23,12 +23,12 @@ def bootstrap() -> tuple[DatabaseRegistry, SchemaIndex, LLMProvider, LLMProvider
2323
settings = get_settings()
2424
if not settings.mistral_api_key:
2525
raise RuntimeError(
26-
"MISTRAL_API_KEY is not set in .env — required for codestral + mistral-embed."
26+
"MISTRAL_API_KEY is not set in .env — required for embeddings."
2727
)
2828

2929
registry = get_default_registry()
3030

31-
persist_dir = Path("chroma_data")
31+
persist_dir = under_root("chroma_data")
3232
if not persist_dir.is_dir():
3333
raise RuntimeError(
3434
f"Chroma persist dir {persist_dir!r} not found. "
@@ -49,7 +49,7 @@ def bootstrap() -> tuple[DatabaseRegistry, SchemaIndex, LLMProvider, LLMProvider
4949
)
5050
schema_index = SchemaIndex(persist_dir=persist_dir, embedder=embedder, client=chroma_client)
5151

52-
raw_sql = build_provider("mistral", settings=settings)
52+
raw_sql = build_provider(settings.default_provider, settings=settings)
5353
sql_provider: LLMProvider = CachingLLMProvider(
5454
raw_sql,
5555
cache_dir=settings.llm_cache_dir,

‎tests/app/test_bootstrap.py‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from __future__ import annotations
2+
3+
import importlib
4+
import sys
5+
import types
6+
from pathlib import Path
7+
from types import SimpleNamespace
8+
from typing import Any
9+
10+
import pytest
11+
12+
13+
class _FakeStreamlit(types.ModuleType):
14+
def __init__(self) -> None:
15+
super().__init__("streamlit")
16+
17+
def cache_resource(self, **_: Any) -> Any:
18+
return lambda function: function
19+
20+
21+
@pytest.fixture
22+
def bootstrap_module(monkeypatch: pytest.MonkeyPatch) -> Any:
23+
monkeypatch.delitem(sys.modules, "app.bootstrap", raising=False)
24+
monkeypatch.setitem(sys.modules, "streamlit", _FakeStreamlit())
25+
return importlib.import_module("app.bootstrap")
26+
27+
28+
def _settings(tmp_path: Path, **overrides: Any) -> SimpleNamespace:
29+
values = {
30+
"default_provider": "ollama",
31+
"mistral_api_key": "embed-key",
32+
"mistral_gen_model": "codestral-latest",
33+
"mistral_embed_model": "mistral-embed",
34+
"mistral_base_url": "https://api.mistral.ai/v1",
35+
"llm_cache_dir": tmp_path / "cache",
36+
"llm_cache_size_limit_gb": 1.0,
37+
}
38+
values.update(overrides)
39+
return SimpleNamespace(**values)
40+
41+
42+
def test_bootstrap_uses_configured_sql_provider(
43+
bootstrap_module: Any,
44+
monkeypatch: pytest.MonkeyPatch,
45+
tmp_path: Path,
46+
) -> None:
47+
settings = _settings(tmp_path)
48+
persist_dir = tmp_path / "repo" / "chroma_data"
49+
persist_dir.mkdir(parents=True)
50+
foreign_cwd = tmp_path / "foreign"
51+
foreign_cwd.mkdir()
52+
monkeypatch.chdir(foreign_cwd)
53+
54+
registry = object()
55+
schema_index = object()
56+
sql_provider = object()
57+
provider_calls: list[tuple[str, object]] = []
58+
client_paths: list[str] = []
59+
60+
monkeypatch.setattr(bootstrap_module, "get_settings", lambda: settings)
61+
monkeypatch.setattr(
62+
bootstrap_module,
63+
"under_root",
64+
lambda *parts: persist_dir.parent.joinpath(*parts),
65+
raising=False,
66+
)
67+
monkeypatch.setattr(bootstrap_module, "get_default_registry", lambda: registry)
68+
monkeypatch.setattr(
69+
bootstrap_module.chromadb,
70+
"PersistentClient",
71+
lambda **kwargs: client_paths.append(kwargs["path"]) or object(),
72+
)
73+
monkeypatch.setattr(bootstrap_module, "MistralProvider", lambda **_: object())
74+
monkeypatch.setattr(bootstrap_module, "CachingEmbeddingProvider", lambda *_, **__: object())
75+
monkeypatch.setattr(bootstrap_module, "SchemaIndex", lambda **_: schema_index)
76+
monkeypatch.setattr(
77+
bootstrap_module,
78+
"build_provider",
79+
lambda name, settings: provider_calls.append((name, settings)) or object(),
80+
)
81+
monkeypatch.setattr(
82+
bootstrap_module,
83+
"CachingLLMProvider",
84+
lambda *_, **__: sql_provider,
85+
)
86+
87+
result = bootstrap_module.bootstrap()
88+
89+
assert provider_calls == [("ollama", settings)]
90+
assert client_paths == [str(persist_dir)]
91+
assert result == (registry, schema_index, sql_provider, sql_provider)
92+
93+
94+
def test_bootstrap_reports_mistral_key_is_for_embeddings(
95+
bootstrap_module: Any,
96+
monkeypatch: pytest.MonkeyPatch,
97+
tmp_path: Path,
98+
) -> None:
99+
monkeypatch.setattr(
100+
bootstrap_module,
101+
"get_settings",
102+
lambda: _settings(tmp_path, mistral_api_key=""),
103+
)
104+
105+
with pytest.raises(RuntimeError, match="required for embeddings"):
106+
bootstrap_module.bootstrap()

‎tests/test_readme_local_first.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def test_readme_quick_start_local_first_contract() -> None:
3939
assert "uv run python scripts/build_index.py --db all" in section
4040

4141
assert "required for embeddings" in section
42+
assert "NL_SQL_DEFAULT_PROVIDER" in section
4243
assert "GITHUB_TOKEN" in section
4344
assert "GROQ_API_KEY" in section
4445
assert "Ollama" in section

0 commit comments

Comments
 (0)