nl2sql-engine 0.1.0__py3-none-any.whl
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.
- nl2sql/__init__.py +38 -0
- nl2sql/adapters/__init__.py +0 -0
- nl2sql/adapters/duckdb/__init__.py +0 -0
- nl2sql/adapters/duckdb/adapter.py +71 -0
- nl2sql/adapters/mssql/__init__.py +0 -0
- nl2sql/adapters/mssql/adapter.py +122 -0
- nl2sql/adapters/mysql/__init__.py +0 -0
- nl2sql/adapters/mysql/adapter.py +123 -0
- nl2sql/adapters/postgres/__init__.py +0 -0
- nl2sql/adapters/postgres/adapter.py +115 -0
- nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
- nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
- nl2sql/adapters/sqlalchemy_base/models.py +36 -0
- nl2sql/adapters/sqlite/__init__.py +0 -0
- nl2sql/adapters/sqlite/adapter.py +88 -0
- nl2sql/aggregation/__init__.py +3 -0
- nl2sql/aggregation/aggregator.py +98 -0
- nl2sql/aggregation/engines/__init__.py +3 -0
- nl2sql/aggregation/engines/polars_duckdb.py +125 -0
- nl2sql/api/__init__.py +0 -0
- nl2sql/api/auth_api.py +60 -0
- nl2sql/api/benchmark_api.py +114 -0
- nl2sql/api/datasource_api.py +132 -0
- nl2sql/api/indexing_api.py +59 -0
- nl2sql/api/llm_api.py +82 -0
- nl2sql/api/policy_api.py +135 -0
- nl2sql/api/query_api.py +138 -0
- nl2sql/api/result_api.py +24 -0
- nl2sql/api/settings_api.py +65 -0
- nl2sql/auth/__init__.py +8 -0
- nl2sql/auth/models.py +36 -0
- nl2sql/auth/rbac.py +25 -0
- nl2sql/cli/__init__.py +0 -0
- nl2sql/cli/checks.py +53 -0
- nl2sql/cli/commands/__init__.py +0 -0
- nl2sql/cli/commands/benchmark.py +34 -0
- nl2sql/cli/commands/doctor.py +49 -0
- nl2sql/cli/commands/indexing.py +126 -0
- nl2sql/cli/commands/info.py +25 -0
- nl2sql/cli/commands/install.py +27 -0
- nl2sql/cli/commands/policy.py +57 -0
- nl2sql/cli/commands/run.py +166 -0
- nl2sql/cli/commands/setup.py +415 -0
- nl2sql/cli/commands/visualize.py +34 -0
- nl2sql/cli/common/decorators.py +34 -0
- nl2sql/cli/config.py +24 -0
- nl2sql/cli/console.py +52 -0
- nl2sql/cli/demo/__init__.py +1 -0
- nl2sql/cli/demo/data.py +87 -0
- nl2sql/cli/demo/defaults.py +122 -0
- nl2sql/cli/demo/factory.py +289 -0
- nl2sql/cli/demo/manager.py +230 -0
- nl2sql/cli/demo/schemas.py +336 -0
- nl2sql/cli/demo/writers/__init__.py +0 -0
- nl2sql/cli/demo/writers/docker.py +182 -0
- nl2sql/cli/demo/writers/sqlite.py +88 -0
- nl2sql/cli/generators/datasources/__init__.py +3 -0
- nl2sql/cli/generators/datasources/generator.py +24 -0
- nl2sql/cli/generators/datasources/templates.py +7 -0
- nl2sql/cli/generators/env/__init__.py +3 -0
- nl2sql/cli/generators/env/generator.py +46 -0
- nl2sql/cli/generators/env/templates.py +25 -0
- nl2sql/cli/generators/llm/__init__.py +3 -0
- nl2sql/cli/generators/llm/generator.py +24 -0
- nl2sql/cli/generators/llm/templates.py +4 -0
- nl2sql/cli/generators/policies/__init__.py +3 -0
- nl2sql/cli/generators/policies/generator.py +20 -0
- nl2sql/cli/generators/policies/templates.py +2 -0
- nl2sql/cli/main.py +195 -0
- nl2sql/cli/reporting.py +878 -0
- nl2sql/cli/types.py +13 -0
- nl2sql/common/__init__.py +1 -0
- nl2sql/common/cancellation.py +25 -0
- nl2sql/common/context.py +5 -0
- nl2sql/common/errors.py +109 -0
- nl2sql/common/event_logger.py +88 -0
- nl2sql/common/exceptions.py +3 -0
- nl2sql/common/logger.py +119 -0
- nl2sql/common/metrics.py +50 -0
- nl2sql/common/resilience.py +59 -0
- nl2sql/common/settings.py +195 -0
- nl2sql/configs/__init__.py +6 -0
- nl2sql/configs/datasources.py +10 -0
- nl2sql/configs/llm.py +36 -0
- nl2sql/configs/manager.py +176 -0
- nl2sql/configs/policies.py +14 -0
- nl2sql/configs/sample_questions.py +11 -0
- nl2sql/configs/secrets.py +11 -0
- nl2sql/context.py +106 -0
- nl2sql/datasources/__init__.py +21 -0
- nl2sql/datasources/discovery.py +28 -0
- nl2sql/datasources/models.py +21 -0
- nl2sql/datasources/protocols.py +3 -0
- nl2sql/datasources/registry.py +172 -0
- nl2sql/evaluation/__init__.py +6 -0
- nl2sql/evaluation/benchmark_runner.py +320 -0
- nl2sql/evaluation/evaluator.py +134 -0
- nl2sql/evaluation/types.py +22 -0
- nl2sql/execution/__init__.py +4 -0
- nl2sql/execution/artifacts/__init__.py +3 -0
- nl2sql/execution/artifacts/parquet.py +41 -0
- nl2sql/execution/artifacts/store.py +165 -0
- nl2sql/execution/contracts.py +57 -0
- nl2sql/execution/execution_store.py +25 -0
- nl2sql/execution/executor/__init__.py +3 -0
- nl2sql/execution/executor/sql_executor.py +116 -0
- nl2sql/indexing/__init__.py +7 -0
- nl2sql/indexing/chunk_builder.py +227 -0
- nl2sql/indexing/embeddings.py +180 -0
- nl2sql/indexing/enrichment_service.py +316 -0
- nl2sql/indexing/models.py +209 -0
- nl2sql/indexing/orchestrator.py +90 -0
- nl2sql/indexing/vector_store.py +422 -0
- nl2sql/llm/__init__.py +8 -0
- nl2sql/llm/models.py +10 -0
- nl2sql/llm/registry.py +214 -0
- nl2sql/pipeline/__init__.py +1 -0
- nl2sql/pipeline/graph.py +73 -0
- nl2sql/pipeline/graph_utils.py +141 -0
- nl2sql/pipeline/nodes/__init__.py +25 -0
- nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
- nl2sql/pipeline/nodes/aggregator/node.py +55 -0
- nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
- nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
- nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
- nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
- nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
- nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
- nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
- nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
- nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
- nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
- nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
- nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
- nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
- nl2sql/pipeline/nodes/decomposer/node.py +219 -0
- nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
- nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
- nl2sql/pipeline/nodes/executor/__init__.py +3 -0
- nl2sql/pipeline/nodes/executor/node.py +107 -0
- nl2sql/pipeline/nodes/generator/__init__.py +4 -0
- nl2sql/pipeline/nodes/generator/node.py +267 -0
- nl2sql/pipeline/nodes/generator/schemas.py +13 -0
- nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/global_planner/node.py +186 -0
- nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
- nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
- nl2sql/pipeline/nodes/refiner/node.py +132 -0
- nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
- nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
- nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
- nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
- nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
- nl2sql/pipeline/nodes/validator/__init__.py +7 -0
- nl2sql/pipeline/nodes/validator/node.py +839 -0
- nl2sql/pipeline/nodes/validator/schemas.py +12 -0
- nl2sql/pipeline/pipeline_runner.py +72 -0
- nl2sql/pipeline/routes.py +72 -0
- nl2sql/pipeline/runtime.py +153 -0
- nl2sql/pipeline/state.py +92 -0
- nl2sql/pipeline/subgraphs/__init__.py +5 -0
- nl2sql/pipeline/subgraphs/schemas.py +23 -0
- nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
- nl2sql/public_api.py +199 -0
- nl2sql/schema/__init__.py +37 -0
- nl2sql/schema/in_memory_store.py +173 -0
- nl2sql/schema/protocol.py +88 -0
- nl2sql/schema/sqlite_store.py +233 -0
- nl2sql/schema/store.py +29 -0
- nl2sql/secrets/__init__.py +14 -0
- nl2sql/secrets/factory.py +85 -0
- nl2sql/secrets/interfaces.py +16 -0
- nl2sql/secrets/manager.py +139 -0
- nl2sql/secrets/models.py +56 -0
- nl2sql/secrets/providers/aws.py +30 -0
- nl2sql/secrets/providers/azure.py +49 -0
- nl2sql/secrets/providers/env.py +8 -0
- nl2sql/secrets/providers/hashi.py +46 -0
- nl2sql/services/__init__.py +0 -0
- nl2sql/services/callbacks/__init__.py +0 -0
- nl2sql/services/callbacks/monitor.py +84 -0
- nl2sql/services/callbacks/node_context.py +7 -0
- nl2sql/services/callbacks/node_handlers.py +187 -0
- nl2sql/services/callbacks/node_metrics.py +14 -0
- nl2sql/services/callbacks/presenter.py +12 -0
- nl2sql/services/callbacks/token_handler.py +56 -0
- nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
- nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
- nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
- nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
- nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Optional, Dict, Any, Union, List
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from nl2sql.datasources import DatasourceConfig, ConnectionConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatasourceFileConfig(BaseModel):
|
|
8
|
+
"""File-level schema for datasources.yaml."""
|
|
9
|
+
version: int = Field(1, description="Schema version")
|
|
10
|
+
datasources: List[DatasourceConfig]
|
nl2sql/configs/llm.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
from pydantic import BaseModel, Field, SecretStr, field_serializer
|
|
4
|
+
|
|
5
|
+
class AgentConfig(BaseModel):
|
|
6
|
+
"""Configuration for a specific agent's LLM.
|
|
7
|
+
|
|
8
|
+
The single definition. ``nl2sql.llm.models`` re-exports this class, so
|
|
9
|
+
``ConfigManager``/``LLMGenerator`` and ``LLMRegistry`` share one model.
|
|
10
|
+
Do not add a second copy: the previous duplicate silently swallowed a fix
|
|
11
|
+
applied to only one of the two.
|
|
12
|
+
"""
|
|
13
|
+
provider: str
|
|
14
|
+
model: str
|
|
15
|
+
temperature: float = 0.0
|
|
16
|
+
api_key: Optional[SecretStr] = None
|
|
17
|
+
base_url: Optional[str] = Field(
|
|
18
|
+
None,
|
|
19
|
+
description=(
|
|
20
|
+
"Override the provider endpoint. Defaults to the provider preset: "
|
|
21
|
+
"the OpenRouter gateway for 'openrouter', the local Ollama daemon "
|
|
22
|
+
"for 'ollama', the client default for 'openai'. Set it to reach any "
|
|
23
|
+
"other OpenAI-compatible endpoint."
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
name: str = Field("default", description="Name of the agent")
|
|
27
|
+
|
|
28
|
+
@field_serializer("api_key", when_used="json")
|
|
29
|
+
def _serialize_api_key(self, value):
|
|
30
|
+
return value.get_secret_value() if value else None
|
|
31
|
+
|
|
32
|
+
class LLMFileConfig(BaseModel):
|
|
33
|
+
"""Global LLM configuration (File Envelope)."""
|
|
34
|
+
version: int = Field(1, description="Schema version")
|
|
35
|
+
default: AgentConfig
|
|
36
|
+
agents: Optional[Dict[str, AgentConfig]] = Field(default_factory=dict)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
|
|
2
|
+
import yaml
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import pathlib
|
|
6
|
+
from typing import List, Dict, Optional, Union
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from nl2sql.common.settings import settings
|
|
10
|
+
from .datasources import DatasourceConfig, DatasourceFileConfig
|
|
11
|
+
from .llm import LLMFileConfig
|
|
12
|
+
from .policies import PolicyFileConfig
|
|
13
|
+
from .secrets import SecretProviderConfig, SecretsFileConfig
|
|
14
|
+
from .sample_questions import SampleQuestionsFileConfig
|
|
15
|
+
|
|
16
|
+
class ConfigManager:
|
|
17
|
+
"""
|
|
18
|
+
Centralized manager for reading and writing application configuration.
|
|
19
|
+
Enforces consistency and handles file I/O for Datasources, LLMs, Policies, and Secrets.
|
|
20
|
+
|
|
21
|
+
Secret references such as ``${env:VAR}`` are returned verbatim. Resolution
|
|
22
|
+
happens at point of use, in ``DatasourceRegistry`` and ``LLMRegistry``.
|
|
23
|
+
Do not resolve them here: a resolved config written back to disk would
|
|
24
|
+
persist real credentials in plaintext.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, project_root: Optional[pathlib.Path] = None):
|
|
28
|
+
"""
|
|
29
|
+
Args:
|
|
30
|
+
project_root: Optional override for project root.
|
|
31
|
+
If None, uses settings paths or CWD resolution strategy.
|
|
32
|
+
"""
|
|
33
|
+
self.project_root = project_root
|
|
34
|
+
|
|
35
|
+
# Resolve root: Use override, or CWD
|
|
36
|
+
root = self.project_root or pathlib.Path.cwd()
|
|
37
|
+
|
|
38
|
+
# Default paths from settings if not overridden
|
|
39
|
+
self._ds_path = root / settings.datasource_config_path
|
|
40
|
+
self._llm_path = root / settings.llm_config_path
|
|
41
|
+
self._policy_path = root / settings.policies_config_path
|
|
42
|
+
self._secrets_path = root / settings.secrets_config_path
|
|
43
|
+
self._sample_questions_path = root / settings.sample_questions_path
|
|
44
|
+
|
|
45
|
+
def ensure_config_dirs(self) -> None:
|
|
46
|
+
"""Ensures that the configuration directories exist."""
|
|
47
|
+
for path in [self._ds_path, self._llm_path, self._policy_path, self._secrets_path]:
|
|
48
|
+
if not path.parent.exists():
|
|
49
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
def load_datasources(self, path: Optional[pathlib.Path] = None) -> List[DatasourceConfig]:
|
|
52
|
+
"""
|
|
53
|
+
Loads datasource configurations from YAML.
|
|
54
|
+
Handles Legacy (Dict) vs V2 (List under 'datasources' key) formats.
|
|
55
|
+
"""
|
|
56
|
+
target_path = path or self._ds_path
|
|
57
|
+
|
|
58
|
+
if not target_path.exists():
|
|
59
|
+
raise FileNotFoundError(f"Datasource config not found: {target_path}")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
content = target_path.read_text(encoding="utf-8")
|
|
63
|
+
raw = yaml.safe_load(content) or {}
|
|
64
|
+
except ImportError as exc:
|
|
65
|
+
raise RuntimeError("PyYAML is required to load datasource configs") from exc
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise ValueError(f"Failed to parse YAML from {target_path}: {e}")
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Structurally validate the file envelope
|
|
71
|
+
file_config = DatasourceFileConfig.model_validate(raw)
|
|
72
|
+
return file_config.datasources
|
|
73
|
+
except ValidationError as e:
|
|
74
|
+
raise ValueError(f"Datasource Configuration Invalid: {e}")
|
|
75
|
+
|
|
76
|
+
def load_llm(self, path: Optional[pathlib.Path] = None) -> LLMFileConfig:
|
|
77
|
+
"""
|
|
78
|
+
Loads LLM configuration.
|
|
79
|
+
Returns nl2sql.configs.LLMFileConfig object.
|
|
80
|
+
"""
|
|
81
|
+
target_path = path or self._llm_path
|
|
82
|
+
|
|
83
|
+
if not target_path.exists():
|
|
84
|
+
raise FileNotFoundError(f"LLM config not found: {target_path}")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
data = yaml.safe_load(target_path.read_text()) or {}
|
|
88
|
+
|
|
89
|
+
# Use File Config for validation
|
|
90
|
+
return LLMFileConfig.model_validate(data)
|
|
91
|
+
except ValidationError as e:
|
|
92
|
+
raise ValueError(f"LLM Configuration Invalid: {e}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def load_policies(self, path: Optional[pathlib.Path] = None) -> PolicyFileConfig:
|
|
97
|
+
"""
|
|
98
|
+
Loads Policy configuration.
|
|
99
|
+
Returns nl2sql.configs.PolicyFileConfig object.
|
|
100
|
+
"""
|
|
101
|
+
target_path = path or self._policy_path
|
|
102
|
+
|
|
103
|
+
if not target_path.exists():
|
|
104
|
+
raise FileNotFoundError(f"Policy config not found: {target_path}")
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
with open(target_path, "r") as f:
|
|
108
|
+
raw_json = f.read()
|
|
109
|
+
|
|
110
|
+
data = json.loads(raw_json)
|
|
111
|
+
# Use File Config for validation
|
|
112
|
+
return PolicyFileConfig.model_validate(data)
|
|
113
|
+
except ValidationError as ve:
|
|
114
|
+
raise ValueError(f"Policy Schema Validation Failed: {ve}")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
raise ValueError(f"Failed to load policies: {e}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def load_secrets(self, path: Optional[pathlib.Path] = None) -> List[SecretProviderConfig]:
|
|
121
|
+
"""Loads Secret configurations."""
|
|
122
|
+
target_path = path or self._secrets_path
|
|
123
|
+
if not target_path.exists():
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
content = target_path.read_text(encoding="utf-8")
|
|
128
|
+
raw = yaml.safe_load(content) or []
|
|
129
|
+
|
|
130
|
+
file_config = SecretsFileConfig.model_validate(raw)
|
|
131
|
+
return file_config.providers
|
|
132
|
+
except ValidationError as e:
|
|
133
|
+
raise ValueError(f"Secret Configuration Invalid: {e}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
raise ValueError(f"Failed to load secrets: {e}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def load_sample_questions(self, path: Optional[pathlib.Path] = None, ds_id: Optional[str] = None) -> Union[Dict[str, List[str]], List[str]]:
|
|
139
|
+
"""Loads sample questions from a YAML file."""
|
|
140
|
+
target_path = path or self._sample_questions_path
|
|
141
|
+
|
|
142
|
+
if not target_path.exists():
|
|
143
|
+
raise FileNotFoundError(f"Sample questions file not found: {target_path}")
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
raw = yaml.safe_load(target_path.read_text(encoding="utf-8")) or {}
|
|
147
|
+
|
|
148
|
+
if ds_id:
|
|
149
|
+
return raw.get(ds_id, [])
|
|
150
|
+
|
|
151
|
+
return raw
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise ValueError(f"Failed to load sample questions: {e}")
|
|
154
|
+
|
|
155
|
+
def get_example_questions(self, datasource_id: str) -> List[str]:
|
|
156
|
+
"""Returns example questions for a datasource, if configured."""
|
|
157
|
+
try:
|
|
158
|
+
questions = self.load_sample_questions(ds_id=datasource_id)
|
|
159
|
+
return questions or []
|
|
160
|
+
except FileNotFoundError:
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
def get_datasource_description(self, datasource_id: str) -> Optional[str]:
|
|
164
|
+
"""Returns the configured datasource description if available."""
|
|
165
|
+
try:
|
|
166
|
+
datasources = self.load_datasources()
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
return None
|
|
169
|
+
for ds in datasources:
|
|
170
|
+
if ds.id == datasource_id:
|
|
171
|
+
return ds.description
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import List, Dict, Optional
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
from nl2sql.auth import RolePolicy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PolicyFileConfig(BaseModel):
|
|
10
|
+
"""File-level schema for policies.json."""
|
|
11
|
+
version: int = Field(1, description="Schema version")
|
|
12
|
+
roles: Dict[str, RolePolicy]
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SampleQuestionsFileConfig(BaseModel):
|
|
7
|
+
"""File-level schema for sample_questions.yaml."""
|
|
8
|
+
|
|
9
|
+
datasources: Dict[str, List[str]] = Field(default_factory=dict)
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(extra="ignore")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from nl2sql.secrets.models import SecretProviderConfig
|
|
6
|
+
|
|
7
|
+
class SecretsFileConfig(BaseModel):
|
|
8
|
+
"""File-level schema for secrets.yaml."""
|
|
9
|
+
version: int = Field(1, description="Schema version")
|
|
10
|
+
providers: List[SecretProviderConfig]
|
|
11
|
+
|
nl2sql/context.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from nl2sql.configs import ConfigManager
|
|
6
|
+
from nl2sql.datasources import DatasourceRegistry
|
|
7
|
+
from nl2sql.llm import LLMRegistry
|
|
8
|
+
from nl2sql.indexing.vector_store import VectorStore
|
|
9
|
+
from nl2sql.secrets import SecretManager
|
|
10
|
+
from nl2sql.common.settings import settings
|
|
11
|
+
from nl2sql.auth import RBAC
|
|
12
|
+
|
|
13
|
+
from nl2sql.schema import build_schema_store
|
|
14
|
+
from nl2sql.execution import ExecutionStore
|
|
15
|
+
from nl2sql.execution.artifacts import build_artifact_store
|
|
16
|
+
|
|
17
|
+
from nl2sql.common.logger import get_logger
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NL2SQLContext:
|
|
22
|
+
"""
|
|
23
|
+
Centralized application context that manages the initialization lifecycle.
|
|
24
|
+
|
|
25
|
+
Ensures that secrets are loaded BEFORE datasources, and handles
|
|
26
|
+
registry instantiation in a consistent order.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
ds_config_path: Optional[pathlib.Path] = None,
|
|
32
|
+
secrets_config_path: Optional[pathlib.Path] = None,
|
|
33
|
+
llm_config_path: Optional[pathlib.Path] = None,
|
|
34
|
+
vector_store_path: Optional[pathlib.Path] = None,
|
|
35
|
+
policies_config_path: Optional[pathlib.Path] = None,
|
|
36
|
+
) :
|
|
37
|
+
"""
|
|
38
|
+
Factory method to create a context from configuration paths.
|
|
39
|
+
Resolves defaults from global settings if paths are not provided.
|
|
40
|
+
"""
|
|
41
|
+
ds_config_path = ds_config_path or pathlib.Path(settings.datasource_config_path)
|
|
42
|
+
secrets_config_path = secrets_config_path or pathlib.Path(settings.secrets_config_path)
|
|
43
|
+
llm_config_path = llm_config_path or pathlib.Path(settings.llm_config_path)
|
|
44
|
+
policies_config_path = policies_config_path or pathlib.Path(settings.policies_config_path)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
logger.info(f"Loading datasource configuration from {ds_config_path}")
|
|
48
|
+
logger.info(f"Loading secrets configuration from {secrets_config_path}")
|
|
49
|
+
logger.info(f"Loading LLM configuration from {llm_config_path}")
|
|
50
|
+
logger.info(f"Loading policies configuration from {policies_config_path}")
|
|
51
|
+
logger.info(f"Loading vector store configuration from {vector_store_path}")
|
|
52
|
+
|
|
53
|
+
# Validate vector store configuration first so misconfiguration fails fast,
|
|
54
|
+
# before secrets, datasource and LLM registries are constructed.
|
|
55
|
+
vector_store_collection_name = settings.vector_store_collection_name
|
|
56
|
+
if not vector_store_collection_name:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"VECTOR_STORE_COLLECTION name is not configured. "
|
|
59
|
+
"Set vector_store_collection_name in settings."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
vector_store_path = vector_store_path or (
|
|
63
|
+
pathlib.Path(settings.vector_store_path) if settings.vector_store_path else None
|
|
64
|
+
)
|
|
65
|
+
if not vector_store_path:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"VECTOR_STORE path is not configured. "
|
|
68
|
+
"Pass vector_store_path or set vector_store_path in settings."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
cm = ConfigManager()
|
|
72
|
+
self.tenant_id = settings.tenant_id
|
|
73
|
+
self.config_manager = cm
|
|
74
|
+
|
|
75
|
+
secret_configs = cm.load_secrets(secrets_config_path)
|
|
76
|
+
secret_manager = SecretManager()
|
|
77
|
+
if secret_configs:
|
|
78
|
+
secret_manager.configure(secret_configs)
|
|
79
|
+
|
|
80
|
+
ds_configs = cm.load_datasources(ds_config_path)
|
|
81
|
+
self.ds_registry = DatasourceRegistry(secret_manager)
|
|
82
|
+
self.ds_registry.register_datasources(ds_configs)
|
|
83
|
+
|
|
84
|
+
llm_cfg = cm.load_llm(llm_config_path)
|
|
85
|
+
self.llm_registry = LLMRegistry(secret_manager)
|
|
86
|
+
|
|
87
|
+
agents = llm_cfg.agents or {}
|
|
88
|
+
agents["default"] = llm_cfg.default
|
|
89
|
+
self.llm_registry.register_llms(agents)
|
|
90
|
+
|
|
91
|
+
self.policies_cfg = cm.load_policies(policies_config_path)
|
|
92
|
+
self.rbac = RBAC(self.policies_cfg.roles)
|
|
93
|
+
|
|
94
|
+
self.vector_store = VectorStore(
|
|
95
|
+
collection_name=vector_store_collection_name,
|
|
96
|
+
persist_directory=vector_store_path,
|
|
97
|
+
)
|
|
98
|
+
self.schema_store = build_schema_store(
|
|
99
|
+
settings.schema_store_backend,
|
|
100
|
+
settings.schema_store_max_versions,
|
|
101
|
+
path=pathlib.Path(settings.schema_store_path),
|
|
102
|
+
)
|
|
103
|
+
self.execution_store = ExecutionStore()
|
|
104
|
+
self.artifact_store = build_artifact_store()
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Datasource management, configuration, and adapter discovery."""
|
|
2
|
+
from nl2sql.datasources.registry import DatasourceRegistry
|
|
3
|
+
from nl2sql.datasources.discovery import discover_adapters
|
|
4
|
+
from nl2sql.datasources.models import (
|
|
5
|
+
DatasourceConfig,
|
|
6
|
+
ConnectionConfig,
|
|
7
|
+
)
|
|
8
|
+
from nl2sql.datasources.protocols import DatasourceAdapterProtocol
|
|
9
|
+
from nl2sql_adapter_sdk.contracts import AdapterRequest, ResultFrame
|
|
10
|
+
from nl2sql_adapter_sdk.capabilities import DatasourceCapability
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DatasourceRegistry",
|
|
14
|
+
"discover_adapters",
|
|
15
|
+
"DatasourceConfig",
|
|
16
|
+
"ConnectionConfig",
|
|
17
|
+
"DatasourceAdapterProtocol",
|
|
18
|
+
"AdapterRequest",
|
|
19
|
+
"DatasourceCapability",
|
|
20
|
+
"ResultFrame",
|
|
21
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from importlib.metadata import entry_points
|
|
2
|
+
from typing import Dict, Type
|
|
3
|
+
|
|
4
|
+
from nl2sql.datasources.protocols import DatasourceAdapterProtocol
|
|
5
|
+
from nl2sql.common.logger import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
def discover_adapters() -> Dict[str, Type[DatasourceAdapterProtocol]]:
|
|
10
|
+
"""Discovers installed adapters via 'nl2sql.adapters' entry points.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Dict[str, Type[DatasourceAdapterProtocol]]: Dict mapping adapter name (e.g., 'postgres')
|
|
14
|
+
to the Adapter Class.
|
|
15
|
+
"""
|
|
16
|
+
adapters = {}
|
|
17
|
+
try:
|
|
18
|
+
eps = entry_points(group="nl2sql.adapters")
|
|
19
|
+
except TypeError:
|
|
20
|
+
eps = entry_points().get("nl2sql.adapters", [])
|
|
21
|
+
for ep in eps:
|
|
22
|
+
try:
|
|
23
|
+
AdapterCls = ep.load()
|
|
24
|
+
adapters[ep.name] = AdapterCls
|
|
25
|
+
except Exception as e:
|
|
26
|
+
logger.error(f"Failed to load adapter {ep.name}: {e}")
|
|
27
|
+
|
|
28
|
+
return adapters
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from typing import Optional, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConnectionConfig(BaseModel):
|
|
8
|
+
"""Database connection details."""
|
|
9
|
+
type: str
|
|
10
|
+
|
|
11
|
+
model_config = {"extra": "allow"}
|
|
12
|
+
|
|
13
|
+
class DatasourceConfig(BaseModel):
|
|
14
|
+
"""Configuration for a single datasource."""
|
|
15
|
+
id: str
|
|
16
|
+
description: Optional[str] = None
|
|
17
|
+
connection: ConnectionConfig
|
|
18
|
+
options: Dict[str, Any] = Field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from typing import Dict, List, Any, Set
|
|
2
|
+
from threading import RLock
|
|
3
|
+
|
|
4
|
+
from nl2sql_adapter_sdk.capabilities import DatasourceCapability
|
|
5
|
+
from nl2sql.datasources.discovery import discover_adapters
|
|
6
|
+
from nl2sql.datasources.protocols import DatasourceAdapterProtocol
|
|
7
|
+
from nl2sql.secrets import SecretManager
|
|
8
|
+
from .models import DatasourceConfig, ConnectionConfig
|
|
9
|
+
|
|
10
|
+
class DatasourceRegistry:
|
|
11
|
+
"""Manages a collection of active DatasourceAdapters.
|
|
12
|
+
|
|
13
|
+
Acts as the factory and cache for DataSourceAdapter instances.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, secret_manager: SecretManager):
|
|
17
|
+
"""Initializes the registry by eagerly creating adapters for all configs."""
|
|
18
|
+
self._adapters: Dict[str, DatasourceAdapterProtocol] = {}
|
|
19
|
+
self._capabilities: Dict[str, Set[str]] = {}
|
|
20
|
+
self._available_adapters = discover_adapters()
|
|
21
|
+
self._secret_manager = secret_manager
|
|
22
|
+
self._lock = RLock()
|
|
23
|
+
|
|
24
|
+
def find_and_resolve_secret(self, key: str) -> str:
|
|
25
|
+
"""Attempts to resolve a secret using the secret manager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
key (str): The name of the secret to resolve.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: The resolved secret value.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ValueError: If the secret cannot be resolved.
|
|
35
|
+
"""
|
|
36
|
+
return self._secret_manager.resolve(key)
|
|
37
|
+
|
|
38
|
+
def resolved_connection(self, unresolved_connection: ConnectionConfig) -> ConnectionConfig:
|
|
39
|
+
"""Resolves all secrets in a connection dictionary.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
unresolved_connection (ConnectionConfig): The connection dictionary with potential secrets.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
ConnectionConfig: The connection dictionary with resolved secrets.
|
|
46
|
+
"""
|
|
47
|
+
from pydantic import SecretStr
|
|
48
|
+
|
|
49
|
+
resolved_connection = unresolved_connection.model_dump()
|
|
50
|
+
for key, value in unresolved_connection.model_dump().items():
|
|
51
|
+
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
|
|
52
|
+
secret_val = self.find_and_resolve_secret(value)
|
|
53
|
+
resolved_connection[key] = SecretStr(secret_val)
|
|
54
|
+
return ConnectionConfig(**resolved_connection)
|
|
55
|
+
|
|
56
|
+
def register_datasources(self, configs: List[DatasourceConfig]):
|
|
57
|
+
for config in configs:
|
|
58
|
+
try:
|
|
59
|
+
self.register_datasource(config)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
raise ValueError(f"Failed to initialize adapter for '{config.id}': {e}") from e
|
|
62
|
+
|
|
63
|
+
def _normalize_capabilities(self, caps: Any) -> Set[str]:
|
|
64
|
+
if not caps:
|
|
65
|
+
return set()
|
|
66
|
+
normalized = set()
|
|
67
|
+
for cap in caps:
|
|
68
|
+
if isinstance(cap, DatasourceCapability):
|
|
69
|
+
normalized.add(cap.value)
|
|
70
|
+
else:
|
|
71
|
+
normalized.add(str(cap))
|
|
72
|
+
return normalized
|
|
73
|
+
|
|
74
|
+
def register_datasource(self, config: DatasourceConfig) -> DatasourceAdapterProtocol:
|
|
75
|
+
"""Registers a new datasource dynamically.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config: The datasource configuration dictionary or object.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
DatasourceAdapterProtocol: The created and registered adapter.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: If configuration is invalid or adapter type is unknown.
|
|
85
|
+
"""
|
|
86
|
+
ds_id = config.id
|
|
87
|
+
if not ds_id:
|
|
88
|
+
raise ValueError("Datasource ID is required. Please check your configuration.")
|
|
89
|
+
|
|
90
|
+
connection = config.connection
|
|
91
|
+
conn_type = connection.type.lower()
|
|
92
|
+
resolved_connection = self.resolved_connection(connection)
|
|
93
|
+
connection_args = resolved_connection.model_dump()
|
|
94
|
+
|
|
95
|
+
if conn_type in self._available_adapters:
|
|
96
|
+
adapter_cls = self._available_adapters[conn_type]
|
|
97
|
+
|
|
98
|
+
adapter = adapter_cls(
|
|
99
|
+
datasource_id=ds_id,
|
|
100
|
+
datasource_engine_type=conn_type,
|
|
101
|
+
connection_args=connection_args,
|
|
102
|
+
statement_timeout_ms=config.options.get("statement_timeout_ms"),
|
|
103
|
+
row_limit=config.options.get("row_limit"),
|
|
104
|
+
max_bytes=config.options.get("max_bytes"),
|
|
105
|
+
)
|
|
106
|
+
with self._lock:
|
|
107
|
+
self._adapters[ds_id] = adapter
|
|
108
|
+
if hasattr(adapter, "capabilities"):
|
|
109
|
+
self._capabilities[ds_id] = self._normalize_capabilities(
|
|
110
|
+
adapter.capabilities()
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
self._capabilities[ds_id] = {DatasourceCapability.SUPPORTS_SQL.value}
|
|
114
|
+
return adapter
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"No adapter found for engine type: '{conn_type}' in datasource '{ds_id}'"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_adapter(self, datasource_id: str) -> DatasourceAdapterProtocol:
|
|
122
|
+
"""Retrieves the adapter for a datasource.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
datasource_id: The ID of the datasource.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
DatasourceAdapterProtocol: The active adapter instance.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ValueError: If the datasource ID is unknown.
|
|
132
|
+
"""
|
|
133
|
+
with self._lock:
|
|
134
|
+
if datasource_id not in self._adapters:
|
|
135
|
+
raise ValueError(f"Unknown datasource ID: {datasource_id}")
|
|
136
|
+
return self._adapters[datasource_id]
|
|
137
|
+
|
|
138
|
+
def get_dialect(self, datasource_id: str) -> str:
|
|
139
|
+
"""Returns a normalized dialect string from the adapter.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
datasource_id: The ID of the datasource.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
str: The dialect string (e.g., 'postgres').
|
|
146
|
+
"""
|
|
147
|
+
return self.get_adapter(datasource_id).get_dialect()
|
|
148
|
+
|
|
149
|
+
def get_capabilities(self, datasource_id: str) -> Set[str]:
|
|
150
|
+
"""Returns the capability flags for a datasource."""
|
|
151
|
+
with self._lock:
|
|
152
|
+
if datasource_id not in self._capabilities:
|
|
153
|
+
raise ValueError(f"Unknown datasource ID: {datasource_id}")
|
|
154
|
+
return set(self._capabilities[datasource_id])
|
|
155
|
+
|
|
156
|
+
def list_adapters(self) -> List[DatasourceAdapterProtocol]:
|
|
157
|
+
"""Returns a list of all registered adapters.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List[DatasourceAdapterProtocol]: All active adapters.
|
|
161
|
+
"""
|
|
162
|
+
with self._lock:
|
|
163
|
+
return list(self._adapters.values())
|
|
164
|
+
|
|
165
|
+
def list_ids(self) -> List[str]:
|
|
166
|
+
"""Returns a list of all registered datasource IDs.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List[str]: All registered IDs.
|
|
170
|
+
"""
|
|
171
|
+
with self._lock:
|
|
172
|
+
return list(self._adapters.keys())
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Evaluation tools for the NL2SQL pipeline."""
|
|
2
|
+
from nl2sql.evaluation.evaluator import ModelEvaluator
|
|
3
|
+
from nl2sql.evaluation.types import BenchmarkConfig
|
|
4
|
+
from nl2sql.evaluation.benchmark_runner import BenchmarkRunner
|
|
5
|
+
|
|
6
|
+
__all__ = ["ModelEvaluator", "BenchmarkConfig", "BenchmarkRunner"]
|