griptape-nodes 0.46.0__py3-none-any.whl → 0.48.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.
- griptape_nodes/app/app.py +1 -1
- griptape_nodes/exe_types/core_types.py +129 -10
- griptape_nodes/exe_types/node_types.py +9 -3
- griptape_nodes/machines/node_resolution.py +10 -8
- griptape_nodes/mcp_server/ws_request_manager.py +6 -6
- griptape_nodes/retained_mode/events/base_events.py +74 -1
- griptape_nodes/retained_mode/events/secrets_events.py +2 -0
- griptape_nodes/retained_mode/griptape_nodes.py +17 -13
- griptape_nodes/retained_mode/managers/agent_manager.py +8 -6
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +1 -1
- griptape_nodes/retained_mode/managers/config_manager.py +36 -45
- griptape_nodes/retained_mode/managers/flow_manager.py +98 -98
- griptape_nodes/retained_mode/managers/library_manager.py +57 -57
- griptape_nodes/retained_mode/managers/node_manager.py +121 -124
- griptape_nodes/retained_mode/managers/object_manager.py +9 -10
- griptape_nodes/retained_mode/managers/os_manager.py +31 -31
- griptape_nodes/retained_mode/managers/secrets_manager.py +5 -5
- griptape_nodes/retained_mode/managers/static_files_manager.py +19 -21
- griptape_nodes/retained_mode/managers/sync_manager.py +3 -2
- griptape_nodes/retained_mode/managers/workflow_manager.py +153 -174
- griptape_nodes/retained_mode/retained_mode.py +25 -47
- {griptape_nodes-0.46.0.dist-info → griptape_nodes-0.48.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.46.0.dist-info → griptape_nodes-0.48.0.dist-info}/RECORD +25 -25
- {griptape_nodes-0.46.0.dist-info → griptape_nodes-0.48.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.46.0.dist-info → griptape_nodes-0.48.0.dist-info}/entry_points.txt +0 -0
griptape_nodes/app/app.py
CHANGED
|
@@ -118,7 +118,7 @@ def _ensure_api_key() -> str:
|
|
|
118
118
|
"[code]gtn init --api-key <your key>[/code]\n"
|
|
119
119
|
"[bold red]You can generate a new key from [/bold red][bold blue][link=https://nodes.griptape.ai]https://nodes.griptape.ai[/link][/bold blue]",
|
|
120
120
|
),
|
|
121
|
-
title="
|
|
121
|
+
title="[red]X[/red] Missing Nodes API Key",
|
|
122
122
|
border_style="red",
|
|
123
123
|
padding=(1, 4),
|
|
124
124
|
)
|
|
@@ -65,25 +65,52 @@ class ParameterType:
|
|
|
65
65
|
return ret_val
|
|
66
66
|
|
|
67
67
|
@staticmethod
|
|
68
|
-
def
|
|
68
|
+
def _extract_base_type(type_str: str) -> str:
|
|
69
|
+
"""Extract the base type from a potentially generic type string.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
'list[any]' -> 'list'
|
|
73
|
+
'dict[str, int]' -> 'dict'
|
|
74
|
+
'str' -> 'str'
|
|
75
|
+
"""
|
|
76
|
+
bracket_index = type_str.find("[")
|
|
77
|
+
if bracket_index == -1:
|
|
78
|
+
return type_str
|
|
79
|
+
return type_str[:bracket_index]
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def are_types_compatible(source_type: str | None, target_type: str | None) -> bool: # noqa: PLR0911
|
|
69
83
|
if source_type is None or target_type is None:
|
|
70
84
|
return False
|
|
71
85
|
|
|
72
|
-
ret_val = False
|
|
73
86
|
source_type_lower = source_type.lower()
|
|
74
87
|
target_type_lower = target_type.lower()
|
|
75
88
|
|
|
76
89
|
# If either are None, bail.
|
|
77
90
|
if ParameterTypeBuiltin.NONE.value in (source_type_lower, target_type_lower):
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
return False
|
|
92
|
+
if target_type_lower == ParameterTypeBuiltin.ANY.value:
|
|
80
93
|
# If the TARGET accepts Any, we're good. Not always true the other way 'round.
|
|
81
|
-
|
|
82
|
-
else:
|
|
83
|
-
# Do a compare.
|
|
84
|
-
ret_val = source_type_lower == target_type_lower
|
|
94
|
+
return True
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
# First try exact match
|
|
97
|
+
if source_type_lower == target_type_lower:
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
source_base = ParameterType._extract_base_type(source_type_lower)
|
|
101
|
+
target_base = ParameterType._extract_base_type(target_type_lower)
|
|
102
|
+
|
|
103
|
+
# If base types match
|
|
104
|
+
if source_base == target_base:
|
|
105
|
+
# Allow any generic to flow to base type (list[any] -> list, list[str] -> list)
|
|
106
|
+
if target_type_lower == target_base:
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
# Allow specific types to flow to [any] generic (list[str] -> list[any])
|
|
110
|
+
if target_type_lower == f"{target_base}[{ParameterTypeBuiltin.ANY.value}]":
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
return False
|
|
87
114
|
|
|
88
115
|
@staticmethod
|
|
89
116
|
def parse_kv_type_pair(type_str: str) -> KeyValueTypePair | None: # noqa: C901
|
|
@@ -250,7 +277,6 @@ class BaseNodeElement:
|
|
|
250
277
|
self._changes["ui_options"] = complete_dict["ui_options"]
|
|
251
278
|
|
|
252
279
|
event_data.update(self._changes)
|
|
253
|
-
|
|
254
280
|
# Publish the event
|
|
255
281
|
event = ExecutionGriptapeNodeEvent(
|
|
256
282
|
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
@@ -1326,6 +1352,27 @@ class ParameterList(ParameterContainer):
|
|
|
1326
1352
|
result = f"list[{base_type}]"
|
|
1327
1353
|
return result
|
|
1328
1354
|
|
|
1355
|
+
def _custom_setter_for_property_type(self, value: str | None) -> None:
|
|
1356
|
+
# If we are setting a type, we need to propagate this to our children as well.
|
|
1357
|
+
for child in self._children:
|
|
1358
|
+
if isinstance(child, Parameter):
|
|
1359
|
+
child.type = value
|
|
1360
|
+
super()._custom_setter_for_property_type(value)
|
|
1361
|
+
|
|
1362
|
+
def _custom_setter_for_property_input_types(self, value: list[str] | None) -> None:
|
|
1363
|
+
# If we are setting a type, we need to propagate this to our children as well.
|
|
1364
|
+
for child in self._children:
|
|
1365
|
+
if isinstance(child, Parameter):
|
|
1366
|
+
child.input_types = value
|
|
1367
|
+
return super()._custom_setter_for_property_input_types(value)
|
|
1368
|
+
|
|
1369
|
+
def _custom_setter_for_property_output_type(self, value: str | None) -> None:
|
|
1370
|
+
# If we are setting a type, we need to propagate this to our children as well.
|
|
1371
|
+
for child in self._children:
|
|
1372
|
+
if isinstance(child, Parameter):
|
|
1373
|
+
child.output_type = value
|
|
1374
|
+
return super()._custom_setter_for_property_output_type(value)
|
|
1375
|
+
|
|
1329
1376
|
def _custom_getter_for_property_input_types(self) -> list[str]:
|
|
1330
1377
|
# For every valid input type, also accept a list variant of that for the CONTAINER Parameter only.
|
|
1331
1378
|
# Children still use the input types given to them.
|
|
@@ -1395,6 +1442,78 @@ class ParameterList(ParameterContainer):
|
|
|
1395
1442
|
|
|
1396
1443
|
return param
|
|
1397
1444
|
|
|
1445
|
+
def clear_list(self) -> None:
|
|
1446
|
+
"""Remove all children that have been added to the list."""
|
|
1447
|
+
children = self.find_elements_by_type(element_type=Parameter)
|
|
1448
|
+
for child in children:
|
|
1449
|
+
if isinstance(child, Parameter):
|
|
1450
|
+
self.remove_child(child)
|
|
1451
|
+
del child
|
|
1452
|
+
|
|
1453
|
+
# --- Convenience methods for stable list management ---
|
|
1454
|
+
def get_child_parameters(self) -> list[Parameter]:
|
|
1455
|
+
"""Return direct child parameters only, in order of appearance."""
|
|
1456
|
+
return self.find_elements_by_type(element_type=Parameter, find_recursively=False)
|
|
1457
|
+
|
|
1458
|
+
def append_child_parameter(self, display_name: str | None = None) -> Parameter:
|
|
1459
|
+
"""Append one child parameter and optionally set a display name.
|
|
1460
|
+
|
|
1461
|
+
This preserves existing children and adds a new one at the end.
|
|
1462
|
+
"""
|
|
1463
|
+
child = self.add_child_parameter()
|
|
1464
|
+
if display_name is not None:
|
|
1465
|
+
ui_opts = child.ui_options or {}
|
|
1466
|
+
ui_opts["display_name"] = display_name
|
|
1467
|
+
child.ui_options = ui_opts
|
|
1468
|
+
return child
|
|
1469
|
+
|
|
1470
|
+
def remove_last_child_parameter(self) -> None:
|
|
1471
|
+
"""Remove the last child parameter if one exists.
|
|
1472
|
+
|
|
1473
|
+
This removes from the end to preserve earlier children and their connections.
|
|
1474
|
+
"""
|
|
1475
|
+
children = self.get_child_parameters()
|
|
1476
|
+
if children:
|
|
1477
|
+
last = children[-1]
|
|
1478
|
+
self.remove_child(last)
|
|
1479
|
+
del last
|
|
1480
|
+
|
|
1481
|
+
def ensure_length(self, desired_count: int, display_name_prefix: str | None = None) -> None:
|
|
1482
|
+
"""Grow or shrink the list to the desired length while preserving existing items.
|
|
1483
|
+
|
|
1484
|
+
- If increasing, appends new children to the end.
|
|
1485
|
+
- If decreasing, removes children from the end.
|
|
1486
|
+
- Optionally sets display names like "{prefix} 1", "{prefix} 2", ...
|
|
1487
|
+
"""
|
|
1488
|
+
if desired_count is None:
|
|
1489
|
+
return
|
|
1490
|
+
try:
|
|
1491
|
+
desired_count = int(desired_count)
|
|
1492
|
+
except Exception:
|
|
1493
|
+
desired_count = 0
|
|
1494
|
+
desired_count = max(desired_count, 0)
|
|
1495
|
+
|
|
1496
|
+
current_children = self.get_child_parameters()
|
|
1497
|
+
current_len = len(current_children)
|
|
1498
|
+
|
|
1499
|
+
# Grow
|
|
1500
|
+
if current_len < desired_count:
|
|
1501
|
+
for index in range(current_len, desired_count):
|
|
1502
|
+
name = f"{display_name_prefix} {index + 1}" if display_name_prefix else None
|
|
1503
|
+
self.append_child_parameter(display_name=name)
|
|
1504
|
+
|
|
1505
|
+
# Shrink
|
|
1506
|
+
elif current_len > desired_count:
|
|
1507
|
+
for _ in range(current_len - desired_count):
|
|
1508
|
+
self.remove_last_child_parameter()
|
|
1509
|
+
|
|
1510
|
+
# Optionally re-apply display names to existing children to keep indices tidy
|
|
1511
|
+
if display_name_prefix:
|
|
1512
|
+
for index, child in enumerate(self.get_child_parameters()):
|
|
1513
|
+
ui_opts = child.ui_options or {}
|
|
1514
|
+
ui_opts["display_name"] = f"{display_name_prefix} {index + 1}"
|
|
1515
|
+
child.ui_options = ui_opts
|
|
1516
|
+
|
|
1398
1517
|
def add_child(self, child: BaseNodeElement) -> None:
|
|
1399
1518
|
"""Override to mark parent node as unresolved when children are added.
|
|
1400
1519
|
|
|
@@ -848,12 +848,12 @@ class BaseNode(ABC):
|
|
|
848
848
|
|
|
849
849
|
# Create event data using the parameter's to_event method
|
|
850
850
|
if remove:
|
|
851
|
+
# Import logger here to avoid circular dependency
|
|
851
852
|
event = ExecutionGriptapeNodeEvent(
|
|
852
853
|
wrapped_event=ExecutionEvent(payload=RemoveElementEvent(element_id=parameter.element_id))
|
|
853
854
|
)
|
|
854
855
|
else:
|
|
855
856
|
event_data = parameter.to_event(self)
|
|
856
|
-
|
|
857
857
|
# Publish the event
|
|
858
858
|
event = ExecutionGriptapeNodeEvent(
|
|
859
859
|
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
@@ -1053,11 +1053,17 @@ class EndNode(BaseNode):
|
|
|
1053
1053
|
|
|
1054
1054
|
|
|
1055
1055
|
class StartLoopNode(BaseNode):
|
|
1056
|
-
finished: bool
|
|
1057
|
-
current_index: int
|
|
1058
1056
|
end_node: EndLoopNode | None = None
|
|
1059
1057
|
"""Creating class for Start Loop Node in order to implement loop functionality in execution."""
|
|
1060
1058
|
|
|
1059
|
+
@abstractmethod
|
|
1060
|
+
def is_loop_finished(self) -> bool:
|
|
1061
|
+
"""Return True if the loop has finished executing.
|
|
1062
|
+
|
|
1063
|
+
This method must be implemented by subclasses to define when
|
|
1064
|
+
the loop should terminate.
|
|
1065
|
+
"""
|
|
1066
|
+
|
|
1061
1067
|
|
|
1062
1068
|
class EndLoopNode(BaseNode):
|
|
1063
1069
|
start_node: StartLoopNode | None = None
|
|
@@ -225,16 +225,18 @@ class ExecuteNodeState(State):
|
|
|
225
225
|
# If the upstream node is resolved, collect its output value
|
|
226
226
|
if upstream_parameter.name in upstream_node.parameter_output_values:
|
|
227
227
|
output_value = upstream_node.parameter_output_values[upstream_parameter.name]
|
|
228
|
+
else:
|
|
229
|
+
output_value = upstream_node.get_parameter_value(upstream_parameter.name)
|
|
228
230
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
231
|
+
# Pass the value through using the same mechanism as normal resolution
|
|
232
|
+
GriptapeNodes.get_instance().handle_request(
|
|
233
|
+
SetParameterValueRequest(
|
|
234
|
+
parameter_name=parameter.name,
|
|
235
|
+
node_name=current_node.name,
|
|
236
|
+
value=output_value,
|
|
237
|
+
data_type=upstream_parameter.output_type,
|
|
237
238
|
)
|
|
239
|
+
)
|
|
238
240
|
|
|
239
241
|
@staticmethod
|
|
240
242
|
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
@@ -41,7 +41,7 @@ class WebSocketConnectionManager:
|
|
|
41
41
|
try:
|
|
42
42
|
message = json.dumps(data)
|
|
43
43
|
await self.websocket.send(message)
|
|
44
|
-
logger.debug("
|
|
44
|
+
logger.debug("Sent message: %s", message)
|
|
45
45
|
except Exception as e:
|
|
46
46
|
logger.error("Failed to send message: %s", e)
|
|
47
47
|
raise
|
|
@@ -157,7 +157,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
|
157
157
|
|
|
158
158
|
except Exception as e:
|
|
159
159
|
self.connection_manager.connected = False
|
|
160
|
-
logger.error("
|
|
160
|
+
logger.error("[red]X[/red] WebSocket connection failed: %s", str(e))
|
|
161
161
|
msg = f"Failed to connect to WebSocket: {e!s}"
|
|
162
162
|
raise ConnectionError(msg) from e
|
|
163
163
|
|
|
@@ -192,7 +192,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
|
192
192
|
"""Send an event to the API without waiting for a response."""
|
|
193
193
|
from griptape_nodes.app.app import _determine_request_topic
|
|
194
194
|
|
|
195
|
-
logger.debug("
|
|
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
198
|
topic = _determine_request_topic()
|
|
@@ -231,7 +231,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
|
231
231
|
def success_handler(response: Any, _: Any) -> None:
|
|
232
232
|
if not response_future.done():
|
|
233
233
|
result = response.get("payload", {}).get("result", "Success")
|
|
234
|
-
logger.debug("
|
|
234
|
+
logger.debug("[green]OK[/green] Request succeeded: %s", result)
|
|
235
235
|
response_future.set_result(result)
|
|
236
236
|
|
|
237
237
|
def failure_handler(response: Any, _: Any) -> None:
|
|
@@ -239,14 +239,14 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
|
239
239
|
error = (
|
|
240
240
|
response.get("payload", {}).get("result", {}).get("exception", "Unknown error") or "Unknown error"
|
|
241
241
|
)
|
|
242
|
-
logger.error("
|
|
242
|
+
logger.error("[red]X[/red] Request failed: %s", error)
|
|
243
243
|
response_future.set_exception(Exception(error))
|
|
244
244
|
|
|
245
245
|
# Generate request ID and subscribe
|
|
246
246
|
request_id = self.connection_manager.subscribe_to_request_event(success_handler, failure_handler)
|
|
247
247
|
payload["request_id"] = request_id
|
|
248
248
|
|
|
249
|
-
logger.debug("
|
|
249
|
+
logger.debug("Request (%s): %s %s", request_id, request_type, json.dumps(payload))
|
|
250
250
|
|
|
251
251
|
try:
|
|
252
252
|
# Send the event
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from dataclasses import asdict, dataclass, field, is_dataclass
|
|
6
|
-
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar
|
|
7
8
|
|
|
8
9
|
from griptape.artifacts import BaseArtifact
|
|
9
10
|
from griptape.events import BaseEvent as GtBaseEvent
|
|
@@ -16,6 +17,64 @@ if TYPE_CHECKING:
|
|
|
16
17
|
import builtins
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
@dataclass
|
|
21
|
+
class ResultDetail:
|
|
22
|
+
"""A single detail about an operation result, including logging level and human readable message."""
|
|
23
|
+
|
|
24
|
+
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
25
|
+
message: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ResultDetails:
|
|
30
|
+
"""Container for multiple ResultDetail objects."""
|
|
31
|
+
|
|
32
|
+
result_details: list[ResultDetail]
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*result_details: ResultDetail,
|
|
37
|
+
message: str | None = None,
|
|
38
|
+
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
|
39
|
+
logger: logging.Logger | str | None = "griptape_nodes",
|
|
40
|
+
):
|
|
41
|
+
"""Initialize with ResultDetail objects or create a single one from message/level.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
*result_details: Variable number of ResultDetail objects
|
|
45
|
+
message: If provided, creates a single ResultDetail with this message
|
|
46
|
+
level: Logging level for the single ResultDetail (required if message is provided)
|
|
47
|
+
logger: Logger to use for auto-logging. String for logger name, Logger object, or None to skip
|
|
48
|
+
"""
|
|
49
|
+
# Handle single message/level convenience
|
|
50
|
+
if message is not None:
|
|
51
|
+
if level is None:
|
|
52
|
+
err_msg = "level is required when message is provided"
|
|
53
|
+
raise ValueError(err_msg)
|
|
54
|
+
if result_details:
|
|
55
|
+
err_msg = "Cannot provide both result_details and message/level"
|
|
56
|
+
raise ValueError(err_msg)
|
|
57
|
+
self.result_details = [ResultDetail(level=level, message=message)]
|
|
58
|
+
else:
|
|
59
|
+
if not result_details:
|
|
60
|
+
err_msg = "ResultDetails requires at least one ResultDetail or message/level"
|
|
61
|
+
raise ValueError(err_msg)
|
|
62
|
+
self.result_details = list(result_details)
|
|
63
|
+
|
|
64
|
+
# Auto-log if logger is provided
|
|
65
|
+
if logger is not None:
|
|
66
|
+
try:
|
|
67
|
+
if isinstance(logger, str):
|
|
68
|
+
logger = logging.getLogger(logger)
|
|
69
|
+
|
|
70
|
+
for detail in self.result_details:
|
|
71
|
+
numeric_level = getattr(logging, detail.level)
|
|
72
|
+
logger.log(numeric_level, detail.message)
|
|
73
|
+
except Exception: # noqa: S110
|
|
74
|
+
# If logging fails for any reason, don't let it break the ResultDetails creation
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
19
78
|
# The Payload class is a marker interface
|
|
20
79
|
class Payload(ABC): # noqa: B024
|
|
21
80
|
"""Base class for all payload types. Customers will derive from this."""
|
|
@@ -32,6 +91,7 @@ class RequestPayload(Payload, ABC):
|
|
|
32
91
|
class ResultPayload(Payload, ABC):
|
|
33
92
|
"""Base class for all result payloads."""
|
|
34
93
|
|
|
94
|
+
result_details: ResultDetails | str
|
|
35
95
|
"""When set to True, alerts clients that this result made changes to the workflow state.
|
|
36
96
|
Editors can use this to determine if the workflow is dirty and needs to be re-saved, for example."""
|
|
37
97
|
altered_workflow_state: bool = False
|
|
@@ -76,6 +136,13 @@ class SkipTheLineMixin:
|
|
|
76
136
|
class ResultPayloadSuccess(ResultPayload, ABC):
|
|
77
137
|
"""Abstract base class for success result payloads."""
|
|
78
138
|
|
|
139
|
+
result_details: ResultDetails | str = "Success"
|
|
140
|
+
|
|
141
|
+
def __post_init__(self) -> None:
|
|
142
|
+
"""Initialize success result with INFO level default for strings."""
|
|
143
|
+
if isinstance(self.result_details, str):
|
|
144
|
+
self.result_details = ResultDetails(message=self.result_details, level="DEBUG")
|
|
145
|
+
|
|
79
146
|
def succeeded(self) -> bool:
|
|
80
147
|
"""Returns True as this is a success result.
|
|
81
148
|
|
|
@@ -90,8 +157,14 @@ class ResultPayloadSuccess(ResultPayload, ABC):
|
|
|
90
157
|
class ResultPayloadFailure(ResultPayload, ABC):
|
|
91
158
|
"""Abstract base class for failure result payloads."""
|
|
92
159
|
|
|
160
|
+
result_details: ResultDetails | str = "Failure"
|
|
93
161
|
exception: Exception | None = None
|
|
94
162
|
|
|
163
|
+
def __post_init__(self) -> None:
|
|
164
|
+
"""Initialize failure result with ERROR level default for strings."""
|
|
165
|
+
if isinstance(self.result_details, str):
|
|
166
|
+
self.result_details = ResultDetails(message=self.result_details, level="ERROR")
|
|
167
|
+
|
|
95
168
|
def succeeded(self) -> bool:
|
|
96
169
|
"""Returns False as this is a failure result.
|
|
97
170
|
|
|
@@ -20,11 +20,13 @@ class GetSecretValueRequest(RequestPayload):
|
|
|
20
20
|
|
|
21
21
|
Args:
|
|
22
22
|
key: Name of the secret key to retrieve
|
|
23
|
+
should_error_on_not_found: Whether to error if the key is not found (default: True)
|
|
23
24
|
|
|
24
25
|
Results: GetSecretValueResultSuccess (with value) | GetSecretValueResultFailure (key not found)
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
28
|
key: str
|
|
29
|
+
should_error_on_not_found: bool = True
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
@dataclass
|
|
@@ -389,11 +389,11 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
389
389
|
)
|
|
390
390
|
details = f"Attempted to get engine version. Failed because version string '{engine_ver}' wasn't in expected major.minor.patch format."
|
|
391
391
|
logger.error(details)
|
|
392
|
-
return GetEngineVersionResultFailure()
|
|
392
|
+
return GetEngineVersionResultFailure(result_details=details)
|
|
393
393
|
except Exception as err:
|
|
394
394
|
details = f"Attempted to get engine version. Failed due to '{err}'."
|
|
395
395
|
logger.error(details)
|
|
396
|
-
return GetEngineVersionResultFailure()
|
|
396
|
+
return GetEngineVersionResultFailure(result_details=details)
|
|
397
397
|
|
|
398
398
|
def handle_session_start_request(self, request: AppStartSessionRequest) -> ResultPayload: # noqa: ARG002
|
|
399
399
|
from griptape_nodes.app.app import subscribe_to_topic
|
|
@@ -434,7 +434,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
434
434
|
except Exception as err:
|
|
435
435
|
details = f"Failed to end session due to '{err}'."
|
|
436
436
|
logger.error(details)
|
|
437
|
-
return AppEndSessionResultFailure()
|
|
437
|
+
return AppEndSessionResultFailure(result_details=details)
|
|
438
438
|
|
|
439
439
|
def handle_get_session_request(self, _: AppGetSessionRequest) -> ResultPayload:
|
|
440
440
|
return AppGetSessionResultSuccess(session_id=GriptapeNodes.SessionManager().get_active_session_id())
|
|
@@ -447,14 +447,17 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
447
447
|
try:
|
|
448
448
|
active_session_id = GriptapeNodes.SessionManager().get_active_session_id()
|
|
449
449
|
if active_session_id is None:
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
details = "Session heartbeat received but no active session found"
|
|
451
|
+
logger.warning(details)
|
|
452
|
+
return SessionHeartbeatResultFailure(result_details=details)
|
|
452
453
|
|
|
453
|
-
|
|
454
|
+
details = f"Session heartbeat successful for session: {active_session_id}"
|
|
455
|
+
logger.debug(details)
|
|
454
456
|
return SessionHeartbeatResultSuccess()
|
|
455
457
|
except Exception as err:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
details = f"Failed to handle session heartbeat: {err}"
|
|
459
|
+
logger.error(details)
|
|
460
|
+
return SessionHeartbeatResultFailure(result_details=details)
|
|
458
461
|
|
|
459
462
|
def handle_engine_heartbeat_request(self, request: EngineHeartbeatRequest) -> ResultPayload:
|
|
460
463
|
"""Handle engine heartbeat requests.
|
|
@@ -483,8 +486,9 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
483
486
|
**workflow_info,
|
|
484
487
|
)
|
|
485
488
|
except Exception as err:
|
|
486
|
-
|
|
487
|
-
|
|
489
|
+
details = f"Failed to handle engine heartbeat: {err}"
|
|
490
|
+
logger.error(details)
|
|
491
|
+
return EngineHeartbeatResultFailure(heartbeat_id=request.heartbeat_id, result_details=details)
|
|
488
492
|
|
|
489
493
|
def handle_get_engine_name_request(self, request: GetEngineNameRequest) -> ResultPayload: # noqa: ARG002
|
|
490
494
|
"""Handle requests to get the current engine name."""
|
|
@@ -495,7 +499,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
495
499
|
except Exception as err:
|
|
496
500
|
error_message = f"Failed to get engine name: {err}"
|
|
497
501
|
logger.error(error_message)
|
|
498
|
-
return GetEngineNameResultFailure(error_message=error_message)
|
|
502
|
+
return GetEngineNameResultFailure(error_message=error_message, result_details=error_message)
|
|
499
503
|
|
|
500
504
|
def handle_set_engine_name_request(self, request: SetEngineNameRequest) -> ResultPayload:
|
|
501
505
|
"""Handle requests to set a new engine name."""
|
|
@@ -504,7 +508,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
504
508
|
if not request.engine_name or not request.engine_name.strip():
|
|
505
509
|
error_message = "Engine name cannot be empty"
|
|
506
510
|
logger.warning(error_message)
|
|
507
|
-
return SetEngineNameResultFailure(error_message=error_message)
|
|
511
|
+
return SetEngineNameResultFailure(error_message=error_message, result_details=error_message)
|
|
508
512
|
|
|
509
513
|
# Set the new engine name
|
|
510
514
|
GriptapeNodes.EngineIdentityManager().set_engine_name(request.engine_name.strip())
|
|
@@ -514,7 +518,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
514
518
|
except Exception as err:
|
|
515
519
|
error_message = f"Failed to set engine name: {err}"
|
|
516
520
|
logger.error(error_message)
|
|
517
|
-
return SetEngineNameResultFailure(error_message=error_message)
|
|
521
|
+
return SetEngineNameResultFailure(error_message=error_message, result_details=error_message)
|
|
518
522
|
|
|
519
523
|
def _get_instance_info(self) -> dict[str, str | None]:
|
|
520
524
|
"""Get instance information from environment variables.
|
|
@@ -204,16 +204,18 @@ class AgentManager:
|
|
|
204
204
|
pass # Ignore incomplete JSON
|
|
205
205
|
if isinstance(last_event, FinishTaskEvent):
|
|
206
206
|
if isinstance(last_event.task_output, ErrorArtifact):
|
|
207
|
-
return RunAgentResultFailure(
|
|
207
|
+
return RunAgentResultFailure(
|
|
208
|
+
error=last_event.task_output.to_dict(), result_details=last_event.task_output.to_json()
|
|
209
|
+
)
|
|
208
210
|
if isinstance(last_event.task_output, JsonArtifact):
|
|
209
211
|
return RunAgentResultSuccess(last_event.task_output.to_dict())
|
|
210
212
|
err_msg = f"Unexpected final event: {last_event}"
|
|
211
213
|
logger.error(err_msg)
|
|
212
|
-
return RunAgentResultFailure(ErrorArtifact(last_event).to_dict())
|
|
214
|
+
return RunAgentResultFailure(error=ErrorArtifact(last_event).to_dict(), result_details=err_msg)
|
|
213
215
|
except Exception as e:
|
|
214
216
|
err_msg = f"Error running agent: {e}"
|
|
215
217
|
logger.error(err_msg)
|
|
216
|
-
return RunAgentResultFailure(ErrorArtifact(e).to_dict())
|
|
218
|
+
return RunAgentResultFailure(error=ErrorArtifact(e).to_dict(), result_details=err_msg)
|
|
217
219
|
|
|
218
220
|
def on_handle_configure_agent_request(self, request: ConfigureAgentRequest) -> ResultPayload:
|
|
219
221
|
try:
|
|
@@ -224,7 +226,7 @@ class AgentManager:
|
|
|
224
226
|
except Exception as e:
|
|
225
227
|
details = f"Error configuring agent: {e}"
|
|
226
228
|
logger.error(details)
|
|
227
|
-
return ConfigureAgentResultFailure()
|
|
229
|
+
return ConfigureAgentResultFailure(result_details=details)
|
|
228
230
|
return ConfigureAgentResultSuccess()
|
|
229
231
|
|
|
230
232
|
def on_handle_reset_agent_conversation_memory_request(
|
|
@@ -235,7 +237,7 @@ class AgentManager:
|
|
|
235
237
|
except Exception as e:
|
|
236
238
|
details = f"Error resetting agent conversation memory: {e}"
|
|
237
239
|
logger.error(details)
|
|
238
|
-
return ResetAgentConversationMemoryResultFailure()
|
|
240
|
+
return ResetAgentConversationMemoryResultFailure(result_details=details)
|
|
239
241
|
return ResetAgentConversationMemoryResultSuccess()
|
|
240
242
|
|
|
241
243
|
def on_handle_get_conversation_memory_request(self, _: GetConversationMemoryRequest) -> ResultPayload:
|
|
@@ -244,5 +246,5 @@ class AgentManager:
|
|
|
244
246
|
except Exception as e:
|
|
245
247
|
details = f"Error getting conversation memory: {e}"
|
|
246
248
|
logger.error(details)
|
|
247
|
-
return GetConversationMemoryResultFailure()
|
|
249
|
+
return GetConversationMemoryResultFailure(result_details=details)
|
|
248
250
|
return GetConversationMemoryResultSuccess(runs=conversation_memory)
|
|
@@ -46,6 +46,6 @@ class ArbitraryCodeExecManager:
|
|
|
46
46
|
result = RunArbitraryPythonStringResultSuccess(python_output=captured_output)
|
|
47
47
|
except Exception as e:
|
|
48
48
|
python_output = f"ERROR: {e}"
|
|
49
|
-
result = RunArbitraryPythonStringResultFailure(python_output=python_output)
|
|
49
|
+
result = RunArbitraryPythonStringResultFailure(python_output=python_output, result_details=python_output)
|
|
50
50
|
|
|
51
51
|
return result
|