Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/architecture/determinism.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ answer". **Model output is not reproducible.** What is stable is the shape of a
run:

- **Stable sub-query and DAG ids.** Both are SHA-256 content hashes over
canonical JSON, not counters or UUIDs ([`decomposer/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py), [`global_planner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/global_planner/node.py)). The same decomposition always yields the same ids.
canonical JSON, not counters or UUIDs ([`decomposer/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py), [`decomposer/dag.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/dag.py)). The same decomposition always yields the same ids.
- **Sorted layer order.** The topological sort sorts each ready set and each
dependent set, so the execution layers of a given DAG are fixed
([`execution/dag.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/execution/dag.py)).
Expand Down Expand Up @@ -101,7 +101,7 @@ gives every result a total row order.
- Non-deterministic: User query interpretation depends on LLMs in the decomposer, planner, and refiner nodes. These nodes set nothing themselves; `temperature` (default `0.0`, omitted when configured as `null`) and `seed=42` are applied once, where the client is built in [`llm/registry.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/llm/registry.py), and neither makes the output reproducible ([`pipeline/nodes/decomposer/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py), [`pipeline/nodes/ast_planner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/ast_planner/node.py), [`pipeline/nodes/refiner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/refiner/node.py)).

### Planner and DAG Construction
- Deterministic: Global planner sorts nodes and edges by IDs and roles before constructing the DAG, then hashes a sorted JSON payload to produce a stable `dag_id` for a given logical plan ([`pipeline/nodes/global_planner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/global_planner/node.py)).
- Deterministic: The decomposer sorts nodes and edges by IDs and roles before constructing the DAG, so the same decomposition always gives the same layers ([`pipeline/nodes/decomposer/dag.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/dag.py)).
- Deterministic: DAG layers are computed with a topological sort that sorts ready nodes and dependents, yielding stable layer ordering given the same node/edge sets ([`execution/dag.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/execution/dag.py)).
- Non-deterministic: The AST planner is LLM-driven; the PlanModel content is not stabilized inside the node ([`pipeline/nodes/ast_planner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/ast_planner/node.py)).
- Deterministic (conditional): Once a sub-query's plan has validated and executed, the plan cache pins it: the same normalised intent, datasource and schema version reuse that plan without a planner call, and it is validated again on every use (see [The plan cache](#the-plan-cache-determinism-from-the-architecture)).
Expand Down Expand Up @@ -146,7 +146,7 @@ gives every result a total row order.
- Potentially non-deterministic: GraphState merges dict fields with a last-write-wins reducer and concatenates lists; in parallel branches, merge order is not constrained in this code ([`pipeline/state.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/state.py)).

### Hashing/Fingerprinting
- Deterministic: Stable hashing is consistently performed with sorted JSON and fixed separators for sub-query IDs, DAG hashes, schema fingerprints, and artifact content hashes ([`pipeline/nodes/decomposer/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py), [`pipeline/nodes/global_planner/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/global_planner/node.py), [`schema/protocol.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/schema/protocol.py), [`execution/artifacts/store.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/execution/artifacts/store.py)).
- Deterministic: Stable hashing is consistently performed with sorted JSON and fixed separators for sub-query IDs, schema fingerprints, and artifact content hashes ([`pipeline/nodes/decomposer/node.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py), [`schema/protocol.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/schema/protocol.py), [`execution/artifacts/store.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/execution/artifacts/store.py)).

### Time-Based Logic and Runtime Controls
- Non-deterministic: Schema versions and artifact creation timestamps are derived from wall-clock time ([`schema/in_memory_store.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/schema/in_memory_store.py), [`schema/sqlite_store.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/schema/sqlite_store.py), [`execution/artifacts/store.py`](https://github.com/nadeem4/nl2sql/blob/main/packages/nl2sql/src/nl2sql/execution/artifacts/store.py)).
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/failure_recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Failure in this system is represented as structured `PipelineError` objects accu
### Planning
- Decomposer LLM failures return `ORCHESTRATOR_CRASH` (critical) and empty responses.
- AST planner LLM failures return `PLANNING_FAILURE` and a `None` plan.
- Global planner failures return `PLANNER_FAILED` and no `global_planner_response`; the layer router ends the run.
- A decomposition that cannot be turned into an execution DAG returns `PLANNER_FAILED` and no `execution_dag`; the layer router ends the run.

### Validation
- Logical validation returns structured errors for missing tables, columns, invalid plan structure, or security violations.
Expand Down
24 changes: 11 additions & 13 deletions docs/architecture/graph_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Fields (exact names and types from code):
- `datasource_id: Optional[str]`
- `datasource_resolver_response: Optional[DatasourceResolverResponse]`
- `decomposer_response: Optional[DecomposerResponse]`
- `global_planner_response: Optional[GlobalPlannerResponse]`
- `execution_dag: Optional[ExecutionDAG]`
- `aggregator_response: Optional[AggregatorResponse]`
- `answer_synthesizer_response: Optional[AnswerSynthesizerResponse]`
- `artifact_refs: Annotated[Dict[str, ArtifactRef], update_results]`
Expand Down Expand Up @@ -105,11 +105,11 @@ The lifecycle below lists creation, mutation, reads, and resets based strictly o

### `decomposer_response`
- Creation/mutation: returned by `DecomposerNode` as `decomposer_response`.
- Read points: `GlobalPlannerNode`, `build_scan_payload`, and `wrap_subgraph` (to find `sub_query` by id).
- Read points: `build_scan_layer_router`, `build_scan_payload`, and `wrap_subgraph` (to find `sub_query` by id).
- Reset: none in code.

### `global_planner_response`
- Creation/mutation: returned by `GlobalPlannerNode` as `global_planner_response`.
### `execution_dag`
- Creation/mutation: returned by `DecomposerNode`, which builds it from its own response (`decomposer/dag.py`).
- Read points: `build_scan_layer_router` and `EngineAggregatorNode`.
- Reset: none in code.

Expand Down Expand Up @@ -165,7 +165,7 @@ The lifecycle below lists creation, mutation, reads, and resets based strictly o
Ownership is defined by which node returns updates for a field:
- `datasource_resolver_response`: `DatasourceResolverNode`
- `decomposer_response`: `DecomposerNode`
- `global_planner_response`: `GlobalPlannerNode`
- `execution_dag`: `DecomposerNode`
- `aggregator_response`: `EngineAggregatorNode`
- `answer_synthesizer_response`: `AnswerSynthesizerNode`
- `artifact_refs`: `wrap_subgraph` (subgraph wrapper in `graph_utils.py`)
Expand All @@ -187,16 +187,15 @@ Step-by-step execution flow as defined in `build_graph` and routing:
1. `run_with_graph` constructs `GraphState` with `user_query`, `user_context`, and optional `datasource_id`, then calls `graph.invoke(initial_state.model_dump())`.
2. `DatasourceResolverNode` runs first and populates `datasource_resolver_response`, `reasoning`, and `errors`.
3. `resolver_route` decides whether to continue based on `datasource_resolver_response`.
4. `DecomposerNode` produces `decomposer_response` and reasoning.
5. `GlobalPlannerNode` produces `global_planner_response` (including the `ExecutionDAG`).
6. `build_scan_layer_router` emits `Send` branches using `build_scan_payload` for each pending scan node. The payload contains `subgraph_id`, `subgraph_name`, `trace_id`, `user_context`, `decomposer_response`, and `datasource_resolver_response`.
7. Each subgraph is wrapped by `wrap_subgraph`, which:
4. `DecomposerNode` produces `decomposer_response`, the `execution_dag` built from it, and reasoning.
5. `build_scan_layer_router` emits `Send` branches using `build_scan_payload` for each pending scan node. The payload contains `subgraph_id`, `subgraph_name`, `trace_id`, `user_context`, `decomposer_response`, and `datasource_resolver_response`.
6. Each subgraph is wrapped by `wrap_subgraph`, which:
- Builds a `SubgraphExecutionState` using the payload and a `sub_query` resolved from `decomposer_response`.
- Invokes the subgraph and validates the result into `SubgraphExecutionState`.
- Returns updates for `artifact_refs`, `subgraph_outputs`, `errors`, and `reasoning`.
8. The router checks `artifact_refs` to decide which scan nodes are still pending. When none remain, it routes to `aggregator`.
9. `EngineAggregatorNode` consumes `global_planner_response` and `artifact_refs` to produce `aggregator_response`.
10. `AnswerSynthesizerNode` consumes `aggregator_response` and `decomposer_response` to produce `answer_synthesizer_response`.
7. The router checks `artifact_refs` to decide which scan nodes are still pending. When none remain, it routes to `aggregator`.
8. `EngineAggregatorNode` consumes `execution_dag` and `artifact_refs` to produce `aggregator_response`.
9. `AnswerSynthesizerNode` consumes `aggregator_response` and `decomposer_response` to produce `answer_synthesizer_response`.

Subgraph internal flow uses `SubgraphExecutionState` and is defined in `build_sql_agent_graph`:
`schema_retriever` -> `ast_planner` -> `logical_validator` -> `generator` -> `executor`, with a retry loop via `retry_handler` and `refiner`.
Expand Down Expand Up @@ -274,7 +273,6 @@ Subgraph state and execution:
Mutators / consumers:
- `packages/nl2sql/src/nl2sql/pipeline/nodes/datasource_resolver/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/global_planner/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/aggregator/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/answer_synthesizer/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/schema_retriever/node.py`
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/invariants.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ Prevents invalid execution requests and ensures capability compatibility.
Post-combine ops must target known combine groups, all edges must reference existing nodes, and the DAG must be acyclic.

### Enforcement Points
- `GlobalPlannerNode.__call__()` in `nl2sql.pipeline.nodes.global_planner.node`
- `build_execution_dag()` in `nl2sql.pipeline.nodes.decomposer.dag`, called by `DecomposerNode.__call__()`
- `ExecutionDAG._layered_toposort()` in `nl2sql.execution.dag`

### Failure Behavior
Expand Down Expand Up @@ -417,7 +417,7 @@ The validator resolves columns against a throw-away query in which every table i
- `packages/nl2sql/src/nl2sql/pipeline/nodes/generator/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/executor/node.py`
- `packages/nl2sql/src/nl2sql/execution/executor/sql_executor.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/global_planner/node.py`
- `packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/dag.py`
- `packages/nl2sql/src/nl2sql/execution/dag.py`
- `packages/nl2sql/src/nl2sql/aggregation/aggregator.py`
- `packages/nl2sql/src/nl2sql/context.py`
Expand Down
7 changes: 4 additions & 3 deletions docs/architecture/nodes/decomposer_node.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- Decomposes the user query into datasource‑scoped sub‑queries and combination operations.
- Produces deterministic IDs for sub‑queries and post‑combine ops.
- Sits after `DatasourceResolverNode` and before `GlobalPlannerNode`.
- Sits after `DatasourceResolverNode` and before the layer router.
- Class: `DecomposerNode`
- Source: `packages/nl2sql/src/nl2sql/pipeline/nodes/decomposer/node.py`

Expand All @@ -16,6 +16,7 @@
- Filter sub‑queries by resolved/allowed/unsupported datasources.
- Stabilize IDs using a hash of sub‑query content.
- Normalize and sort combine groups and post‑combine operations.
- Build the `ExecutionDAG` the layer router walks and the aggregator runs (`decomposer/dag.py`). The DAG is a pure function of the response above, so it is code here rather than a node of its own.

---

Expand All @@ -25,14 +26,14 @@ Upstream:
- `DatasourceResolverNode`

Downstream:
- `GlobalPlannerNode`
- `layer_router`

Trigger conditions:
- Executed only when `resolver_route` returns `continue`.

```mermaid
flowchart LR
Resolver[DatasourceResolverNode] --> Decomposer[DecomposerNode] --> Planner[GlobalPlannerNode]
Resolver[DatasourceResolverNode] --> Decomposer[DecomposerNode] --> Router[layer_router]
```

---
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/nodes/engine_aggregator_node.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ flowchart LR

From `GraphState`:

- `global_planner_response.execution_dag` (required)
- `execution_dag` (required)
- `artifact_refs` (required)

Validation performed:
Expand Down
167 changes: 0 additions & 167 deletions docs/architecture/nodes/global_planner_node.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/architecture/nodes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ For pipeline-level wiring, see `../pipeline.md`. For subgraph wiring and lifecyc

- [DatasourceResolverNode](datasource_resolver_node.md)
- [DecomposerNode](decomposer_node.md)
- [GlobalPlannerNode](global_planner_node.md)
- [EngineAggregatorNode](engine_aggregator_node.md)
- [AnswerSynthesizerNode](answer_synthesizer_node.md)

Expand Down
9 changes: 3 additions & 6 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ flowchart TD
Runtime --> Graph[build_graph()]
Graph --> Resolver[DatasourceResolverNode]
Resolver --> Decomposer[DecomposerNode]
Decomposer --> Planner[GlobalPlannerNode]
Planner --> Router[Scan Layer Router]
Decomposer --> Router[Scan Layer Router]
Router --> Subgraph[SQL Agent Subgraph]
Subgraph --> Router
Router --> Aggregator[EngineAggregatorNode]
Expand Down Expand Up @@ -61,7 +60,7 @@ flowchart LR

## Major subsystems (and responsibilities)

- **Planner / Decomposer**: `DecomposerNode` produces stable, semantically-scoped sub-queries; `GlobalPlannerNode` produces a deterministic `ExecutionDAG`.
- **Planner / Decomposer**: `DecomposerNode` produces stable, semantically-scoped sub-queries and, from them, a deterministic `ExecutionDAG`.
- **Schema Store**: `SchemaStore` persists versioned schema snapshots with fingerprints.
- **Chunking + Retrieval**: `SchemaChunkBuilder` produces typed chunks; `VectorStore` provides staged retrieval for routing and planning context.
- **Validation layer**: `LogicalValidatorNode` enforces schema correctness and RBAC.
Expand All @@ -78,7 +77,6 @@ sequenceDiagram
participant Runtime as run_with_graph
participant Resolver as DatasourceResolverNode
participant Decomposer as DecomposerNode
participant Planner as GlobalPlannerNode
participant Router as Scan Layer Router
participant Subgraph as SQL Agent Subgraph
participant Agg as EngineAggregatorNode
Expand All @@ -87,8 +85,7 @@ sequenceDiagram
User->>Runtime: user_query
Runtime->>Resolver: GraphState
Resolver->>Decomposer: resolved datasources
Decomposer->>Planner: sub_queries + combine groups
Planner->>Router: ExecutionDAG
Decomposer->>Router: ExecutionDAG over the sub-queries
Router->>Subgraph: Send(sub_query)
Subgraph-->>Router: ArtifactRef + diagnostics
Router->>Agg: all scan artifacts
Expand Down
Loading
Loading