Skip to content

Commit d90ecfd

Browse files
Merge pull request #31 from luiscarbonel1991/feat/dsl-api-provider-config-hexagonal-ports
feat: DSL API, ProviderConfig, hexagonal architecture improvements (0.2.0rc8)
2 parents c16a71c + 4216a09 commit d90ecfd

40 files changed

Lines changed: 1999 additions & 783 deletions

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, develop]
6+
pull_request:
7+
branches: [main, develop]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python 3.12
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: '3.12'
21+
22+
- name: Install uv
23+
run: python -m pip install uv
24+
25+
- name: Install dependencies
26+
run: |
27+
uv pip install --system .
28+
uv pip install --system ".[dev]"
29+
30+
- name: Unit tests
31+
env:
32+
PYTHONPATH: src
33+
run: pytest -m "not integration and not llm" -q

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ cython_debug/
166166
nplsql.toml
167167
embeddings/
168168
embeddings
169+
examples_index/
169170
query_cache.db
170171
*.iml
171172
*.idea/

README.md

Lines changed: 81 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -54,111 +54,109 @@ pip install nlp2sql[embeddings-openai] # OpenAI embeddings
5454

5555
## Quick Start
5656

57-
### 1. Set Environment Variables
57+
### 1. Set an API Key
5858

5959
```bash
60-
# At least one AI provider key required
6160
export OPENAI_API_KEY="your-openai-key"
62-
# export ANTHROPIC_API_KEY="your-anthropic-key"
63-
# export GOOGLE_API_KEY="your-google-key"
61+
# or ANTHROPIC_API_KEY, GOOGLE_API_KEY
6462
```
6563

66-
### 2. One-Line Usage
64+
### 2. Connect and Ask
6765

6866
```python
6967
import asyncio
70-
import os
71-
from nlp2sql import generate_sql_from_db
68+
import nlp2sql
69+
from nlp2sql import ProviderConfig
7270

7371
async def main():
74-
result = await generate_sql_from_db(
75-
database_url="postgresql://user:pass@localhost:5432/mydb",
76-
question="Show me all active users",
77-
ai_provider="openai",
78-
api_key=os.getenv("OPENAI_API_KEY")
72+
nlp = await nlp2sql.connect(
73+
"postgresql://user:pass@localhost:5432/mydb",
74+
provider=ProviderConfig(provider="openai", api_key="sk-..."),
7975
)
80-
print(result['sql'])
81-
print(f"Confidence: {result['confidence']}")
76+
77+
result = await nlp.ask("Show me all active users")
78+
print(result.sql)
79+
print(result.confidence)
80+
print(result.is_valid)
8281

8382
asyncio.run(main())
8483
```
8584

86-
### 3. Pre-Initialized Service (Better Performance)
87-
88-
```python
89-
from nlp2sql import create_and_initialize_service
85+
`connect()` auto-detects the database type from the URL, loads the schema, and builds the FAISS embedding index. Subsequent `ask()` calls reuse everything from disk cache.
9086

91-
async def main():
92-
# Initialize once
93-
service = await create_and_initialize_service(
94-
database_url="postgresql://user:pass@localhost:5432/mydb",
95-
ai_provider="openai",
96-
api_key=os.getenv("OPENAI_API_KEY")
97-
)
98-
99-
# Use multiple times
100-
result1 = await service.generate_sql("Count total users")
101-
result2 = await service.generate_sql("Show recent orders")
102-
```
87+
### 3. Few-Shot Examples
10388

104-
### 4. Few-Shot Examples (Improved Accuracy)
89+
Pass a list of dicts -- `connect()` handles embedding and indexing automatically:
10590

10691
```python
107-
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
108-
109-
# Create example store with domain-specific examples
110-
embedding_provider = create_embedding_provider("openai", api_key=os.getenv("OPENAI_API_KEY"))
111-
example_store = ExampleStore(embedding_provider=embedding_provider)
112-
await example_store.add_examples([
113-
{
114-
"question": "What was the total revenue last month?",
115-
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
116-
"database_type": "redshift",
117-
"metadata": {"category": "aggregation"}
118-
}
119-
])
120-
121-
service = await create_and_initialize_service(
122-
database_url="redshift://user:pass@host:5439/db",
123-
ai_provider="openai",
124-
api_key=os.getenv("OPENAI_API_KEY"),
125-
example_store=example_store
92+
nlp = await nlp2sql.connect(
93+
"redshift://user:pass@host:5439/db",
94+
provider=ProviderConfig(provider="openai", api_key="sk-..."),
95+
schema="dwh_data_share_llm",
96+
examples=[
97+
{
98+
"question": "Total revenue last month?",
99+
"sql": "SELECT SUM(revenue) FROM sales WHERE date >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')",
100+
"database_type": "redshift",
101+
},
102+
],
126103
)
127-
result = await service.generate_sql("Show me total sales this quarter")
104+
105+
result = await nlp.ask("Show me total sales this quarter")
128106
```
129107

130-
### 5. Large Database with Schema Filtering
108+
### 4. Schema Filtering (Large Databases)
131109

132110
```python
133-
from nlp2sql import create_and_initialize_service
134-
135-
service = await create_and_initialize_service(
136-
database_url="postgresql://localhost/enterprise",
137-
ai_provider="anthropic", # Best for large schemas (200K context)
138-
api_key=os.getenv("ANTHROPIC_API_KEY"),
111+
nlp = await nlp2sql.connect(
112+
"postgresql://localhost/enterprise",
113+
provider=ProviderConfig(provider="anthropic", api_key="sk-ant-..."),
139114
schema_filters={
140115
"include_schemas": ["sales", "finance"],
141-
"exclude_system_tables": True
142-
}
116+
"exclude_system_tables": True,
117+
},
143118
)
119+
```
120+
121+
### 5. Custom Model and Temperature
144122

145-
result = await service.generate_sql("Show revenue by month")
123+
```python
124+
nlp = await nlp2sql.connect(
125+
"postgresql://localhost/mydb",
126+
provider=ProviderConfig(
127+
provider="openai",
128+
api_key="sk-...",
129+
model="gpt-4o",
130+
temperature=0.0,
131+
max_tokens=4000,
132+
),
133+
)
146134
```
147135

148-
### 5. CLI Usage
136+
### 6. CLI
149137

150138
```bash
151-
# Generate SQL
152139
nlp2sql query \
153140
--database-url postgresql://user:pass@localhost:5432/mydb \
154141
--question "Show all active users" \
155142
--explain
156143

157-
# Inspect schema
158144
nlp2sql inspect --database-url postgresql://localhost/mydb
145+
```
159146

160-
# Benchmark providers
161-
nlp2sql benchmark --database-url postgresql://localhost/mydb
147+
### Advanced: Direct Service Access
148+
149+
For full control over the lifecycle, the lower-level API is still available:
150+
151+
```python
152+
from nlp2sql import create_and_initialize_service, ProviderConfig, DatabaseType
153+
154+
service = await create_and_initialize_service(
155+
database_url="postgresql://localhost/mydb",
156+
provider_config=ProviderConfig(provider="openai", api_key="sk-..."),
157+
database_type=DatabaseType.POSTGRES,
158+
)
159+
result = await service.generate_sql("Count total users", database_type=DatabaseType.POSTGRES)
162160
```
163161

164162
## How It Works
@@ -180,27 +178,31 @@ See [Architecture](docs/ARCHITECTURE.md) for the detailed flow with method refer
180178

181179
## Provider Comparison
182180

183-
| Provider | Context Size | Best For |
184-
|----------|-------------|----------|
185-
| OpenAI GPT-4 | 128K | Complex reasoning |
186-
| Anthropic Claude | 200K | Large schemas |
187-
| Google Gemini | 1M | High volume, cost efficiency |
181+
| Provider | Default Model | Context Size | Best For |
182+
|----------|--------------|-------------|----------|
183+
| OpenAI | gpt-4o-mini | 128K | Cost-effective, fast |
184+
| Anthropic | claude-sonnet-4-20250514 | 200K | Large schemas |
185+
| Google Gemini | gemini-2.0-flash | 1M | High volume |
188186

189-
See [Configuration](docs/CONFIGURATION.md) for detailed provider setup.
187+
All models are configurable via `ProviderConfig(model="...")`. See [Configuration](docs/CONFIGURATION.md) for details.
190188

191189
## Architecture
192190

193191
Clean Architecture (Ports & Adapters) with three layers: core entities, port interfaces, and adapter implementations. The schema management layer uses FAISS + TF-IDF hybrid search for relevance filtering at scale.
194192

195193
```
196194
nlp2sql/
197-
├── core/ # Business entities (pure Python, no dependencies)
198-
├── ports/ # Interfaces (AIProviderPort, SchemaRepositoryPort, EmbeddingProviderPort)
199-
├── adapters/ # Implementations (OpenAI, Anthropic, Gemini, PostgreSQL, Redshift)
200-
├── services/ # Orchestration (QueryGenerationService)
201-
├── schema/ # Schema management (SchemaManager, SchemaAnalyzer, SchemaEmbeddingManager)
202-
├── config/ # Pydantic Settings configuration
203-
└── exceptions/ # Custom exception hierarchy
195+
├── client.py # DSL: connect() + NLP2SQL class (recommended entry point)
196+
├── core/ # Pure Python: entities, ProviderConfig, QueryResult, sql_safety, sql_keywords
197+
├── ports/ # Interfaces: AIProviderPort, SchemaRepositoryPort, EmbeddingProviderPort,
198+
│ # ExampleRepositoryPort, QuerySafetyPort, QueryValidatorPort, CachePort
199+
├── adapters/ # Implementations: OpenAI, Anthropic, Gemini, PostgreSQL, Redshift,
200+
│ # RegexQueryValidator
201+
├── services/ # Orchestration: QueryGenerationService
202+
├── schema/ # Schema management: SchemaManager, SchemaAnalyzer, SchemaEmbeddingManager,
203+
│ # ExampleStore
204+
├── config/ # Pydantic Settings (centralized defaults)
205+
└── exceptions/ # Exception hierarchy (NLP2SQLException -> 8 subclasses)
204206
```
205207

206208
See [Architecture](docs/ARCHITECTURE.md) for the full component diagram, data flow, and design decisions.

0 commit comments

Comments
 (0)