griptape-nodes 0.62.3__py3-none-any.whl → 0.63.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/cli/commands/libraries.py +6 -21
- griptape_nodes/cli/commands/models.py +21 -4
- griptape_nodes/drivers/thread_storage/__init__.py +15 -0
- griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
- griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
- griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
- griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
- griptape_nodes/node_library/library_registry.py +16 -9
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/agent_events.py +232 -9
- griptape_nodes/retained_mode/events/library_events.py +32 -3
- griptape_nodes/retained_mode/events/model_events.py +4 -4
- griptape_nodes/retained_mode/events/os_events.py +101 -1
- griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
- griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
- griptape_nodes/retained_mode/managers/library_manager.py +159 -75
- griptape_nodes/retained_mode/managers/model_manager.py +182 -205
- griptape_nodes/retained_mode/managers/os_manager.py +172 -1
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
- griptape_nodes/retained_mode/managers/workflow_manager.py +154 -137
- griptape_nodes/servers/static.py +18 -19
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
- griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/METADATA +3 -2
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/RECORD +76 -23
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/entry_points.txt +0 -0
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import threading
|
|
6
6
|
import uuid
|
|
7
|
+
from collections.abc import Iterator
|
|
7
8
|
from typing import TYPE_CHECKING, ClassVar
|
|
8
9
|
|
|
9
10
|
from attrs import define, field
|
|
@@ -22,21 +23,41 @@ from griptape.utils.decorators import activity
|
|
|
22
23
|
from json_repair import repair_json
|
|
23
24
|
from pydantic import create_model
|
|
24
25
|
from schema import Literal, Schema
|
|
26
|
+
from xdg_base_dirs import xdg_data_home
|
|
25
27
|
|
|
28
|
+
from griptape_nodes.drivers.thread_storage import (
|
|
29
|
+
GriptapeCloudThreadStorageDriver,
|
|
30
|
+
LocalThreadStorageDriver,
|
|
31
|
+
)
|
|
26
32
|
from griptape_nodes.retained_mode.events.agent_events import (
|
|
27
33
|
AgentStreamEvent,
|
|
34
|
+
ArchiveThreadRequest,
|
|
35
|
+
ArchiveThreadResultFailure,
|
|
36
|
+
ArchiveThreadResultSuccess,
|
|
28
37
|
ConfigureAgentRequest,
|
|
29
38
|
ConfigureAgentResultFailure,
|
|
30
39
|
ConfigureAgentResultSuccess,
|
|
40
|
+
CreateThreadRequest,
|
|
41
|
+
CreateThreadResultFailure,
|
|
42
|
+
CreateThreadResultSuccess,
|
|
43
|
+
DeleteThreadRequest,
|
|
44
|
+
DeleteThreadResultFailure,
|
|
45
|
+
DeleteThreadResultSuccess,
|
|
31
46
|
GetConversationMemoryRequest,
|
|
32
47
|
GetConversationMemoryResultFailure,
|
|
33
48
|
GetConversationMemoryResultSuccess,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
ListThreadsRequest,
|
|
50
|
+
ListThreadsResultFailure,
|
|
51
|
+
ListThreadsResultSuccess,
|
|
52
|
+
RenameThreadRequest,
|
|
53
|
+
RenameThreadResultFailure,
|
|
54
|
+
RenameThreadResultSuccess,
|
|
37
55
|
RunAgentRequest,
|
|
38
56
|
RunAgentResultFailure,
|
|
39
57
|
RunAgentResultSuccess,
|
|
58
|
+
UnarchiveThreadRequest,
|
|
59
|
+
UnarchiveThreadResultFailure,
|
|
60
|
+
UnarchiveThreadResultSuccess,
|
|
40
61
|
)
|
|
41
62
|
from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
|
|
42
63
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent, ResultPayload
|
|
@@ -104,27 +125,321 @@ class AgentManager:
|
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
def __init__(self, static_files_manager: StaticFilesManager, event_manager: EventManager | None = None) -> None:
|
|
107
|
-
self.conversation_memory = ConversationMemory()
|
|
108
128
|
self.prompt_driver = None
|
|
109
129
|
self.image_tool = None
|
|
110
130
|
self.mcp_tool = None
|
|
111
131
|
self.static_files_manager = static_files_manager
|
|
112
132
|
|
|
133
|
+
# Thread management
|
|
134
|
+
self._threads_dir = xdg_data_home() / "griptape_nodes" / "threads"
|
|
135
|
+
self._threads_dir.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
|
|
137
|
+
# Initialize thread storage driver based on config
|
|
138
|
+
self.thread_storage_driver = self._initialize_thread_storage_driver()
|
|
139
|
+
|
|
113
140
|
if event_manager is not None:
|
|
141
|
+
# Existing handlers
|
|
114
142
|
event_manager.assign_manager_to_request_type(RunAgentRequest, self.on_handle_run_agent_request)
|
|
115
143
|
event_manager.assign_manager_to_request_type(ConfigureAgentRequest, self.on_handle_configure_agent_request)
|
|
116
144
|
event_manager.assign_manager_to_request_type(
|
|
117
|
-
|
|
145
|
+
GetConversationMemoryRequest, self.on_handle_get_conversation_memory_request
|
|
118
146
|
)
|
|
147
|
+
|
|
148
|
+
# New thread management handlers
|
|
149
|
+
event_manager.assign_manager_to_request_type(CreateThreadRequest, self.on_handle_create_thread_request)
|
|
150
|
+
event_manager.assign_manager_to_request_type(ListThreadsRequest, self.on_handle_list_threads_request)
|
|
151
|
+
event_manager.assign_manager_to_request_type(DeleteThreadRequest, self.on_handle_delete_thread_request)
|
|
152
|
+
event_manager.assign_manager_to_request_type(RenameThreadRequest, self.on_handle_rename_thread_request)
|
|
153
|
+
event_manager.assign_manager_to_request_type(ArchiveThreadRequest, self.on_handle_archive_thread_request)
|
|
119
154
|
event_manager.assign_manager_to_request_type(
|
|
120
|
-
|
|
155
|
+
UnarchiveThreadRequest, self.on_handle_unarchive_thread_request
|
|
121
156
|
)
|
|
157
|
+
|
|
122
158
|
event_manager.add_listener_to_app_event(
|
|
123
159
|
AppInitializationComplete,
|
|
124
160
|
self.on_app_initialization_complete,
|
|
125
161
|
)
|
|
126
162
|
# TODO: Listen for shutdown event (https://github.com/griptape-ai/griptape-nodes/issues/2149) to stop mcp server
|
|
127
163
|
|
|
164
|
+
async def on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
165
|
+
if self.prompt_driver is None:
|
|
166
|
+
self.prompt_driver = self._initialize_prompt_driver()
|
|
167
|
+
if self.image_tool is None:
|
|
168
|
+
self.image_tool = self._initialize_image_tool()
|
|
169
|
+
if self.mcp_tool is None:
|
|
170
|
+
self.mcp_tool = self._initialize_mcp_tool()
|
|
171
|
+
try:
|
|
172
|
+
return await asyncio.to_thread(self._on_handle_run_agent_request, request)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
err_msg = f"Error handling run agent request: {e}"
|
|
175
|
+
return RunAgentResultFailure(error=ErrorArtifact(e).to_dict(), result_details=err_msg)
|
|
176
|
+
|
|
177
|
+
def on_handle_configure_agent_request(self, request: ConfigureAgentRequest) -> ResultPayload:
|
|
178
|
+
try:
|
|
179
|
+
if self.prompt_driver is None:
|
|
180
|
+
self.prompt_driver = self._initialize_prompt_driver()
|
|
181
|
+
for key, value in request.prompt_driver.items():
|
|
182
|
+
setattr(self.prompt_driver, key, value)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
details = f"Error configuring agent: {e}"
|
|
185
|
+
logger.error(details)
|
|
186
|
+
return ConfigureAgentResultFailure(result_details=details)
|
|
187
|
+
return ConfigureAgentResultSuccess(result_details="Agent configured successfully.")
|
|
188
|
+
|
|
189
|
+
def on_handle_get_conversation_memory_request(self, request: GetConversationMemoryRequest) -> ResultPayload:
|
|
190
|
+
try:
|
|
191
|
+
thread_id = request.thread_id
|
|
192
|
+
|
|
193
|
+
driver = self.thread_storage_driver.get_conversation_memory_driver(thread_id)
|
|
194
|
+
conversation_memory = ConversationMemory(conversation_memory_driver=driver)
|
|
195
|
+
runs = conversation_memory.runs
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
details = f"Error getting conversation memory: {e}"
|
|
199
|
+
logger.error(details)
|
|
200
|
+
return GetConversationMemoryResultFailure(result_details=details)
|
|
201
|
+
|
|
202
|
+
return GetConversationMemoryResultSuccess(
|
|
203
|
+
runs=runs, thread_id=thread_id, result_details="Conversation memory retrieved successfully."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def on_handle_create_thread_request(self, request: CreateThreadRequest) -> ResultPayload:
|
|
207
|
+
try:
|
|
208
|
+
thread_id, meta = self.thread_storage_driver.create_thread(title=request.title, local_id=request.local_id)
|
|
209
|
+
|
|
210
|
+
return CreateThreadResultSuccess(
|
|
211
|
+
thread_id=thread_id,
|
|
212
|
+
title=meta.get("title"),
|
|
213
|
+
created_at=meta["created_at"],
|
|
214
|
+
updated_at=meta["updated_at"],
|
|
215
|
+
result_details="Thread created successfully.",
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
details = f"Error creating thread: {e}"
|
|
219
|
+
logger.error(details)
|
|
220
|
+
return CreateThreadResultFailure(result_details=details)
|
|
221
|
+
|
|
222
|
+
def on_handle_list_threads_request(self, _: ListThreadsRequest) -> ResultPayload:
|
|
223
|
+
try:
|
|
224
|
+
threads = self.thread_storage_driver.list_threads()
|
|
225
|
+
return ListThreadsResultSuccess(threads=threads, result_details="Threads retrieved successfully.")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
details = f"Error listing threads: {e}"
|
|
228
|
+
logger.error(details)
|
|
229
|
+
return ListThreadsResultFailure(result_details=details)
|
|
230
|
+
|
|
231
|
+
def on_handle_delete_thread_request(self, request: DeleteThreadRequest) -> ResultPayload:
|
|
232
|
+
try:
|
|
233
|
+
self.thread_storage_driver.delete_thread(request.thread_id)
|
|
234
|
+
return DeleteThreadResultSuccess(thread_id=request.thread_id, result_details="Thread deleted successfully.")
|
|
235
|
+
except ValueError as e:
|
|
236
|
+
details = str(e)
|
|
237
|
+
logger.error(details)
|
|
238
|
+
return DeleteThreadResultFailure(result_details=details)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
details = f"Error deleting thread: {e}"
|
|
241
|
+
logger.error(details)
|
|
242
|
+
return DeleteThreadResultFailure(result_details=details)
|
|
243
|
+
|
|
244
|
+
def on_handle_rename_thread_request(self, request: RenameThreadRequest) -> ResultPayload:
|
|
245
|
+
try:
|
|
246
|
+
if not self.thread_storage_driver.thread_exists(request.thread_id):
|
|
247
|
+
details = f"Thread {request.thread_id} not found"
|
|
248
|
+
logger.error(details)
|
|
249
|
+
return RenameThreadResultFailure(result_details=details)
|
|
250
|
+
|
|
251
|
+
updated_meta = self.thread_storage_driver.update_thread_metadata(request.thread_id, title=request.new_title)
|
|
252
|
+
|
|
253
|
+
return RenameThreadResultSuccess(
|
|
254
|
+
thread_id=request.thread_id,
|
|
255
|
+
title=updated_meta["title"],
|
|
256
|
+
updated_at=updated_meta["updated_at"],
|
|
257
|
+
result_details="Thread renamed successfully.",
|
|
258
|
+
)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
details = f"Error renaming thread: {e}"
|
|
261
|
+
logger.error(details)
|
|
262
|
+
return RenameThreadResultFailure(result_details=details)
|
|
263
|
+
|
|
264
|
+
def on_handle_archive_thread_request(self, request: ArchiveThreadRequest) -> ResultPayload:
|
|
265
|
+
try:
|
|
266
|
+
if not self.thread_storage_driver.thread_exists(request.thread_id):
|
|
267
|
+
details = f"Thread {request.thread_id} not found"
|
|
268
|
+
logger.error(details)
|
|
269
|
+
return ArchiveThreadResultFailure(result_details=details)
|
|
270
|
+
|
|
271
|
+
meta = self.thread_storage_driver.get_thread_metadata(request.thread_id)
|
|
272
|
+
if meta.get("archived", False):
|
|
273
|
+
details = f"Thread {request.thread_id} is already archived"
|
|
274
|
+
logger.error(details)
|
|
275
|
+
return ArchiveThreadResultFailure(result_details=details)
|
|
276
|
+
|
|
277
|
+
updated_meta = self.thread_storage_driver.update_thread_metadata(request.thread_id, archived=True)
|
|
278
|
+
|
|
279
|
+
return ArchiveThreadResultSuccess(
|
|
280
|
+
thread_id=request.thread_id,
|
|
281
|
+
updated_at=updated_meta["updated_at"],
|
|
282
|
+
result_details="Thread archived successfully.",
|
|
283
|
+
)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
details = f"Error archiving thread: {e}"
|
|
286
|
+
logger.error(details)
|
|
287
|
+
return ArchiveThreadResultFailure(result_details=details)
|
|
288
|
+
|
|
289
|
+
def on_handle_unarchive_thread_request(self, request: UnarchiveThreadRequest) -> ResultPayload:
|
|
290
|
+
try:
|
|
291
|
+
if not self.thread_storage_driver.thread_exists(request.thread_id):
|
|
292
|
+
details = f"Thread {request.thread_id} not found"
|
|
293
|
+
logger.error(details)
|
|
294
|
+
return UnarchiveThreadResultFailure(result_details=details)
|
|
295
|
+
|
|
296
|
+
meta = self.thread_storage_driver.get_thread_metadata(request.thread_id)
|
|
297
|
+
if not meta.get("archived", False):
|
|
298
|
+
details = f"Thread {request.thread_id} is not archived"
|
|
299
|
+
logger.error(details)
|
|
300
|
+
return UnarchiveThreadResultFailure(result_details=details)
|
|
301
|
+
|
|
302
|
+
updated_meta = self.thread_storage_driver.update_thread_metadata(request.thread_id, archived=False)
|
|
303
|
+
|
|
304
|
+
return UnarchiveThreadResultSuccess(
|
|
305
|
+
thread_id=request.thread_id,
|
|
306
|
+
updated_at=updated_meta["updated_at"],
|
|
307
|
+
result_details="Thread unarchived successfully.",
|
|
308
|
+
)
|
|
309
|
+
except Exception as e:
|
|
310
|
+
details = f"Error unarchiving thread: {e}"
|
|
311
|
+
logger.error(details)
|
|
312
|
+
return UnarchiveThreadResultFailure(result_details=details)
|
|
313
|
+
|
|
314
|
+
def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
|
|
315
|
+
secrets_manager = GriptapeNodes.SecretsManager()
|
|
316
|
+
api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY")
|
|
317
|
+
# Start MCP server in daemon thread
|
|
318
|
+
threading.Thread(target=start_mcp_server, args=(api_key,), daemon=True, name="mcp-server").start()
|
|
319
|
+
|
|
320
|
+
def _on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
321
|
+
# EventBus functionality removed - events now go directly to event queue
|
|
322
|
+
try:
|
|
323
|
+
# Get or create thread and validate
|
|
324
|
+
try:
|
|
325
|
+
thread_id = self._validate_thread_for_run(request.thread_id)
|
|
326
|
+
except ValueError as e:
|
|
327
|
+
details = str(e)
|
|
328
|
+
return RunAgentResultFailure(error={"message": details}, result_details=details)
|
|
329
|
+
|
|
330
|
+
# Check if this is the first run
|
|
331
|
+
driver = self.thread_storage_driver.get_conversation_memory_driver(thread_id)
|
|
332
|
+
conversation_memory = ConversationMemory(conversation_memory_driver=driver)
|
|
333
|
+
is_first_run = len(conversation_memory.runs) == 0
|
|
334
|
+
|
|
335
|
+
artifacts = [
|
|
336
|
+
ImageLoader().parse(ImageUrlArtifact.from_dict(url_artifact).to_bytes())
|
|
337
|
+
for url_artifact in request.url_artifacts
|
|
338
|
+
if url_artifact["type"] == "ImageUrlArtifact"
|
|
339
|
+
]
|
|
340
|
+
agent = self._create_agent(thread_id=thread_id, additional_mcp_servers=request.additional_mcp_servers)
|
|
341
|
+
event_stream = agent.run_stream([request.input, *artifacts])
|
|
342
|
+
self._process_agent_stream(event_stream)
|
|
343
|
+
|
|
344
|
+
if isinstance(agent.output, ErrorArtifact):
|
|
345
|
+
return RunAgentResultFailure(error=agent.output.to_dict(), result_details=agent.output.to_json())
|
|
346
|
+
|
|
347
|
+
# Auto-generate title from first message if needed
|
|
348
|
+
if is_first_run:
|
|
349
|
+
title = self._generate_title_from_input(request.input)
|
|
350
|
+
self.thread_storage_driver.update_thread_metadata(thread_id, title=title)
|
|
351
|
+
else:
|
|
352
|
+
# Just update the timestamp
|
|
353
|
+
self.thread_storage_driver.update_thread_metadata(thread_id)
|
|
354
|
+
|
|
355
|
+
return RunAgentResultSuccess(
|
|
356
|
+
output=agent.output.to_dict(),
|
|
357
|
+
thread_id=thread_id,
|
|
358
|
+
result_details="Agent execution completed successfully.",
|
|
359
|
+
)
|
|
360
|
+
except Exception as e:
|
|
361
|
+
err_msg = f"Error running agent: {e}"
|
|
362
|
+
logger.exception(err_msg)
|
|
363
|
+
return RunAgentResultFailure(error=ErrorArtifact(e).to_dict(), result_details=err_msg)
|
|
364
|
+
|
|
365
|
+
def _create_agent(self, thread_id: str, additional_mcp_servers: list[str] | None = None) -> Agent:
|
|
366
|
+
output_schema = create_model(
|
|
367
|
+
"AgentOutputSchema",
|
|
368
|
+
conversation_output=(str, ...),
|
|
369
|
+
generated_image_urls=(list[str], ...),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
tools = []
|
|
373
|
+
if self.image_tool is not None:
|
|
374
|
+
tools.append(self.image_tool)
|
|
375
|
+
if self.mcp_tool is not None:
|
|
376
|
+
tools.append(self.mcp_tool)
|
|
377
|
+
|
|
378
|
+
# Add additional MCP servers if specified
|
|
379
|
+
if additional_mcp_servers:
|
|
380
|
+
additional_tools = self._create_additional_mcp_tools(additional_mcp_servers)
|
|
381
|
+
tools.extend(additional_tools)
|
|
382
|
+
|
|
383
|
+
# Get thread-specific conversation memory
|
|
384
|
+
driver = self.thread_storage_driver.get_conversation_memory_driver(thread_id)
|
|
385
|
+
conversation_memory = ConversationMemory(conversation_memory_driver=driver)
|
|
386
|
+
|
|
387
|
+
return Agent(
|
|
388
|
+
prompt_driver=self.prompt_driver,
|
|
389
|
+
conversation_memory=conversation_memory,
|
|
390
|
+
tools=tools,
|
|
391
|
+
output_schema=output_schema,
|
|
392
|
+
rulesets=[
|
|
393
|
+
Ruleset(
|
|
394
|
+
name="generated_image_urls",
|
|
395
|
+
rules=[
|
|
396
|
+
Rule("Do not hallucinate generated_image_urls."),
|
|
397
|
+
Rule("Only set generated_image_urls with images generated with your tools."),
|
|
398
|
+
],
|
|
399
|
+
),
|
|
400
|
+
],
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def _validate_thread_for_run(self, thread_id: str | None) -> str:
|
|
404
|
+
"""Validate and return thread_id for agent run, or raise ValueError."""
|
|
405
|
+
if thread_id is None:
|
|
406
|
+
thread_id, _ = self.thread_storage_driver.create_thread()
|
|
407
|
+
return thread_id
|
|
408
|
+
|
|
409
|
+
meta = self.thread_storage_driver.get_thread_metadata(thread_id)
|
|
410
|
+
if meta.get("archived", False):
|
|
411
|
+
details = f"Cannot run agent on archived thread {thread_id}. Unarchive it first."
|
|
412
|
+
logger.error(details)
|
|
413
|
+
raise ValueError(details)
|
|
414
|
+
|
|
415
|
+
return thread_id
|
|
416
|
+
|
|
417
|
+
def _process_agent_stream(self, event_stream: Iterator) -> None:
|
|
418
|
+
"""Process agent stream events and emit streaming tokens."""
|
|
419
|
+
full_result = ""
|
|
420
|
+
last_conversation_output = ""
|
|
421
|
+
for event in event_stream:
|
|
422
|
+
if isinstance(event, TextChunkEvent):
|
|
423
|
+
full_result += event.token
|
|
424
|
+
try:
|
|
425
|
+
result_json = json.loads(repair_json(full_result))
|
|
426
|
+
|
|
427
|
+
if isinstance(result_json, dict) and "conversation_output" in result_json:
|
|
428
|
+
new_conversation_output = result_json["conversation_output"]
|
|
429
|
+
if new_conversation_output != last_conversation_output:
|
|
430
|
+
GriptapeNodes.EventManager().put_event(
|
|
431
|
+
ExecutionGriptapeNodeEvent(
|
|
432
|
+
wrapped_event=ExecutionEvent(
|
|
433
|
+
payload=AgentStreamEvent(
|
|
434
|
+
token=new_conversation_output[len(last_conversation_output) :]
|
|
435
|
+
)
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
)
|
|
439
|
+
last_conversation_output = new_conversation_output
|
|
440
|
+
except json.JSONDecodeError:
|
|
441
|
+
pass # Ignore incomplete JSON
|
|
442
|
+
|
|
128
443
|
def _initialize_prompt_driver(self) -> GriptapeCloudPromptDriver:
|
|
129
444
|
api_key = secrets_manager.get_secret(API_KEY_ENV_VAR)
|
|
130
445
|
if not api_key:
|
|
@@ -149,6 +464,15 @@ class AgentManager:
|
|
|
149
464
|
}
|
|
150
465
|
return MCPTool(connection=connection, name="mcpGriptapeNodes")
|
|
151
466
|
|
|
467
|
+
def _initialize_thread_storage_driver(self) -> LocalThreadStorageDriver | GriptapeCloudThreadStorageDriver:
|
|
468
|
+
"""Initialize the appropriate thread storage driver based on configuration."""
|
|
469
|
+
storage_backend = config_manager.get_config_value("thread_storage_backend")
|
|
470
|
+
|
|
471
|
+
if storage_backend == "gtc":
|
|
472
|
+
return GriptapeCloudThreadStorageDriver(config_manager, secrets_manager)
|
|
473
|
+
|
|
474
|
+
return LocalThreadStorageDriver(self._threads_dir, config_manager, secrets_manager)
|
|
475
|
+
|
|
152
476
|
def _create_additional_mcp_tools(self, server_names: list[str]) -> list[MCPTool]:
|
|
153
477
|
"""Create MCP tools for additional servers specified in the request."""
|
|
154
478
|
additional_tools = []
|
|
@@ -195,133 +519,9 @@ class AgentManager:
|
|
|
195
519
|
|
|
196
520
|
return connection
|
|
197
521
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
self.image_tool = self._initialize_image_tool()
|
|
203
|
-
if self.mcp_tool is None:
|
|
204
|
-
self.mcp_tool = self._initialize_mcp_tool()
|
|
205
|
-
try:
|
|
206
|
-
return await asyncio.to_thread(self._on_handle_run_agent_request, request)
|
|
207
|
-
except Exception as e:
|
|
208
|
-
err_msg = f"Error handling run agent request: {e}"
|
|
209
|
-
return RunAgentResultFailure(error=ErrorArtifact(e).to_dict(), result_details=err_msg)
|
|
210
|
-
|
|
211
|
-
def _create_agent(self, additional_mcp_servers: list[str] | None = None) -> Agent:
|
|
212
|
-
output_schema = create_model(
|
|
213
|
-
"AgentOutputSchema",
|
|
214
|
-
conversation_output=(str, ...),
|
|
215
|
-
generated_image_urls=(list[str], ...),
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
tools = []
|
|
219
|
-
if self.image_tool is not None:
|
|
220
|
-
tools.append(self.image_tool)
|
|
221
|
-
if self.mcp_tool is not None:
|
|
222
|
-
tools.append(self.mcp_tool)
|
|
223
|
-
|
|
224
|
-
# Add additional MCP servers if specified
|
|
225
|
-
if additional_mcp_servers:
|
|
226
|
-
additional_tools = self._create_additional_mcp_tools(additional_mcp_servers)
|
|
227
|
-
tools.extend(additional_tools)
|
|
228
|
-
|
|
229
|
-
return Agent(
|
|
230
|
-
prompt_driver=self.prompt_driver,
|
|
231
|
-
conversation_memory=self.conversation_memory,
|
|
232
|
-
tools=tools,
|
|
233
|
-
output_schema=output_schema,
|
|
234
|
-
rulesets=[
|
|
235
|
-
Ruleset(
|
|
236
|
-
name="generated_image_urls",
|
|
237
|
-
rules=[
|
|
238
|
-
Rule("Do not hallucinate generated_image_urls."),
|
|
239
|
-
Rule("Only set generated_image_urls with images generated with your tools."),
|
|
240
|
-
],
|
|
241
|
-
),
|
|
242
|
-
],
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
def _on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
246
|
-
# EventBus functionality removed - events now go directly to event queue
|
|
247
|
-
try:
|
|
248
|
-
artifacts = [
|
|
249
|
-
ImageLoader().parse(ImageUrlArtifact.from_dict(url_artifact).to_bytes())
|
|
250
|
-
for url_artifact in request.url_artifacts
|
|
251
|
-
if url_artifact["type"] == "ImageUrlArtifact"
|
|
252
|
-
]
|
|
253
|
-
agent = self._create_agent(additional_mcp_servers=request.additional_mcp_servers)
|
|
254
|
-
event_stream = agent.run_stream([request.input, *artifacts])
|
|
255
|
-
full_result = ""
|
|
256
|
-
last_conversation_output = ""
|
|
257
|
-
for event in event_stream:
|
|
258
|
-
if isinstance(event, TextChunkEvent):
|
|
259
|
-
full_result += event.token
|
|
260
|
-
try:
|
|
261
|
-
result_json = json.loads(repair_json(full_result))
|
|
262
|
-
|
|
263
|
-
if isinstance(result_json, dict) and "conversation_output" in result_json:
|
|
264
|
-
new_conversation_output = result_json["conversation_output"]
|
|
265
|
-
if new_conversation_output != last_conversation_output:
|
|
266
|
-
GriptapeNodes.EventManager().put_event(
|
|
267
|
-
ExecutionGriptapeNodeEvent(
|
|
268
|
-
wrapped_event=ExecutionEvent(
|
|
269
|
-
payload=AgentStreamEvent(
|
|
270
|
-
token=new_conversation_output[len(last_conversation_output) :]
|
|
271
|
-
)
|
|
272
|
-
)
|
|
273
|
-
)
|
|
274
|
-
)
|
|
275
|
-
last_conversation_output = new_conversation_output
|
|
276
|
-
except json.JSONDecodeError:
|
|
277
|
-
pass # Ignore incomplete JSON
|
|
278
|
-
if isinstance(agent.output, ErrorArtifact):
|
|
279
|
-
return RunAgentResultFailure(error=agent.output.to_dict(), result_details=agent.output.to_json())
|
|
280
|
-
|
|
281
|
-
return RunAgentResultSuccess(
|
|
282
|
-
agent.output.to_dict(), result_details="Agent execution completed successfully."
|
|
283
|
-
)
|
|
284
|
-
except Exception as e:
|
|
285
|
-
err_msg = f"Error running agent: {e}"
|
|
286
|
-
logger.exception(err_msg)
|
|
287
|
-
return RunAgentResultFailure(error=ErrorArtifact(e).to_dict(), result_details=err_msg)
|
|
522
|
+
def _generate_title_from_input(self, user_input: str, max_length: int = 50) -> str:
|
|
523
|
+
"""Generate a thread title from user input."""
|
|
524
|
+
if len(user_input) <= max_length:
|
|
525
|
+
return user_input
|
|
288
526
|
|
|
289
|
-
|
|
290
|
-
try:
|
|
291
|
-
if self.prompt_driver is None:
|
|
292
|
-
self.prompt_driver = self._initialize_prompt_driver()
|
|
293
|
-
for key, value in request.prompt_driver.items():
|
|
294
|
-
setattr(self.prompt_driver, key, value)
|
|
295
|
-
except Exception as e:
|
|
296
|
-
details = f"Error configuring agent: {e}"
|
|
297
|
-
logger.error(details)
|
|
298
|
-
return ConfigureAgentResultFailure(result_details=details)
|
|
299
|
-
return ConfigureAgentResultSuccess(result_details="Agent configured successfully.")
|
|
300
|
-
|
|
301
|
-
def on_handle_reset_agent_conversation_memory_request(
|
|
302
|
-
self, _: ResetAgentConversationMemoryRequest
|
|
303
|
-
) -> ResultPayload:
|
|
304
|
-
try:
|
|
305
|
-
self.conversation_memory = ConversationMemory()
|
|
306
|
-
except Exception as e:
|
|
307
|
-
details = f"Error resetting agent conversation memory: {e}"
|
|
308
|
-
logger.error(details)
|
|
309
|
-
return ResetAgentConversationMemoryResultFailure(result_details=details)
|
|
310
|
-
return ResetAgentConversationMemoryResultSuccess(result_details="Agent conversation memory reset successfully.")
|
|
311
|
-
|
|
312
|
-
def on_handle_get_conversation_memory_request(self, _: GetConversationMemoryRequest) -> ResultPayload:
|
|
313
|
-
try:
|
|
314
|
-
conversation_memory = self.conversation_memory.runs
|
|
315
|
-
except Exception as e:
|
|
316
|
-
details = f"Error getting conversation memory: {e}"
|
|
317
|
-
logger.error(details)
|
|
318
|
-
return GetConversationMemoryResultFailure(result_details=details)
|
|
319
|
-
return GetConversationMemoryResultSuccess(
|
|
320
|
-
runs=conversation_memory, result_details="Conversation memory retrieved successfully."
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
|
|
324
|
-
secrets_manager = GriptapeNodes.SecretsManager()
|
|
325
|
-
api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY")
|
|
326
|
-
# Start MCP server in daemon thread
|
|
327
|
-
threading.Thread(target=start_mcp_server, args=(api_key,), daemon=True, name="mcp-server").start()
|
|
527
|
+
return user_input[:max_length].rsplit(" ", 1)[0] + "..."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Fitness problems package for library and workflow validation issues."""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Library fitness problems for validation and loading issues."""
|
|
2
|
+
|
|
3
|
+
from .advanced_library_load_failure_problem import AdvancedLibraryLoadFailureProblem
|
|
4
|
+
from .after_library_callback_problem import AfterLibraryCallbackProblem
|
|
5
|
+
from .before_library_callback_problem import BeforeLibraryCallbackProblem
|
|
6
|
+
from .create_config_category_problem import CreateConfigCategoryProblem
|
|
7
|
+
from .dependency_installation_failed_problem import DependencyInstallationFailedProblem
|
|
8
|
+
from .deprecated_node_warning_problem import DeprecatedNodeWarningProblem
|
|
9
|
+
from .duplicate_library_problem import DuplicateLibraryProblem
|
|
10
|
+
from .duplicate_node_registration_problem import DuplicateNodeRegistrationProblem
|
|
11
|
+
from .engine_version_error_problem import EngineVersionErrorProblem
|
|
12
|
+
from .insufficient_disk_space_problem import InsufficientDiskSpaceProblem
|
|
13
|
+
from .invalid_version_string_problem import InvalidVersionStringProblem
|
|
14
|
+
from .library_json_decode_problem import LibraryJsonDecodeProblem
|
|
15
|
+
from .library_load_exception_problem import LibraryLoadExceptionProblem
|
|
16
|
+
from .library_not_found_problem import LibraryNotFoundProblem
|
|
17
|
+
from .library_problem import LibraryProblem
|
|
18
|
+
from .library_schema_exception_problem import LibrarySchemaExceptionProblem
|
|
19
|
+
from .library_schema_validation_problem import LibrarySchemaValidationProblem
|
|
20
|
+
from .modified_parameters_set_deprecation_warning_problem import ModifiedParametersSetDeprecationWarningProblem
|
|
21
|
+
from .modified_parameters_set_removed_problem import ModifiedParametersSetRemovedProblem
|
|
22
|
+
from .node_class_not_base_node_problem import NodeClassNotBaseNodeProblem
|
|
23
|
+
from .node_class_not_found_problem import NodeClassNotFoundProblem
|
|
24
|
+
from .node_module_import_problem import NodeModuleImportProblem
|
|
25
|
+
from .sandbox_directory_missing_problem import SandboxDirectoryMissingProblem
|
|
26
|
+
from .ui_options_field_modified_incompatible_problem import UiOptionsFieldModifiedIncompatibleProblem
|
|
27
|
+
from .ui_options_field_modified_warning_problem import UiOptionsFieldModifiedWarningProblem
|
|
28
|
+
from .update_config_category_problem import UpdateConfigCategoryProblem
|
|
29
|
+
from .venv_creation_failed_problem import VenvCreationFailedProblem
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"AdvancedLibraryLoadFailureProblem",
|
|
33
|
+
"AfterLibraryCallbackProblem",
|
|
34
|
+
"BeforeLibraryCallbackProblem",
|
|
35
|
+
"CreateConfigCategoryProblem",
|
|
36
|
+
"DependencyInstallationFailedProblem",
|
|
37
|
+
"DeprecatedNodeWarningProblem",
|
|
38
|
+
"DuplicateLibraryProblem",
|
|
39
|
+
"DuplicateNodeRegistrationProblem",
|
|
40
|
+
"EngineVersionErrorProblem",
|
|
41
|
+
"InsufficientDiskSpaceProblem",
|
|
42
|
+
"InvalidVersionStringProblem",
|
|
43
|
+
"LibraryJsonDecodeProblem",
|
|
44
|
+
"LibraryLoadExceptionProblem",
|
|
45
|
+
"LibraryNotFoundProblem",
|
|
46
|
+
"LibraryProblem",
|
|
47
|
+
"LibrarySchemaExceptionProblem",
|
|
48
|
+
"LibrarySchemaValidationProblem",
|
|
49
|
+
"ModifiedParametersSetDeprecationWarningProblem",
|
|
50
|
+
"ModifiedParametersSetRemovedProblem",
|
|
51
|
+
"NodeClassNotBaseNodeProblem",
|
|
52
|
+
"NodeClassNotFoundProblem",
|
|
53
|
+
"NodeModuleImportProblem",
|
|
54
|
+
"SandboxDirectoryMissingProblem",
|
|
55
|
+
"UiOptionsFieldModifiedIncompatibleProblem",
|
|
56
|
+
"UiOptionsFieldModifiedWarningProblem",
|
|
57
|
+
"UpdateConfigCategoryProblem",
|
|
58
|
+
"VenvCreationFailedProblem",
|
|
59
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.retained_mode.managers.fitness_problems.libraries.library_problem import LibraryProblem
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AdvancedLibraryLoadFailureProblem(LibraryProblem):
|
|
13
|
+
"""Problem indicating an advanced library module failed to load."""
|
|
14
|
+
|
|
15
|
+
advanced_library_path: str
|
|
16
|
+
error_message: str
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def collate_problems_for_display(cls, instances: list[AdvancedLibraryLoadFailureProblem]) -> str:
|
|
20
|
+
"""Display advanced library load failure problem.
|
|
21
|
+
|
|
22
|
+
There should only be one instance per library since each LibraryInfo
|
|
23
|
+
is already associated with a specific library path.
|
|
24
|
+
"""
|
|
25
|
+
if len(instances) > 1:
|
|
26
|
+
logger.error(
|
|
27
|
+
"AdvancedLibraryLoadFailureProblem: Expected 1 instance but got %s. Each LibraryInfo should only have one AdvancedLibraryLoadFailureProblem.",
|
|
28
|
+
len(instances),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Use the first instance's details
|
|
32
|
+
problem = instances[0]
|
|
33
|
+
return f"Failed to load Advanced Library module from '{problem.advanced_library_path}': {problem.error_message}"
|
griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.retained_mode.managers.fitness_problems.libraries.library_problem import LibraryProblem
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AfterLibraryCallbackProblem(LibraryProblem):
|
|
13
|
+
"""Problem indicating an error calling the after_library_nodes_loaded callback."""
|
|
14
|
+
|
|
15
|
+
error_message: str
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def collate_problems_for_display(cls, instances: list[AfterLibraryCallbackProblem]) -> str:
|
|
19
|
+
"""Display after library callback problem.
|
|
20
|
+
|
|
21
|
+
There should only be one instance per library since there's only one
|
|
22
|
+
after_library_nodes_loaded callback per library.
|
|
23
|
+
"""
|
|
24
|
+
if len(instances) > 1:
|
|
25
|
+
logger.error(
|
|
26
|
+
"AfterLibraryCallbackProblem: Expected 1 instance but got %s. Each LibraryInfo should only have one AfterLibraryCallbackProblem.",
|
|
27
|
+
len(instances),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Use the first instance's error message
|
|
31
|
+
error_msg = instances[0].error_message
|
|
32
|
+
return f"Error calling after_library_nodes_loaded callback: {error_msg}"
|