autobyteus 1.1.2__py3-none-any.whl → 1.1.4__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.
- autobyteus/agent/agent.py +1 -1
- autobyteus/agent/bootstrap_steps/__init__.py +2 -0
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
- autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/agent_config.py +36 -5
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
- autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
- autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +68 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_worker.py +25 -34
- autobyteus/agent/shutdown_steps/__init__.py +17 -0
- autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
- autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
- autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
- autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
- autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
- autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
- autobyteus/agent/workspace/base_workspace.py +17 -2
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/cli_display.py +1 -1
- autobyteus/cli/workflow_tui/__init__.py +4 -0
- autobyteus/cli/workflow_tui/app.py +210 -0
- autobyteus/cli/workflow_tui/state.py +189 -0
- autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
- autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
- autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
- autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
- autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
- autobyteus/events/event_types.py +3 -0
- autobyteus/llm/api/lmstudio_llm.py +37 -0
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/llm_factory.py +2 -0
- autobyteus/llm/lmstudio_provider.py +89 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/base_tool.py +2 -0
- autobyteus/tools/bash/bash_executor.py +2 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
- autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
- autobyteus/tools/browser/standalone/navigate_to.py +2 -0
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
- autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
- autobyteus/tools/file/file_reader.py +36 -9
- autobyteus/tools/file/file_writer.py +37 -9
- autobyteus/tools/functional_tool.py +5 -4
- autobyteus/tools/image_downloader.py +2 -0
- autobyteus/tools/mcp/__init__.py +10 -7
- autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
- autobyteus/tools/mcp/config_service.py +1 -6
- autobyteus/tools/mcp/factory.py +12 -26
- autobyteus/tools/mcp/server/__init__.py +16 -0
- autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
- autobyteus/tools/mcp/server/proxy.py +36 -0
- autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
- autobyteus/tools/mcp/server_instance_manager.py +93 -0
- autobyteus/tools/mcp/tool.py +28 -46
- autobyteus/tools/mcp/tool_registrar.py +179 -0
- autobyteus/tools/mcp/types.py +10 -21
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +20 -7
- autobyteus/tools/registry/tool_registry.py +75 -28
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +14 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/workflow/agentic_workflow.py +93 -0
- autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
- autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
- autobyteus/workflow/context/__init__.py +17 -0
- autobyteus/workflow/context/team_manager.py +147 -0
- autobyteus/workflow/context/workflow_config.py +30 -0
- autobyteus/workflow/context/workflow_context.py +61 -0
- autobyteus/workflow/context/workflow_node_config.py +76 -0
- autobyteus/workflow/context/workflow_runtime_state.py +53 -0
- autobyteus/workflow/events/__init__.py +29 -0
- autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
- autobyteus/workflow/events/workflow_events.py +53 -0
- autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
- autobyteus/workflow/exceptions.py +8 -0
- autobyteus/workflow/factory/__init__.py +9 -0
- autobyteus/workflow/factory/workflow_factory.py +99 -0
- autobyteus/workflow/handlers/__init__.py +19 -0
- autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
- autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
- autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
- autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
- autobyteus/workflow/phases/__init__.py +11 -0
- autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
- autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
- autobyteus/workflow/runtime/__init__.py +13 -0
- autobyteus/workflow/runtime/workflow_runtime.py +82 -0
- autobyteus/workflow/runtime/workflow_worker.py +117 -0
- autobyteus/workflow/shutdown_steps/__init__.py +17 -0
- autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
- autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
- autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
- autobyteus/workflow/streaming/__init__.py +26 -0
- autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
- autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
- autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
- autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
- autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
- autobyteus/workflow/utils/__init__.py +9 -0
- autobyteus/workflow/utils/wait_for_idle.py +46 -0
- autobyteus/workflow/workflow_builder.py +151 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +260 -0
- examples/run_google_slides_agent.py +286 -0
- examples/run_mcp_browser_client.py +174 -0
- examples/run_mcp_google_slides_client.py +270 -0
- examples/run_mcp_list_tools.py +189 -0
- examples/run_poem_writer.py +274 -0
- examples/run_sqlite_agent.py +293 -0
- examples/workflow/__init__.py +1 -0
- examples/workflow/run_basic_research_workflow.py +189 -0
- examples/workflow/run_code_review_workflow.py +269 -0
- examples/workflow/run_debate_workflow.py +212 -0
- examples/workflow/run_workflow_with_tui.py +153 -0
- autobyteus/agent/context/agent_phase_manager.py +0 -264
- autobyteus/agent/context/phases.py +0 -49
- autobyteus/agent/group/__init__.py +0 -0
- autobyteus/agent/group/agent_group.py +0 -164
- autobyteus/agent/group/agent_group_context.py +0 -81
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
- autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
- autobyteus/agent/workflow/__init__.py +0 -11
- autobyteus/agent/workflow/agentic_workflow.py +0 -89
- autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
- autobyteus/tools/mcp/registrar.py +0 -323
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,149 +2,118 @@
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Optional
|
|
4
4
|
|
|
5
|
-
from autobyteus.agent.message.inter_agent_message import InterAgentMessage
|
|
6
5
|
from autobyteus.tools.base_tool import BaseTool
|
|
7
|
-
from autobyteus.
|
|
8
|
-
# Updated imports for schema
|
|
6
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
9
7
|
from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
10
|
-
|
|
8
|
+
from autobyteus.tools.tool_config import ToolConfig
|
|
9
|
+
# This import is for type hinting only and avoids circular dependencies at runtime
|
|
11
10
|
if TYPE_CHECKING:
|
|
12
11
|
from autobyteus.agent.context import AgentContext
|
|
13
|
-
from autobyteus.
|
|
12
|
+
from autobyteus.workflow.context.team_manager import TeamManager
|
|
13
|
+
from autobyteus.workflow.events.workflow_events import InterAgentMessageRequestEvent
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
class SendMessageTo(BaseTool):
|
|
18
18
|
"""
|
|
19
|
-
A tool for sending messages to other agents within the same
|
|
20
|
-
|
|
21
|
-
to
|
|
19
|
+
A tool for sending messages to other agents within the same workflow team.
|
|
20
|
+
This tool requires a TeamManager to be injected at runtime by the
|
|
21
|
+
workflow framework to enable communication with the parent orchestrator.
|
|
22
22
|
"""
|
|
23
|
-
TOOL_NAME = "SendMessageTo"
|
|
23
|
+
TOOL_NAME = "SendMessageTo"
|
|
24
|
+
CATEGORY = ToolCategory.AGENT_COMMUNICATION
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: Optional[ToolConfig] = None):
|
|
27
|
+
"""
|
|
28
|
+
Initializes the SendMessageTo tool. The TeamManager is injected separately
|
|
29
|
+
after instantiation.
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(config=config)
|
|
32
|
+
self._team_manager: Optional['TeamManager'] = None
|
|
33
|
+
logger.debug("SendMessageTo tool initialized. TeamManager is not yet injected.")
|
|
24
34
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
def set_team_manager(self, team_manager: 'TeamManager'):
|
|
36
|
+
"""Sets the TeamManager instance after the tool has been created."""
|
|
37
|
+
self._team_manager = team_manager
|
|
38
|
+
logger.debug(f"TeamManager was set on SendMessageTo instance post-creation.")
|
|
28
39
|
|
|
29
40
|
@classmethod
|
|
30
|
-
def get_name(cls) -> str:
|
|
41
|
+
def get_name(cls) -> str:
|
|
31
42
|
return cls.TOOL_NAME
|
|
32
43
|
|
|
33
44
|
@classmethod
|
|
34
45
|
def get_description(cls) -> str:
|
|
35
|
-
return ("Sends a message to another agent within the same
|
|
36
|
-
"
|
|
46
|
+
return ("Sends a message to another agent within the same team, starting them if necessary. "
|
|
47
|
+
"You must specify the recipient by their unique name as provided in your team manifest.")
|
|
37
48
|
|
|
38
49
|
@classmethod
|
|
39
50
|
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
40
51
|
schema = ParameterSchema()
|
|
41
52
|
schema.add_parameter(ParameterDefinition(
|
|
42
|
-
name="
|
|
53
|
+
name="recipient_name",
|
|
43
54
|
param_type=ParameterType.STRING,
|
|
44
|
-
description=
|
|
55
|
+
description='The unique name of the recipient agent (e.g., "Researcher", "Writer_1"). This MUST match a name from your team manifest.',
|
|
45
56
|
required=True
|
|
46
57
|
))
|
|
47
58
|
schema.add_parameter(ParameterDefinition(
|
|
48
59
|
name="content",
|
|
49
60
|
param_type=ParameterType.STRING,
|
|
50
|
-
description="The actual message
|
|
61
|
+
description="The actual message content or task instruction.",
|
|
51
62
|
required=True
|
|
52
63
|
))
|
|
53
64
|
schema.add_parameter(ParameterDefinition(
|
|
54
65
|
name="message_type",
|
|
55
|
-
param_type=ParameterType.STRING,
|
|
66
|
+
param_type=ParameterType.STRING,
|
|
56
67
|
description="Type of the message (e.g., TASK_ASSIGNMENT, CLARIFICATION). Custom types allowed.",
|
|
57
68
|
required=True
|
|
58
69
|
))
|
|
59
|
-
schema.add_parameter(ParameterDefinition(
|
|
60
|
-
name="recipient_agent_id",
|
|
61
|
-
param_type=ParameterType.STRING,
|
|
62
|
-
description='Optional. Specific ID of the recipient agent. If "unknown" or omitted, resolves by role.',
|
|
63
|
-
required=False,
|
|
64
|
-
default_value=None # Explicitly no default, truly optional
|
|
65
|
-
))
|
|
66
70
|
return schema
|
|
67
71
|
|
|
68
|
-
# tool_usage_xml() and tool_usage_json() are inherited from BaseTool
|
|
69
|
-
# get_config_schema() for instantiation defaults to None
|
|
70
|
-
|
|
71
72
|
async def _execute(self,
|
|
72
73
|
context: 'AgentContext',
|
|
73
|
-
|
|
74
|
+
recipient_name: str,
|
|
74
75
|
content: str,
|
|
75
|
-
message_type: str
|
|
76
|
-
recipient_agent_id: Optional[str] = None) -> str: # Named parameters
|
|
76
|
+
message_type: str) -> str:
|
|
77
77
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
Creates and dispatches a InterAgentMessageRequestEvent to the parent workflow
|
|
79
|
+
using the injected team_manager.
|
|
80
80
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
f"Recipient Role: '{recipient_role_name}', Recipient ID: '{recipient_agent_id}', Type: '{message_type}'.")
|
|
81
|
+
# Local import to break circular dependency at module load time.
|
|
82
|
+
from autobyteus.workflow.events.workflow_events import InterAgentMessageRequestEvent
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error_msg = f"Tool '{self.get_name()}' critical error: AgentGroupContext not found or invalid in AgentContext.custom_data for agent '{sender_agent_id}'. Cannot send message."
|
|
89
|
-
logger.error(error_msg)
|
|
84
|
+
if self._team_manager is None:
|
|
85
|
+
error_msg = "Critical error: SendMessageTo tool is not configured for workflow communication. It can only be used within a managed AgenticWorkflow."
|
|
86
|
+
logger.error(f"Agent '{context.agent_id}': {error_msg}")
|
|
90
87
|
return f"Error: {error_msg}"
|
|
91
|
-
|
|
92
|
-
group_context: AgentGroupContext = group_context_any # Type cast after check
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if not
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
logger.error(f"Tool '{self.get_name()}': {error_msg}")
|
|
108
|
-
return f"Error: {error_msg}"
|
|
89
|
+
# --- Input Validation ---
|
|
90
|
+
if not isinstance(recipient_name, str) or not recipient_name.strip():
|
|
91
|
+
error_msg = "Error: `recipient_name` must be a non-empty string."
|
|
92
|
+
logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
|
|
93
|
+
return error_msg
|
|
94
|
+
if not isinstance(content, str) or not content.strip():
|
|
95
|
+
error_msg = "Error: `content` must be a non-empty string."
|
|
96
|
+
logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
|
|
97
|
+
return error_msg
|
|
98
|
+
if not isinstance(message_type, str) or not message_type.strip():
|
|
99
|
+
error_msg = "Error: `message_type` must be a non-empty string."
|
|
100
|
+
logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
|
|
101
|
+
return error_msg
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
try:
|
|
125
|
-
message_to_send = InterAgentMessage.create_with_dynamic_message_type(
|
|
126
|
-
recipient_role_name=target_agent.context.config.role,
|
|
127
|
-
recipient_agent_id=target_agent.agent_id, # Use the definitively resolved agent ID
|
|
128
|
-
content=content,
|
|
129
|
-
message_type=message_type,
|
|
130
|
-
sender_agent_id=sender_agent_id
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
await target_agent.post_inter_agent_message(message_to_send)
|
|
134
|
-
success_msg = (f"Message successfully sent from '{sender_agent_id}' to agent "
|
|
135
|
-
f"'{target_agent.agent_id}' (Role: '{target_agent.context.config.role}').")
|
|
136
|
-
logger.info(f"Tool '{self.get_name()}': {success_msg}")
|
|
137
|
-
return success_msg
|
|
138
|
-
except ValueError as ve:
|
|
139
|
-
error_msg = f"Error creating message: {str(ve)}"
|
|
140
|
-
logger.error(f"Tool '{self.get_name()}': {error_msg}", exc_info=True)
|
|
141
|
-
return f"Error: {error_msg}"
|
|
142
|
-
except Exception as e:
|
|
143
|
-
error_msg = f"An unexpected error occurred while sending message: {str(e)}"
|
|
144
|
-
logger.error(f"Tool '{self.get_name()}': {error_msg}", exc_info=True)
|
|
145
|
-
return f"Error: {error_msg}"
|
|
103
|
+
sender_agent_id = context.agent_id
|
|
104
|
+
logger.info(f"Tool '{self.get_name()}': Agent '{sender_agent_id}' requesting to send message to '{recipient_name}'.")
|
|
105
|
+
|
|
106
|
+
# Create the event for the workflow to handle
|
|
107
|
+
event = InterAgentMessageRequestEvent(
|
|
108
|
+
sender_agent_id=sender_agent_id,
|
|
109
|
+
recipient_name=recipient_name,
|
|
110
|
+
content=content,
|
|
111
|
+
message_type=message_type
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Dispatch the event "up" to the workflow's event loop via the team manager
|
|
115
|
+
await self._team_manager.dispatch_inter_agent_message_request(event)
|
|
146
116
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
# then this method could be overridden. For now, assume auto-generated is fine.
|
|
117
|
+
success_msg = f"Message dispatch for recipient '{recipient_name}' has been successfully requested."
|
|
118
|
+
logger.info(f"Tool '{self.get_name()}': {success_msg}")
|
|
119
|
+
return success_msg
|
|
@@ -3,7 +3,8 @@ import inspect
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
|
|
6
|
-
from autobyteus.agent.
|
|
6
|
+
from autobyteus.agent.phases.manager import AgentPhaseManager
|
|
7
|
+
|
|
7
8
|
from .transition_info import PhaseTransitionInfo
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
@@ -15,6 +15,7 @@ from autobyteus.agent.events import (
|
|
|
15
15
|
from autobyteus.agent.events import WorkerEventDispatcher
|
|
16
16
|
from autobyteus.agent.runtime.agent_thread_pool_manager import AgentThreadPoolManager
|
|
17
17
|
from autobyteus.agent.bootstrap_steps.agent_bootstrapper import AgentBootstrapper
|
|
18
|
+
from autobyteus.agent.shutdown_steps import AgentShutdownOrchestrator
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from autobyteus.agent.context import AgentContext
|
|
@@ -176,41 +177,22 @@ class AgentWorker:
|
|
|
176
177
|
logger.error(f"Fatal error in AgentWorker '{agent_id}' async_run() loop: {e}", exc_info=True)
|
|
177
178
|
finally:
|
|
178
179
|
logger.info(f"AgentWorker '{agent_id}' async_run() loop has finished.")
|
|
180
|
+
# --- Shutdown sequence moved here, inside the original task's finally block ---
|
|
181
|
+
logger.info(f"AgentWorker '{agent_id}': Running shutdown sequence on worker loop.")
|
|
182
|
+
orchestrator = AgentShutdownOrchestrator()
|
|
183
|
+
cleanup_successful = await orchestrator.run(self.context)
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
if not cleanup_successful:
|
|
186
|
+
logger.critical(f"AgentWorker '{agent_id}': Shutdown resource cleanup failed. The agent may not have shut down cleanly.")
|
|
187
|
+
else:
|
|
188
|
+
logger.info(f"AgentWorker '{agent_id}': Shutdown resource cleanup completed successfully.")
|
|
189
|
+
logger.info(f"AgentWorker '{agent_id}': Shutdown sequence completed.")
|
|
185
190
|
|
|
186
|
-
async def _shutdown_sequence(self):
|
|
187
|
-
"""
|
|
188
|
-
The explicit, ordered shutdown sequence for the worker, executed on its own event loop.
|
|
189
|
-
"""
|
|
190
|
-
agent_id = self.context.agent_id
|
|
191
|
-
logger.info(f"AgentWorker '{agent_id}': Running shutdown sequence on worker loop.")
|
|
192
|
-
|
|
193
|
-
# 1. Clean up resources like the LLM instance.
|
|
194
|
-
if self.context.llm_instance and hasattr(self.context.llm_instance, 'cleanup'):
|
|
195
|
-
logger.info(f"AgentWorker '{agent_id}': Running LLM instance cleanup.")
|
|
196
|
-
try:
|
|
197
|
-
cleanup_func = self.context.llm_instance.cleanup
|
|
198
|
-
if asyncio.iscoroutinefunction(cleanup_func):
|
|
199
|
-
await cleanup_func()
|
|
200
|
-
else:
|
|
201
|
-
cleanup_func()
|
|
202
|
-
logger.info(f"AgentWorker '{agent_id}': LLM instance cleanup completed.")
|
|
203
|
-
except Exception as e:
|
|
204
|
-
logger.error(f"AgentWorker '{agent_id}': Error during LLM instance cleanup: {e}", exc_info=True)
|
|
205
|
-
|
|
206
|
-
# 2. Signal the main event loop to stop.
|
|
207
|
-
await self._signal_internal_stop()
|
|
208
|
-
logger.info(f"AgentWorker '{agent_id}': Shutdown sequence completed.")
|
|
209
191
|
|
|
210
192
|
async def stop(self, timeout: float = 10.0) -> None:
|
|
211
193
|
"""
|
|
212
|
-
Gracefully stops the worker by
|
|
213
|
-
|
|
194
|
+
Gracefully stops the worker by signaling its event loop to terminate,
|
|
195
|
+
then waiting for the thread to complete its cleanup and exit.
|
|
214
196
|
"""
|
|
215
197
|
if not self._is_active or self._stop_initiated:
|
|
216
198
|
return
|
|
@@ -219,18 +201,27 @@ class AgentWorker:
|
|
|
219
201
|
logger.info(f"AgentWorker '{agent_id}': Stop requested.")
|
|
220
202
|
self._stop_initiated = True
|
|
221
203
|
|
|
222
|
-
# Schedule
|
|
204
|
+
# Schedule a coroutine on the worker's loop to set the stop event.
|
|
223
205
|
if self.get_worker_loop():
|
|
224
|
-
|
|
206
|
+
def _coro_factory():
|
|
207
|
+
async def _signal_coro():
|
|
208
|
+
if self._async_stop_event and not self._async_stop_event.is_set():
|
|
209
|
+
self._async_stop_event.set()
|
|
210
|
+
if self.context.state.input_event_queues:
|
|
211
|
+
await self.context.state.input_event_queues.enqueue_internal_system_event(AgentStoppedEvent())
|
|
212
|
+
return _signal_coro()
|
|
213
|
+
|
|
214
|
+
future = self.schedule_coroutine_on_worker_loop(_coro_factory)
|
|
225
215
|
try:
|
|
226
|
-
# Wait for the
|
|
216
|
+
# Wait for the signal to be processed.
|
|
227
217
|
future.result(timeout=max(1.0, timeout-1))
|
|
228
218
|
except Exception as e:
|
|
229
|
-
logger.error(f"AgentWorker '{agent_id}': Error
|
|
219
|
+
logger.error(f"AgentWorker '{agent_id}': Error signaling stop event: {e}", exc_info=True)
|
|
230
220
|
|
|
231
221
|
# Wait for the main thread future to complete.
|
|
232
222
|
if self._thread_future:
|
|
233
223
|
try:
|
|
224
|
+
# FIX: Use asyncio.wait_for() to handle the timeout correctly.
|
|
234
225
|
await asyncio.wait_for(asyncio.wrap_future(self._thread_future), timeout=timeout)
|
|
235
226
|
logger.info(f"AgentWorker '{agent_id}': Worker thread has terminated.")
|
|
236
227
|
except asyncio.TimeoutError:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/shutdown_steps/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Defines individual, self-contained steps for the agent shutdown process.
|
|
4
|
+
These steps are orchestrated by the AgentShutdownOrchestrator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base_shutdown_step import BaseShutdownStep
|
|
8
|
+
from .llm_instance_cleanup_step import LLMInstanceCleanupStep
|
|
9
|
+
from .mcp_server_cleanup_step import McpServerCleanupStep
|
|
10
|
+
from .agent_shutdown_orchestrator import AgentShutdownOrchestrator
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BaseShutdownStep",
|
|
14
|
+
"LLMInstanceCleanupStep",
|
|
15
|
+
"McpServerCleanupStep",
|
|
16
|
+
"AgentShutdownOrchestrator",
|
|
17
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
4
|
+
|
|
5
|
+
from .base_shutdown_step import BaseShutdownStep
|
|
6
|
+
from .llm_instance_cleanup_step import LLMInstanceCleanupStep
|
|
7
|
+
from .mcp_server_cleanup_step import McpServerCleanupStep
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from autobyteus.agent.context import AgentContext
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class AgentShutdownOrchestrator:
|
|
15
|
+
"""
|
|
16
|
+
Orchestrates the agent's shutdown process by executing a sequence of
|
|
17
|
+
self-contained cleanup steps.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, steps: Optional[List[BaseShutdownStep]] = None):
|
|
20
|
+
"""
|
|
21
|
+
Initializes the AgentShutdownOrchestrator.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
steps: An optional list of shutdown steps to execute. If not provided,
|
|
25
|
+
a default sequence will be used.
|
|
26
|
+
"""
|
|
27
|
+
if steps is None:
|
|
28
|
+
self.shutdown_steps: List[BaseShutdownStep] = [
|
|
29
|
+
LLMInstanceCleanupStep(),
|
|
30
|
+
McpServerCleanupStep(),
|
|
31
|
+
]
|
|
32
|
+
logger.debug("AgentShutdownOrchestrator initialized with default steps.")
|
|
33
|
+
else:
|
|
34
|
+
self.shutdown_steps = steps
|
|
35
|
+
logger.debug(f"AgentShutdownOrchestrator initialized with {len(steps)} custom steps.")
|
|
36
|
+
|
|
37
|
+
async def run(self, context: 'AgentContext') -> bool:
|
|
38
|
+
"""
|
|
39
|
+
Executes the configured sequence of shutdown steps.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
context: The agent's context.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True if all steps completed successfully, False otherwise.
|
|
46
|
+
"""
|
|
47
|
+
agent_id = context.agent_id
|
|
48
|
+
logger.info(f"Agent '{agent_id}': AgentShutdownOrchestrator starting execution.")
|
|
49
|
+
|
|
50
|
+
for step_index, step_instance in enumerate(self.shutdown_steps):
|
|
51
|
+
step_name = step_instance.__class__.__name__
|
|
52
|
+
logger.debug(f"Agent '{agent_id}': Executing shutdown step {step_index + 1}/{len(self.shutdown_steps)}: {step_name}")
|
|
53
|
+
|
|
54
|
+
success = await step_instance.execute(context)
|
|
55
|
+
|
|
56
|
+
if not success:
|
|
57
|
+
error_message = f"Shutdown step {step_name} failed."
|
|
58
|
+
logger.error(f"Agent '{agent_id}': {error_message} Halting shutdown orchestration.")
|
|
59
|
+
# The step itself is responsible for detailed error logging.
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
logger.info(f"Agent '{agent_id}': All shutdown steps completed successfully.")
|
|
63
|
+
return True
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/shutdown_steps/base_shutdown_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from autobyteus.agent.context import AgentContext
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class BaseShutdownStep(ABC):
|
|
12
|
+
"""
|
|
13
|
+
Abstract base class for individual steps in the agent shutdown process.
|
|
14
|
+
Each step is responsible for a specific part of the cleanup and
|
|
15
|
+
for reporting its success or failure.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def execute(self, context: 'AgentContext') -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Executes the shutdown step.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
context: The agent's context, providing access to state and resources.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
True if the step completed successfully, False otherwise.
|
|
28
|
+
If False, the step is expected to have handled its own detailed logging.
|
|
29
|
+
"""
|
|
30
|
+
raise NotImplementedError("Subclasses must implement the 'execute' method.")
|
|
31
|
+
|
|
32
|
+
def __repr__(self) -> str:
|
|
33
|
+
return f"<{self.__class__.__name__}>"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_shutdown_step import BaseShutdownStep
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent.context import AgentContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class LLMInstanceCleanupStep(BaseShutdownStep):
|
|
14
|
+
"""
|
|
15
|
+
Shutdown step for cleaning up the agent's LLM instance.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
logger.debug("LLMInstanceCleanupStep initialized.")
|
|
19
|
+
|
|
20
|
+
async def execute(self, context: 'AgentContext') -> bool:
|
|
21
|
+
agent_id = context.agent_id
|
|
22
|
+
logger.info(f"Agent '{agent_id}': Executing LLMInstanceCleanupStep.")
|
|
23
|
+
|
|
24
|
+
llm_instance = context.llm_instance
|
|
25
|
+
if not llm_instance:
|
|
26
|
+
logger.debug(f"Agent '{agent_id}': No LLM instance found in context. Skipping cleanup.")
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
if hasattr(llm_instance, 'cleanup') and callable(getattr(llm_instance, 'cleanup')):
|
|
30
|
+
try:
|
|
31
|
+
logger.info(f"Agent '{agent_id}': Running LLM instance cleanup for '{llm_instance.__class__.__name__}'.")
|
|
32
|
+
cleanup_func = llm_instance.cleanup
|
|
33
|
+
if asyncio.iscoroutinefunction(cleanup_func):
|
|
34
|
+
await cleanup_func()
|
|
35
|
+
else:
|
|
36
|
+
cleanup_func()
|
|
37
|
+
logger.info(f"Agent '{agent_id}': LLM instance cleanup completed successfully.")
|
|
38
|
+
return True
|
|
39
|
+
except Exception as e:
|
|
40
|
+
error_message = f"Agent '{agent_id}': Error during LLM instance cleanup: {e}"
|
|
41
|
+
logger.error(error_message, exc_info=True)
|
|
42
|
+
return False
|
|
43
|
+
else:
|
|
44
|
+
logger.debug(f"Agent '{agent_id}': LLM instance of type '{llm_instance.__class__.__name__}' does not have a 'cleanup' method. Skipping.")
|
|
45
|
+
return True
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .base_shutdown_step import BaseShutdownStep
|
|
6
|
+
from autobyteus.tools.mcp.server_instance_manager import McpServerInstanceManager
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent.context import AgentContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class McpServerCleanupStep(BaseShutdownStep):
|
|
14
|
+
"""
|
|
15
|
+
Shutdown step for cleaning up all MCP server instances associated with an agent.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._instance_manager = McpServerInstanceManager()
|
|
19
|
+
logger.debug("McpServerCleanupStep initialized.")
|
|
20
|
+
|
|
21
|
+
async def execute(self, context: 'AgentContext') -> bool:
|
|
22
|
+
agent_id = context.agent_id
|
|
23
|
+
logger.info(f"Agent '{agent_id}': Executing McpServerCleanupStep.")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
await self._instance_manager.cleanup_mcp_server_instances_for_agent(agent_id)
|
|
27
|
+
logger.info(f"Agent '{agent_id}': MCP server instance cleanup completed successfully.")
|
|
28
|
+
return True
|
|
29
|
+
except Exception as e:
|
|
30
|
+
error_message = f"Agent '{agent_id}': Critical failure during McpServerCleanupStep: {e}"
|
|
31
|
+
logger.error(error_message, exc_info=True)
|
|
32
|
+
return False
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/tool_execution_result_processor/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Components for processing tool execution results before they are sent to the LLM.
|
|
4
|
+
"""
|
|
5
|
+
from .base_processor import BaseToolExecutionResultProcessor
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseToolExecutionResultProcessor",
|
|
9
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/tool_execution_result_processor/base_processor.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .processor_meta import ToolExecutionResultProcessorMeta
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent.context import AgentContext
|
|
10
|
+
from autobyteus.agent.events import ToolResultEvent
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class BaseToolExecutionResultProcessor(ABC, metaclass=ToolExecutionResultProcessorMeta):
|
|
15
|
+
"""
|
|
16
|
+
Abstract base class for processors that can modify a tool's execution result.
|
|
17
|
+
These processors are applied after a tool runs but before its result is formatted
|
|
18
|
+
for the LLM.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_name(cls) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Returns the unique registration name for this processor.
|
|
25
|
+
Defaults to the class name.
|
|
26
|
+
"""
|
|
27
|
+
return cls.__name__
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
async def process(self,
|
|
31
|
+
event: 'ToolResultEvent',
|
|
32
|
+
context: 'AgentContext') -> 'ToolResultEvent':
|
|
33
|
+
"""
|
|
34
|
+
Processes the given ToolResultEvent.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
event: The ToolResultEvent containing the tool's output or error.
|
|
38
|
+
context: The agent's context, providing access to config and state.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The processed (potentially modified) ToolResultEvent.
|
|
42
|
+
"""
|
|
43
|
+
raise NotImplementedError("Subclasses must implement the 'process' method.")
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
return f"<{self.__class__.__name__}>"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/tool_execution_result_processor/processor_definition.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Type, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .base_processor import BaseToolExecutionResultProcessor
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class ToolExecutionResultProcessorDefinition:
|
|
11
|
+
"""
|
|
12
|
+
Represents the definition of a tool execution result processor.
|
|
13
|
+
Contains its registered name and the class itself.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, name: str, processor_class: Type['BaseToolExecutionResultProcessor']):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the ToolExecutionResultProcessorDefinition.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: The unique registered name of the processor.
|
|
21
|
+
processor_class: The class of the tool execution result processor.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If name is empty or processor_class is not a type.
|
|
25
|
+
"""
|
|
26
|
+
if not name or not isinstance(name, str):
|
|
27
|
+
raise ValueError("Tool Execution Result Processor name must be a non-empty string.")
|
|
28
|
+
if not isinstance(processor_class, type):
|
|
29
|
+
raise ValueError("processor_class must be a class type.")
|
|
30
|
+
|
|
31
|
+
self.name: str = name
|
|
32
|
+
self.processor_class: Type['BaseToolExecutionResultProcessor'] = processor_class
|
|
33
|
+
logger.debug(f"ToolExecutionResultProcessorDefinition created: name='{name}', class='{processor_class.__name__}'.")
|
|
34
|
+
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return f"<ToolExecutionResultProcessorDefinition name='{self.name}', class='{self.processor_class.__name__}'>"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/tool_execution_result_processor/processor_meta.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABCMeta
|
|
4
|
+
|
|
5
|
+
from .processor_registry import default_tool_execution_result_processor_registry
|
|
6
|
+
from .processor_definition import ToolExecutionResultProcessorDefinition
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class ToolExecutionResultProcessorMeta(ABCMeta):
|
|
11
|
+
"""
|
|
12
|
+
Metaclass for BaseToolExecutionResultProcessor that automatically registers
|
|
13
|
+
concrete processor subclasses with the default registry.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(cls, name, bases, dct):
|
|
16
|
+
super().__init__(name, bases, dct)
|
|
17
|
+
|
|
18
|
+
if name == 'BaseToolExecutionResultProcessor' or getattr(cls, "__abstractmethods__", None):
|
|
19
|
+
logger.debug(f"Skipping registration for abstract tool execution result processor class: {name}")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
processor_name = cls.get_name()
|
|
24
|
+
|
|
25
|
+
if not processor_name or not isinstance(processor_name, str):
|
|
26
|
+
logger.error(f"Tool execution result processor class {name} must return a valid string from get_name(). Skipping.")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
definition = ToolExecutionResultProcessorDefinition(name=processor_name, processor_class=cls)
|
|
30
|
+
default_tool_execution_result_processor_registry.register_processor(definition)
|
|
31
|
+
logger.info(f"Auto-registered tool execution result processor: '{processor_name}' from class {name}.")
|
|
32
|
+
|
|
33
|
+
except AttributeError as e:
|
|
34
|
+
logger.error(f"Tool execution result processor class {name} is missing 'get_name' method ({e}). Skipping registration.")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.error(f"Failed to auto-register tool execution result processor class {name}: {e}", exc_info=True)
|