nlp2sql 0.2.0rc5__tar.gz → 0.2.0rc7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.gitignore +1 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/CLAUDE.md +4 -1
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/PKG-INFO +66 -13
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/README.md +65 -12
- nlp2sql-0.2.0rc7/assets/nlp2sql-logo.png +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docs/API.md +85 -1
- nlp2sql-0.2.0rc7/docs/ARCHITECTURE.md +267 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docs/CONFIGURATION.md +9 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/README.md +31 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/.dockerignore +41 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/.env.example +9 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/DOCKER_README.md +126 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/DOCKER_SETUP.md +1529 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/DOCKER_TESTING_SUMMARY.md +350 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/Dockerfile +60 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/INDEX.md +227 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/README.md +278 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/docker/init-ecommerce-schema.sql +362 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/docker-compose.yml +54 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/main.py +459 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/pyproject.toml +15 -0
- nlp2sql-0.2.0rc7/examples/strands-fastapi-agent/test-docker-endpoints.sh +101 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/mcp_server/__init__.py +1 -1
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/pyproject.toml +1 -1
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/__init__.py +11 -4
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/anthropic_adapter.py +7 -9
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/gemini_adapter.py +7 -9
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/openai_adapter.py +7 -9
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/postgres_repository.py +34 -28
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/redshift_adapter.py +32 -26
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/cli.py +2 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/config/settings.py +1 -1
- nlp2sql-0.2.0rc7/src/nlp2sql/core/database_prompts.py +48 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/analyzer.py +122 -74
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/embedding_manager.py +32 -36
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/manager.py +23 -30
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/services/query_service.py +112 -1
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_batch_scoring.py +258 -32
- nlp2sql-0.2.0rc7/tests/test_column_validation.py +180 -0
- nlp2sql-0.2.0rc7/tests/test_database_prompts.py +64 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_embedding_manager.py +75 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/uv.lock +1 -1
- nlp2sql-0.2.0rc5/docs/ARCHITECTURE.md +0 -138
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/commands/db.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/commands/quality.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/commands/test.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/rules/adapters.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/rules/architecture.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/rules/python-style.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/rules/testing.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.claude/settings.json +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.github/workflows/publish-pypi.yml +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.github/workflows/publish-testpypi.yml +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.mcp.json.example +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/.python-version +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/CONTRIBUTING.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/LICENSE +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/SECURITY.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/benchmarks/enterprise_benchmark_results.json +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/.env.example +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/README.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/docker-compose.yml +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/get-db-urls.sh +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/init-schema.sql +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/large-schema.sql +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/localstack-init.sh +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docker/test-redshift.sh +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docs/ENTERPRISE.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/docs/Redshift.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/advanced/test_multiple_providers.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/advanced/test_simple_api.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/database_specific/test_odoo_integration.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/default_examples.json +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/documentation/real_world_example.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/getting_started/basic_usage.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/getting_started/simple_demo.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/getting_started/test_api_setup.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/schema_management/test_auto_schema.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/examples/schema_management/test_schema_filters_comprehensive.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/install-dev.sh +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/mcp_server/README.md +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/mcp_server/example_config.json +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/mcp_server/pyproject.toml +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/mcp_server/server.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/local_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/openai_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/config/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/core/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/core/entities.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/exceptions/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/factories/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/factories/repository_factory.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/ai_provider.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/cache.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/embedding_provider.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/query_optimizer.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/schema_repository.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/schema_strategy.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/example_loader.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/example_store.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/services/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/src/nlp2sql/utils/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/test_tmp/.gitignore +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/mcp/__init__.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/mcp/test_sql_security.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_basic.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_integration.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_redshift_integration.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_repository_cache.py +0 -0
- {nlp2sql-0.2.0rc5 → nlp2sql-0.2.0rc7}/tests/test_repository_factory.py +0 -0
|
@@ -51,6 +51,8 @@ The codebase follows Clean Architecture (Hexagonal/Ports & Adapters):
|
|
|
51
51
|
```
|
|
52
52
|
src/nlp2sql/
|
|
53
53
|
├── core/ # Business entities (pure Python, no external dependencies)
|
|
54
|
+
│ ├── entities.py # Query, SQLQuery, DatabaseType
|
|
55
|
+
│ └── database_prompts.py # SQL dialect hints for AI providers
|
|
54
56
|
├── ports/ # Interfaces/abstractions (contracts)
|
|
55
57
|
│ ├── ai_provider.py # AIProviderPort - interface for AI providers
|
|
56
58
|
│ ├── embedding_provider.py # EmbeddingProviderPort - interface for embeddings
|
|
@@ -69,7 +71,8 @@ src/nlp2sql/
|
|
|
69
71
|
├── schema/ # Schema management
|
|
70
72
|
│ ├── schema_manager.py # Coordinates filtering and strategies
|
|
71
73
|
│ ├── schema_analyzer.py # Scores schema relevance
|
|
72
|
-
│
|
|
74
|
+
│ ├── schema_embedding_manager.py # FAISS vector embeddings
|
|
75
|
+
│ └── example_store.py # ExampleStore - FAISS-indexed few-shot examples
|
|
73
76
|
├── config/ # Configuration (Pydantic Settings)
|
|
74
77
|
├── exceptions/ # Custom exceptions hierarchy
|
|
75
78
|
└── cli.py # Click-based CLI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nlp2sql
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0rc7
|
|
4
4
|
Summary: Enterprise-ready Natural Language to SQL converter with multi-provider support. Built for production scale (1000+ tables) with Clean Architecture.
|
|
5
5
|
Project-URL: Homepage, https://github.com/luiscarbonel1991/nlp2sql
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/luiscarbonel1991/nlp2sql/issues
|
|
@@ -77,11 +77,17 @@ Provides-Extra: oracle
|
|
|
77
77
|
Requires-Dist: oracledb>=2.0.0; extra == 'oracle'
|
|
78
78
|
Description-Content-Type: text/markdown
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
<p align="center">
|
|
81
|
+
<img src="assets/nlp2sql-logo.png" alt="nlp2sql logo" width="400">
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
<p align="center">
|
|
85
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
86
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="Python 3.9+"></a>
|
|
87
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
|
|
88
|
+
</p>
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
[](https://www.python.org/downloads/)
|
|
84
|
-
[](https://github.com/psf/black)
|
|
90
|
+
# nlp2sql
|
|
85
91
|
|
|
86
92
|
**Enterprise-ready Natural Language to SQL converter with multi-provider support**
|
|
87
93
|
|
|
@@ -174,7 +180,33 @@ async def main():
|
|
|
174
180
|
result2 = await service.generate_sql("Show recent orders")
|
|
175
181
|
```
|
|
176
182
|
|
|
177
|
-
### 4.
|
|
183
|
+
### 4. Few-Shot Examples (Improved Accuracy)
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
|
|
187
|
+
|
|
188
|
+
# Create example store with domain-specific examples
|
|
189
|
+
embedding_provider = create_embedding_provider("openai", api_key=os.getenv("OPENAI_API_KEY"))
|
|
190
|
+
example_store = ExampleStore(embedding_provider=embedding_provider)
|
|
191
|
+
await example_store.add_examples([
|
|
192
|
+
{
|
|
193
|
+
"question": "What was the total revenue last month?",
|
|
194
|
+
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
|
|
195
|
+
"database_type": "redshift",
|
|
196
|
+
"metadata": {"category": "aggregation"}
|
|
197
|
+
}
|
|
198
|
+
])
|
|
199
|
+
|
|
200
|
+
service = await create_and_initialize_service(
|
|
201
|
+
database_url="redshift://user:pass@host:5439/db",
|
|
202
|
+
ai_provider="openai",
|
|
203
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
204
|
+
example_store=example_store
|
|
205
|
+
)
|
|
206
|
+
result = await service.generate_sql("Show me total sales this quarter")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 5. Large Database with Schema Filtering
|
|
178
210
|
|
|
179
211
|
```python
|
|
180
212
|
from nlp2sql import create_and_initialize_service
|
|
@@ -208,6 +240,23 @@ nlp2sql inspect --database-url postgresql://localhost/mydb
|
|
|
208
240
|
nlp2sql benchmark --database-url postgresql://localhost/mydb
|
|
209
241
|
```
|
|
210
242
|
|
|
243
|
+
## How It Works
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
Question ──► Cache check ──► Schema retrieval ──► Relevance filtering ──► Context building ──► AI generation ──► Validation
|
|
247
|
+
│ │ │
|
|
248
|
+
SchemaRepository FAISS + TF-IDF hybrid Reuses precomputed
|
|
249
|
+
(+ disk cache) + batch scoring relevance scores
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
1. **Schema retrieval** -- Fetches tables from database via `SchemaRepository` (with disk cache for Redshift)
|
|
253
|
+
2. **Relevance filtering** -- FAISS dense search + TF-IDF sparse search (50/50 hybrid) finds candidate tables; batch scoring refines with precomputed embeddings
|
|
254
|
+
3. **Context building** -- Builds optimized schema context within token limits, reusing scores from step 2 (zero additional embedding calls)
|
|
255
|
+
4. **SQL generation** -- AI provider (OpenAI, Anthropic, or Gemini) generates SQL from question + schema context
|
|
256
|
+
5. **Validation** -- SQL syntax and safety checks before returning results
|
|
257
|
+
|
|
258
|
+
See [Architecture](docs/ARCHITECTURE.md) for the detailed flow with method references and design decisions.
|
|
259
|
+
|
|
211
260
|
## Provider Comparison
|
|
212
261
|
|
|
213
262
|
| Provider | Context Size | Best For |
|
|
@@ -220,17 +269,21 @@ See [Configuration](docs/CONFIGURATION.md) for detailed provider setup.
|
|
|
220
269
|
|
|
221
270
|
## Architecture
|
|
222
271
|
|
|
272
|
+
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.
|
|
273
|
+
|
|
223
274
|
```
|
|
224
275
|
nlp2sql/
|
|
225
|
-
├── core/ # Business entities
|
|
226
|
-
├── ports/ # Interfaces
|
|
227
|
-
├── adapters/ #
|
|
228
|
-
├── services/ #
|
|
229
|
-
├── schema/ # Schema management
|
|
230
|
-
├── config/ #
|
|
231
|
-
└── exceptions/ # Custom
|
|
276
|
+
├── core/ # Business entities (pure Python, no dependencies)
|
|
277
|
+
├── ports/ # Interfaces (AIProviderPort, SchemaRepositoryPort, EmbeddingProviderPort)
|
|
278
|
+
├── adapters/ # Implementations (OpenAI, Anthropic, Gemini, PostgreSQL, Redshift)
|
|
279
|
+
├── services/ # Orchestration (QueryGenerationService)
|
|
280
|
+
├── schema/ # Schema management (SchemaManager, SchemaAnalyzer, SchemaEmbeddingManager)
|
|
281
|
+
├── config/ # Pydantic Settings configuration
|
|
282
|
+
└── exceptions/ # Custom exception hierarchy
|
|
232
283
|
```
|
|
233
284
|
|
|
285
|
+
See [Architecture](docs/ARCHITECTURE.md) for the full component diagram, data flow, and design decisions.
|
|
286
|
+
|
|
234
287
|
## Development
|
|
235
288
|
|
|
236
289
|
```bash
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/nlp2sql-logo.png" alt="nlp2sql logo" width="400">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
7
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="Python 3.9+"></a>
|
|
8
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
|
|
9
|
+
</p>
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
[](https://www.python.org/downloads/)
|
|
5
|
-
[](https://github.com/psf/black)
|
|
11
|
+
# nlp2sql
|
|
6
12
|
|
|
7
13
|
**Enterprise-ready Natural Language to SQL converter with multi-provider support**
|
|
8
14
|
|
|
@@ -95,7 +101,33 @@ async def main():
|
|
|
95
101
|
result2 = await service.generate_sql("Show recent orders")
|
|
96
102
|
```
|
|
97
103
|
|
|
98
|
-
### 4.
|
|
104
|
+
### 4. Few-Shot Examples (Improved Accuracy)
|
|
105
|
+
|
|
106
|
+
```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
|
|
126
|
+
)
|
|
127
|
+
result = await service.generate_sql("Show me total sales this quarter")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 5. Large Database with Schema Filtering
|
|
99
131
|
|
|
100
132
|
```python
|
|
101
133
|
from nlp2sql import create_and_initialize_service
|
|
@@ -129,6 +161,23 @@ nlp2sql inspect --database-url postgresql://localhost/mydb
|
|
|
129
161
|
nlp2sql benchmark --database-url postgresql://localhost/mydb
|
|
130
162
|
```
|
|
131
163
|
|
|
164
|
+
## How It Works
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Question ──► Cache check ──► Schema retrieval ──► Relevance filtering ──► Context building ──► AI generation ──► Validation
|
|
168
|
+
│ │ │
|
|
169
|
+
SchemaRepository FAISS + TF-IDF hybrid Reuses precomputed
|
|
170
|
+
(+ disk cache) + batch scoring relevance scores
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
1. **Schema retrieval** -- Fetches tables from database via `SchemaRepository` (with disk cache for Redshift)
|
|
174
|
+
2. **Relevance filtering** -- FAISS dense search + TF-IDF sparse search (50/50 hybrid) finds candidate tables; batch scoring refines with precomputed embeddings
|
|
175
|
+
3. **Context building** -- Builds optimized schema context within token limits, reusing scores from step 2 (zero additional embedding calls)
|
|
176
|
+
4. **SQL generation** -- AI provider (OpenAI, Anthropic, or Gemini) generates SQL from question + schema context
|
|
177
|
+
5. **Validation** -- SQL syntax and safety checks before returning results
|
|
178
|
+
|
|
179
|
+
See [Architecture](docs/ARCHITECTURE.md) for the detailed flow with method references and design decisions.
|
|
180
|
+
|
|
132
181
|
## Provider Comparison
|
|
133
182
|
|
|
134
183
|
| Provider | Context Size | Best For |
|
|
@@ -141,17 +190,21 @@ See [Configuration](docs/CONFIGURATION.md) for detailed provider setup.
|
|
|
141
190
|
|
|
142
191
|
## Architecture
|
|
143
192
|
|
|
193
|
+
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.
|
|
194
|
+
|
|
144
195
|
```
|
|
145
196
|
nlp2sql/
|
|
146
|
-
├── core/ # Business entities
|
|
147
|
-
├── ports/ # Interfaces
|
|
148
|
-
├── adapters/ #
|
|
149
|
-
├── services/ #
|
|
150
|
-
├── schema/ # Schema management
|
|
151
|
-
├── config/ #
|
|
152
|
-
└── exceptions/ # Custom
|
|
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
|
|
153
204
|
```
|
|
154
205
|
|
|
206
|
+
See [Architecture](docs/ARCHITECTURE.md) for the full component diagram, data flow, and design decisions.
|
|
207
|
+
|
|
155
208
|
## Development
|
|
156
209
|
|
|
157
210
|
```bash
|
|
Binary file
|
|
@@ -4,6 +4,50 @@ Complete reference for the nlp2sql Python API and CLI.
|
|
|
4
4
|
|
|
5
5
|
## Python API
|
|
6
6
|
|
|
7
|
+
### Request Flow
|
|
8
|
+
|
|
9
|
+
The three entry points differ in lifecycle and performance characteristics:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
generate_sql_from_db(question)
|
|
13
|
+
└─ create_and_initialize_service() # creates service + loads schema every call
|
|
14
|
+
└─ create_query_service() # wires components (AI adapter, repository, embeddings)
|
|
15
|
+
└─ service.initialize() # fetches schema, builds FAISS index (skips if cached on disk)
|
|
16
|
+
└─ service.generate_sql()
|
|
17
|
+
|
|
18
|
+
create_and_initialize_service() # init once, reuse for many queries (recommended)
|
|
19
|
+
└─ create_query_service()
|
|
20
|
+
└─ service.initialize()
|
|
21
|
+
└─ service.generate_sql() # schema + FAISS index already loaded
|
|
22
|
+
└─ service.generate_sql() # in-memory caches warm
|
|
23
|
+
|
|
24
|
+
create_query_service() # full manual control
|
|
25
|
+
└─ caller must call service.initialize() before first query
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**When to use which:**
|
|
29
|
+
|
|
30
|
+
| Function | Best for | Schema loading |
|
|
31
|
+
|----------|----------|----------------|
|
|
32
|
+
| `generate_sql_from_db` | One-off scripts, notebooks | Every call (init + query) |
|
|
33
|
+
| `create_and_initialize_service` | APIs, multi-query sessions | Once at startup |
|
|
34
|
+
| `create_query_service` | Custom initialization logic | Manual (`initialize()`) |
|
|
35
|
+
|
|
36
|
+
For multiple queries, always prefer `create_and_initialize_service` -- it loads the schema and FAISS index once, and subsequent `generate_sql()` calls benefit from in-memory caches:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Init once, query many times
|
|
40
|
+
service = await create_and_initialize_service(
|
|
41
|
+
database_url="postgresql://user:pass@localhost:5432/db",
|
|
42
|
+
ai_provider="openai",
|
|
43
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
for question in ["Count users", "Show recent orders", "Revenue by month"]:
|
|
47
|
+
result = await service.generate_sql(question)
|
|
48
|
+
print(result["sql"])
|
|
49
|
+
```
|
|
50
|
+
|
|
7
51
|
### Quick Start Functions
|
|
8
52
|
|
|
9
53
|
#### `generate_sql_from_db`
|
|
@@ -36,6 +80,7 @@ print(result['validation']) # Validation results
|
|
|
36
80
|
| `api_key` | `str` | Yes | Provider API key |
|
|
37
81
|
| `embedding_provider_type` | `str` | No | Embedding provider: `local` (default), `openai` |
|
|
38
82
|
| `schema_filters` | `dict` | No | Schema filtering options |
|
|
83
|
+
| `example_store` | `ExampleStore` | No | Pre-loaded few-shot examples for better SQL generation |
|
|
39
84
|
|
|
40
85
|
**Returns:** `dict` with keys `sql`, `confidence`, `explanation`, `validation`
|
|
41
86
|
|
|
@@ -65,8 +110,11 @@ result2 = await service.generate_sql("Show recent orders")
|
|
|
65
110
|
| `ai_provider` | `str` | Yes | AI provider name |
|
|
66
111
|
| `api_key` | `str` | Yes | Provider API key |
|
|
67
112
|
| `schema_filters` | `dict` | No | Schema filtering options |
|
|
68
|
-
| `embedding_provider_type` | `str` | No | Embedding provider type |
|
|
113
|
+
| `embedding_provider_type` | `str` | No | Embedding provider type (see note below) |
|
|
69
114
|
| `database_type` | `DatabaseType` | No | Database type (auto-detected) |
|
|
115
|
+
| `example_store` | `ExampleStore` | No | Pre-loaded few-shot examples for better SQL generation |
|
|
116
|
+
|
|
117
|
+
**Note on `embedding_provider_type`:** This controls how schema relevance filtering works. With `"local"` or `"openai"`, the system builds a FAISS vector index for semantic search (recommended for databases with 50+ tables). With `None` (default), the system attempts to load local embeddings silently and falls back to text-only matching if `sentence-transformers` is not installed. Text-only matching uses exact name, normalized singular/plural, and token overlap -- effective for small schemas but less accurate for large ones.
|
|
70
118
|
|
|
71
119
|
#### `create_query_service`
|
|
72
120
|
|
|
@@ -95,6 +143,42 @@ result = await service.generate_sql(
|
|
|
95
143
|
)
|
|
96
144
|
```
|
|
97
145
|
|
|
146
|
+
### Few-Shot Examples
|
|
147
|
+
|
|
148
|
+
Improve SQL accuracy by providing domain-specific examples via `ExampleStore`. Examples are indexed with FAISS for semantic retrieval -- the most relevant examples are automatically included in the LLM prompt.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
|
|
152
|
+
|
|
153
|
+
# Create example store
|
|
154
|
+
embedding_provider = create_embedding_provider("openai", api_key="your-key")
|
|
155
|
+
example_store = ExampleStore(embedding_provider=embedding_provider)
|
|
156
|
+
|
|
157
|
+
# Add domain-specific examples
|
|
158
|
+
await example_store.add_examples([
|
|
159
|
+
{
|
|
160
|
+
"question": "What was the total revenue last month?",
|
|
161
|
+
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
|
|
162
|
+
"database_type": "redshift",
|
|
163
|
+
"metadata": {"category": "aggregation"}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"question": "Show orders by country",
|
|
167
|
+
"sql": "SELECT country, COUNT(*) AS total_orders FROM orders GROUP BY country ORDER BY total_orders DESC",
|
|
168
|
+
"database_type": "redshift",
|
|
169
|
+
"metadata": {"category": "aggregation"}
|
|
170
|
+
}
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
# Pass to service
|
|
174
|
+
service = await create_and_initialize_service(
|
|
175
|
+
database_url="redshift://user:pass@host:5439/db",
|
|
176
|
+
ai_provider="openai",
|
|
177
|
+
api_key="your-key",
|
|
178
|
+
example_store=example_store
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
98
182
|
### Embedding Providers
|
|
99
183
|
|
|
100
184
|
```python
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
nlp2sql follows Clean Architecture (Hexagonal/Ports & Adapters) principles, enabling multi-provider support and maintainability at enterprise scale.
|
|
4
|
+
|
|
5
|
+
## Component Diagram
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TB
|
|
9
|
+
subgraph "Public API"
|
|
10
|
+
API[create_query_service<br>generate_sql_from_db]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
subgraph "Service Layer"
|
|
14
|
+
QGS[QueryGenerationService]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
subgraph "Schema Management"
|
|
18
|
+
SM[SchemaManager]
|
|
19
|
+
SA[SchemaAnalyzer<br>- relevance scoring<br>- compression]
|
|
20
|
+
SEM[SchemaEmbeddingManager<br>- FAISS index<br>- TF-IDF search]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
subgraph "Ports (Interfaces)"
|
|
24
|
+
AIP[AIProviderPort]
|
|
25
|
+
SRP[SchemaRepositoryPort]
|
|
26
|
+
EP[EmbeddingProviderPort]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph "AI Adapters"
|
|
30
|
+
OA[OpenAI Adapter]
|
|
31
|
+
AA[Anthropic Adapter]
|
|
32
|
+
GA[Gemini Adapter]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
subgraph "Database Adapters"
|
|
36
|
+
PG[PostgreSQL Repository]
|
|
37
|
+
RS[Redshift Repository]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph "Embedding Adapters"
|
|
41
|
+
LE[Local Embeddings<br>sentence-transformers]
|
|
42
|
+
OE[OpenAI Embeddings]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
API --> QGS
|
|
46
|
+
QGS --> SM
|
|
47
|
+
QGS --> AIP
|
|
48
|
+
SM --> SA
|
|
49
|
+
SM --> SEM
|
|
50
|
+
SM --> SRP
|
|
51
|
+
SEM --> EP
|
|
52
|
+
AIP --> OA
|
|
53
|
+
AIP --> AA
|
|
54
|
+
AIP --> GA
|
|
55
|
+
SRP --> PG
|
|
56
|
+
SRP --> RS
|
|
57
|
+
EP --> LE
|
|
58
|
+
EP --> OE
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Component Descriptions
|
|
62
|
+
|
|
63
|
+
### Public API
|
|
64
|
+
|
|
65
|
+
Entry points for the library (`__init__.py`):
|
|
66
|
+
|
|
67
|
+
| Function | Purpose |
|
|
68
|
+
|----------|---------|
|
|
69
|
+
| `generate_sql_from_db` | One-line convenience: creates service, loads schema, generates SQL, returns result |
|
|
70
|
+
| `create_and_initialize_service` | Creates and initializes service once; reuse for multiple queries (recommended) |
|
|
71
|
+
| `create_query_service` | Creates service with full control; caller must call `initialize()` manually |
|
|
72
|
+
|
|
73
|
+
### Service Layer
|
|
74
|
+
|
|
75
|
+
**QueryGenerationService** (`services/query_service.py`) orchestrates the full pipeline:
|
|
76
|
+
|
|
77
|
+
1. Check query cache
|
|
78
|
+
2. Get optimal schema context via `SchemaManager`
|
|
79
|
+
3. Find relevant examples (from `ExampleStore` or hardcoded fallback)
|
|
80
|
+
4. Build `QueryContext` and call `AIProvider.generate_query()`
|
|
81
|
+
5. Optionally optimize via `QueryOptimizerPort`
|
|
82
|
+
6. Validate SQL via `AIProvider.validate_query()`
|
|
83
|
+
7. Cache valid results
|
|
84
|
+
|
|
85
|
+
### Schema Management
|
|
86
|
+
|
|
87
|
+
| Component | File | Responsibility |
|
|
88
|
+
|-----------|------|----------------|
|
|
89
|
+
| **SchemaManager** | `schema/manager.py` | Coordinates filtering, relevance search, and context building |
|
|
90
|
+
| **SchemaAnalyzer** | `schema/analyzer.py` | Scores table/column relevance (text + semantic), compresses schema within token limits |
|
|
91
|
+
| **SchemaEmbeddingManager** | `schema/embedding_manager.py` | Maintains FAISS vector index + TF-IDF sparse index for hybrid search |
|
|
92
|
+
|
|
93
|
+
### Ports (Interfaces)
|
|
94
|
+
|
|
95
|
+
Abstract contracts in `ports/` that define layer boundaries:
|
|
96
|
+
|
|
97
|
+
- **AIProviderPort** (`ports/ai_provider.py`): `generate_query()`, `validate_query()`
|
|
98
|
+
- **SchemaRepositoryPort** (`ports/schema_repository.py`): `get_tables()`, `get_table_info()`, `get_related_tables()`
|
|
99
|
+
- **EmbeddingProviderPort** (`ports/embedding_provider.py`): `encode()`, `get_embedding_dimension()`
|
|
100
|
+
- **SchemaStrategyPort** (`ports/schema_strategy.py`): `build_context()`, `score_relevance()`, `chunk_schema()`
|
|
101
|
+
- **CachePort** (`ports/cache.py`): `get()`, `set()`, `clear()`
|
|
102
|
+
|
|
103
|
+
### Adapters
|
|
104
|
+
|
|
105
|
+
Concrete implementations of ports:
|
|
106
|
+
|
|
107
|
+
| Layer | Adapters | Files |
|
|
108
|
+
|-------|----------|-------|
|
|
109
|
+
| AI Providers | OpenAI GPT, Anthropic Claude, Google Gemini | `adapters/openai_adapter.py`, `anthropic_adapter.py`, `gemini_adapter.py` |
|
|
110
|
+
| Databases | PostgreSQL, Amazon Redshift | `adapters/postgres_repository.py`, `redshift_adapter.py` |
|
|
111
|
+
| Embeddings | Local (sentence-transformers), OpenAI | `adapters/local_embedding_adapter.py`, `openai_embedding_adapter.py` |
|
|
112
|
+
|
|
113
|
+
## Detailed Query Flow
|
|
114
|
+
|
|
115
|
+
```mermaid
|
|
116
|
+
sequenceDiagram
|
|
117
|
+
participant User
|
|
118
|
+
participant QGS as QueryGenerationService
|
|
119
|
+
participant SM as SchemaManager
|
|
120
|
+
participant SEM as SchemaEmbeddingManager
|
|
121
|
+
participant SA as SchemaAnalyzer
|
|
122
|
+
participant Repo as SchemaRepository
|
|
123
|
+
participant AI as AIProvider
|
|
124
|
+
|
|
125
|
+
User->>QGS: generate_sql(question)
|
|
126
|
+
QGS->>QGS: Check query cache
|
|
127
|
+
QGS->>SM: get_optimal_schema_context(question)
|
|
128
|
+
|
|
129
|
+
SM->>SM: _find_relevant_tables(question)
|
|
130
|
+
SM->>SEM: search_similar_with_embedding(query)
|
|
131
|
+
SEM->>SEM: FAISS dense + TF-IDF sparse (50/50 hybrid)
|
|
132
|
+
SEM-->>SM: (results, query_embedding)
|
|
133
|
+
|
|
134
|
+
SM->>SEM: get_table_embeddings(table_names)
|
|
135
|
+
SEM->>SEM: Reconstruct from FAISS index (zero API calls)
|
|
136
|
+
SEM-->>SM: precomputed_element_embeddings
|
|
137
|
+
|
|
138
|
+
SM->>SA: score_relevance_batch(precomputed embeddings)
|
|
139
|
+
SA->>SA: Text scoring + semantic scoring (zero API calls)
|
|
140
|
+
SA-->>SM: scored tables
|
|
141
|
+
|
|
142
|
+
SM->>Repo: get_table_info(per table)
|
|
143
|
+
Repo-->>SM: TableInfo with columns, keys, indexes
|
|
144
|
+
|
|
145
|
+
Note over SM: Attaches relevance_score to each table dict
|
|
146
|
+
|
|
147
|
+
SM->>SA: build_context(tables with relevance_score)
|
|
148
|
+
SA->>SA: Reuses existing scores (zero API calls)
|
|
149
|
+
SA-->>SM: schema context string
|
|
150
|
+
|
|
151
|
+
SM-->>QGS: schema_context
|
|
152
|
+
|
|
153
|
+
QGS->>AI: generate_query(QueryContext)
|
|
154
|
+
AI-->>QGS: SQL + confidence + explanation
|
|
155
|
+
QGS->>AI: validate_query(sql, schema_context)
|
|
156
|
+
AI-->>QGS: validation result
|
|
157
|
+
QGS-->>User: {sql, confidence, explanation, validation}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Step-by-step with file references
|
|
161
|
+
|
|
162
|
+
1. **`generate_sql()`** (`services/query_service.py`) -- checks query cache, then calls `SchemaManager.get_optimal_schema_context()`
|
|
163
|
+
2. **`get_optimal_schema_context()`** (`schema/manager.py`) -- checks schema cache, then calls `_find_relevant_tables()`
|
|
164
|
+
3. **`_find_relevant_tables()`** (`schema/manager.py`) -- two strategies:
|
|
165
|
+
- **Strategy 1**: `SchemaEmbeddingManager.search_similar_with_embedding()` -- FAISS + TF-IDF hybrid search returns candidate tables + the query embedding
|
|
166
|
+
- **Strategy 2**: `SchemaAnalyzer.score_relevance_batch()` -- batch scores token-matched tables using precomputed query + element embeddings (reconstructed from FAISS via `get_table_embeddings()`)
|
|
167
|
+
4. **`get_table_info()`** (`ports/schema_repository.py`) -- fetches full table metadata; manager attaches `relevance_score` to each table dict
|
|
168
|
+
5. **`build_context()`** (`schema/analyzer.py`) -- builds schema context string within token limits; reuses pre-attached `relevance_score` (no re-scoring, zero additional embedding calls)
|
|
169
|
+
6. **`generate_query()`** (`ports/ai_provider.py`) -- AI provider generates SQL from question + schema context
|
|
170
|
+
7. **`validate_query()`** (`ports/ai_provider.py`) -- validates SQL syntax and safety
|
|
171
|
+
|
|
172
|
+
## Schema Relevance Pipeline
|
|
173
|
+
|
|
174
|
+
The pipeline in `_find_relevant_tables()` (`schema/manager.py`) uses two complementary strategies to find the most relevant tables for a query:
|
|
175
|
+
|
|
176
|
+
### Strategy 1: Hybrid FAISS + TF-IDF Search
|
|
177
|
+
|
|
178
|
+
`SchemaEmbeddingManager._search_similar_core()` (`schema/embedding_manager.py`) combines:
|
|
179
|
+
|
|
180
|
+
- **Dense search (FAISS)**: Semantic similarity via vector embeddings (`IndexFlatIP` inner product). Captures meaning (e.g., "revenue" matches "sales_amount").
|
|
181
|
+
- **Sparse search (TF-IDF)**: Exact keyword matching via `TfidfVectorizer` with unigram+bigram features. Captures exact names (e.g., "organizations" matches `organization` table).
|
|
182
|
+
- **Hybrid score**: `0.5 * dense_score + 0.5 * sparse_score`. Equal weighting ensures both semantic meaning and exact keyword matches contribute.
|
|
183
|
+
|
|
184
|
+
This returns candidate tables + the query embedding (for reuse downstream).
|
|
185
|
+
|
|
186
|
+
### Strategy 2: Batch Scoring with Precomputed Embeddings
|
|
187
|
+
|
|
188
|
+
`SchemaAnalyzer.score_relevance_batch()` (`schema/analyzer.py`) refines candidates:
|
|
189
|
+
|
|
190
|
+
1. **Text-based scoring** (fast, no API calls): exact name match, normalized singular/plural matching, token overlap, column-name matching
|
|
191
|
+
2. **Semantic scoring** (only for elements with text score < 0.8): uses precomputed embeddings passed from the manager, avoiding new API calls
|
|
192
|
+
3. The manager passes both the `query_embedding` from Strategy 1 and `element_embeddings` reconstructed from the FAISS index via `get_table_embeddings()`
|
|
193
|
+
|
|
194
|
+
### Score Reuse in build_context
|
|
195
|
+
|
|
196
|
+
`SchemaManager.get_optimal_schema_context()` attaches the final `relevance_score` to each table dict before calling `SchemaAnalyzer.build_context()`. The analyzer checks for this key and skips re-scoring when present. This eliminates ~10-15 embedding API calls per query that would otherwise happen in the per-table `score_relevance()` path.
|
|
197
|
+
|
|
198
|
+
**Net result**: ~1 embedding API call per query (for the initial FAISS search), down from ~15-18 without these optimizations.
|
|
199
|
+
|
|
200
|
+
## Caching Layers
|
|
201
|
+
|
|
202
|
+
The system has multiple caching layers:
|
|
203
|
+
|
|
204
|
+
| Layer | Scope | Storage | TTL |
|
|
205
|
+
|-------|-------|---------|-----|
|
|
206
|
+
| **Query result cache** | Full SQL response | `CachePort` (external) | Indefinite (valid results) |
|
|
207
|
+
| **Schema context cache** | Built schema string | `CachePort` (external) | Per query+db+tokens key |
|
|
208
|
+
| **Table relevance cache** | Relevant tables list | In-memory dict | Per `SchemaManager` instance |
|
|
209
|
+
| **All-tables cache** | Filtered table list | In-memory dict (`_schema_cache`) | Until `refresh_schema()` |
|
|
210
|
+
| **FAISS + TF-IDF index** | Schema embeddings | Disk (pickle + FAISS) | Until `NLP2SQL_SCHEMA_CACHE_TTL_HOURS` expires |
|
|
211
|
+
| **Query embedding cache** | Per-query embeddings | In-memory dict | Cleared per `_find_relevant_tables()` call |
|
|
212
|
+
| **Repository disk cache** | Schema metadata (Redshift) | Pickle file | `NLP2SQL_SCHEMA_CACHE_TTL_HOURS` |
|
|
213
|
+
|
|
214
|
+
## Design Decisions
|
|
215
|
+
|
|
216
|
+
| Decision | Rationale |
|
|
217
|
+
|----------|-----------|
|
|
218
|
+
| **Ports & Adapters** | Swap AI/DB/embedding providers without touching core logic. Adding a new provider means implementing one port interface. |
|
|
219
|
+
| **FAISS + TF-IDF hybrid (50/50)** | Semantic embeddings alone miss exact keyword matches (e.g., "count organizations" should find `organization` table). TF-IDF catches these. Equal weighting balances both signals. |
|
|
220
|
+
| **Disk-cached FAISS index** | Avoids re-embedding 600+ schema elements on every run. Isolated per database+schema via MD5 hash of `{database_url}:{schema_name}`. |
|
|
221
|
+
| **Precomputed embeddings in batch scoring** | `_find_relevant_tables` reuses the query embedding from FAISS search and reconstructs table embeddings from the index. Result: ~1 embedding API call per query instead of ~15. |
|
|
222
|
+
| **relevance_score passthrough to build_context** | Manager already scored tables during `_find_relevant_tables`; analyzer reuses those scores in `build_context` instead of re-computing them (zero redundant API calls). |
|
|
223
|
+
| **Repository disk cache (Redshift)** | Redshift's `SVV_TABLES`/`SVV_COLUMNS` queries are expensive. Bulk query + pickle cache avoids repeated system catalog scans. |
|
|
224
|
+
| **Graceful embedding degradation** | System works without embeddings (text-based matching only). If `sentence-transformers` is not installed, logs a warning and continues. |
|
|
225
|
+
| **Schema filters applied before indexing** | Filters reduce the number of elements indexed in FAISS, making search faster and more focused. |
|
|
226
|
+
|
|
227
|
+
## Directory Structure
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
src/nlp2sql/
|
|
231
|
+
├── core/ # Business entities (pure Python, no external dependencies)
|
|
232
|
+
│ ├── entities.py # Query, SQLQuery, DatabaseType
|
|
233
|
+
│ └── database_prompts.py # SQL dialect hints for AI providers (centralized)
|
|
234
|
+
├── ports/ # Interfaces/abstractions
|
|
235
|
+
│ ├── ai_provider.py # AIProviderPort, QueryContext, QueryResponse
|
|
236
|
+
│ ├── schema_repository.py # SchemaRepositoryPort, TableInfo
|
|
237
|
+
│ ├── embedding_provider.py # EmbeddingProviderPort
|
|
238
|
+
│ ├── schema_strategy.py # SchemaStrategyPort, SchemaContext, SchemaChunk
|
|
239
|
+
│ ├── cache.py # CachePort
|
|
240
|
+
│ └── query_optimizer.py # QueryOptimizerPort
|
|
241
|
+
├── adapters/ # External implementations
|
|
242
|
+
│ ├── openai_adapter.py # OpenAI GPT
|
|
243
|
+
│ ├── anthropic_adapter.py # Anthropic Claude
|
|
244
|
+
│ ├── gemini_adapter.py # Google Gemini
|
|
245
|
+
│ ├── postgres_repository.py # PostgreSQL
|
|
246
|
+
│ ├── redshift_adapter.py # Amazon Redshift (with disk cache)
|
|
247
|
+
│ ├── local_embedding_adapter.py # sentence-transformers
|
|
248
|
+
│ └── openai_embedding_adapter.py # OpenAI embeddings
|
|
249
|
+
├── services/ # Application services
|
|
250
|
+
│ └── query_service.py # QueryGenerationService (orchestrator)
|
|
251
|
+
├── schema/ # Schema management
|
|
252
|
+
│ ├── manager.py # SchemaManager (coordinates strategies)
|
|
253
|
+
│ ├── analyzer.py # SchemaAnalyzer (scoring, compression)
|
|
254
|
+
│ ├── embedding_manager.py # SchemaEmbeddingManager (FAISS + TF-IDF)
|
|
255
|
+
│ └── example_store.py # ExampleStore (FAISS-indexed few-shot examples)
|
|
256
|
+
├── config/ # Configuration
|
|
257
|
+
│ └── settings.py # Pydantic Settings (env vars)
|
|
258
|
+
├── exceptions/ # Custom exception hierarchy
|
|
259
|
+
└── factories.py # RepositoryFactory
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Related Documentation
|
|
263
|
+
|
|
264
|
+
- [API Reference](API.md) - Python API and CLI
|
|
265
|
+
- [Configuration](CONFIGURATION.md) - Environment variables and design rationale
|
|
266
|
+
- [Enterprise Guide](ENTERPRISE.md) - Large-scale deployment
|
|
267
|
+
- [Redshift Support](Redshift.md) - Amazon Redshift setup
|
|
@@ -55,6 +55,13 @@ export NLP2SQL_EMBEDDING_MODEL=all-MiniLM-L6-v2
|
|
|
55
55
|
export NLP2SQL_EMBEDDING_PROVIDER=local
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
**Design rationale for key variables:**
|
|
59
|
+
|
|
60
|
+
- **`NLP2SQL_MAX_SCHEMA_TOKENS`** -- Limits the schema context sent to the AI provider. Higher values give the AI more schema information (better accuracy for complex joins) but cost more tokens. The default 8000 covers ~20-30 tables with full column definitions. For large schemas with many relevant tables, increase to 12000-16000.
|
|
61
|
+
- **`NLP2SQL_SCHEMA_CACHE_TTL_HOURS`** -- Controls when the FAISS index and schema metadata are refreshed from the database. The 24h default balances freshness against the cost of re-querying system catalogs (especially expensive for Redshift's `SVV_TABLES`/`SVV_COLUMNS`). Set lower in development or when schema changes frequently.
|
|
62
|
+
- **`NLP2SQL_EMBEDDINGS_DIR`** -- Where FAISS index files and TF-IDF metadata are stored on disk. Each database+schema combination gets its own subdirectory (MD5 hash of `{database_url}:{schema_name}`). Must be writable. In containerized environments (e.g., Claude Desktop MCP), the system falls back to `/tmp/nlp2sql_embeddings` if the default `./embeddings` is read-only.
|
|
63
|
+
- **`NLP2SQL_EMBEDDING_MODEL`** -- The `sentence-transformers` model for local embeddings. `all-MiniLM-L6-v2` (384 dimensions) is a good balance of speed and quality. Changing this after an index is built requires clearing the cache (`nlp2sql cache clear --embeddings`) since dimensions must match.
|
|
64
|
+
|
|
58
65
|
### General Settings
|
|
59
66
|
|
|
60
67
|
| Variable | Required | Default | Description |
|
|
@@ -76,6 +83,8 @@ export TOKENIZERS_PARALLELISM=false
|
|
|
76
83
|
|
|
77
84
|
Schema filters reduce the database schema to relevant tables for better performance and accuracy.
|
|
78
85
|
|
|
86
|
+
**Important:** Filters are applied *before* FAISS indexing (`SchemaManager._apply_schema_filters()` in `schema/manager.py`). Fewer elements indexed means faster semantic search and a more focused index. For enterprise databases (1000+ tables), well-chosen filters can reduce indexing time from minutes to seconds.
|
|
87
|
+
|
|
79
88
|
### Filter Options
|
|
80
89
|
|
|
81
90
|
| Filter | Type | Description |
|