griptape-nodes 0.51.2__py3-none-any.whl → 0.52.1__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.
- griptape_nodes/__init__.py +5 -4
- griptape_nodes/app/api.py +22 -30
- griptape_nodes/app/app.py +374 -289
- griptape_nodes/app/watch.py +17 -2
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
- griptape_nodes/exe_types/core_types.py +16 -4
- griptape_nodes/exe_types/node_types.py +74 -16
- griptape_nodes/machines/control_flow.py +21 -26
- griptape_nodes/machines/fsm.py +16 -16
- griptape_nodes/machines/node_resolution.py +30 -119
- griptape_nodes/mcp_server/server.py +14 -10
- griptape_nodes/mcp_server/ws_request_manager.py +2 -2
- griptape_nodes/node_library/workflow_registry.py +5 -0
- griptape_nodes/retained_mode/events/base_events.py +12 -7
- griptape_nodes/retained_mode/events/execution_events.py +0 -6
- griptape_nodes/retained_mode/events/node_events.py +38 -0
- griptape_nodes/retained_mode/events/parameter_events.py +11 -0
- griptape_nodes/retained_mode/events/variable_events.py +361 -0
- griptape_nodes/retained_mode/events/workflow_events.py +35 -0
- griptape_nodes/retained_mode/griptape_nodes.py +61 -26
- griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
- griptape_nodes/retained_mode/managers/event_manager.py +215 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +20 -19
- griptape_nodes/retained_mode/managers/node_manager.py +83 -8
- griptape_nodes/retained_mode/managers/object_manager.py +4 -0
- griptape_nodes/retained_mode/managers/settings.py +1 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
- griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
- griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes/utils/__init__.py +4 -0
- griptape_nodes/utils/async_utils.py +89 -0
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/METADATA +2 -3
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/RECORD +45 -42
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/WHEEL +1 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/entry_points.txt +0 -0
|
@@ -5,8 +5,6 @@ import logging
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from griptape.events import EventBus
|
|
9
|
-
|
|
10
8
|
from griptape_nodes.exe_types.core_types import Parameter
|
|
11
9
|
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
12
10
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
@@ -18,6 +16,7 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
18
16
|
CurrentControlNodeEvent,
|
|
19
17
|
SelectedControlOutputEvent,
|
|
20
18
|
)
|
|
19
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@dataclass
|
|
@@ -54,8 +53,6 @@ class ControlFlowContext:
|
|
|
54
53
|
NextNodeInfo | None: Information about the next node or None if no connection
|
|
55
54
|
"""
|
|
56
55
|
if self.current_node is not None:
|
|
57
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
58
|
-
|
|
59
56
|
node_connection = (
|
|
60
57
|
GriptapeNodes.FlowManager().get_connections().get_connected_node(self.current_node, output_parameter)
|
|
61
58
|
)
|
|
@@ -81,7 +78,7 @@ class ControlFlowContext:
|
|
|
81
78
|
# GOOD!
|
|
82
79
|
class ResolveNodeState(State):
|
|
83
80
|
@staticmethod
|
|
84
|
-
def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
81
|
+
async def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
85
82
|
# The state machine has started, but it hasn't began to execute yet.
|
|
86
83
|
if context.current_node is None:
|
|
87
84
|
# We don't have anything else to do. Move back to Complete State so it has to restart.
|
|
@@ -95,7 +92,7 @@ class ResolveNodeState(State):
|
|
|
95
92
|
)
|
|
96
93
|
)
|
|
97
94
|
# Now broadcast that we have a current control node.
|
|
98
|
-
|
|
95
|
+
GriptapeNodes.EventManager().put_event(
|
|
99
96
|
ExecutionGriptapeNodeEvent(
|
|
100
97
|
wrapped_event=ExecutionEvent(payload=CurrentControlNodeEvent(node_name=context.current_node.name))
|
|
101
98
|
)
|
|
@@ -108,12 +105,12 @@ class ResolveNodeState(State):
|
|
|
108
105
|
|
|
109
106
|
# This is necessary to transition to the next step.
|
|
110
107
|
@staticmethod
|
|
111
|
-
def on_update(context: ControlFlowContext) -> type[State] | None:
|
|
108
|
+
async def on_update(context: ControlFlowContext) -> type[State] | None:
|
|
112
109
|
# If node has not already been resolved!
|
|
113
110
|
if context.current_node is None:
|
|
114
111
|
return CompleteState
|
|
115
112
|
if context.current_node.state != NodeResolutionState.RESOLVED:
|
|
116
|
-
context.resolution_machine.resolve_node(context.current_node)
|
|
113
|
+
await context.resolution_machine.resolve_node(context.current_node)
|
|
117
114
|
|
|
118
115
|
if context.resolution_machine.is_complete():
|
|
119
116
|
return NextNodeState
|
|
@@ -122,7 +119,7 @@ class ResolveNodeState(State):
|
|
|
122
119
|
|
|
123
120
|
class NextNodeState(State):
|
|
124
121
|
@staticmethod
|
|
125
|
-
def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
122
|
+
async def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
126
123
|
if context.current_node is None:
|
|
127
124
|
return CompleteState
|
|
128
125
|
# I did define this on the ControlNode.
|
|
@@ -136,7 +133,7 @@ class NextNodeState(State):
|
|
|
136
133
|
if next_output is not None:
|
|
137
134
|
context.selected_output = next_output
|
|
138
135
|
next_node_info = context.get_next_node(context.selected_output)
|
|
139
|
-
|
|
136
|
+
GriptapeNodes.EventManager().put_event(
|
|
140
137
|
ExecutionGriptapeNodeEvent(
|
|
141
138
|
wrapped_event=ExecutionEvent(
|
|
142
139
|
payload=SelectedControlOutputEvent(
|
|
@@ -147,8 +144,6 @@ class NextNodeState(State):
|
|
|
147
144
|
)
|
|
148
145
|
)
|
|
149
146
|
else:
|
|
150
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
151
|
-
|
|
152
147
|
# Get the next node in the execution queue, or None if queue is empty
|
|
153
148
|
next_node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
|
|
154
149
|
if next_node is not None:
|
|
@@ -169,15 +164,15 @@ class NextNodeState(State):
|
|
|
169
164
|
return None
|
|
170
165
|
|
|
171
166
|
@staticmethod
|
|
172
|
-
def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
|
|
167
|
+
async def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
|
|
173
168
|
return ResolveNodeState
|
|
174
169
|
|
|
175
170
|
|
|
176
171
|
class CompleteState(State):
|
|
177
172
|
@staticmethod
|
|
178
|
-
def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
173
|
+
async def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
179
174
|
if context.current_node is not None:
|
|
180
|
-
|
|
175
|
+
GriptapeNodes.EventManager().put_event(
|
|
181
176
|
ExecutionGriptapeNodeEvent(
|
|
182
177
|
wrapped_event=ExecutionEvent(
|
|
183
178
|
payload=ControlFlowResolvedEvent(
|
|
@@ -193,7 +188,7 @@ class CompleteState(State):
|
|
|
193
188
|
return None
|
|
194
189
|
|
|
195
190
|
@staticmethod
|
|
196
|
-
def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
|
|
191
|
+
async def on_update(context: ControlFlowContext) -> type[State] | None: # noqa: ARG004
|
|
197
192
|
return None
|
|
198
193
|
|
|
199
194
|
|
|
@@ -203,46 +198,46 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
|
203
198
|
context = ControlFlowContext()
|
|
204
199
|
super().__init__(context)
|
|
205
200
|
|
|
206
|
-
def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
|
|
201
|
+
async def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
|
|
207
202
|
self._context.current_node = start_node
|
|
208
203
|
# Set entry control parameter for initial node (None for workflow start)
|
|
209
204
|
start_node.set_entry_control_parameter(None)
|
|
210
205
|
# Set up to debug
|
|
211
206
|
self._context.paused = debug_mode
|
|
212
|
-
self.start(ResolveNodeState) # Begins the flow
|
|
207
|
+
await self.start(ResolveNodeState) # Begins the flow
|
|
213
208
|
|
|
214
|
-
def update(self) -> None:
|
|
209
|
+
async def update(self) -> None:
|
|
215
210
|
if self._current_state is None:
|
|
216
211
|
msg = "Attempted to run the next step of a workflow that was either already complete or has not started."
|
|
217
212
|
raise RuntimeError(msg)
|
|
218
|
-
super().update()
|
|
213
|
+
await super().update()
|
|
219
214
|
|
|
220
215
|
def change_debug_mode(self, debug_mode: bool) -> None: # noqa: FBT001
|
|
221
216
|
self._context.paused = debug_mode
|
|
222
217
|
self._context.resolution_machine.change_debug_mode(debug_mode)
|
|
223
218
|
|
|
224
|
-
def granular_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
|
|
219
|
+
async def granular_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
|
|
225
220
|
resolution_machine = self._context.resolution_machine
|
|
226
221
|
if change_debug_mode:
|
|
227
222
|
resolution_machine.change_debug_mode(True)
|
|
228
|
-
resolution_machine.update()
|
|
223
|
+
await resolution_machine.update()
|
|
229
224
|
|
|
230
225
|
# Tick the control flow if the resolution machine inside it isn't busy.
|
|
231
226
|
if resolution_machine.is_complete() or not resolution_machine.is_started(): # noqa: SIM102
|
|
232
227
|
# Don't tick ourselves if we are already complete.
|
|
233
228
|
if self._current_state is not None:
|
|
234
|
-
self.update()
|
|
229
|
+
await self.update()
|
|
235
230
|
|
|
236
|
-
def node_step(self) -> None:
|
|
231
|
+
async def node_step(self) -> None:
|
|
237
232
|
resolution_machine = self._context.resolution_machine
|
|
238
233
|
resolution_machine.change_debug_mode(False)
|
|
239
|
-
resolution_machine.update()
|
|
234
|
+
await resolution_machine.update()
|
|
240
235
|
|
|
241
236
|
# Tick the control flow if the resolution machine inside it isn't busy.
|
|
242
237
|
if resolution_machine.is_complete() or not resolution_machine.is_started(): # noqa: SIM102
|
|
243
238
|
# Don't tick ourselves if we are already complete.
|
|
244
239
|
if self._current_state is not None:
|
|
245
|
-
self.update()
|
|
240
|
+
await self.update()
|
|
246
241
|
|
|
247
242
|
def reset_machine(self) -> None:
|
|
248
243
|
self._context.reset()
|
griptape_nodes/machines/fsm.py
CHANGED
|
@@ -5,22 +5,22 @@ T = TypeVar("T")
|
|
|
5
5
|
|
|
6
6
|
class State:
|
|
7
7
|
@staticmethod
|
|
8
|
-
def on_enter(context: Any) -> type["State"] | None: # noqa: ARG004
|
|
8
|
+
async def on_enter(context: Any) -> type["State"] | None: # noqa: ARG004
|
|
9
9
|
"""Called when entering the state."""
|
|
10
10
|
return None
|
|
11
11
|
|
|
12
12
|
@staticmethod
|
|
13
|
-
def on_update(context: Any) -> type["State"] | None: # noqa: ARG004
|
|
13
|
+
async def on_update(context: Any) -> type["State"] | None: # noqa: ARG004
|
|
14
14
|
"""Called each update until a transition occurs."""
|
|
15
15
|
return None
|
|
16
16
|
|
|
17
17
|
@staticmethod
|
|
18
|
-
def on_exit(context: Any) -> None: # noqa: ARG004
|
|
18
|
+
async def on_exit(context: Any) -> None: # noqa: ARG004
|
|
19
19
|
"""Called when exiting the state."""
|
|
20
20
|
return
|
|
21
21
|
|
|
22
22
|
@staticmethod
|
|
23
|
-
def on_event(context: Any, event: Any) -> type["State"] | None: # noqa: ARG004
|
|
23
|
+
async def on_event(context: Any, event: Any) -> type["State"] | None: # noqa: ARG004
|
|
24
24
|
"""Called on an event, which may trigger a State transition."""
|
|
25
25
|
return None
|
|
26
26
|
|
|
@@ -30,40 +30,40 @@ class FSM[T]:
|
|
|
30
30
|
self._context = context
|
|
31
31
|
self._current_state = None
|
|
32
32
|
|
|
33
|
-
def start(self, initial_state: type[State]) -> None:
|
|
33
|
+
async def start(self, initial_state: type[State]) -> None:
|
|
34
34
|
# Enter the initial state.
|
|
35
|
-
self.transition_state(initial_state)
|
|
35
|
+
await self.transition_state(initial_state)
|
|
36
36
|
|
|
37
37
|
def get_current_state(self) -> type[State] | None:
|
|
38
38
|
return self._current_state
|
|
39
39
|
|
|
40
|
-
def transition_state(self, new_state: type[State] | None) -> None:
|
|
40
|
+
async def transition_state(self, new_state: type[State] | None) -> None:
|
|
41
41
|
while new_state is not None:
|
|
42
42
|
# Exit the current state.
|
|
43
43
|
if self._current_state is not None and new_state is self._current_state:
|
|
44
|
-
new_state = self._current_state.on_update(self._context)
|
|
44
|
+
new_state = await self._current_state.on_update(self._context)
|
|
45
45
|
continue
|
|
46
46
|
if self._current_state is not None:
|
|
47
|
-
self._current_state.on_exit(self._context)
|
|
47
|
+
await self._current_state.on_exit(self._context)
|
|
48
48
|
# Update current
|
|
49
49
|
self._current_state = new_state
|
|
50
50
|
# Enter the now-current state
|
|
51
|
-
new_state = self._current_state.on_enter(self._context)
|
|
51
|
+
new_state = await self._current_state.on_enter(self._context)
|
|
52
52
|
|
|
53
|
-
def update(self) -> None:
|
|
53
|
+
async def update(self) -> None:
|
|
54
54
|
if self._current_state is None:
|
|
55
55
|
new_state = None
|
|
56
56
|
else:
|
|
57
|
-
new_state = self._current_state.on_update(self._context)
|
|
57
|
+
new_state = await self._current_state.on_update(self._context)
|
|
58
58
|
|
|
59
59
|
if new_state is not None:
|
|
60
|
-
self.transition_state(new_state)
|
|
60
|
+
await self.transition_state(new_state)
|
|
61
61
|
|
|
62
|
-
def handle_event(self, event: Any) -> None:
|
|
62
|
+
async def handle_event(self, event: Any) -> None:
|
|
63
63
|
if self._current_state is None:
|
|
64
64
|
new_state = None
|
|
65
65
|
else:
|
|
66
|
-
new_state = self._current_state.on_event(self._context, event)
|
|
66
|
+
new_state = await self._current_state.on_event(self._context, event)
|
|
67
67
|
|
|
68
68
|
if new_state is not None:
|
|
69
|
-
self.transition_state(new_state)
|
|
69
|
+
await self.transition_state(new_state)
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from collections.abc import Generator
|
|
5
|
-
from concurrent.futures import Future, ThreadPoolExecutor
|
|
6
4
|
from dataclasses import dataclass
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from griptape.events import EventBus
|
|
10
|
-
from griptape.utils import with_contextvars
|
|
11
5
|
|
|
12
6
|
from griptape_nodes.exe_types.core_types import ParameterType, ParameterTypeBuiltin
|
|
13
7
|
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
@@ -25,11 +19,11 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
25
19
|
NodeStartProcessEvent,
|
|
26
20
|
ParameterSpotlightEvent,
|
|
27
21
|
ParameterValueUpdateEvent,
|
|
28
|
-
ResumeNodeProcessingEvent,
|
|
29
22
|
)
|
|
30
23
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
31
24
|
SetParameterValueRequest,
|
|
32
25
|
)
|
|
26
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
33
27
|
|
|
34
28
|
logger = logging.getLogger("griptape_nodes")
|
|
35
29
|
|
|
@@ -37,8 +31,6 @@ logger = logging.getLogger("griptape_nodes")
|
|
|
37
31
|
@dataclass
|
|
38
32
|
class Focus:
|
|
39
33
|
node: BaseNode
|
|
40
|
-
scheduled_value: Any | None = None
|
|
41
|
-
process_generator: Generator | None = None
|
|
42
34
|
|
|
43
35
|
|
|
44
36
|
# This is on a per-node basis
|
|
@@ -55,18 +47,16 @@ class ResolutionContext:
|
|
|
55
47
|
node = self.focus_stack[-1].node
|
|
56
48
|
# clear the data node being resolved.
|
|
57
49
|
node.clear_node()
|
|
58
|
-
self.focus_stack[-1].process_generator = None
|
|
59
|
-
self.focus_stack[-1].scheduled_value = None
|
|
60
50
|
self.focus_stack.clear()
|
|
61
51
|
self.paused = False
|
|
62
52
|
|
|
63
53
|
|
|
64
54
|
class InitializeSpotlightState(State):
|
|
65
55
|
@staticmethod
|
|
66
|
-
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
56
|
+
async def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
67
57
|
# If the focus stack is empty
|
|
68
58
|
current_node = context.focus_stack[-1].node
|
|
69
|
-
|
|
59
|
+
GriptapeNodes.EventManager().put_event(
|
|
70
60
|
ExecutionGriptapeNodeEvent(
|
|
71
61
|
wrapped_event=ExecutionEvent(payload=CurrentDataNodeEvent(node_name=current_node.name))
|
|
72
62
|
)
|
|
@@ -76,7 +66,7 @@ class InitializeSpotlightState(State):
|
|
|
76
66
|
return None
|
|
77
67
|
|
|
78
68
|
@staticmethod
|
|
79
|
-
def on_update(context: ResolutionContext) -> type[State] | None:
|
|
69
|
+
async def on_update(context: ResolutionContext) -> type[State] | None:
|
|
80
70
|
# If the focus stack is empty
|
|
81
71
|
if not len(context.focus_stack):
|
|
82
72
|
return CompleteState
|
|
@@ -104,13 +94,13 @@ class InitializeSpotlightState(State):
|
|
|
104
94
|
|
|
105
95
|
class EvaluateParameterState(State):
|
|
106
96
|
@staticmethod
|
|
107
|
-
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
97
|
+
async def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
108
98
|
current_node = context.focus_stack[-1].node
|
|
109
99
|
current_parameter = current_node.get_current_parameter()
|
|
110
100
|
if current_parameter is None:
|
|
111
101
|
return ExecuteNodeState
|
|
112
102
|
# if not in debug mode - keep going!
|
|
113
|
-
|
|
103
|
+
GriptapeNodes.EventManager().put_event(
|
|
114
104
|
ExecutionGriptapeNodeEvent(
|
|
115
105
|
wrapped_event=ExecutionEvent(
|
|
116
106
|
payload=ParameterSpotlightEvent(
|
|
@@ -125,7 +115,7 @@ class EvaluateParameterState(State):
|
|
|
125
115
|
return None
|
|
126
116
|
|
|
127
117
|
@staticmethod
|
|
128
|
-
def on_update(context: ResolutionContext) -> type[State] | None:
|
|
118
|
+
async def on_update(context: ResolutionContext) -> type[State] | None:
|
|
129
119
|
current_node = context.focus_stack[-1].node
|
|
130
120
|
current_parameter = current_node.get_current_parameter()
|
|
131
121
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
@@ -153,11 +143,9 @@ class EvaluateParameterState(State):
|
|
|
153
143
|
|
|
154
144
|
|
|
155
145
|
class ExecuteNodeState(State):
|
|
156
|
-
executor: ThreadPoolExecutor = ThreadPoolExecutor()
|
|
157
|
-
|
|
158
146
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/864
|
|
159
147
|
@staticmethod
|
|
160
|
-
def clear_parameter_output_values(context: ResolutionContext) -> None:
|
|
148
|
+
async def clear_parameter_output_values(context: ResolutionContext) -> None:
|
|
161
149
|
"""Clears all parameter output values for the currently focused node in the resolution context.
|
|
162
150
|
|
|
163
151
|
This method iterates through each parameter output value stored in the current node,
|
|
@@ -192,11 +180,13 @@ class ExecuteNodeState(State):
|
|
|
192
180
|
data_type=parameter_type,
|
|
193
181
|
value=None,
|
|
194
182
|
)
|
|
195
|
-
|
|
183
|
+
GriptapeNodes.EventManager().put_event(
|
|
184
|
+
ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=payload))
|
|
185
|
+
)
|
|
196
186
|
current_node.parameter_output_values.clear()
|
|
197
187
|
|
|
198
188
|
@staticmethod
|
|
199
|
-
def collect_values_from_upstream_nodes(context: ResolutionContext) -> None:
|
|
189
|
+
async def collect_values_from_upstream_nodes(context: ResolutionContext) -> None:
|
|
200
190
|
"""Collect output values from resolved upstream nodes and pass them to the current node.
|
|
201
191
|
|
|
202
192
|
This method iterates through all input parameters of the current node, finds their
|
|
@@ -240,19 +230,21 @@ class ExecuteNodeState(State):
|
|
|
240
230
|
node_name=current_node.name,
|
|
241
231
|
value=output_value,
|
|
242
232
|
data_type=upstream_parameter.output_type,
|
|
233
|
+
incoming_connection_source_node_name=upstream_node.name,
|
|
234
|
+
incoming_connection_source_parameter_name=upstream_parameter.name,
|
|
243
235
|
)
|
|
244
236
|
)
|
|
245
237
|
|
|
246
238
|
@staticmethod
|
|
247
|
-
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
239
|
+
async def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
248
240
|
current_node = context.focus_stack[-1].node
|
|
249
241
|
|
|
250
242
|
# Clear all of the current output values
|
|
251
243
|
# if node is locked, don't clear anything. skip all of this.
|
|
252
244
|
if current_node.lock:
|
|
253
245
|
return ExecuteNodeState
|
|
254
|
-
ExecuteNodeState.collect_values_from_upstream_nodes(context)
|
|
255
|
-
ExecuteNodeState.clear_parameter_output_values(context)
|
|
246
|
+
await ExecuteNodeState.collect_values_from_upstream_nodes(context)
|
|
247
|
+
await ExecuteNodeState.clear_parameter_output_values(context)
|
|
256
248
|
for parameter in current_node.parameters:
|
|
257
249
|
if ParameterTypeBuiltin.CONTROL_TYPE.value.lower() == parameter.output_type:
|
|
258
250
|
continue
|
|
@@ -267,7 +259,7 @@ class ExecuteNodeState(State):
|
|
|
267
259
|
data_type = parameter.type
|
|
268
260
|
if data_type is None:
|
|
269
261
|
data_type = ParameterTypeBuiltin.NONE.value
|
|
270
|
-
|
|
262
|
+
GriptapeNodes.EventManager().put_event(
|
|
271
263
|
ExecutionGriptapeNodeEvent(
|
|
272
264
|
wrapped_event=ExecutionEvent(
|
|
273
265
|
payload=ParameterValueUpdateEvent(
|
|
@@ -291,7 +283,7 @@ class ExecuteNodeState(State):
|
|
|
291
283
|
return None
|
|
292
284
|
|
|
293
285
|
@staticmethod
|
|
294
|
-
def on_update(context: ResolutionContext) -> type[State] | None:
|
|
286
|
+
async def on_update(context: ResolutionContext) -> type[State] | None:
|
|
295
287
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
296
288
|
|
|
297
289
|
# Once everything has been set
|
|
@@ -300,18 +292,16 @@ class ExecuteNodeState(State):
|
|
|
300
292
|
# If the node is not locked, execute all of this.
|
|
301
293
|
if not current_node.lock:
|
|
302
294
|
# To set the event manager without circular import errors
|
|
303
|
-
|
|
295
|
+
GriptapeNodes.EventManager().put_event(
|
|
304
296
|
ExecutionGriptapeNodeEvent(
|
|
305
297
|
wrapped_event=ExecutionEvent(payload=NodeStartProcessEvent(node_name=current_node.name))
|
|
306
298
|
)
|
|
307
299
|
)
|
|
308
300
|
logger.info("Node '%s' is processing.", current_node.name)
|
|
301
|
+
current_node = current_focus.node
|
|
309
302
|
|
|
310
303
|
try:
|
|
311
|
-
|
|
312
|
-
if work_is_scheduled:
|
|
313
|
-
logger.debug("Pausing Node '%s' to run background work", current_node.name)
|
|
314
|
-
return None
|
|
304
|
+
await current_node.aprocess()
|
|
315
305
|
except Exception as e:
|
|
316
306
|
logger.exception("Error processing node '%s", current_node.name)
|
|
317
307
|
msg = f"Canceling flow run. Node '{current_node.name}' encountered a problem: {e}"
|
|
@@ -321,14 +311,12 @@ class ExecuteNodeState(State):
|
|
|
321
311
|
{NodeResolutionState.UNRESOLVED, NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
|
|
322
312
|
)
|
|
323
313
|
)
|
|
324
|
-
current_focus.process_generator = None
|
|
325
|
-
current_focus.scheduled_value = None
|
|
326
314
|
|
|
327
315
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
328
316
|
|
|
329
317
|
GriptapeNodes.FlowManager().cancel_flow_run()
|
|
330
318
|
|
|
331
|
-
|
|
319
|
+
GriptapeNodes.EventManager().put_event(
|
|
332
320
|
ExecutionGriptapeNodeEvent(
|
|
333
321
|
wrapped_event=ExecutionEvent(payload=NodeFinishProcessEvent(node_name=current_node.name))
|
|
334
322
|
)
|
|
@@ -337,7 +325,7 @@ class ExecuteNodeState(State):
|
|
|
337
325
|
|
|
338
326
|
logger.info("Node '%s' finished processing.", current_node.name)
|
|
339
327
|
|
|
340
|
-
|
|
328
|
+
GriptapeNodes.EventManager().put_event(
|
|
341
329
|
ExecutionGriptapeNodeEvent(
|
|
342
330
|
wrapped_event=ExecutionEvent(payload=NodeFinishProcessEvent(node_name=current_node.name))
|
|
343
331
|
)
|
|
@@ -363,7 +351,7 @@ class ExecuteNodeState(State):
|
|
|
363
351
|
data_type = parameter.type
|
|
364
352
|
if data_type is None:
|
|
365
353
|
data_type = ParameterTypeBuiltin.NONE.value
|
|
366
|
-
|
|
354
|
+
GriptapeNodes.EventManager().put_event(
|
|
367
355
|
ExecutionGriptapeNodeEvent(
|
|
368
356
|
wrapped_event=ExecutionEvent(
|
|
369
357
|
payload=ParameterValueUpdateEvent(
|
|
@@ -381,7 +369,7 @@ class ExecuteNodeState(State):
|
|
|
381
369
|
library_name = library[0]
|
|
382
370
|
else:
|
|
383
371
|
library_name = None
|
|
384
|
-
|
|
372
|
+
GriptapeNodes.EventManager().put_event(
|
|
385
373
|
ExecutionGriptapeNodeEvent(
|
|
386
374
|
wrapped_event=ExecutionEvent(
|
|
387
375
|
payload=NodeResolvedEvent(
|
|
@@ -399,91 +387,14 @@ class ExecuteNodeState(State):
|
|
|
399
387
|
|
|
400
388
|
return CompleteState
|
|
401
389
|
|
|
402
|
-
@staticmethod
|
|
403
|
-
def _process_node(current_focus: Focus) -> bool:
|
|
404
|
-
"""Run the process method of the node.
|
|
405
|
-
|
|
406
|
-
If the node's process method returns a generator, take the next value from the generator (a callable) and run
|
|
407
|
-
that in a thread pool executor. The result of that callable will be passed to the generator when it is resumed.
|
|
408
|
-
|
|
409
|
-
This has the effect of pausing at a yield expression, running the expression in a thread, and resuming when the thread pool is done.
|
|
410
|
-
|
|
411
|
-
Args:
|
|
412
|
-
current_focus (Focus): The current focus.
|
|
413
|
-
|
|
414
|
-
Returns:
|
|
415
|
-
bool: True if work has been scheduled, False if the node is done processing.
|
|
416
|
-
"""
|
|
417
|
-
|
|
418
|
-
def on_future_done(future: Future) -> None:
|
|
419
|
-
"""Called when the future is done.
|
|
420
|
-
|
|
421
|
-
Stores the result of the future in the node's context, and publishes an event to resume the flow.
|
|
422
|
-
"""
|
|
423
|
-
try:
|
|
424
|
-
current_focus.scheduled_value = future.result()
|
|
425
|
-
except Exception as e:
|
|
426
|
-
logger.debug("Error in future: %s", e)
|
|
427
|
-
current_focus.scheduled_value = e
|
|
428
|
-
finally:
|
|
429
|
-
# If it hasn't been cancelled.
|
|
430
|
-
if current_focus.process_generator:
|
|
431
|
-
EventBus.publish_event(
|
|
432
|
-
ExecutionGriptapeNodeEvent(
|
|
433
|
-
wrapped_event=ExecutionEvent(payload=ResumeNodeProcessingEvent(node_name=current_node.name))
|
|
434
|
-
)
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
current_node = current_focus.node
|
|
438
|
-
# Only start the processing if we don't already have a generator
|
|
439
|
-
logger.debug("Node '%s' process generator: %s", current_node.name, current_focus.process_generator)
|
|
440
|
-
if current_focus.process_generator is None:
|
|
441
|
-
result = current_node.process()
|
|
442
|
-
|
|
443
|
-
# If the process returned a generator, we need to store it for later
|
|
444
|
-
if isinstance(result, Generator):
|
|
445
|
-
current_focus.process_generator = result
|
|
446
|
-
logger.debug("Node '%s' returned a generator.", current_node.name)
|
|
447
|
-
|
|
448
|
-
# We now have a generator, so we need to run it
|
|
449
|
-
if current_focus.process_generator is not None:
|
|
450
|
-
try:
|
|
451
|
-
logger.debug(
|
|
452
|
-
"Node '%s' has an active generator, sending scheduled value of type: %s",
|
|
453
|
-
current_node.name,
|
|
454
|
-
type(current_focus.scheduled_value),
|
|
455
|
-
)
|
|
456
|
-
if isinstance(current_focus.scheduled_value, Exception):
|
|
457
|
-
func = current_focus.process_generator.throw(current_focus.scheduled_value)
|
|
458
|
-
else:
|
|
459
|
-
func = current_focus.process_generator.send(current_focus.scheduled_value)
|
|
460
|
-
|
|
461
|
-
# Once we've passed on the scheduled value, we should clear it out just in case
|
|
462
|
-
current_focus.scheduled_value = None
|
|
463
|
-
|
|
464
|
-
future = ExecuteNodeState.executor.submit(with_contextvars(func))
|
|
465
|
-
future.add_done_callback(with_contextvars(on_future_done))
|
|
466
|
-
except StopIteration:
|
|
467
|
-
logger.debug("Node '%s' generator is done.", current_node.name)
|
|
468
|
-
# If that was the last generator, clear out the generator and indicate that there is no more work scheduled
|
|
469
|
-
current_focus.process_generator = None
|
|
470
|
-
current_focus.scheduled_value = None
|
|
471
|
-
return False
|
|
472
|
-
else:
|
|
473
|
-
# If the generator is not done, indicate that there is work scheduled
|
|
474
|
-
logger.debug("Node '%s' generator is not done.", current_node.name)
|
|
475
|
-
return True
|
|
476
|
-
logger.debug("Node '%s' did not return a generator.", current_node.name)
|
|
477
|
-
return False
|
|
478
|
-
|
|
479
390
|
|
|
480
391
|
class CompleteState(State):
|
|
481
392
|
@staticmethod
|
|
482
|
-
def on_enter(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
|
|
393
|
+
async def on_enter(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
|
|
483
394
|
return None
|
|
484
395
|
|
|
485
396
|
@staticmethod
|
|
486
|
-
def on_update(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
|
|
397
|
+
async def on_update(context: ResolutionContext) -> type[State] | None: # noqa: ARG004
|
|
487
398
|
return None
|
|
488
399
|
|
|
489
400
|
|
|
@@ -494,9 +405,9 @@ class NodeResolutionMachine(FSM[ResolutionContext]):
|
|
|
494
405
|
resolution_context = ResolutionContext()
|
|
495
406
|
super().__init__(resolution_context)
|
|
496
407
|
|
|
497
|
-
def resolve_node(self, node: BaseNode) -> None:
|
|
408
|
+
async def resolve_node(self, node: BaseNode) -> None:
|
|
498
409
|
self._context.focus_stack.append(Focus(node=node))
|
|
499
|
-
self.start(InitializeSpotlightState)
|
|
410
|
+
await self.start(InitializeSpotlightState)
|
|
500
411
|
|
|
501
412
|
def change_debug_mode(self, debug_mode: bool) -> None: # noqa: FBT001
|
|
502
413
|
self._context.paused = debug_mode
|
|
@@ -69,8 +69,8 @@ mcp_server_logger.addHandler(RichHandler(show_time=True, show_path=False, markup
|
|
|
69
69
|
mcp_server_logger.setLevel(logging.INFO)
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def
|
|
73
|
-
"""
|
|
72
|
+
def start_mcp_server(api_key: str) -> None:
|
|
73
|
+
"""Synchronous version of main entry point for the Griptape Nodes MCP server."""
|
|
74
74
|
mcp_server_logger.debug("Starting MCP GTN server...")
|
|
75
75
|
# Give these a session ID
|
|
76
76
|
connection_manager = WebSocketConnectionManager()
|
|
@@ -116,7 +116,6 @@ def main(api_key: str) -> None:
|
|
|
116
116
|
finally:
|
|
117
117
|
mcp_server_logger.debug("GTN MCP server shutting down...")
|
|
118
118
|
|
|
119
|
-
# Create an ASGI application using the transport
|
|
120
119
|
mcp_server_app = FastAPI(lifespan=lifespan)
|
|
121
120
|
|
|
122
121
|
# ASGI handler for streamable HTTP connections
|
|
@@ -125,10 +124,15 @@ def main(api_key: str) -> None:
|
|
|
125
124
|
|
|
126
125
|
mcp_server_app.mount("/mcp", app=handle_streamable_http)
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
try:
|
|
128
|
+
# Run server using uvicorn.run
|
|
129
|
+
uvicorn.run(
|
|
130
|
+
mcp_server_app,
|
|
131
|
+
host=GTN_MCP_SERVER_HOST,
|
|
132
|
+
port=GTN_MCP_SERVER_PORT,
|
|
133
|
+
log_config=None,
|
|
134
|
+
log_level=GTN_MCP_SERVER_LOG_LEVEL,
|
|
135
|
+
)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
mcp_server_logger.error("MCP server failed: %s", e)
|
|
138
|
+
raise
|
|
@@ -190,12 +190,12 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
|
190
190
|
|
|
191
191
|
async def create_event(self, request_type: str, payload: dict[str, Any]) -> None:
|
|
192
192
|
"""Send an event to the API without waiting for a response."""
|
|
193
|
-
from griptape_nodes.app.app import
|
|
193
|
+
from griptape_nodes.app.app import determine_request_topic
|
|
194
194
|
|
|
195
195
|
logger.debug("Creating Event: %s - %s", request_type, json.dumps(payload))
|
|
196
196
|
|
|
197
197
|
data = {"event_type": "EventRequest", "request_type": request_type, "request": payload}
|
|
198
|
-
topic =
|
|
198
|
+
topic = determine_request_topic()
|
|
199
199
|
|
|
200
200
|
request_data = {"payload": data, "type": data["event_type"], "topic": topic}
|
|
201
201
|
|
|
@@ -69,6 +69,11 @@ class WorkflowRegistry(metaclass=SingletonMeta):
|
|
|
69
69
|
def get_complete_file_path(cls, relative_file_path: str) -> str:
|
|
70
70
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
71
71
|
|
|
72
|
+
# If the path is already absolute, return it as-is
|
|
73
|
+
if Path(relative_file_path).is_absolute():
|
|
74
|
+
return relative_file_path
|
|
75
|
+
|
|
76
|
+
# Otherwise, resolve it relative to the workspace
|
|
72
77
|
config_mgr = GriptapeNodes.ConfigManager()
|
|
73
78
|
workspace_path = config_mgr.workspace_path
|
|
74
79
|
complete_file_path = workspace_path / relative_file_path
|