Skip to content

Commit a4aaf80

Browse files
committed
feat: add NL2SQL CLI with interactive setup wizard, diagnostic tools, and PyPI publishing workflow.
1 parent 712cd02 commit a4aaf80

24 files changed

Lines changed: 738 additions & 602 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
id-token: write # IMPORTANT: Mandatory for trusted publishing
12+
contents: read
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: '3.10'
21+
22+
- name: Install build tools
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install build twine
26+
27+
- name: Build Adapter SDK
28+
run: python -m build packages/adapter-sdk
29+
30+
- name: Build Core
31+
run: python -m build packages/core
32+
33+
- name: Build CLI
34+
run: python -m build packages/cli
35+
36+
- name: Publish distributions to PyPI
37+
uses: pypa/gh-action-pypi-publish@release/v1
38+
with:
39+
packages-dir: packages/*/dist/
40+
# note: 'password' is not needed with trusted publishing

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ Detailed documentation is available in the `docs/` directory.
3030

3131
### 1. Installation
3232

33-
The platform is a monorepo. Install the core engine:
33+
The platform is a monorepo. Install the CLI application:
3434

3535
```bash
3636
# Core & SDK
3737
pip install -e packages/adapter-sdk
38-
pip install -e packages/core
38+
pip install -e packages/cli # Installs 'nl2sql' command
3939

4040
# Database Adapters (install as needed)
4141
pip install -e packages/adapters/postgres
@@ -56,13 +56,13 @@ Create a `datasources.yaml` file defining your connections:
5656
**a. Indexing** (Required once)
5757
5858
```bash
59-
python -m nl2sql.cli --index --config datasources.yaml
59+
nl2sql --index --config datasources.yaml
6060
```
6161

6262
**b. Querying**
6363

6464
```bash
65-
python -m nl2sql.cli --query "Show me the top 5 users by sales"
65+
nl2sql --query "Show me the top 5 users by sales"
6666
```
6767

6868
---

docs/guides/getting_started.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ cd nl2sql
1818

1919
# Install Core
2020
pip install -e packages/adapter-sdk
21-
pip install -e packages/adapter-sqlalchemy
22-
pip install -e packages/core
21+
pip install -e packages/cli # Installs 'nl2sql' command
2322

2423
# Install Adapters (e.g. Postgres)
2524
pip install -e packages/adapters/postgres
@@ -40,15 +39,15 @@ Create a `datasources.yaml` file. This tells the system how to connect to your d
4039
Before the AI can understand your schema, you must index it. Additional indexing strategies are discussed in the [Configuration Guide](configuration.md).
4140
4241
```bash
43-
python -m nl2sql.cli --index --config datasources.yaml
42+
nl2sql --index --config datasources.yaml
4443
```
4544

4645
## 4. Run a Query
4746

4847
Now you are ready to ask questions!
4948

5049
```bash
51-
python -m nl2sql.cli --query "Show me the top 5 users by sales"
50+
nl2sql --query "Show me the top 5 users by sales"
5251
```
5352

5453
The system will:

docs/reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The Command Line Interface (CLI) is the primary way to interact with the platform.
44

5-
**Command**: `python -m nl2sql.cli`
5+
**Command**: `nl2sql`
66

77
## Common Arguments
88

packages/cli/pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ authors = [{name = "Antigravity Team"}]
1010
readme = "README.md"
1111
requires-python = ">=3.9"
1212
dependencies = [
13-
"typer[all]>=0.9.0",
13+
"nl2sql-core",
1414
"rich>=13.0.0",
15-
"shellingham>=1.5.0",
16-
"shellingham>=1.5.0",
15+
"typer[all]>=0.9.0",
1716
]
1817

1918
[project.scripts]
20-
nl2sql = "nl2sql_cli.main:app"
19+
nl2sql = "nl2sql_cli.main:main"
2120

2221
[tool.setuptools.packages.find]
2322
where = ["src"]

packages/cli/src/nl2sql_cli/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import importlib.util
2+
from typing import Dict, Tuple
3+
import pathlib
4+
from rich.table import Table
5+
from nl2sql_cli.console import console, print_success, print_error
6+
7+
def check_package(name: str) -> bool:
8+
"""Checks if a python package is installed."""
9+
import_name = name.replace("-", "_")
10+
return importlib.util.find_spec(import_name) is not None
11+
12+
def verify_connectivity(print_table: bool = True) -> bool:
13+
"""
14+
Checks connectivity for all profiles in the default config.
15+
Returns True if all checks passed, False otherwise.
16+
"""
17+
from nl2sql.common.settings import settings
18+
from nl2sql.datasources import load_profiles
19+
from nl2sql.diagnostics import check_connectivity as core_check
20+
21+
try:
22+
profiles = load_profiles(pathlib.Path(settings.datasource_config_path))
23+
24+
with console.status("[bold green]Verifying connectivity...[/bold green]"):
25+
results = core_check(list(profiles.values()))
26+
27+
all_ok = True
28+
29+
if print_table:
30+
conn_table = Table(show_header=True, header_style="bold cyan")
31+
conn_table.add_column("Datasource ID")
32+
conn_table.add_column("Status")
33+
conn_table.add_column("Details")
34+
35+
for ds_id, (success, msg) in results.items():
36+
if not success:
37+
all_ok = False
38+
39+
if print_table:
40+
status = "[green]OK[/green]" if success else "[red]Failed[/red]"
41+
details = msg if not success else ""
42+
conn_table.add_row(ds_id, status, details)
43+
44+
if print_table:
45+
console.print(conn_table)
46+
47+
return all_ok
48+
49+
except Exception as e:
50+
console.print(f"[red]Connectivity check failed: {e}[/red]")
51+
return False

packages/cli/src/nl2sql_cli/commands/__init__.py

Whitespace-only changes.

packages/core/src/nl2sql/commands/benchmark.py renamed to packages/cli/src/nl2sql_cli/commands/benchmark.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sys
2-
import argparse
32
import statistics
43
import yaml
54
from nl2sql.services.llm import parse_llm_config, LLMRegistry, get_usage_summary
@@ -9,17 +8,18 @@
98
from nl2sql.pipeline.graph import run_with_graph
109
from nl2sql.evaluation.evaluator import ModelEvaluator
1110
from nl2sql.reporting import ConsolePresenter
11+
from nl2sql_cli.types import BenchmarkConfig
1212

1313

1414
def run_benchmark(
15-
args: argparse.Namespace,
15+
config: BenchmarkConfig,
1616
datasource_registry: DatasourceRegistry,
1717
vector_store: OrchestratorVectorStore
1818
) -> None:
1919
"""Runs the benchmark suite based on provided arguments.
2020
2121
Args:
22-
args (argparse.Namespace): Command-line arguments.
22+
config (BenchmarkConfig): Benchmark run configuration.
2323
datasource_registry (DatasourceRegistry): Datasource registry.
2424
vector_store (OrchestratorVectorStore): Vector store instance.
2525
"""
@@ -28,9 +28,9 @@ def run_benchmark(
2828
# Matrix Benchmarking
2929
llm_configs = {}
3030

31-
if args.bench_config and args.bench_config.exists():
31+
if config.bench_config_path and config.bench_config_path.exists():
3232
try:
33-
bench_data = yaml.safe_load(args.bench_config.read_text()) or {}
33+
bench_data = yaml.safe_load(config.bench_config_path.read_text()) or {}
3434
for name, cfg_data in bench_data.items():
3535
if isinstance(cfg_data, dict):
3636
llm_configs[name] = parse_llm_config(cfg_data)
@@ -40,11 +40,11 @@ def run_benchmark(
4040

4141
if not llm_configs:
4242
llm_cfg = parse_llm_config({"default": {"provider": "openai", "model": "gpt-4o"}}) # Fallback
43-
if args.llm_config and args.llm_config.exists():
43+
if config.llm_config_path and config.llm_config_path.exists():
4444
from nl2sql.services.llm import load_llm_config
45-
llm_cfg = load_llm_config(args.llm_config)
45+
llm_cfg = load_llm_config(config.llm_config_path)
4646

47-
if getattr(args, "stub_llm", False):
47+
if config.stub_llm:
4848
llm_cfg.default.provider = "stub"
4949
for agent_cfg in llm_cfg.agents.values():
5050
agent_cfg.provider = "stub"
@@ -55,7 +55,7 @@ def run_benchmark(
5555
for name, llm_cfg in llm_configs.items():
5656
llm_registry = LLMRegistry(llm_cfg)
5757
_run_dataset_evaluation(
58-
args,
58+
config,
5959
datasource_registry,
6060
vector_store,
6161
llm_registry,
@@ -64,7 +64,7 @@ def run_benchmark(
6464

6565

6666
def _run_dataset_evaluation(
67-
args: argparse.Namespace,
67+
config: BenchmarkConfig,
6868
datasource_registry: DatasourceRegistry,
6969
vector_store: OrchestratorVectorStore,
7070
llm_registry: LLMRegistry,
@@ -73,7 +73,7 @@ def _run_dataset_evaluation(
7373
"""Runs evaluation against a golden dataset for a specific config.
7474
7575
Args:
76-
args (argparse.Namespace): CLI arguments.
76+
config (BenchmarkConfig): CLI arguments.
7777
datasource_registry (DatasourceRegistry): Registry of datasources.
7878
vector_store (OrchestratorVectorStore): Vector store.
7979
llm_registry (LLMRegistry): LLM registry.
@@ -84,7 +84,7 @@ def _run_dataset_evaluation(
8484

8585
presenter = ConsolePresenter()
8686

87-
dataset_path = args.dataset
87+
dataset_path = config.dataset_path
8888
if not dataset_path.exists():
8989
presenter.print_error(f"Dataset file not found: {dataset_path}")
9090
sys.exit(1)
@@ -99,10 +99,10 @@ def _run_dataset_evaluation(
9999
presenter.print_error("Dataset must be a list of test cases.")
100100
sys.exit(1)
101101

102-
if args.include_ids:
103-
dataset = [item for item in dataset if item.get("id") in args.include_ids]
102+
if config.include_ids:
103+
dataset = [item for item in dataset if item.get("id") in config.include_ids]
104104
if not dataset:
105-
presenter.print_error(f"No test cases found matching IDs: {args.include_ids}")
105+
presenter.print_error(f"No test cases found matching IDs: {config.include_ids}")
106106
sys.exit(1)
107107

108108
presenter.print_header(f"Evaluating Config: {config_name}")
@@ -122,9 +122,9 @@ def _evaluate_case(item: dict) -> dict:
122122
llm_registry=llm_registry,
123123
user_query=question,
124124
datasource_id=None,
125-
execute=not args.routing_only,
125+
execute=not config.routing_only,
126126
vector_store=vector_store,
127-
vector_store_path=args.vector_store
127+
vector_store_path=config.vector_store_path
128128
)
129129
except Exception as e:
130130
return {
@@ -181,7 +181,7 @@ def get_val(obj, key, default=None):
181181

182182
layer_match = (routing_layer == expected_layer)
183183

184-
if args.routing_only:
184+
if config.routing_only:
185185
return {
186186
"id": q_id,
187187
"question": question,
@@ -329,7 +329,7 @@ def get_val(obj, key, default=None):
329329

330330
import concurrent.futures
331331
workers = 5
332-
iterations = args.iterations if args.iterations else 1
332+
iterations = config.iterations if config.iterations else 1
333333

334334
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
335335
futures = []
@@ -345,11 +345,11 @@ def get_val(obj, key, default=None):
345345
results.sort(key=lambda x: x["id"])
346346

347347
# Delegate Reporting
348-
presenter.print_dataset_benchmark_results(results, iterations=iterations, routing_only=args.routing_only)
348+
presenter.print_dataset_benchmark_results(results, iterations=iterations, routing_only=config.routing_only)
349349

350350
# Calculate Metrics and Print Summary
351351
metrics = ModelEvaluator.calculate_aggregate_metrics(results, len(results))
352-
presenter.print_metrics_summary(metrics, results, routing_only=args.routing_only)
352+
presenter.print_metrics_summary(metrics, results, routing_only=config.routing_only)
353353

354-
if args.export_path:
355-
presenter.export_results(results, args.export_path)
354+
if config.export_path:
355+
presenter.export_results(results, config.export_path)

packages/cli/src/nl2sql_cli/commands/chat.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)