autobyteus 1.1.3__py3-none-any.whl → 1.1.5__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/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/__init__.py +4 -2
- autobyteus/agent/context/agent_config.py +35 -8
- autobyteus/agent/context/agent_context_registry.py +73 -0
- autobyteus/agent/events/notifiers.py +4 -0
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +8 -3
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +19 -19
- 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 +16 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +74 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_runtime.py +10 -2
- autobyteus/agent/runtime/agent_worker.py +1 -0
- autobyteus/agent/sender_type.py +15 -0
- autobyteus/agent/streaming/agent_event_stream.py +6 -0
- autobyteus/agent/streaming/stream_event_payloads.py +12 -0
- autobyteus/agent/streaming/stream_events.py +3 -0
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +7 -4
- 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/agent_team/__init__.py +1 -0
- autobyteus/agent_team/agent_team.py +93 -0
- autobyteus/agent_team/agent_team_builder.py +184 -0
- autobyteus/agent_team/base_agent_team.py +86 -0
- autobyteus/agent_team/bootstrap_steps/__init__.py +24 -0
- autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +73 -0
- autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +54 -0
- autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +25 -0
- autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +23 -0
- autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +85 -0
- autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +51 -0
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +45 -0
- autobyteus/agent_team/context/__init__.py +17 -0
- autobyteus/agent_team/context/agent_team_config.py +33 -0
- autobyteus/agent_team/context/agent_team_context.py +61 -0
- autobyteus/agent_team/context/agent_team_runtime_state.py +56 -0
- autobyteus/agent_team/context/team_manager.py +147 -0
- autobyteus/agent_team/context/team_node_config.py +76 -0
- autobyteus/agent_team/events/__init__.py +29 -0
- autobyteus/agent_team/events/agent_team_event_dispatcher.py +39 -0
- autobyteus/agent_team/events/agent_team_events.py +53 -0
- autobyteus/agent_team/events/agent_team_input_event_queue_manager.py +21 -0
- autobyteus/agent_team/exceptions.py +8 -0
- autobyteus/agent_team/factory/__init__.py +9 -0
- autobyteus/agent_team/factory/agent_team_factory.py +99 -0
- autobyteus/agent_team/handlers/__init__.py +19 -0
- autobyteus/agent_team/handlers/agent_team_event_handler_registry.py +23 -0
- autobyteus/agent_team/handlers/base_agent_team_event_handler.py +16 -0
- autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +27 -0
- autobyteus/agent_team/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +48 -0
- autobyteus/agent_team/phases/__init__.py +11 -0
- autobyteus/agent_team/phases/agent_team_operational_phase.py +19 -0
- autobyteus/agent_team/phases/agent_team_phase_manager.py +48 -0
- autobyteus/agent_team/runtime/__init__.py +13 -0
- autobyteus/agent_team/runtime/agent_team_runtime.py +82 -0
- autobyteus/agent_team/runtime/agent_team_worker.py +117 -0
- autobyteus/agent_team/shutdown_steps/__init__.py +17 -0
- autobyteus/agent_team/shutdown_steps/agent_team_shutdown_orchestrator.py +35 -0
- autobyteus/agent_team/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/agent_team/shutdown_steps/base_agent_team_shutdown_step.py +16 -0
- autobyteus/agent_team/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/agent_team/shutdown_steps/sub_team_shutdown_step.py +41 -0
- autobyteus/agent_team/streaming/__init__.py +26 -0
- autobyteus/agent_team/streaming/agent_event_bridge.py +48 -0
- autobyteus/agent_team/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/agent_team/streaming/agent_team_event_notifier.py +64 -0
- autobyteus/agent_team/streaming/agent_team_event_stream.py +33 -0
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +32 -0
- autobyteus/agent_team/streaming/agent_team_stream_events.py +56 -0
- autobyteus/agent_team/streaming/team_event_bridge.py +50 -0
- autobyteus/agent_team/task_notification/__init__.py +11 -0
- autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +164 -0
- autobyteus/agent_team/task_notification/task_notification_mode.py +24 -0
- autobyteus/agent_team/utils/__init__.py +9 -0
- autobyteus/agent_team/utils/wait_for_idle.py +46 -0
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/agent_team_tui/__init__.py +4 -0
- autobyteus/cli/agent_team_tui/app.py +210 -0
- autobyteus/cli/agent_team_tui/state.py +180 -0
- autobyteus/cli/agent_team_tui/widgets/__init__.py +6 -0
- autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +320 -0
- autobyteus/cli/agent_team_tui/widgets/logo.py +20 -0
- autobyteus/cli/agent_team_tui/widgets/renderables.py +77 -0
- autobyteus/cli/agent_team_tui/widgets/shared.py +60 -0
- autobyteus/cli/agent_team_tui/widgets/status_bar.py +14 -0
- autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +82 -0
- 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 +8 -0
- autobyteus/llm/api/autobyteus_llm.py +11 -12
- autobyteus/llm/api/lmstudio_llm.py +34 -0
- autobyteus/llm/api/ollama_llm.py +8 -13
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/autobyteus_provider.py +73 -46
- autobyteus/llm/llm_factory.py +103 -139
- autobyteus/llm/lmstudio_provider.py +104 -0
- autobyteus/llm/models.py +83 -53
- autobyteus/llm/ollama_provider.py +69 -61
- autobyteus/llm/ollama_provider_resolver.py +1 -0
- autobyteus/llm/providers.py +13 -12
- autobyteus/llm/runtimes.py +11 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/task_management/__init__.py +43 -0
- autobyteus/task_management/base_task_board.py +68 -0
- autobyteus/task_management/converters/__init__.py +11 -0
- autobyteus/task_management/converters/task_board_converter.py +64 -0
- autobyteus/task_management/converters/task_plan_converter.py +48 -0
- autobyteus/task_management/deliverable.py +16 -0
- autobyteus/task_management/deliverables/__init__.py +8 -0
- autobyteus/task_management/deliverables/file_deliverable.py +15 -0
- autobyteus/task_management/events.py +27 -0
- autobyteus/task_management/in_memory_task_board.py +126 -0
- autobyteus/task_management/schemas/__init__.py +15 -0
- autobyteus/task_management/schemas/deliverable_schema.py +13 -0
- autobyteus/task_management/schemas/plan_definition.py +35 -0
- autobyteus/task_management/schemas/task_status_report.py +27 -0
- autobyteus/task_management/task_plan.py +110 -0
- autobyteus/task_management/tools/__init__.py +14 -0
- autobyteus/task_management/tools/get_task_board_status.py +68 -0
- autobyteus/task_management/tools/publish_task_plan.py +113 -0
- autobyteus/task_management/tools/update_task_status.py +135 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/bash/bash_executor.py +61 -15
- 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/config_service.py +63 -58
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +14 -2
- autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +14 -2
- autobyteus/tools/mcp/server_instance_manager.py +30 -4
- autobyteus/tools/mcp/tool_registrar.py +106 -51
- autobyteus/tools/parameter_schema.py +17 -11
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +36 -37
- autobyteus/tools/registry/tool_registry.py +50 -2
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +15 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +78 -3
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +23 -3
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +6 -0
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +7 -0
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +6 -4
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +23 -7
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +14 -25
- autobyteus/tools/usage/providers/__init__.py +2 -12
- autobyteus/tools/usage/providers/tool_manifest_provider.py +36 -29
- autobyteus/tools/usage/registries/__init__.py +7 -12
- autobyteus/tools/usage/registries/tool_formatter_pair.py +15 -0
- autobyteus/tools/usage/registries/tool_formatting_registry.py +58 -0
- autobyteus/tools/usage/registries/tool_usage_parser_registry.py +55 -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.3.dist-info → autobyteus-1.1.5.dist-info}/METADATA +16 -14
- autobyteus-1.1.5.dist-info/RECORD +455 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/agent_team/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +262 -0
- examples/run_google_slides_agent.py +287 -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 +284 -0
- examples/run_sqlite_agent.py +295 -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/__init__.py +0 -16
- autobyteus/tools/mcp/call_handlers/base_handler.py +0 -40
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +0 -76
- autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +0 -55
- autobyteus/tools/mcp/registrar.py +0 -202
- autobyteus/tools/usage/providers/json_example_provider.py +0 -32
- autobyteus/tools/usage/providers/json_schema_provider.py +0 -35
- autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +0 -28
- autobyteus/tools/usage/providers/xml_example_provider.py +0 -28
- autobyteus/tools/usage/providers/xml_schema_provider.py +0 -29
- autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +0 -26
- autobyteus/tools/usage/registries/json_example_formatter_registry.py +0 -51
- autobyteus/tools/usage/registries/json_schema_formatter_registry.py +0 -51
- autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +0 -42
- autobyteus/tools/usage/registries/xml_example_formatter_registry.py +0 -30
- autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +0 -33
- autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +0 -30
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- autobyteus-1.1.3.dist-info/RECORD +0 -312
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/context/agent_phase_manager.py
|
|
2
|
-
import asyncio
|
|
3
|
-
import logging
|
|
4
|
-
from typing import TYPE_CHECKING, Optional, Dict, Any
|
|
5
|
-
|
|
6
|
-
from autobyteus.agent.phases import AgentOperationalPhase, phase_transition
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from autobyteus.agent.context.agent_context import AgentContext
|
|
10
|
-
from autobyteus.agent.tool_invocation import ToolInvocation
|
|
11
|
-
from autobyteus.agent.events.notifiers import AgentExternalEventNotifier
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
class AgentPhaseManager:
|
|
17
|
-
"""
|
|
18
|
-
Manages the operational phase of an agent, uses an AgentExternalEventNotifier
|
|
19
|
-
to signal phase changes externally, and executes phase transition hooks.
|
|
20
|
-
"""
|
|
21
|
-
def __init__(self, context: 'AgentContext', notifier: 'AgentExternalEventNotifier'):
|
|
22
|
-
self.context: 'AgentContext' = context
|
|
23
|
-
self.notifier: 'AgentExternalEventNotifier' = notifier
|
|
24
|
-
|
|
25
|
-
self.context.current_phase = AgentOperationalPhase.UNINITIALIZED
|
|
26
|
-
|
|
27
|
-
logger.debug(f"AgentPhaseManager initialized for agent_id '{self.context.agent_id}'. "
|
|
28
|
-
f"Initial phase: {self.context.current_phase.value}. Uses provided notifier.")
|
|
29
|
-
|
|
30
|
-
async def _execute_hooks(self, old_phase: AgentOperationalPhase, new_phase: AgentOperationalPhase):
|
|
31
|
-
"""Asynchronously executes hooks that match the given phase transition."""
|
|
32
|
-
hooks_to_run = [
|
|
33
|
-
hook for hook in self.context.config.phase_hooks
|
|
34
|
-
if hook.source_phase == old_phase and hook.target_phase == new_phase
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
if not hooks_to_run:
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
hook_names = [hook.__class__.__name__ for hook in hooks_to_run]
|
|
41
|
-
logger.info(f"Agent '{self.context.agent_id}': Executing {len(hooks_to_run)} hooks for transition "
|
|
42
|
-
f"'{old_phase.value}' -> '{new_phase.value}': {hook_names}")
|
|
43
|
-
|
|
44
|
-
for hook in hooks_to_run:
|
|
45
|
-
try:
|
|
46
|
-
await hook.execute(self.context)
|
|
47
|
-
logger.debug(f"Agent '{self.context.agent_id}': Hook '{hook.__class__.__name__}' executed successfully.")
|
|
48
|
-
except Exception as e:
|
|
49
|
-
logger.error(f"Agent '{self.context.agent_id}': Error executing phase transition hook "
|
|
50
|
-
f"'{hook.__class__.__name__}' for '{old_phase.value}' -> '{new_phase.value}': {e}",
|
|
51
|
-
exc_info=True)
|
|
52
|
-
# We log the error but do not halt the agent's phase transition.
|
|
53
|
-
|
|
54
|
-
async def _transition_phase(self, new_phase: AgentOperationalPhase,
|
|
55
|
-
notify_method_name: str,
|
|
56
|
-
additional_data: Optional[Dict[str, Any]] = None):
|
|
57
|
-
"""
|
|
58
|
-
Private async helper to change the agent's phase, execute hooks, and then
|
|
59
|
-
call the appropriate notifier method. Hooks are now awaited.
|
|
60
|
-
"""
|
|
61
|
-
if not isinstance(new_phase, AgentOperationalPhase):
|
|
62
|
-
logger.error(f"AgentPhaseManager for '{self.context.agent_id}' received invalid type for new_phase: {type(new_phase)}. Must be AgentOperationalPhase.")
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
old_phase = self.context.current_phase
|
|
66
|
-
|
|
67
|
-
if old_phase == new_phase:
|
|
68
|
-
logger.debug(f"AgentPhaseManager for '{self.context.agent_id}': already in phase {new_phase.value}. No transition.")
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
logger.info(f"Agent '{self.context.agent_id}' phase transitioning from {old_phase.value} to {new_phase.value}.")
|
|
72
|
-
self.context.current_phase = new_phase
|
|
73
|
-
|
|
74
|
-
# Execute and wait for hooks to complete *before* notifying externally.
|
|
75
|
-
await self._execute_hooks(old_phase, new_phase)
|
|
76
|
-
|
|
77
|
-
notifier_method = getattr(self.notifier, notify_method_name, None)
|
|
78
|
-
if notifier_method and callable(notifier_method):
|
|
79
|
-
notify_args = {"old_phase": old_phase}
|
|
80
|
-
if additional_data:
|
|
81
|
-
notify_args.update(additional_data)
|
|
82
|
-
|
|
83
|
-
notifier_method(**notify_args)
|
|
84
|
-
else:
|
|
85
|
-
logger.error(f"AgentPhaseManager for '{self.context.agent_id}': Notifier method '{notify_method_name}' not found or not callable on {type(self.notifier).__name__}.")
|
|
86
|
-
|
|
87
|
-
@phase_transition(
|
|
88
|
-
source_phases=[AgentOperationalPhase.SHUTDOWN_COMPLETE, AgentOperationalPhase.ERROR],
|
|
89
|
-
target_phase=AgentOperationalPhase.UNINITIALIZED,
|
|
90
|
-
description="Triggered when the agent runtime is started or restarted after being in a terminal state."
|
|
91
|
-
)
|
|
92
|
-
async def notify_runtime_starting_and_uninitialized(self) -> None:
|
|
93
|
-
if self.context.current_phase == AgentOperationalPhase.UNINITIALIZED:
|
|
94
|
-
await self._transition_phase(AgentOperationalPhase.UNINITIALIZED, "notify_phase_uninitialized_entered")
|
|
95
|
-
elif self.context.current_phase.is_terminal():
|
|
96
|
-
await self._transition_phase(AgentOperationalPhase.UNINITIALIZED, "notify_phase_uninitialized_entered")
|
|
97
|
-
else:
|
|
98
|
-
logger.warning(f"Agent '{self.context.agent_id}' notify_runtime_starting_and_uninitialized called in unexpected phase: {self.context.current_phase.value}")
|
|
99
|
-
|
|
100
|
-
@phase_transition(
|
|
101
|
-
source_phases=[AgentOperationalPhase.UNINITIALIZED],
|
|
102
|
-
target_phase=AgentOperationalPhase.BOOTSTRAPPING,
|
|
103
|
-
description="Occurs when the agent's internal bootstrapping process begins."
|
|
104
|
-
)
|
|
105
|
-
async def notify_bootstrapping_started(self) -> None:
|
|
106
|
-
await self._transition_phase(AgentOperationalPhase.BOOTSTRAPPING, "notify_phase_bootstrapping_started")
|
|
107
|
-
|
|
108
|
-
@phase_transition(
|
|
109
|
-
source_phases=[AgentOperationalPhase.BOOTSTRAPPING],
|
|
110
|
-
target_phase=AgentOperationalPhase.IDLE,
|
|
111
|
-
description="Occurs when the agent successfully completes bootstrapping and is ready for input."
|
|
112
|
-
)
|
|
113
|
-
async def notify_initialization_complete(self) -> None:
|
|
114
|
-
if self.context.current_phase.is_initializing() or self.context.current_phase == AgentOperationalPhase.UNINITIALIZED:
|
|
115
|
-
# This will now be a BOOTSTRAPPING -> IDLE transition
|
|
116
|
-
await self._transition_phase(AgentOperationalPhase.IDLE, "notify_phase_idle_entered")
|
|
117
|
-
else:
|
|
118
|
-
logger.warning(f"Agent '{self.context.agent_id}' notify_initialization_complete called in unexpected phase: {self.context.current_phase.value}")
|
|
119
|
-
|
|
120
|
-
@phase_transition(
|
|
121
|
-
source_phases=[
|
|
122
|
-
AgentOperationalPhase.IDLE, AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
|
|
123
|
-
AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.EXECUTING_TOOL,
|
|
124
|
-
AgentOperationalPhase.TOOL_DENIED
|
|
125
|
-
],
|
|
126
|
-
target_phase=AgentOperationalPhase.PROCESSING_USER_INPUT,
|
|
127
|
-
description="Fires when the agent begins processing a new user message or inter-agent message."
|
|
128
|
-
)
|
|
129
|
-
async def notify_processing_input_started(self, trigger_info: Optional[str] = None) -> None:
|
|
130
|
-
if self.context.current_phase in [AgentOperationalPhase.IDLE, AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.EXECUTING_TOOL, AgentOperationalPhase.TOOL_DENIED]:
|
|
131
|
-
data = {"trigger_info": trigger_info} if trigger_info else {}
|
|
132
|
-
await self._transition_phase(AgentOperationalPhase.PROCESSING_USER_INPUT, "notify_phase_processing_user_input_started", additional_data=data)
|
|
133
|
-
elif self.context.current_phase == AgentOperationalPhase.PROCESSING_USER_INPUT:
|
|
134
|
-
logger.debug(f"Agent '{self.context.agent_id}' already in PROCESSING_USER_INPUT phase.")
|
|
135
|
-
else:
|
|
136
|
-
logger.warning(f"Agent '{self.context.agent_id}' notify_processing_input_started called in unexpected phase: {self.context.current_phase.value}")
|
|
137
|
-
|
|
138
|
-
@phase_transition(
|
|
139
|
-
source_phases=[AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.PROCESSING_TOOL_RESULT],
|
|
140
|
-
target_phase=AgentOperationalPhase.AWAITING_LLM_RESPONSE,
|
|
141
|
-
description="Occurs just before the agent makes a call to the LLM."
|
|
142
|
-
)
|
|
143
|
-
async def notify_awaiting_llm_response(self) -> None:
|
|
144
|
-
await self._transition_phase(AgentOperationalPhase.AWAITING_LLM_RESPONSE, "notify_phase_awaiting_llm_response_started")
|
|
145
|
-
|
|
146
|
-
@phase_transition(
|
|
147
|
-
source_phases=[AgentOperationalPhase.AWAITING_LLM_RESPONSE],
|
|
148
|
-
target_phase=AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
|
|
149
|
-
description="Occurs after the agent has received a complete response from the LLM and begins to analyze it."
|
|
150
|
-
)
|
|
151
|
-
async def notify_analyzing_llm_response(self) -> None:
|
|
152
|
-
await self._transition_phase(AgentOperationalPhase.ANALYZING_LLM_RESPONSE, "notify_phase_analyzing_llm_response_started")
|
|
153
|
-
|
|
154
|
-
@phase_transition(
|
|
155
|
-
source_phases=[AgentOperationalPhase.ANALYZING_LLM_RESPONSE],
|
|
156
|
-
target_phase=AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
|
|
157
|
-
description="Occurs if the agent proposes a tool use that requires manual user approval."
|
|
158
|
-
)
|
|
159
|
-
async def notify_tool_execution_pending_approval(self, tool_invocation: 'ToolInvocation') -> None:
|
|
160
|
-
await self._transition_phase(AgentOperationalPhase.AWAITING_TOOL_APPROVAL, "notify_phase_awaiting_tool_approval_started")
|
|
161
|
-
|
|
162
|
-
@phase_transition(
|
|
163
|
-
source_phases=[AgentOperationalPhase.AWAITING_TOOL_APPROVAL],
|
|
164
|
-
target_phase=AgentOperationalPhase.EXECUTING_TOOL,
|
|
165
|
-
description="Occurs after a pending tool use has been approved and is about to be executed."
|
|
166
|
-
)
|
|
167
|
-
async def notify_tool_execution_resumed_after_approval(self, approved: bool, tool_name: Optional[str]) -> None:
|
|
168
|
-
if approved and tool_name:
|
|
169
|
-
await self._transition_phase(AgentOperationalPhase.EXECUTING_TOOL, "notify_phase_executing_tool_started", additional_data={"tool_name": tool_name})
|
|
170
|
-
else:
|
|
171
|
-
logger.info(f"Agent '{self.context.agent_id}' tool execution denied for '{tool_name}'. Transitioning to allow LLM to process denial.")
|
|
172
|
-
await self.notify_tool_denied(tool_name)
|
|
173
|
-
|
|
174
|
-
@phase_transition(
|
|
175
|
-
source_phases=[AgentOperationalPhase.AWAITING_TOOL_APPROVAL],
|
|
176
|
-
target_phase=AgentOperationalPhase.TOOL_DENIED,
|
|
177
|
-
description="Occurs after a pending tool use has been denied by the user."
|
|
178
|
-
)
|
|
179
|
-
async def notify_tool_denied(self, tool_name: Optional[str]) -> None:
|
|
180
|
-
"""Notifies that a tool execution has been denied."""
|
|
181
|
-
await self._transition_phase(
|
|
182
|
-
AgentOperationalPhase.TOOL_DENIED,
|
|
183
|
-
"notify_phase_tool_denied_started",
|
|
184
|
-
additional_data={"tool_name": tool_name, "denial_for_tool": tool_name}
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
@phase_transition(
|
|
188
|
-
source_phases=[AgentOperationalPhase.ANALYZING_LLM_RESPONSE],
|
|
189
|
-
target_phase=AgentOperationalPhase.EXECUTING_TOOL,
|
|
190
|
-
description="Occurs when an agent with auto-approval executes a tool."
|
|
191
|
-
)
|
|
192
|
-
async def notify_tool_execution_started(self, tool_name: str) -> None:
|
|
193
|
-
await self._transition_phase(AgentOperationalPhase.EXECUTING_TOOL, "notify_phase_executing_tool_started", additional_data={"tool_name": tool_name})
|
|
194
|
-
|
|
195
|
-
@phase_transition(
|
|
196
|
-
source_phases=[AgentOperationalPhase.EXECUTING_TOOL],
|
|
197
|
-
target_phase=AgentOperationalPhase.PROCESSING_TOOL_RESULT,
|
|
198
|
-
description="Fires after a tool has finished executing and the agent begins processing its result."
|
|
199
|
-
)
|
|
200
|
-
async def notify_processing_tool_result(self, tool_name: str) -> None:
|
|
201
|
-
await self._transition_phase(AgentOperationalPhase.PROCESSING_TOOL_RESULT, "notify_phase_processing_tool_result_started", additional_data={"tool_name": tool_name})
|
|
202
|
-
|
|
203
|
-
@phase_transition(
|
|
204
|
-
source_phases=[
|
|
205
|
-
AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
|
|
206
|
-
AgentOperationalPhase.PROCESSING_TOOL_RESULT
|
|
207
|
-
],
|
|
208
|
-
target_phase=AgentOperationalPhase.IDLE,
|
|
209
|
-
description="Occurs when an agent completes a processing cycle and is waiting for new input."
|
|
210
|
-
)
|
|
211
|
-
async def notify_processing_complete_and_idle(self) -> None:
|
|
212
|
-
if not self.context.current_phase.is_terminal() and self.context.current_phase != AgentOperationalPhase.IDLE:
|
|
213
|
-
await self._transition_phase(AgentOperationalPhase.IDLE, "notify_phase_idle_entered")
|
|
214
|
-
elif self.context.current_phase == AgentOperationalPhase.IDLE:
|
|
215
|
-
logger.debug(f"Agent '{self.context.agent_id}' processing complete, already IDLE.")
|
|
216
|
-
else:
|
|
217
|
-
logger.warning(f"Agent '{self.context.agent_id}' notify_processing_complete_and_idle called in unexpected phase: {self.context.current_phase.value}")
|
|
218
|
-
|
|
219
|
-
@phase_transition(
|
|
220
|
-
source_phases=[
|
|
221
|
-
AgentOperationalPhase.UNINITIALIZED, AgentOperationalPhase.BOOTSTRAPPING, AgentOperationalPhase.IDLE,
|
|
222
|
-
AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.AWAITING_LLM_RESPONSE,
|
|
223
|
-
AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
|
|
224
|
-
AgentOperationalPhase.TOOL_DENIED, AgentOperationalPhase.EXECUTING_TOOL,
|
|
225
|
-
AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.SHUTTING_DOWN
|
|
226
|
-
],
|
|
227
|
-
target_phase=AgentOperationalPhase.ERROR,
|
|
228
|
-
description="A catch-all transition that can occur from any non-terminal state if an unrecoverable error happens."
|
|
229
|
-
)
|
|
230
|
-
async def notify_error_occurred(self, error_message: str, error_details: Optional[str] = None) -> None:
|
|
231
|
-
if self.context.current_phase != AgentOperationalPhase.ERROR:
|
|
232
|
-
data = {"error_message": error_message, "error_details": error_details}
|
|
233
|
-
await self._transition_phase(AgentOperationalPhase.ERROR, "notify_phase_error_entered", additional_data=data)
|
|
234
|
-
else:
|
|
235
|
-
logger.debug(f"Agent '{self.context.agent_id}' already in ERROR phase when another error notified: {error_message}")
|
|
236
|
-
|
|
237
|
-
@phase_transition(
|
|
238
|
-
source_phases=[
|
|
239
|
-
AgentOperationalPhase.UNINITIALIZED, AgentOperationalPhase.BOOTSTRAPPING, AgentOperationalPhase.IDLE,
|
|
240
|
-
AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.AWAITING_LLM_RESPONSE,
|
|
241
|
-
AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
|
|
242
|
-
AgentOperationalPhase.TOOL_DENIED, AgentOperationalPhase.EXECUTING_TOOL,
|
|
243
|
-
AgentOperationalPhase.PROCESSING_TOOL_RESULT
|
|
244
|
-
],
|
|
245
|
-
target_phase=AgentOperationalPhase.SHUTTING_DOWN,
|
|
246
|
-
description="Fires when the agent begins its graceful shutdown sequence."
|
|
247
|
-
)
|
|
248
|
-
async def notify_shutdown_initiated(self) -> None:
|
|
249
|
-
if not self.context.current_phase.is_terminal():
|
|
250
|
-
await self._transition_phase(AgentOperationalPhase.SHUTTING_DOWN, "notify_phase_shutting_down_started")
|
|
251
|
-
else:
|
|
252
|
-
logger.debug(f"Agent '{self.context.agent_id}' shutdown initiated but already in a terminal phase: {self.context.current_phase.value}")
|
|
253
|
-
|
|
254
|
-
@phase_transition(
|
|
255
|
-
source_phases=[AgentOperationalPhase.SHUTTING_DOWN],
|
|
256
|
-
target_phase=AgentOperationalPhase.SHUTDOWN_COMPLETE,
|
|
257
|
-
description="The final transition when the agent has successfully shut down and released its resources."
|
|
258
|
-
)
|
|
259
|
-
async def notify_final_shutdown_complete(self) -> None:
|
|
260
|
-
final_phase = AgentOperationalPhase.ERROR if self.context.current_phase == AgentOperationalPhase.ERROR else AgentOperationalPhase.SHUTDOWN_COMPLETE
|
|
261
|
-
if final_phase == AgentOperationalPhase.ERROR:
|
|
262
|
-
await self._transition_phase(AgentOperationalPhase.ERROR, "notify_phase_error_entered", additional_data={"error_message": "Shutdown completed with agent in error state."})
|
|
263
|
-
else:
|
|
264
|
-
await self._transition_phase(AgentOperationalPhase.SHUTDOWN_COMPLETE, "notify_phase_shutdown_completed")
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/context/phases.py
|
|
2
|
-
from enum import Enum
|
|
3
|
-
|
|
4
|
-
class AgentOperationalPhase(str, Enum):
|
|
5
|
-
"""
|
|
6
|
-
Defines the fine-grained operational phases of an agent.
|
|
7
|
-
This is the single source of truth for an agent's current state of operation.
|
|
8
|
-
"""
|
|
9
|
-
UNINITIALIZED = "uninitialized" # Agent object created, but runtime not started or fully set up.
|
|
10
|
-
BOOTSTRAPPING = "bootstrapping" # Agent is running its internal initialization/bootstrap sequence.
|
|
11
|
-
IDLE = "idle" # Fully initialized and ready for new input.
|
|
12
|
-
|
|
13
|
-
PROCESSING_USER_INPUT = "processing_user_input" # Actively processing a user message, typically preparing for an LLM call.
|
|
14
|
-
AWAITING_LLM_RESPONSE = "awaiting_llm_response" # Sent a request to LLM, waiting for the full response or stream.
|
|
15
|
-
ANALYZING_LLM_RESPONSE = "analyzing_llm_response" # Received LLM response, analyzing it for next actions (e.g., tool use, direct reply).
|
|
16
|
-
|
|
17
|
-
AWAITING_TOOL_APPROVAL = "awaiting_tool_approval" # Paused, needs external (user) approval for a tool invocation.
|
|
18
|
-
TOOL_DENIED = "tool_denied" # A proposed tool execution was denied by the user. Agent is processing the denial.
|
|
19
|
-
EXECUTING_TOOL = "executing_tool" # Tool has been approved (or auto-approved) and is currently running.
|
|
20
|
-
PROCESSING_TOOL_RESULT = "processing_tool_result" # Received a tool's result, actively processing it (often leading to another LLM call).
|
|
21
|
-
|
|
22
|
-
SHUTTING_DOWN = "shutting_down" # Shutdown sequence has been initiated.
|
|
23
|
-
SHUTDOWN_COMPLETE = "shutdown_complete" # Agent has fully stopped and released resources.
|
|
24
|
-
ERROR = "error" # An unrecoverable error has occurred. Agent might be non-operational.
|
|
25
|
-
|
|
26
|
-
def __str__(self) -> str:
|
|
27
|
-
return self.value
|
|
28
|
-
|
|
29
|
-
def is_initializing(self) -> bool:
|
|
30
|
-
"""Checks if the agent is in any of the initializing phases."""
|
|
31
|
-
return self in [
|
|
32
|
-
AgentOperationalPhase.BOOTSTRAPPING,
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
def is_processing(self) -> bool:
|
|
36
|
-
"""Checks if the agent is in any active processing phase (post-initialization, pre-shutdown)."""
|
|
37
|
-
return self in [
|
|
38
|
-
AgentOperationalPhase.PROCESSING_USER_INPUT,
|
|
39
|
-
AgentOperationalPhase.AWAITING_LLM_RESPONSE,
|
|
40
|
-
AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
|
|
41
|
-
AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
|
|
42
|
-
AgentOperationalPhase.TOOL_DENIED,
|
|
43
|
-
AgentOperationalPhase.EXECUTING_TOOL,
|
|
44
|
-
AgentOperationalPhase.PROCESSING_TOOL_RESULT,
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
def is_terminal(self) -> bool:
|
|
48
|
-
"""Checks if the phase is a terminal state (shutdown or error)."""
|
|
49
|
-
return self in [AgentOperationalPhase.SHUTDOWN_COMPLETE, AgentOperationalPhase.ERROR]
|
|
File without changes
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/group/agent_group.py
|
|
2
|
-
import asyncio
|
|
3
|
-
import logging
|
|
4
|
-
import uuid
|
|
5
|
-
from typing import List, Dict, Optional, Any
|
|
6
|
-
|
|
7
|
-
from autobyteus.agent.context.agent_config import AgentConfig
|
|
8
|
-
from autobyteus.agent.factory import AgentFactory
|
|
9
|
-
from autobyteus.agent.agent import Agent
|
|
10
|
-
from autobyteus.agent.group.agent_group_context import AgentGroupContext
|
|
11
|
-
from autobyteus.agent.message.send_message_to import SendMessageTo
|
|
12
|
-
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
13
|
-
from autobyteus.agent.streaming.agent_event_stream import AgentEventStream
|
|
14
|
-
from autobyteus.llm.utils.response_types import CompleteResponse
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
class AgentGroup:
|
|
19
|
-
def __init__(self,
|
|
20
|
-
agent_configs: List[AgentConfig],
|
|
21
|
-
coordinator_config_name: str,
|
|
22
|
-
group_id: Optional[str] = None):
|
|
23
|
-
if not agent_configs or not all(isinstance(c, AgentConfig) for c in agent_configs):
|
|
24
|
-
raise TypeError("agent_configs must be a non-empty list of AgentConfig instances.")
|
|
25
|
-
if not coordinator_config_name or not isinstance(coordinator_config_name, str):
|
|
26
|
-
raise TypeError("coordinator_config_name must be a non-empty string.")
|
|
27
|
-
|
|
28
|
-
self.group_id: str = group_id or f"group_{uuid.uuid4()}"
|
|
29
|
-
self.agent_factory = AgentFactory() # Get singleton instance
|
|
30
|
-
self._agent_configs_map: Dict[str, AgentConfig] = {
|
|
31
|
-
config.name: config for config in agent_configs
|
|
32
|
-
}
|
|
33
|
-
self.coordinator_config_name: str = coordinator_config_name
|
|
34
|
-
self.agents: List[Agent] = []
|
|
35
|
-
self.coordinator_agent: Optional[Agent] = None
|
|
36
|
-
self.group_context: Optional[AgentGroupContext] = None
|
|
37
|
-
self._is_initialized: bool = False
|
|
38
|
-
self._is_running: bool = False
|
|
39
|
-
|
|
40
|
-
if self.coordinator_config_name not in self._agent_configs_map:
|
|
41
|
-
raise ValueError(f"Coordinator config name '{self.coordinator_config_name}' "
|
|
42
|
-
f"not found in provided agent_configs. Available: {list(self._agent_configs_map.keys())}")
|
|
43
|
-
logger.info(f"AgentGroup '{self.group_id}' created with {len(agent_configs)} configurations. "
|
|
44
|
-
f"Coordinator: '{self.coordinator_config_name}'.")
|
|
45
|
-
self._initialize_agents()
|
|
46
|
-
|
|
47
|
-
def _initialize_agents(self):
|
|
48
|
-
if self._is_initialized:
|
|
49
|
-
logger.warning(f"AgentGroup '{self.group_id}' agents already initialized. Skipping.")
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
temp_agents_list: List[Agent] = []
|
|
53
|
-
temp_coordinator_agent: Optional[Agent] = None
|
|
54
|
-
for config_name, original_config in self._agent_configs_map.items():
|
|
55
|
-
|
|
56
|
-
modified_tools = list(original_config.tools)
|
|
57
|
-
is_send_message_present = any(isinstance(tool, SendMessageTo) for tool in modified_tools)
|
|
58
|
-
if not is_send_message_present:
|
|
59
|
-
modified_tools.append(SendMessageTo())
|
|
60
|
-
|
|
61
|
-
# This logic correctly re-uses the user-provided LLM instance and other properties
|
|
62
|
-
# when creating the effective config for the agent factory.
|
|
63
|
-
effective_config = AgentConfig(
|
|
64
|
-
name=original_config.name,
|
|
65
|
-
role=original_config.role,
|
|
66
|
-
description=original_config.description,
|
|
67
|
-
llm_instance=original_config.llm_instance,
|
|
68
|
-
system_prompt=original_config.system_prompt,
|
|
69
|
-
tools=modified_tools,
|
|
70
|
-
auto_execute_tools=original_config.auto_execute_tools,
|
|
71
|
-
use_xml_tool_format=original_config.use_xml_tool_format,
|
|
72
|
-
input_processors=original_config.input_processors,
|
|
73
|
-
llm_response_processors=original_config.llm_response_processors,
|
|
74
|
-
system_prompt_processors=original_config.system_prompt_processors,
|
|
75
|
-
workspace=original_config.workspace,
|
|
76
|
-
phase_hooks=original_config.phase_hooks,
|
|
77
|
-
initial_custom_data=original_config.initial_custom_data
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
agent_instance = self.agent_factory.create_agent(config=effective_config)
|
|
82
|
-
temp_agents_list.append(agent_instance)
|
|
83
|
-
|
|
84
|
-
if config_name == self.coordinator_config_name:
|
|
85
|
-
temp_coordinator_agent = agent_instance
|
|
86
|
-
logger.debug(f"Agent '{agent_instance.agent_id}' (Role: {original_config.role}) created for group '{self.group_id}'.")
|
|
87
|
-
except Exception as e:
|
|
88
|
-
logger.error(f"Failed to create agent for config '{config_name}' for group '{self.group_id}': {e}", exc_info=True)
|
|
89
|
-
raise RuntimeError(f"Failed to initialize agent for config '{config_name}' in group '{self.group_id}'.") from e
|
|
90
|
-
|
|
91
|
-
if not temp_coordinator_agent:
|
|
92
|
-
raise RuntimeError(f"Coordinator agent '{self.coordinator_config_name}' could not be instantiated.")
|
|
93
|
-
|
|
94
|
-
self.agents = temp_agents_list
|
|
95
|
-
self.coordinator_agent = temp_coordinator_agent
|
|
96
|
-
self.group_context = AgentGroupContext(group_id=self.group_id, agents=self.agents, coordinator_agent_id=self.coordinator_agent.agent_id)
|
|
97
|
-
for agent in self.agents:
|
|
98
|
-
agent.context.custom_data['agent_group_context'] = self.group_context
|
|
99
|
-
self._is_initialized = True
|
|
100
|
-
logger.info(f"AgentGroup '{self.group_id}' all {len(self.agents)} agents initialized successfully.")
|
|
101
|
-
|
|
102
|
-
async def start(self):
|
|
103
|
-
if not self._is_initialized: raise RuntimeError(f"AgentGroup '{self.group_id}' must be initialized before starting.")
|
|
104
|
-
if self._is_running: logger.warning(f"AgentGroup '{self.group_id}' is already running."); return
|
|
105
|
-
logger.info(f"Starting all agents in AgentGroup '{self.group_id}'..."); self._is_running = True
|
|
106
|
-
try:
|
|
107
|
-
for agent in self.agents:
|
|
108
|
-
if not agent.is_running:
|
|
109
|
-
agent.start()
|
|
110
|
-
# Give loops a chance to start
|
|
111
|
-
await asyncio.sleep(0.01)
|
|
112
|
-
logger.info(f"All agents in AgentGroup '{self.group_id}' have been requested to start.")
|
|
113
|
-
except Exception as e:
|
|
114
|
-
self._is_running = False; logger.error(f"Error starting agents in AgentGroup '{self.group_id}': {e}", exc_info=True)
|
|
115
|
-
await self.stop(timeout=2.0); raise
|
|
116
|
-
|
|
117
|
-
async def stop(self, timeout: float = 10.0):
|
|
118
|
-
if not self._is_running and not any(a.is_running for a in self.agents):
|
|
119
|
-
logger.info(f"AgentGroup '{self.group_id}' is already stopped or was never started."); self._is_running = False; return
|
|
120
|
-
logger.info(f"Stopping all agents in AgentGroup '{self.group_id}' with timeout {timeout}s...")
|
|
121
|
-
stop_tasks = [agent.stop(timeout=timeout) for agent in self.agents]
|
|
122
|
-
results = await asyncio.gather(*stop_tasks, return_exceptions=True)
|
|
123
|
-
for agent, result in zip(self.agents, results):
|
|
124
|
-
if isinstance(result, Exception): logger.error(f"Error stopping agent '{agent.agent_id}': {result}", exc_info=result)
|
|
125
|
-
self._is_running = False; logger.info(f"All agents in AgentGroup '{self.group_id}' have been requested to stop.")
|
|
126
|
-
|
|
127
|
-
async def process_task_for_coordinator(self, initial_input_content: str, user_id: Optional[str] = None) -> Any:
|
|
128
|
-
if not self.coordinator_agent: raise RuntimeError(f"Coordinator agent not set in group '{self.group_id}'.")
|
|
129
|
-
await self.start()
|
|
130
|
-
final_response_aggregator = ""
|
|
131
|
-
output_stream_listener_task = None
|
|
132
|
-
streamer = None
|
|
133
|
-
try:
|
|
134
|
-
streamer = AgentEventStream(self.coordinator_agent)
|
|
135
|
-
async def listen_for_final_output():
|
|
136
|
-
nonlocal final_response_aggregator
|
|
137
|
-
try:
|
|
138
|
-
async for complete_response_data in streamer.stream_assistant_final_response():
|
|
139
|
-
final_response_aggregator += complete_response_data.content
|
|
140
|
-
except Exception as e_stream:
|
|
141
|
-
logger.error(f"Error streaming final output from coordinator: {e_stream}", exc_info=True)
|
|
142
|
-
output_stream_listener_task = asyncio.create_task(listen_for_final_output())
|
|
143
|
-
input_message = AgentInputUserMessage(content=initial_input_content, metadata={"user_id": user_id} if user_id else {})
|
|
144
|
-
await self.coordinator_agent.post_user_message(input_message)
|
|
145
|
-
|
|
146
|
-
# Wait for the listener to finish, which happens after the agent is done and the stream closes.
|
|
147
|
-
if output_stream_listener_task:
|
|
148
|
-
await output_stream_listener_task
|
|
149
|
-
|
|
150
|
-
return final_response_aggregator
|
|
151
|
-
finally:
|
|
152
|
-
if output_stream_listener_task and not output_stream_listener_task.done():
|
|
153
|
-
output_stream_listener_task.cancel()
|
|
154
|
-
if streamer: await streamer.close()
|
|
155
|
-
|
|
156
|
-
def get_agent_by_id(self, agent_id: str) -> Optional[Agent]:
|
|
157
|
-
return next((agent for agent in self.agents if agent.agent_id == agent_id), None)
|
|
158
|
-
|
|
159
|
-
def get_agents_by_role(self, role_name: str) -> List[Agent]:
|
|
160
|
-
return [agent for agent in self.agents if agent.context.config.role == role_name]
|
|
161
|
-
|
|
162
|
-
@property
|
|
163
|
-
def is_running(self) -> bool:
|
|
164
|
-
return self._is_running and any(a.is_running for a in self.agents)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/group/agent_group_context.py
|
|
2
|
-
import logging
|
|
3
|
-
from typing import List, Dict, Optional, TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
if TYPE_CHECKING:
|
|
6
|
-
from autobyteus.agent.agent import Agent
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
class AgentGroupContext:
|
|
11
|
-
"""
|
|
12
|
-
Stores contextual information about an agent group, including its ID,
|
|
13
|
-
member agents, and the designated coordinator. Provides methods to
|
|
14
|
-
discover agents within the group.
|
|
15
|
-
"""
|
|
16
|
-
def __init__(self,
|
|
17
|
-
group_id: str,
|
|
18
|
-
agents: List['Agent'],
|
|
19
|
-
coordinator_agent_id: str):
|
|
20
|
-
"""
|
|
21
|
-
Initializes the AgentGroupContext.
|
|
22
|
-
"""
|
|
23
|
-
if not group_id or not isinstance(group_id, str):
|
|
24
|
-
raise ValueError("AgentGroupContext requires a non-empty string 'group_id'.")
|
|
25
|
-
if not coordinator_agent_id or not isinstance(coordinator_agent_id, str):
|
|
26
|
-
raise ValueError("AgentGroupContext requires a non-empty string 'coordinator_agent_id'.")
|
|
27
|
-
if not agents:
|
|
28
|
-
raise ValueError("AgentGroupContext requires a non-empty list of 'agents'.")
|
|
29
|
-
|
|
30
|
-
from autobyteus.agent.agent import Agent as AgentClassRef
|
|
31
|
-
if not all(isinstance(agent, AgentClassRef) for agent in agents):
|
|
32
|
-
raise TypeError("All items in 'agents' list must be instances of the 'Agent' class.")
|
|
33
|
-
|
|
34
|
-
self.group_id: str = group_id
|
|
35
|
-
self._agents_by_id: Dict[str, 'Agent'] = {agent.agent_id: agent for agent in agents}
|
|
36
|
-
self._coordinator_agent_id: str = coordinator_agent_id
|
|
37
|
-
|
|
38
|
-
if self._coordinator_agent_id not in self._agents_by_id:
|
|
39
|
-
logger.error(f"Coordinator agent with ID '{self._coordinator_agent_id}' not found in the provided list of agents for group '{self.group_id}'.")
|
|
40
|
-
|
|
41
|
-
logger.info(f"AgentGroupContext initialized for group_id '{self.group_id}'.")
|
|
42
|
-
|
|
43
|
-
def get_agent(self, agent_id: str) -> Optional['Agent']:
|
|
44
|
-
"""
|
|
45
|
-
Retrieves an agent from the group by its unique agent_id.
|
|
46
|
-
"""
|
|
47
|
-
return self._agents_by_id.get(agent_id)
|
|
48
|
-
|
|
49
|
-
def get_agents_by_role(self, role_name: str) -> List['Agent']:
|
|
50
|
-
"""
|
|
51
|
-
Retrieves all agents within the group that match the specified role name.
|
|
52
|
-
"""
|
|
53
|
-
if not isinstance(role_name, str):
|
|
54
|
-
logger.warning(f"Attempted to get_agents_by_role with non-string role_name: {role_name} in group '{self.group_id}'.")
|
|
55
|
-
return []
|
|
56
|
-
|
|
57
|
-
matching_agents: List['Agent'] = [
|
|
58
|
-
agent for agent in self._agents_by_id.values()
|
|
59
|
-
if agent.context and agent.context.config and agent.context.config.role == role_name
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
if not matching_agents:
|
|
63
|
-
logger.debug(f"No agents found with role '{role_name}' in group '{self.group_id}'.")
|
|
64
|
-
return matching_agents
|
|
65
|
-
|
|
66
|
-
def get_coordinator_agent(self) -> Optional['Agent']:
|
|
67
|
-
"""
|
|
68
|
-
Retrieves the designated coordinator agent for this group.
|
|
69
|
-
"""
|
|
70
|
-
return self.get_agent(self._coordinator_agent_id)
|
|
71
|
-
|
|
72
|
-
def get_all_agents(self) -> List['Agent']:
|
|
73
|
-
"""
|
|
74
|
-
Retrieves all agents currently part of this group.
|
|
75
|
-
"""
|
|
76
|
-
return list(self._agents_by_id.values())
|
|
77
|
-
|
|
78
|
-
def __repr__(self) -> str:
|
|
79
|
-
return (f"<AgentGroupContext group_id='{self.group_id}', "
|
|
80
|
-
f"num_agents={len(self._agents_by_id)}, "
|
|
81
|
-
f"coordinator_id='{self._coordinator_agent_id}'>")
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/input_processor/content_prefixing_input_processor.py
|
|
2
|
-
import logging
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from .base_user_input_processor import BaseAgentUserInputMessageProcessor
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
9
|
-
from autobyteus.agent.context import AgentContext # Composite AgentContext
|
|
10
|
-
from autobyteus.agent.events import UserMessageReceivedEvent
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
class ContentPrefixingInputProcessor(BaseAgentUserInputMessageProcessor):
|
|
15
|
-
"""
|
|
16
|
-
A processor that adds a predefined prefix to the message content.
|
|
17
|
-
The prefix is defined by the agent's custom_data (in AgentRuntimeState) or a default.
|
|
18
|
-
Example prefix key in custom_data: "content_prefix"
|
|
19
|
-
"""
|
|
20
|
-
DEFAULT_PREFIX = "[Processed Message] "
|
|
21
|
-
|
|
22
|
-
async def process(self,
|
|
23
|
-
message: 'AgentInputUserMessage',
|
|
24
|
-
context: 'AgentContext',
|
|
25
|
-
triggering_event: 'UserMessageReceivedEvent') -> 'AgentInputUserMessage':
|
|
26
|
-
"""
|
|
27
|
-
Handles the message by prefixing its content.
|
|
28
|
-
The 'triggering_event' parameter is ignored by this processor.
|
|
29
|
-
"""
|
|
30
|
-
agent_id = context.agent_id # Convenience property
|
|
31
|
-
logger.debug(f"Agent '{agent_id}': ContentPrefixingInputProcessor processing message.")
|
|
32
|
-
|
|
33
|
-
# Access custom_data via convenience property (or context.state.custom_data)
|
|
34
|
-
prefix = context.custom_data.get("content_prefix", self.DEFAULT_PREFIX)
|
|
35
|
-
if not isinstance(prefix, str):
|
|
36
|
-
logger.warning(f"Agent '{agent_id}': 'content_prefix' in custom_data is not a string. Using default prefix. Found: {type(prefix)}")
|
|
37
|
-
prefix = self.DEFAULT_PREFIX
|
|
38
|
-
|
|
39
|
-
message.content = prefix + message.content
|
|
40
|
-
logger.info(f"Agent '{agent_id}': Prefixed message content with '{prefix}'.")
|
|
41
|
-
return message
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/agent/input_processor/metadata_appending_input_processor.py
|
|
2
|
-
import logging
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from .base_user_input_processor import BaseAgentUserInputMessageProcessor
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
9
|
-
from autobyteus.agent.context import AgentContext # Composite AgentContext
|
|
10
|
-
from autobyteus.agent.events import UserMessageReceivedEvent
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
class MetadataAppendingInputProcessor(BaseAgentUserInputMessageProcessor):
|
|
15
|
-
"""
|
|
16
|
-
A processor that appends fixed metadata to the message.
|
|
17
|
-
Example: Appends agent_id and config_name to metadata.
|
|
18
|
-
"""
|
|
19
|
-
async def process(self,
|
|
20
|
-
message: 'AgentInputUserMessage',
|
|
21
|
-
context: 'AgentContext',
|
|
22
|
-
triggering_event: 'UserMessageReceivedEvent') -> 'AgentInputUserMessage':
|
|
23
|
-
"""
|
|
24
|
-
Handles the message by appending metadata.
|
|
25
|
-
The 'triggering_event' parameter is ignored by this processor.
|
|
26
|
-
"""
|
|
27
|
-
agent_id = context.agent_id
|
|
28
|
-
config_name = context.config.name
|
|
29
|
-
|
|
30
|
-
logger.debug(f"Agent '{agent_id}': MetadataAppendingInputProcessor processing message.")
|
|
31
|
-
message.metadata["processed_by_agent_id"] = agent_id
|
|
32
|
-
message.metadata["processed_with_config_name"] = config_name
|
|
33
|
-
logger.info(f"Agent '{agent_id}': Appended 'processed_by_agent_id' and 'processed_with_config_name' to message metadata.")
|
|
34
|
-
return message
|