flowk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowk-0.1.0/PKG-INFO +177 -0
- flowk-0.1.0/README.md +161 -0
- flowk-0.1.0/flowk/__init__.py +20 -0
- flowk-0.1.0/flowk/debugger.py +68 -0
- flowk-0.1.0/flowk/exceptions.py +15 -0
- flowk-0.1.0/flowk/executor.py +256 -0
- flowk-0.1.0/flowk/graph.py +147 -0
- flowk-0.1.0/flowk/memory.py +57 -0
- flowk-0.1.0/flowk/metrics.py +48 -0
- flowk-0.1.0/flowk/node.py +112 -0
- flowk-0.1.0/flowk/plugins/__init__.py +1 -0
- flowk-0.1.0/flowk/plugins/base.py +55 -0
- flowk-0.1.0/flowk/plugins/llm.py +19 -0
- flowk-0.1.0/flowk/plugins/storage.py +27 -0
- flowk-0.1.0/flowk/state.py +45 -0
- flowk-0.1.0/flowk/storage.py +25 -0
- flowk-0.1.0/flowk/utils.py +20 -0
- flowk-0.1.0/flowk/visualization.py +61 -0
- flowk-0.1.0/flowk.egg-info/PKG-INFO +177 -0
- flowk-0.1.0/flowk.egg-info/SOURCES.txt +24 -0
- flowk-0.1.0/flowk.egg-info/dependency_links.txt +1 -0
- flowk-0.1.0/flowk.egg-info/requires.txt +1 -0
- flowk-0.1.0/flowk.egg-info/top_level.txt +1 -0
- flowk-0.1.0/pyproject.toml +27 -0
- flowk-0.1.0/setup.cfg +4 -0
- flowk-0.1.0/tests/test_core.py +50 -0
flowk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, modular, and developer-first workflow orchestration engine for AI/LLM pipelines.
|
|
5
|
+
Author-email: Folk Nallathambi <folkadonis7@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/folkadonis/flowk
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/folkadonis/flowk/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
|
|
17
|
+
# Flowk π
|
|
18
|
+
|
|
19
|
+
**Flowk** is a lightweight, high-performance workflow orchestration engine specifically designed for AI and LLM pipelines. It offers a simpler, developer-first alternative to complex frameworks while providing first-class support for complex routing, shared state memory, observability, and debugging.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## π Key Features
|
|
24
|
+
- **Extremely Simple API:** Turn standard Python functions into executable graph nodes seamlessly.
|
|
25
|
+
- **Node Retries & Fallbacks:** Built-in resilience out-of-the-box.
|
|
26
|
+
- **Dynamic Routing:** Direct your execution paths dynamically on the fly based on outputs.
|
|
27
|
+
- **Stepping & Time Travel:** Pausable execution steps and total trace replay capabilities.
|
|
28
|
+
- **Telemetry & Visualization:** Live terminal tracking, cost metric emulation, and highly readable CLI flow rendering.
|
|
29
|
+
- **Pluggable Architecture:** Tap into lifecycle hooks using Plugins effortlessly.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## π οΈ Core Concepts
|
|
34
|
+
|
|
35
|
+
### 1. The Graph
|
|
36
|
+
The `Graph` is the brain of Flowk. It wires up nodes sequentially or through condition-based router intersections:
|
|
37
|
+
```python
|
|
38
|
+
from flowk import Graph
|
|
39
|
+
g = Graph()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Nodes & State
|
|
43
|
+
Nodes are just typical Python functions decorated with `@g.node()`. An internal `GraphState` mutable dictionary is implicitly available across your pipeline.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# Pass `state` as an argument to read/write shared data across the lifecycle map
|
|
47
|
+
@g.node(retries=3)
|
|
48
|
+
def prepare_prompt(input_text: str, state: dict):
|
|
49
|
+
state["original_query"] = input_text
|
|
50
|
+
return input_text.upper()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Connections
|
|
54
|
+
Bind nodes synchronously. The `Graph` auto-detects the first configured node as the entrypoint. All data returned from Node A automatically gets piped into Node B as the `input_text`.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
g.connect(prepare_prompt, call_llm)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 4. Routing (Conditional Branching)
|
|
61
|
+
When execution forks depend on context (e.g., standard request vs. priority request), use `g.route()`.
|
|
62
|
+
```python
|
|
63
|
+
def check_priority(result_from_previous_node: str):
|
|
64
|
+
return "fast" if "URGENT" in result_from_previous_node else "standard"
|
|
65
|
+
|
|
66
|
+
# Map condition strings to actual handling Nodes
|
|
67
|
+
router_node = g.route(check_priority, {
|
|
68
|
+
"fast": priority_handler_node,
|
|
69
|
+
"standard": normal_handler_node
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
g.connect(prepare_prompt, router_node)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## π Tooling & Observability
|
|
78
|
+
|
|
79
|
+
Flowk ships with beautiful tooling crafted identically for both fast prototyping and robust production monitoring.
|
|
80
|
+
|
|
81
|
+
### Visualizing Graphs
|
|
82
|
+
Check exactly how your configuration looks using `g.show()`.
|
|
83
|
+
```text
|
|
84
|
+
==================================================
|
|
85
|
+
π FLOWK EXECUTION FLOW
|
|
86
|
+
==================================================
|
|
87
|
+
|
|
88
|
+
[ prepare_prompt ]
|
|
89
|
+
β
|
|
90
|
+
βΌ
|
|
91
|
+
βͺ priority_check β« (Router)
|
|
92
|
+
β
|
|
93
|
+
ββ[fast]βββββββΊ [ priority_handler ]
|
|
94
|
+
β β
|
|
95
|
+
β βΌ
|
|
96
|
+
β [ cleanup ]
|
|
97
|
+
β
|
|
98
|
+
ββ[standard]βββΊ [ standard_handler ]
|
|
99
|
+
β
|
|
100
|
+
βΌ
|
|
101
|
+
[ cleanup ] π (already visited)
|
|
102
|
+
|
|
103
|
+
==================================================
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Metrics Tracking
|
|
107
|
+
Built-in timing tracking per node alongside mock LLM tracking usage:
|
|
108
|
+
```python
|
|
109
|
+
g.run("Hello!")
|
|
110
|
+
|
|
111
|
+
from flowk import MetricsRegistry
|
|
112
|
+
print(MetricsRegistry.get_summary())
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### π§ Session Memory Management
|
|
116
|
+
Flowk supports native execution memory persistence across multiple `.run()` calls via the `session_id` parameter. This is critically useful for multi-turn chat workflows where the LLM needs to continually append messages to the `GraphState` instead of wiping the slate clean!
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Turns persist data appended into state automatically
|
|
120
|
+
r1 = g.run("Hello", session_id="user_john")
|
|
121
|
+
r2 = g.run("Are you there?", session_id="user_john")
|
|
122
|
+
|
|
123
|
+
r3_anon = g.run("Who am I?") # Anonymous runs use empty states
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### β‘ Async, Streaming, and Parallel Execution (v2)
|
|
127
|
+
Flowk utilizes high-performance asynchronous primitives to match enterprise scale:
|
|
128
|
+
- Define any node as `async def` and Flowk natively awaits it without blocking the thread pool.
|
|
129
|
+
- Use `g.arun()` for standard async resolution.
|
|
130
|
+
- Broadcast real-time node outputs manually using `async for event in g.astream(...)`. This is extremely optimal for mapping LLM outputs into WebSocket frontends!
|
|
131
|
+
- **Fan-Out Parallelism:** If a node splits into multiple separate nodes, Flowk executes all concurrent branches exactly concurrently using `asyncio.gather`.
|
|
132
|
+
|
|
133
|
+
### π Human-in-The-Loop (Breakpoints)
|
|
134
|
+
Need a human to review an action before it commits to a database? Interrupt the graph!
|
|
135
|
+
```python
|
|
136
|
+
# 1. Compile the graph with a breakpoint
|
|
137
|
+
g.compile(interrupt_before=["commit_to_database"])
|
|
138
|
+
|
|
139
|
+
# 2. Execution will stop and exit when reaching the node
|
|
140
|
+
for event in g.astream(input_data, session_id="user_1"):
|
|
141
|
+
if event["type"] == "interrupt":
|
|
142
|
+
print("Waiting for human...")
|
|
143
|
+
|
|
144
|
+
# 3. Later, resume using the exact same session_id!
|
|
145
|
+
g.arun(None, session_id="user_1")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### π‘οΈ Pydantic Safe-State Validation
|
|
149
|
+
Never let a silent property typo crash a 20-minute LLM pipeline again. Wrap your shared state in a Pydantic schema:
|
|
150
|
+
```python
|
|
151
|
+
from pydantic import BaseModel
|
|
152
|
+
class AgentState(BaseModel):
|
|
153
|
+
messages: list
|
|
154
|
+
cost: float
|
|
155
|
+
|
|
156
|
+
g = Graph(state_schema=AgentState)
|
|
157
|
+
# Flowk will validate `AgentState(**state)` between EVERY node execution.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Debug & Time Travel
|
|
161
|
+
Encountering bugs in a complex run? Flowk saves runs by default!
|
|
162
|
+
- To run with highly verbose sequential logging, replace `g.run()` with `g.debug()`.
|
|
163
|
+
- To sequentially replay historic traces visually in terminal, grab the `run_id` outputted from any run:
|
|
164
|
+
`g.replay("run-123-abc")`
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## π§© Plugins (Extensions)
|
|
169
|
+
|
|
170
|
+
Under the hood, flow runs evaluate through hooks (`on_run_start`, `on_node_start`, `on_node_end`, `on_run_end`). Check `flowk.plugins.base.Plugin` to extend the system yourselfβlike intercepting runs to store trace files via `FileStoragePlugin`!
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from flowk.plugins.base import PluginManager
|
|
174
|
+
from flowk.plugins.storage import FileStoragePlugin
|
|
175
|
+
|
|
176
|
+
PluginManager.register(FileStoragePlugin("server_logs.jsonl"))
|
|
177
|
+
```
|
flowk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Flowk π
|
|
2
|
+
|
|
3
|
+
**Flowk** is a lightweight, high-performance workflow orchestration engine specifically designed for AI and LLM pipelines. It offers a simpler, developer-first alternative to complex frameworks while providing first-class support for complex routing, shared state memory, observability, and debugging.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## π Key Features
|
|
8
|
+
- **Extremely Simple API:** Turn standard Python functions into executable graph nodes seamlessly.
|
|
9
|
+
- **Node Retries & Fallbacks:** Built-in resilience out-of-the-box.
|
|
10
|
+
- **Dynamic Routing:** Direct your execution paths dynamically on the fly based on outputs.
|
|
11
|
+
- **Stepping & Time Travel:** Pausable execution steps and total trace replay capabilities.
|
|
12
|
+
- **Telemetry & Visualization:** Live terminal tracking, cost metric emulation, and highly readable CLI flow rendering.
|
|
13
|
+
- **Pluggable Architecture:** Tap into lifecycle hooks using Plugins effortlessly.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## π οΈ Core Concepts
|
|
18
|
+
|
|
19
|
+
### 1. The Graph
|
|
20
|
+
The `Graph` is the brain of Flowk. It wires up nodes sequentially or through condition-based router intersections:
|
|
21
|
+
```python
|
|
22
|
+
from flowk import Graph
|
|
23
|
+
g = Graph()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Nodes & State
|
|
27
|
+
Nodes are just typical Python functions decorated with `@g.node()`. An internal `GraphState` mutable dictionary is implicitly available across your pipeline.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# Pass `state` as an argument to read/write shared data across the lifecycle map
|
|
31
|
+
@g.node(retries=3)
|
|
32
|
+
def prepare_prompt(input_text: str, state: dict):
|
|
33
|
+
state["original_query"] = input_text
|
|
34
|
+
return input_text.upper()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Connections
|
|
38
|
+
Bind nodes synchronously. The `Graph` auto-detects the first configured node as the entrypoint. All data returned from Node A automatically gets piped into Node B as the `input_text`.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
g.connect(prepare_prompt, call_llm)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 4. Routing (Conditional Branching)
|
|
45
|
+
When execution forks depend on context (e.g., standard request vs. priority request), use `g.route()`.
|
|
46
|
+
```python
|
|
47
|
+
def check_priority(result_from_previous_node: str):
|
|
48
|
+
return "fast" if "URGENT" in result_from_previous_node else "standard"
|
|
49
|
+
|
|
50
|
+
# Map condition strings to actual handling Nodes
|
|
51
|
+
router_node = g.route(check_priority, {
|
|
52
|
+
"fast": priority_handler_node,
|
|
53
|
+
"standard": normal_handler_node
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
g.connect(prepare_prompt, router_node)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## π Tooling & Observability
|
|
62
|
+
|
|
63
|
+
Flowk ships with beautiful tooling crafted identically for both fast prototyping and robust production monitoring.
|
|
64
|
+
|
|
65
|
+
### Visualizing Graphs
|
|
66
|
+
Check exactly how your configuration looks using `g.show()`.
|
|
67
|
+
```text
|
|
68
|
+
==================================================
|
|
69
|
+
π FLOWK EXECUTION FLOW
|
|
70
|
+
==================================================
|
|
71
|
+
|
|
72
|
+
[ prepare_prompt ]
|
|
73
|
+
β
|
|
74
|
+
βΌ
|
|
75
|
+
βͺ priority_check β« (Router)
|
|
76
|
+
β
|
|
77
|
+
ββ[fast]βββββββΊ [ priority_handler ]
|
|
78
|
+
β β
|
|
79
|
+
β βΌ
|
|
80
|
+
β [ cleanup ]
|
|
81
|
+
β
|
|
82
|
+
ββ[standard]βββΊ [ standard_handler ]
|
|
83
|
+
β
|
|
84
|
+
βΌ
|
|
85
|
+
[ cleanup ] π (already visited)
|
|
86
|
+
|
|
87
|
+
==================================================
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Metrics Tracking
|
|
91
|
+
Built-in timing tracking per node alongside mock LLM tracking usage:
|
|
92
|
+
```python
|
|
93
|
+
g.run("Hello!")
|
|
94
|
+
|
|
95
|
+
from flowk import MetricsRegistry
|
|
96
|
+
print(MetricsRegistry.get_summary())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### π§ Session Memory Management
|
|
100
|
+
Flowk supports native execution memory persistence across multiple `.run()` calls via the `session_id` parameter. This is critically useful for multi-turn chat workflows where the LLM needs to continually append messages to the `GraphState` instead of wiping the slate clean!
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Turns persist data appended into state automatically
|
|
104
|
+
r1 = g.run("Hello", session_id="user_john")
|
|
105
|
+
r2 = g.run("Are you there?", session_id="user_john")
|
|
106
|
+
|
|
107
|
+
r3_anon = g.run("Who am I?") # Anonymous runs use empty states
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### β‘ Async, Streaming, and Parallel Execution (v2)
|
|
111
|
+
Flowk utilizes high-performance asynchronous primitives to match enterprise scale:
|
|
112
|
+
- Define any node as `async def` and Flowk natively awaits it without blocking the thread pool.
|
|
113
|
+
- Use `g.arun()` for standard async resolution.
|
|
114
|
+
- Broadcast real-time node outputs manually using `async for event in g.astream(...)`. This is extremely optimal for mapping LLM outputs into WebSocket frontends!
|
|
115
|
+
- **Fan-Out Parallelism:** If a node splits into multiple separate nodes, Flowk executes all concurrent branches exactly concurrently using `asyncio.gather`.
|
|
116
|
+
|
|
117
|
+
### π Human-in-The-Loop (Breakpoints)
|
|
118
|
+
Need a human to review an action before it commits to a database? Interrupt the graph!
|
|
119
|
+
```python
|
|
120
|
+
# 1. Compile the graph with a breakpoint
|
|
121
|
+
g.compile(interrupt_before=["commit_to_database"])
|
|
122
|
+
|
|
123
|
+
# 2. Execution will stop and exit when reaching the node
|
|
124
|
+
for event in g.astream(input_data, session_id="user_1"):
|
|
125
|
+
if event["type"] == "interrupt":
|
|
126
|
+
print("Waiting for human...")
|
|
127
|
+
|
|
128
|
+
# 3. Later, resume using the exact same session_id!
|
|
129
|
+
g.arun(None, session_id="user_1")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### π‘οΈ Pydantic Safe-State Validation
|
|
133
|
+
Never let a silent property typo crash a 20-minute LLM pipeline again. Wrap your shared state in a Pydantic schema:
|
|
134
|
+
```python
|
|
135
|
+
from pydantic import BaseModel
|
|
136
|
+
class AgentState(BaseModel):
|
|
137
|
+
messages: list
|
|
138
|
+
cost: float
|
|
139
|
+
|
|
140
|
+
g = Graph(state_schema=AgentState)
|
|
141
|
+
# Flowk will validate `AgentState(**state)` between EVERY node execution.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Debug & Time Travel
|
|
145
|
+
Encountering bugs in a complex run? Flowk saves runs by default!
|
|
146
|
+
- To run with highly verbose sequential logging, replace `g.run()` with `g.debug()`.
|
|
147
|
+
- To sequentially replay historic traces visually in terminal, grab the `run_id` outputted from any run:
|
|
148
|
+
`g.replay("run-123-abc")`
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## π§© Plugins (Extensions)
|
|
153
|
+
|
|
154
|
+
Under the hood, flow runs evaluate through hooks (`on_run_start`, `on_node_start`, `on_node_end`, `on_run_end`). Check `flowk.plugins.base.Plugin` to extend the system yourselfβlike intercepting runs to store trace files via `FileStoragePlugin`!
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from flowk.plugins.base import PluginManager
|
|
158
|
+
from flowk.plugins.storage import FileStoragePlugin
|
|
159
|
+
|
|
160
|
+
PluginManager.register(FileStoragePlugin("server_logs.jsonl"))
|
|
161
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flowk: A lightweight, modular, and extensible workflow orchestration engine for AI/LLM pipelines.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from flowk.graph import Graph
|
|
6
|
+
from flowk.exceptions import GraphError, NodeExecutionError, InvalidGraphError, ReplayError
|
|
7
|
+
from flowk.state import GraphState
|
|
8
|
+
from flowk.metrics import MetricsRegistry
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Graph",
|
|
12
|
+
"GraphState",
|
|
13
|
+
"MetricsRegistry",
|
|
14
|
+
"GraphError",
|
|
15
|
+
"NodeExecutionError",
|
|
16
|
+
"InvalidGraphError",
|
|
17
|
+
"ReplayError"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from flowk.graph import Graph
|
|
6
|
+
from flowk.storage import StorageRegistry
|
|
7
|
+
from flowk.plugins.base import PluginManager, DebugPlugin
|
|
8
|
+
from flowk.executor import SequentialExecutor
|
|
9
|
+
|
|
10
|
+
class Debugger:
|
|
11
|
+
def __init__(self, graph: Graph):
|
|
12
|
+
self.graph = graph
|
|
13
|
+
|
|
14
|
+
def run(self, input_data: Any, session_id: str = None) -> Any:
|
|
15
|
+
run_id = f"debug_{uuid.uuid4().hex[:8]}"
|
|
16
|
+
debug_plugin = DebugPlugin()
|
|
17
|
+
|
|
18
|
+
# Attach strictly for this run
|
|
19
|
+
PluginManager.register(debug_plugin)
|
|
20
|
+
try:
|
|
21
|
+
executor = SequentialExecutor(self.graph)
|
|
22
|
+
return executor.execute(input_data, run_id=run_id, session_id=session_id)
|
|
23
|
+
finally:
|
|
24
|
+
# Cleanup plugin so subsequent graph.run() goes silent
|
|
25
|
+
PluginManager.plugins.remove(debug_plugin)
|
|
26
|
+
|
|
27
|
+
def replay(self, run_id: str):
|
|
28
|
+
print(f"\nβͺ REPLAYING RUN: {run_id}")
|
|
29
|
+
print("="*50)
|
|
30
|
+
trace = StorageRegistry.get_trace(run_id)
|
|
31
|
+
|
|
32
|
+
for step in trace:
|
|
33
|
+
print(f"\nStep {step['step']}: Node '{step['node']}'")
|
|
34
|
+
print(f" Input: {step['input']}")
|
|
35
|
+
print(f" State: {step['state_snapshot']}")
|
|
36
|
+
print(f" Status: {step['status']}")
|
|
37
|
+
|
|
38
|
+
if step['status'] == 'error':
|
|
39
|
+
print(f" Error: {step['error']}")
|
|
40
|
+
else:
|
|
41
|
+
print(f" Output: {step['output']}")
|
|
42
|
+
print(f" Duration: {step['duration']:.4f}s")
|
|
43
|
+
time.sleep(0.5)
|
|
44
|
+
|
|
45
|
+
print("\n" + "="*50)
|
|
46
|
+
print("βΉοΈ REPLAY COMPLETE\n")
|
|
47
|
+
|
|
48
|
+
def step(self, input_data: Any):
|
|
49
|
+
"""Interactive stepping generator (v1 simple console input)."""
|
|
50
|
+
run_id = f"step_{uuid.uuid4().hex[:8]}"
|
|
51
|
+
executor = SequentialExecutor(self.graph)
|
|
52
|
+
|
|
53
|
+
print("\n" + "="*50)
|
|
54
|
+
print("π STEP DEBUGGER STARTED")
|
|
55
|
+
print("="*50)
|
|
56
|
+
|
|
57
|
+
# We hook into nodes via a custom plugin
|
|
58
|
+
class SteppingPlugin(DebugPlugin):
|
|
59
|
+
def on_node_start(self, run_id, node, input_data, state):
|
|
60
|
+
super().on_node_start(run_id, node, input_data, state)
|
|
61
|
+
input("\n[Press Enter to execute this node...]")
|
|
62
|
+
|
|
63
|
+
step_plugin = SteppingPlugin()
|
|
64
|
+
PluginManager.register(step_plugin)
|
|
65
|
+
try:
|
|
66
|
+
return executor.execute(input_data, run_id=run_id)
|
|
67
|
+
finally:
|
|
68
|
+
PluginManager.plugins.remove(step_plugin)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class GraphError(Exception):
|
|
2
|
+
"""Base exception for all graph-related errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class NodeExecutionError(GraphError):
|
|
6
|
+
"""Raised when a node fails to execute after all retries."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class InvalidGraphError(GraphError):
|
|
10
|
+
"""Raised when the graph structure is invalid."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class ReplayError(GraphError):
|
|
14
|
+
"""Raised when an execution trace cannot be replayed."""
|
|
15
|
+
pass
|