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.
Files changed (192) hide show
  1. nl2sql/__init__.py +38 -0
  2. nl2sql/adapters/__init__.py +0 -0
  3. nl2sql/adapters/duckdb/__init__.py +0 -0
  4. nl2sql/adapters/duckdb/adapter.py +71 -0
  5. nl2sql/adapters/mssql/__init__.py +0 -0
  6. nl2sql/adapters/mssql/adapter.py +122 -0
  7. nl2sql/adapters/mysql/__init__.py +0 -0
  8. nl2sql/adapters/mysql/adapter.py +123 -0
  9. nl2sql/adapters/postgres/__init__.py +0 -0
  10. nl2sql/adapters/postgres/adapter.py +115 -0
  11. nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
  12. nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
  13. nl2sql/adapters/sqlalchemy_base/models.py +36 -0
  14. nl2sql/adapters/sqlite/__init__.py +0 -0
  15. nl2sql/adapters/sqlite/adapter.py +88 -0
  16. nl2sql/aggregation/__init__.py +3 -0
  17. nl2sql/aggregation/aggregator.py +98 -0
  18. nl2sql/aggregation/engines/__init__.py +3 -0
  19. nl2sql/aggregation/engines/polars_duckdb.py +125 -0
  20. nl2sql/api/__init__.py +0 -0
  21. nl2sql/api/auth_api.py +60 -0
  22. nl2sql/api/benchmark_api.py +114 -0
  23. nl2sql/api/datasource_api.py +132 -0
  24. nl2sql/api/indexing_api.py +59 -0
  25. nl2sql/api/llm_api.py +82 -0
  26. nl2sql/api/policy_api.py +135 -0
  27. nl2sql/api/query_api.py +138 -0
  28. nl2sql/api/result_api.py +24 -0
  29. nl2sql/api/settings_api.py +65 -0
  30. nl2sql/auth/__init__.py +8 -0
  31. nl2sql/auth/models.py +36 -0
  32. nl2sql/auth/rbac.py +25 -0
  33. nl2sql/cli/__init__.py +0 -0
  34. nl2sql/cli/checks.py +53 -0
  35. nl2sql/cli/commands/__init__.py +0 -0
  36. nl2sql/cli/commands/benchmark.py +34 -0
  37. nl2sql/cli/commands/doctor.py +49 -0
  38. nl2sql/cli/commands/indexing.py +126 -0
  39. nl2sql/cli/commands/info.py +25 -0
  40. nl2sql/cli/commands/install.py +27 -0
  41. nl2sql/cli/commands/policy.py +57 -0
  42. nl2sql/cli/commands/run.py +166 -0
  43. nl2sql/cli/commands/setup.py +415 -0
  44. nl2sql/cli/commands/visualize.py +34 -0
  45. nl2sql/cli/common/decorators.py +34 -0
  46. nl2sql/cli/config.py +24 -0
  47. nl2sql/cli/console.py +52 -0
  48. nl2sql/cli/demo/__init__.py +1 -0
  49. nl2sql/cli/demo/data.py +87 -0
  50. nl2sql/cli/demo/defaults.py +122 -0
  51. nl2sql/cli/demo/factory.py +289 -0
  52. nl2sql/cli/demo/manager.py +230 -0
  53. nl2sql/cli/demo/schemas.py +336 -0
  54. nl2sql/cli/demo/writers/__init__.py +0 -0
  55. nl2sql/cli/demo/writers/docker.py +182 -0
  56. nl2sql/cli/demo/writers/sqlite.py +88 -0
  57. nl2sql/cli/generators/datasources/__init__.py +3 -0
  58. nl2sql/cli/generators/datasources/generator.py +24 -0
  59. nl2sql/cli/generators/datasources/templates.py +7 -0
  60. nl2sql/cli/generators/env/__init__.py +3 -0
  61. nl2sql/cli/generators/env/generator.py +46 -0
  62. nl2sql/cli/generators/env/templates.py +25 -0
  63. nl2sql/cli/generators/llm/__init__.py +3 -0
  64. nl2sql/cli/generators/llm/generator.py +24 -0
  65. nl2sql/cli/generators/llm/templates.py +4 -0
  66. nl2sql/cli/generators/policies/__init__.py +3 -0
  67. nl2sql/cli/generators/policies/generator.py +20 -0
  68. nl2sql/cli/generators/policies/templates.py +2 -0
  69. nl2sql/cli/main.py +195 -0
  70. nl2sql/cli/reporting.py +878 -0
  71. nl2sql/cli/types.py +13 -0
  72. nl2sql/common/__init__.py +1 -0
  73. nl2sql/common/cancellation.py +25 -0
  74. nl2sql/common/context.py +5 -0
  75. nl2sql/common/errors.py +109 -0
  76. nl2sql/common/event_logger.py +88 -0
  77. nl2sql/common/exceptions.py +3 -0
  78. nl2sql/common/logger.py +119 -0
  79. nl2sql/common/metrics.py +50 -0
  80. nl2sql/common/resilience.py +59 -0
  81. nl2sql/common/settings.py +195 -0
  82. nl2sql/configs/__init__.py +6 -0
  83. nl2sql/configs/datasources.py +10 -0
  84. nl2sql/configs/llm.py +36 -0
  85. nl2sql/configs/manager.py +176 -0
  86. nl2sql/configs/policies.py +14 -0
  87. nl2sql/configs/sample_questions.py +11 -0
  88. nl2sql/configs/secrets.py +11 -0
  89. nl2sql/context.py +106 -0
  90. nl2sql/datasources/__init__.py +21 -0
  91. nl2sql/datasources/discovery.py +28 -0
  92. nl2sql/datasources/models.py +21 -0
  93. nl2sql/datasources/protocols.py +3 -0
  94. nl2sql/datasources/registry.py +172 -0
  95. nl2sql/evaluation/__init__.py +6 -0
  96. nl2sql/evaluation/benchmark_runner.py +320 -0
  97. nl2sql/evaluation/evaluator.py +134 -0
  98. nl2sql/evaluation/types.py +22 -0
  99. nl2sql/execution/__init__.py +4 -0
  100. nl2sql/execution/artifacts/__init__.py +3 -0
  101. nl2sql/execution/artifacts/parquet.py +41 -0
  102. nl2sql/execution/artifacts/store.py +165 -0
  103. nl2sql/execution/contracts.py +57 -0
  104. nl2sql/execution/execution_store.py +25 -0
  105. nl2sql/execution/executor/__init__.py +3 -0
  106. nl2sql/execution/executor/sql_executor.py +116 -0
  107. nl2sql/indexing/__init__.py +7 -0
  108. nl2sql/indexing/chunk_builder.py +227 -0
  109. nl2sql/indexing/embeddings.py +180 -0
  110. nl2sql/indexing/enrichment_service.py +316 -0
  111. nl2sql/indexing/models.py +209 -0
  112. nl2sql/indexing/orchestrator.py +90 -0
  113. nl2sql/indexing/vector_store.py +422 -0
  114. nl2sql/llm/__init__.py +8 -0
  115. nl2sql/llm/models.py +10 -0
  116. nl2sql/llm/registry.py +214 -0
  117. nl2sql/pipeline/__init__.py +1 -0
  118. nl2sql/pipeline/graph.py +73 -0
  119. nl2sql/pipeline/graph_utils.py +141 -0
  120. nl2sql/pipeline/nodes/__init__.py +25 -0
  121. nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
  122. nl2sql/pipeline/nodes/aggregator/node.py +55 -0
  123. nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
  124. nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
  125. nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
  126. nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
  127. nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
  128. nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
  129. nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
  130. nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
  131. nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
  132. nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
  133. nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
  134. nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
  135. nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
  136. nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
  137. nl2sql/pipeline/nodes/decomposer/node.py +219 -0
  138. nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
  139. nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
  140. nl2sql/pipeline/nodes/executor/__init__.py +3 -0
  141. nl2sql/pipeline/nodes/executor/node.py +107 -0
  142. nl2sql/pipeline/nodes/generator/__init__.py +4 -0
  143. nl2sql/pipeline/nodes/generator/node.py +267 -0
  144. nl2sql/pipeline/nodes/generator/schemas.py +13 -0
  145. nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
  146. nl2sql/pipeline/nodes/global_planner/node.py +186 -0
  147. nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
  148. nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
  149. nl2sql/pipeline/nodes/refiner/node.py +132 -0
  150. nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
  151. nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
  152. nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
  153. nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
  154. nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
  155. nl2sql/pipeline/nodes/validator/__init__.py +7 -0
  156. nl2sql/pipeline/nodes/validator/node.py +839 -0
  157. nl2sql/pipeline/nodes/validator/schemas.py +12 -0
  158. nl2sql/pipeline/pipeline_runner.py +72 -0
  159. nl2sql/pipeline/routes.py +72 -0
  160. nl2sql/pipeline/runtime.py +153 -0
  161. nl2sql/pipeline/state.py +92 -0
  162. nl2sql/pipeline/subgraphs/__init__.py +5 -0
  163. nl2sql/pipeline/subgraphs/schemas.py +23 -0
  164. nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
  165. nl2sql/public_api.py +199 -0
  166. nl2sql/schema/__init__.py +37 -0
  167. nl2sql/schema/in_memory_store.py +173 -0
  168. nl2sql/schema/protocol.py +88 -0
  169. nl2sql/schema/sqlite_store.py +233 -0
  170. nl2sql/schema/store.py +29 -0
  171. nl2sql/secrets/__init__.py +14 -0
  172. nl2sql/secrets/factory.py +85 -0
  173. nl2sql/secrets/interfaces.py +16 -0
  174. nl2sql/secrets/manager.py +139 -0
  175. nl2sql/secrets/models.py +56 -0
  176. nl2sql/secrets/providers/aws.py +30 -0
  177. nl2sql/secrets/providers/azure.py +49 -0
  178. nl2sql/secrets/providers/env.py +8 -0
  179. nl2sql/secrets/providers/hashi.py +46 -0
  180. nl2sql/services/__init__.py +0 -0
  181. nl2sql/services/callbacks/__init__.py +0 -0
  182. nl2sql/services/callbacks/monitor.py +84 -0
  183. nl2sql/services/callbacks/node_context.py +7 -0
  184. nl2sql/services/callbacks/node_handlers.py +187 -0
  185. nl2sql/services/callbacks/node_metrics.py +14 -0
  186. nl2sql/services/callbacks/presenter.py +12 -0
  187. nl2sql/services/callbacks/token_handler.py +56 -0
  188. nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
  189. nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
  190. nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
  191. nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
  192. nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Tuple
4
+
5
+ import polars as pl
6
+ from nl2sql.execution.contracts import ArtifactRef
7
+ from nl2sql.pipeline.nodes.global_planner.schemas import ExecutionDAG, LogicalNode, LogicalEdge
8
+
9
+ from .engines.polars_duckdb import PolarsDuckdbEngine
10
+
11
+
12
+ class AggregationService:
13
+ def __init__(self, engine: PolarsDuckdbEngine):
14
+ self.engine = engine
15
+
16
+ def execute(
17
+ self,
18
+ dag: ExecutionDAG,
19
+ artifact_refs: Dict[str, ArtifactRef],
20
+ ) -> Dict[str, List[Dict]]:
21
+ if not dag:
22
+ raise ValueError("No ExecutionDAG found for aggregation.")
23
+
24
+ if not dag.nodes:
25
+ return {}
26
+
27
+ node_index: Dict[str, LogicalNode] = {n.node_id: n for n in dag.nodes}
28
+ edges: List[LogicalEdge] = dag.edges
29
+ incoming_edges: Dict[str, List[LogicalEdge]] = {}
30
+ outgoing_edges: Dict[str, List[LogicalEdge]] = {}
31
+ for edge in edges:
32
+ incoming_edges.setdefault(edge.to_id, []).append(edge)
33
+ outgoing_edges.setdefault(edge.from_id, []).append(edge)
34
+
35
+ computed: Dict[str, pl.DataFrame] = {}
36
+
37
+ for layer in dag.layers:
38
+ for node_id in layer:
39
+ node = node_index.get(node_id)
40
+ if not node:
41
+ continue
42
+ if node.kind == "scan":
43
+ artifact = artifact_refs.get(node_id)
44
+ if not artifact:
45
+ raise ValueError(
46
+ f"Missing artifact for scan node {node_id}."
47
+ )
48
+ computed[node_id] = self.engine.load_scan(artifact)
49
+ continue
50
+
51
+ if node.kind == "combine":
52
+ upstream = incoming_edges.get(node_id, [])
53
+ inputs = self._ordered_inputs(upstream, computed)
54
+ computed[node_id] = self.engine.combine(
55
+ node.attributes.get("operation"),
56
+ inputs,
57
+ node.attributes.get("join_keys", []),
58
+ )
59
+ continue
60
+
61
+ if node.kind.startswith("post_"):
62
+ input_ids = node.inputs or []
63
+ if len(input_ids) != 1 or input_ids[0] not in computed:
64
+ raise ValueError(
65
+ f"Post-combine node '{node_id}' expects a single input."
66
+ )
67
+ computed[node_id] = self.engine.post_op(
68
+ node.attributes.get("operation"),
69
+ computed[input_ids[0]],
70
+ node.attributes,
71
+ )
72
+ continue
73
+
74
+ raise ValueError(
75
+ f"Unsupported logical node kind '{node.kind}'."
76
+ )
77
+
78
+ terminal_nodes = sorted([n.node_id for n in dag.nodes if n.node_id not in outgoing_edges])
79
+ if not terminal_nodes:
80
+ return {}
81
+
82
+ return {
83
+ node_id: self.engine.to_rows(computed[node_id])
84
+ for node_id in terminal_nodes
85
+ if node_id in computed
86
+ }
87
+
88
+ def _ordered_inputs(
89
+ self,
90
+ edges: List[LogicalEdge],
91
+ computed: Dict[str, pl.DataFrame],
92
+ ) -> List[Tuple[str, pl.DataFrame]]:
93
+ def role_rank(role: str) -> int:
94
+ order = {"left": 0, "base": 0, "primary": 0, "right": 1, "compare": 1, "secondary": 1}
95
+ return order.get(role or "", 2)
96
+
97
+ ordered = sorted(edges, key=lambda e: (role_rank(e.role), e.from_id))
98
+ return [(edge.role or "", computed[edge.from_id]) for edge in ordered if edge.from_id in computed]
@@ -0,0 +1,3 @@
1
+ from .polars_duckdb import PolarsDuckdbEngine
2
+
3
+ __all__ = ["PolarsDuckdbEngine"]
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Tuple
4
+
5
+ import duckdb
6
+ import polars as pl
7
+
8
+ from nl2sql.execution.contracts import ArtifactRef
9
+ from nl2sql.execution.artifacts import build_artifact_store
10
+
11
+
12
+ class PolarsDuckdbEngine:
13
+ def __init__(self):
14
+ self.artifact_store = build_artifact_store()
15
+
16
+ def load_scan(self, artifact: ArtifactRef) -> pl.DataFrame:
17
+ return self.artifact_store.read_parquet(artifact)
18
+
19
+ def combine(
20
+ self,
21
+ operation: str,
22
+ inputs: List[Tuple[str, pl.DataFrame]],
23
+ join_keys: List[Dict[str, Any]],
24
+ ) -> pl.DataFrame:
25
+ frames = [frame for _, frame in inputs]
26
+ if not frames:
27
+ return pl.DataFrame()
28
+ if operation == "standalone":
29
+ return frames[0]
30
+ if operation == "union":
31
+ return pl.concat(frames, how="vertical")
32
+ if operation == "join":
33
+ if len(frames) < 2:
34
+ return frames[0]
35
+ left = frames[0]
36
+ right = frames[1]
37
+ left_on = [k.get("left") for k in join_keys]
38
+ right_on = [k.get("right") for k in join_keys]
39
+ return left.join(right, left_on=left_on, right_on=right_on, how="inner", suffix="_right")
40
+ if operation == "compare":
41
+ if len(frames) < 2:
42
+ return frames[0]
43
+ left = frames[0]
44
+ right = frames[1]
45
+ left_on = [k.get("left") for k in join_keys]
46
+ right_on = [k.get("right") for k in join_keys]
47
+ joined = left.join(right, left_on=left_on, right_on=right_on, how="inner", suffix="_right")
48
+ diff_cols = []
49
+ for col in left.columns:
50
+ if col in left_on:
51
+ continue
52
+ right_col = f"{col}_right"
53
+ if right_col in joined.columns:
54
+ diff_cols.append((col, right_col))
55
+ if not diff_cols:
56
+ return joined
57
+ diff_exprs = [(pl.col(l) != pl.col(r)) for l, r in diff_cols]
58
+ return joined.filter(pl.any_horizontal(diff_exprs))
59
+ raise ValueError(f"Unsupported combine operation '{operation}'.")
60
+
61
+ def post_op(self, operation: str, frame: pl.DataFrame, attributes: Dict[str, Any]) -> pl.DataFrame:
62
+ if operation == "filter":
63
+ rows = frame
64
+ for flt in attributes.get("filters", []):
65
+ attr = flt.get("attribute")
66
+ op = flt.get("operator")
67
+ val = flt.get("value")
68
+ col = pl.col(attr)
69
+ if op == "=":
70
+ rows = rows.filter(col == val)
71
+ elif op == "!=":
72
+ rows = rows.filter(col != val)
73
+ elif op == ">":
74
+ rows = rows.filter(col > val)
75
+ elif op == ">=":
76
+ rows = rows.filter(col >= val)
77
+ elif op == "<":
78
+ rows = rows.filter(col < val)
79
+ elif op == "<=":
80
+ rows = rows.filter(col <= val)
81
+ elif op == "between" and isinstance(val, list) and len(val) == 2:
82
+ rows = rows.filter((col >= val[0]) & (col <= val[1]))
83
+ elif op == "in" and isinstance(val, list):
84
+ rows = rows.filter(col.is_in(val))
85
+ elif op == "contains":
86
+ rows = rows.filter(col.cast(pl.Utf8).str.contains(str(val)))
87
+ return rows
88
+ if operation == "aggregate":
89
+ group_by = [g.get("attribute") for g in attributes.get("group_by", []) if g.get("attribute")]
90
+ metrics = attributes.get("metrics", [])
91
+ agg_exprs = []
92
+ for metric in metrics:
93
+ name = metric.get("name")
94
+ agg = metric.get("aggregation")
95
+ col = pl.col(name)
96
+ if agg == "count":
97
+ agg_exprs.append(col.count().alias(name))
98
+ elif agg == "sum":
99
+ agg_exprs.append(col.sum().alias(name))
100
+ elif agg == "avg":
101
+ agg_exprs.append(col.mean().alias(name))
102
+ elif agg == "min":
103
+ agg_exprs.append(col.min().alias(name))
104
+ elif agg == "max":
105
+ agg_exprs.append(col.max().alias(name))
106
+ if group_by:
107
+ return frame.groupby(group_by).agg(agg_exprs)
108
+ return frame.select(agg_exprs)
109
+ if operation == "project":
110
+ columns = [c.get("name") for c in attributes.get("expected_schema", []) if c.get("name")]
111
+ return frame.select(columns) if columns else frame
112
+ if operation == "sort":
113
+ order_by = attributes.get("order_by", [])
114
+ sort_cols = [o.get("attribute") for o in order_by if o.get("attribute")]
115
+ descending = [o.get("direction") == "desc" for o in order_by if o.get("attribute")]
116
+ if sort_cols:
117
+ return frame.sort(sort_cols, descending=descending)
118
+ return frame
119
+ if operation == "limit":
120
+ limit = attributes.get("limit")
121
+ return frame.head(limit) if limit is not None else frame
122
+ raise ValueError(f"Unsupported post-combine operation '{operation}'.")
123
+
124
+ def to_rows(self, frame: pl.DataFrame) -> List[Dict[str, Any]]:
125
+ return frame.to_dicts()
nl2sql/api/__init__.py ADDED
File without changes
nl2sql/api/auth_api.py ADDED
@@ -0,0 +1,60 @@
1
+ """
2
+ Auth API for NL2SQL
3
+
4
+ Provides functionality for authentication and role-based access control.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import List, Optional
10
+
11
+ from nl2sql.context import NL2SQLContext
12
+ from nl2sql.auth.models import UserContext
13
+ from nl2sql.auth.rbac import RBAC
14
+
15
+
16
+ class AuthAPI:
17
+ """
18
+ API for authentication and role-based access control.
19
+ """
20
+
21
+ def __init__(self, ctx: NL2SQLContext):
22
+ self._ctx = ctx
23
+ self._rbac: RBAC = ctx.rbac
24
+
25
+ def check_permissions(
26
+ self,
27
+ user_context: UserContext,
28
+ datasource_id: str,
29
+ table: str
30
+ ) -> bool:
31
+ """
32
+ Check if a user has permission to access a specific resource.
33
+
34
+ Args:
35
+ user_context: User context with roles
36
+ datasource_id: ID of the datasource
37
+ table: Name of the table
38
+
39
+ Returns:
40
+ True if user has permission, False otherwise
41
+ """
42
+ return self._rbac.is_allowed(user_context, datasource_id, table)
43
+
44
+ def get_allowed_resources(
45
+ self,
46
+ user_context: UserContext
47
+ ) -> dict:
48
+ """
49
+ Get resources a user has access to.
50
+
51
+ Args:
52
+ user_context: User context with roles
53
+
54
+ Returns:
55
+ Dictionary with allowed datasources and tables
56
+ """
57
+ return {
58
+ "datasources": self._rbac.get_allowed_datasources(user_context),
59
+ "tables": self._rbac.get_allowed_tables(user_context)
60
+ }
@@ -0,0 +1,114 @@
1
+ """
2
+ Benchmark API for NL2SQL.
3
+
4
+ Provides public entry points for dataset benchmarking without exposing internal runners.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pathlib
10
+ from dataclasses import dataclass
11
+ from typing import Dict, Optional
12
+
13
+ import yaml
14
+
15
+ from nl2sql.common.settings import settings
16
+ from nl2sql.configs import ConfigManager
17
+ from nl2sql.configs.llm import LLMFileConfig, AgentConfig
18
+ from nl2sql.context import NL2SQLContext
19
+ from nl2sql.datasources import DatasourceRegistry
20
+ from nl2sql.evaluation.benchmark_runner import BenchmarkRunner, BenchmarkResult
21
+ from nl2sql.evaluation.types import BenchmarkConfig
22
+ from nl2sql.indexing.vector_store import VectorStore
23
+ from nl2sql.llm import LLMRegistry
24
+ from nl2sql.secrets import SecretManager
25
+
26
+
27
+ @dataclass
28
+ class BenchmarkMatrixResult:
29
+ """Aggregate results for a matrix benchmark run."""
30
+
31
+ results_by_config: Dict[str, BenchmarkResult]
32
+
33
+
34
+ class BenchmarkAPI:
35
+ """
36
+ API for running dataset benchmarks using core evaluation tooling.
37
+ """
38
+
39
+ def __init__(self, ctx: Optional[NL2SQLContext] = None):
40
+ self._ctx = ctx
41
+
42
+ def run_matrix(
43
+ self,
44
+ config: BenchmarkConfig,
45
+ *,
46
+ progress_callback=None,
47
+ ) -> BenchmarkMatrixResult:
48
+ """
49
+ Run a benchmark suite against one or more LLM configs.
50
+
51
+ Args:
52
+ config: Benchmark configuration (dataset, datasource config, etc.).
53
+ progress_callback: Optional progress iterator wrapper.
54
+ """
55
+ cm = self._ctx.config_manager if self._ctx else ConfigManager()
56
+
57
+ secret_manager = SecretManager()
58
+ secrets_path = config.secrets_path or settings.secrets_config_path
59
+ if secrets_path and pathlib.Path(secrets_path).exists():
60
+ secret_configs = cm.load_secrets(pathlib.Path(secrets_path))
61
+ if secret_configs:
62
+ secret_manager.configure(secret_configs)
63
+
64
+ config_path = pathlib.Path(config.config_path) if config.config_path else pathlib.Path(settings.datasource_config_path)
65
+ ds_configs = cm.load_datasources(config_path)
66
+ ds_registry = DatasourceRegistry(secret_manager)
67
+ ds_registry.register_datasources(ds_configs)
68
+
69
+ vector_store = VectorStore(
70
+ collection_name=settings.vector_store_collection_name,
71
+ persist_directory=config.vector_store_path or settings.vector_store_path,
72
+ )
73
+
74
+ llm_configs = self._load_llm_configs(config, cm)
75
+ results: Dict[str, BenchmarkResult] = {}
76
+
77
+ for name, llm_cfg in llm_configs.items():
78
+ llm_registry = LLMRegistry(secret_manager)
79
+ agents = llm_cfg.agents or {}
80
+ agents["default"] = llm_cfg.default
81
+ llm_registry.register_llms(agents)
82
+
83
+ runner = BenchmarkRunner(config, ds_registry, vector_store, llm_registry)
84
+ results[name] = runner.run_dataset(
85
+ config_name=name,
86
+ progress_callback=progress_callback,
87
+ )
88
+
89
+ return BenchmarkMatrixResult(results_by_config=results)
90
+
91
+ def _load_llm_configs(self, config: BenchmarkConfig, cm: ConfigManager) -> Dict[str, LLMFileConfig]:
92
+ llm_configs: Dict[str, LLMFileConfig] = {}
93
+
94
+ if config.bench_config_path and pathlib.Path(config.bench_config_path).exists():
95
+ bench_data = yaml.safe_load(pathlib.Path(config.bench_config_path).read_text()) or {}
96
+ for name, cfg_data in bench_data.items():
97
+ if isinstance(cfg_data, dict):
98
+ llm_configs[name] = LLMFileConfig.model_validate(cfg_data)
99
+
100
+ if not llm_configs:
101
+ if config.llm_config_path and pathlib.Path(config.llm_config_path).exists():
102
+ llm_configs["default"] = cm.load_llm(pathlib.Path(config.llm_config_path))
103
+ else:
104
+ llm_configs["default"] = LLMFileConfig(
105
+ default=AgentConfig(provider="openai", model="gpt-4o")
106
+ )
107
+
108
+ if config.stub_llm:
109
+ for llm_cfg in llm_configs.values():
110
+ llm_cfg.default.provider = "stub"
111
+ for agent_cfg in llm_cfg.agents.values():
112
+ agent_cfg.provider = "stub"
113
+
114
+ return llm_configs
@@ -0,0 +1,132 @@
1
+ """
2
+ Datasource API for NL2SQL
3
+
4
+ Provides functionality for managing datasources programmatically or via config.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pathlib
10
+ from typing import Union, Dict, Any, List
11
+
12
+ from nl2sql.context import NL2SQLContext
13
+ from nl2sql.datasources.registry import DatasourceRegistry
14
+ from nl2sql.datasources.models import DatasourceConfig
15
+
16
+
17
+ class DatasourceAPI:
18
+ """
19
+ API for managing datasources programmatically or via config files.
20
+ """
21
+
22
+ def __init__(self, ctx: NL2SQLContext):
23
+ self._ctx = ctx
24
+ self._registry: DatasourceRegistry = ctx.ds_registry
25
+
26
+ def add_datasource(
27
+ self,
28
+ config: Union[DatasourceConfig, Dict[str, Any]]
29
+ ) -> None:
30
+ """
31
+ Programmatically add a datasource to the engine.
32
+
33
+ Args:
34
+ config: Datasource configuration as either a DatasourceConfig object
35
+ or a dictionary with the configuration
36
+ """
37
+ if isinstance(config, dict):
38
+ config = DatasourceConfig(**config)
39
+
40
+ self._registry.register_datasource(config)
41
+
42
+ def add_datasource_from_config(
43
+ self,
44
+ config_path: Union[str, pathlib.Path]
45
+ ) -> None:
46
+ """
47
+ Add datasources from a configuration file.
48
+
49
+ Args:
50
+ config_path: Path to the datasource configuration file
51
+ """
52
+ from nl2sql.configs import ConfigManager
53
+ cm = ConfigManager()
54
+ config_path = pathlib.Path(config_path)
55
+ ds_configs = cm.load_datasources(config_path)
56
+ self._registry.register_datasources(ds_configs)
57
+
58
+ def list_datasources(self) -> List[str]:
59
+ """
60
+ List all registered datasource IDs.
61
+
62
+ Returns:
63
+ List of datasource IDs
64
+ """
65
+ return self._registry.list_ids()
66
+
67
+ def get_adapter(self, datasource_id: str):
68
+ """
69
+ Get the adapter for a specific datasource.
70
+
71
+ Args:
72
+ datasource_id: ID of the datasource
73
+
74
+ Returns:
75
+ Datasource adapter object
76
+ """
77
+ return self._registry.get_adapter(datasource_id)
78
+
79
+ def get_capabilities(self, datasource_id: str) -> List[str]:
80
+ """
81
+ Get the capabilities of a specific datasource.
82
+
83
+ Args:
84
+ datasource_id: ID of the datasource
85
+
86
+ Returns:
87
+ List of capability strings
88
+ """
89
+ return list(self._registry.get_capabilities(datasource_id))
90
+
91
+
92
+ def validate_connection(
93
+ self,
94
+ ds_id: str,
95
+ ) -> bool:
96
+ """
97
+ Validate a datasource connection configuration without registering it.
98
+
99
+ Args:
100
+ ds_id: ID of the datasource
101
+
102
+ Returns:
103
+ True if the connection is valid, raises an exception otherwise
104
+ """
105
+
106
+ adapter = self._registry.get_adapter(ds_id)
107
+ return adapter.test_connection()
108
+
109
+ def get_datasource_details(
110
+ self,
111
+ datasource_id: str
112
+ ) -> Dict[str, Any]:
113
+ """
114
+ Get detailed information about a specific datasource.
115
+
116
+ Args:
117
+ datasource_id: ID of the datasource
118
+
119
+ Returns:
120
+ Dictionary with datasource details
121
+ """
122
+ adapter = self._registry.get_adapter(datasource_id)
123
+ details = {
124
+ "datasource_id": adapter.datasource_id,
125
+ "datasource_engine_type": adapter.datasource_engine_type,
126
+ "connection_args": adapter.connection_args,
127
+ "statement_timeout_ms": adapter.statement_timeout_ms,
128
+ "row_limit": adapter.row_limit,
129
+ "max_bytes": adapter.max_bytes,
130
+ "capabilities": list(self._registry.get_capabilities(datasource_id)),
131
+ }
132
+ return details
@@ -0,0 +1,59 @@
1
+ """
2
+ Indexing API for NL2SQL
3
+
4
+ Provides functionality for indexing schemas for datasources.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Dict, Any
10
+
11
+ from nl2sql.context import NL2SQLContext
12
+ from nl2sql.indexing.orchestrator import IndexingOrchestrator
13
+
14
+
15
+ class IndexingAPI:
16
+ """
17
+ API for indexing schemas for datasources.
18
+ """
19
+
20
+ def __init__(self, ctx: NL2SQLContext):
21
+ self._ctx = ctx
22
+ self._orchestrator = IndexingOrchestrator(ctx)
23
+
24
+ def index_datasource(
25
+ self,
26
+ datasource_id: str
27
+ ) -> Dict[str, int]:
28
+ """
29
+ Index schema for a specific datasource.
30
+
31
+ Args:
32
+ datasource_id: ID of the datasource to index
33
+
34
+ Returns:
35
+ Dictionary with indexing statistics
36
+ """
37
+ adapter = self._ctx.ds_registry.get_adapter(datasource_id)
38
+ return self._orchestrator.index_datasource(adapter)
39
+
40
+ def index_all_datasources(self) -> Dict[str, Dict[str, int]]:
41
+ """
42
+ Index schema for all registered datasources.
43
+
44
+ Returns:
45
+ Dictionary mapping datasource IDs to indexing statistics
46
+ """
47
+ results = {}
48
+ for datasource_id in self._ctx.ds_registry.list_ids():
49
+ try:
50
+ results[datasource_id] = self.index_datasource(datasource_id)
51
+ except Exception as e:
52
+ results[datasource_id] = {"error": str(e)}
53
+ return results
54
+
55
+ def clear_index(self) -> None:
56
+ """
57
+ Clear the vector store index.
58
+ """
59
+ self._orchestrator.clear_store()
nl2sql/api/llm_api.py ADDED
@@ -0,0 +1,82 @@
1
+ """
2
+ LLM API for NL2SQL
3
+
4
+ Provides functionality for configuring LLMs programmatically or via config.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pathlib
10
+ from typing import Union, Dict, Any
11
+
12
+ from nl2sql.context import NL2SQLContext
13
+ from nl2sql.llm.registry import LLMRegistry
14
+ from nl2sql.llm.models import AgentConfig
15
+
16
+
17
+ class LLM_API:
18
+ """
19
+ API for configuring LLMs programmatically or via config files.
20
+ """
21
+
22
+ def __init__(self, ctx: NL2SQLContext):
23
+ self._ctx = ctx
24
+ self._registry = ctx.llm_registry
25
+
26
+ def configure_llm(
27
+ self,
28
+ config: Union[AgentConfig, Dict[str, Any]]
29
+ ) -> None:
30
+ """
31
+ Programmatically configure an LLM.
32
+
33
+ Args:
34
+ config: LLM configuration as either an AgentConfig object
35
+ or a dictionary with the configuration
36
+ """
37
+ if isinstance(config, dict):
38
+ if 'name' not in config:
39
+ config['name'] = 'default'
40
+ config = AgentConfig(**config)
41
+
42
+ self._registry.register_llm(config)
43
+
44
+ def configure_llm_from_config(
45
+ self,
46
+ config_path: Union[str, pathlib.Path]
47
+ ) -> None:
48
+ """
49
+ Configure LLMs from a configuration file.
50
+
51
+ Args:
52
+ config_path: Path to the LLM configuration file
53
+ """
54
+ from nl2sql.configs import ConfigManager
55
+ cm = ConfigManager()
56
+ config_path = pathlib.Path(config_path)
57
+ llm_cfg = cm.load_llm(config_path)
58
+
59
+ agents = llm_cfg.agents or {}
60
+ agents["default"] = llm_cfg.default
61
+ self._registry.register_llms(agents)
62
+
63
+ def get_llm(self, name: str):
64
+ """
65
+ Get a specific LLM by name.
66
+
67
+ Args:
68
+ name: Name of the LLM to retrieve
69
+
70
+ Returns:
71
+ LLM instance
72
+ """
73
+ return self._registry.get_llm_config(name)
74
+
75
+ def list_llms(self) -> dict:
76
+ """
77
+ List all configured LLMs.
78
+
79
+ Returns:
80
+ List of LLM names
81
+ """
82
+ return self._registry.list_llms()