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,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,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()
|