autobyteus 1.0.6__py3-none-any.whl → 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autobyteus/agent/agent.py +97 -222
- autobyteus/agent/bootstrap_steps/__init__.py +19 -0
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +88 -0
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +57 -0
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +38 -0
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +93 -0
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +47 -0
- autobyteus/agent/context/__init__.py +13 -0
- autobyteus/agent/context/agent_config.py +84 -0
- autobyteus/agent/context/agent_context.py +129 -0
- autobyteus/agent/context/agent_phase_manager.py +264 -0
- autobyteus/agent/context/agent_runtime_state.py +89 -0
- autobyteus/agent/context/phases.py +49 -0
- autobyteus/agent/events/__init__.py +52 -0
- autobyteus/agent/events/agent_events.py +110 -0
- autobyteus/agent/events/agent_input_event_queue_manager.py +174 -0
- autobyteus/agent/events/notifiers.py +122 -0
- autobyteus/agent/events/worker_event_dispatcher.py +118 -0
- autobyteus/agent/factory/__init__.py +9 -0
- autobyteus/agent/factory/agent_factory.py +145 -0
- autobyteus/agent/group/agent_group.py +164 -0
- autobyteus/agent/group/agent_group_context.py +81 -0
- autobyteus/agent/handlers/__init__.py +36 -0
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +148 -0
- autobyteus/agent/handlers/base_event_handler.py +36 -0
- autobyteus/agent/handlers/event_handler_registry.py +76 -0
- autobyteus/agent/handlers/generic_event_handler.py +46 -0
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +76 -0
- autobyteus/agent/handlers/lifecycle_event_logger.py +64 -0
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +138 -0
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +140 -0
- autobyteus/agent/handlers/tool_execution_approval_event_handler.py +85 -0
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +211 -0
- autobyteus/agent/handlers/tool_result_event_handler.py +101 -0
- autobyteus/agent/handlers/user_input_message_event_handler.py +77 -0
- autobyteus/agent/hooks/__init__.py +16 -0
- autobyteus/agent/hooks/base_phase_hook.py +61 -0
- autobyteus/agent/hooks/hook_definition.py +36 -0
- autobyteus/agent/hooks/hook_meta.py +37 -0
- autobyteus/agent/hooks/hook_registry.py +118 -0
- autobyteus/agent/input_processor/__init__.py +18 -0
- autobyteus/agent/input_processor/base_user_input_processor.py +54 -0
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +41 -0
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +34 -0
- autobyteus/agent/input_processor/passthrough_input_processor.py +33 -0
- autobyteus/agent/input_processor/processor_definition.py +42 -0
- autobyteus/agent/input_processor/processor_meta.py +46 -0
- autobyteus/agent/input_processor/processor_registry.py +117 -0
- autobyteus/agent/llm_response_processor/__init__.py +16 -0
- autobyteus/agent/llm_response_processor/base_processor.py +53 -0
- autobyteus/agent/llm_response_processor/processor_definition.py +36 -0
- autobyteus/agent/llm_response_processor/processor_meta.py +37 -0
- autobyteus/agent/llm_response_processor/processor_registry.py +113 -0
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +54 -0
- autobyteus/agent/message/__init__.py +20 -0
- autobyteus/agent/message/agent_input_user_message.py +96 -0
- autobyteus/agent/message/context_file.py +82 -0
- autobyteus/agent/message/context_file_type.py +63 -0
- autobyteus/agent/message/{message.py → inter_agent_message.py} +12 -12
- autobyteus/agent/message/{message_types.py → inter_agent_message_type.py} +8 -6
- autobyteus/agent/message/send_message_to.py +142 -36
- autobyteus/agent/phases/__init__.py +18 -0
- autobyteus/agent/phases/discover.py +52 -0
- autobyteus/agent/phases/manager.py +265 -0
- autobyteus/agent/phases/phase_enum.py +49 -0
- autobyteus/agent/phases/transition_decorator.py +40 -0
- autobyteus/agent/phases/transition_info.py +33 -0
- autobyteus/agent/remote_agent.py +244 -0
- autobyteus/agent/runtime/__init__.py +15 -0
- autobyteus/agent/runtime/agent_runtime.py +137 -0
- autobyteus/agent/runtime/agent_thread_pool_manager.py +88 -0
- autobyteus/agent/runtime/agent_worker.py +200 -0
- autobyteus/agent/streaming/__init__.py +15 -0
- autobyteus/agent/streaming/agent_event_stream.py +173 -0
- autobyteus/agent/streaming/queue_streamer.py +58 -0
- autobyteus/agent/streaming/stream_event_payloads.py +167 -0
- autobyteus/agent/streaming/stream_events.py +126 -0
- autobyteus/agent/system_prompt_processor/__init__.py +14 -0
- autobyteus/agent/system_prompt_processor/base_processor.py +48 -0
- autobyteus/agent/system_prompt_processor/processor_definition.py +40 -0
- autobyteus/agent/system_prompt_processor/processor_meta.py +47 -0
- autobyteus/agent/system_prompt_processor/processor_registry.py +119 -0
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +79 -0
- autobyteus/agent/tool_invocation.py +54 -5
- autobyteus/agent/utils/__init__.py +9 -0
- autobyteus/agent/utils/wait_for_idle.py +59 -0
- autobyteus/agent/workflow/__init__.py +11 -0
- autobyteus/agent/workflow/agentic_workflow.py +89 -0
- autobyteus/agent/workflow/base_agentic_workflow.py +98 -0
- autobyteus/agent/workspace/__init__.py +11 -0
- autobyteus/agent/workspace/base_workspace.py +77 -0
- autobyteus/agent/workspace/workspace_config.py +160 -0
- autobyteus/agent/workspace/workspace_definition.py +36 -0
- autobyteus/agent/workspace/workspace_meta.py +37 -0
- autobyteus/agent/workspace/workspace_registry.py +72 -0
- autobyteus/cli/__init__.py +11 -0
- autobyteus/cli/agent_cli.py +117 -0
- autobyteus/cli/cli_display.py +205 -0
- autobyteus/events/event_emitter.py +33 -56
- autobyteus/events/event_manager.py +134 -66
- autobyteus/events/event_types.py +43 -14
- autobyteus/llm/api/autobyteus_llm.py +13 -25
- autobyteus/llm/api/bedrock_llm.py +9 -3
- autobyteus/llm/api/claude_llm.py +10 -5
- autobyteus/llm/api/deepseek_llm.py +55 -93
- autobyteus/llm/api/gemini_llm.py +10 -4
- autobyteus/llm/api/grok_llm.py +55 -79
- autobyteus/llm/api/groq_llm.py +10 -5
- autobyteus/llm/api/mistral_llm.py +13 -8
- autobyteus/llm/api/nvidia_llm.py +9 -4
- autobyteus/llm/api/ollama_llm.py +58 -50
- autobyteus/llm/api/openai_llm.py +20 -14
- autobyteus/llm/base_llm.py +95 -34
- autobyteus/llm/extensions/base_extension.py +3 -4
- autobyteus/llm/extensions/token_usage_tracking_extension.py +13 -4
- autobyteus/llm/llm_factory.py +116 -53
- autobyteus/llm/models.py +92 -17
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- autobyteus/llm/user_message.py +73 -0
- autobyteus/llm/utils/llm_config.py +124 -27
- autobyteus/llm/utils/response_types.py +3 -2
- autobyteus/llm/utils/token_usage.py +7 -4
- autobyteus/rpc/__init__.py +73 -0
- autobyteus/rpc/client/__init__.py +17 -0
- autobyteus/rpc/client/abstract_client_connection.py +124 -0
- autobyteus/rpc/client/client_connection_manager.py +153 -0
- autobyteus/rpc/client/sse_client_connection.py +306 -0
- autobyteus/rpc/client/stdio_client_connection.py +280 -0
- autobyteus/rpc/config/__init__.py +13 -0
- autobyteus/rpc/config/agent_server_config.py +153 -0
- autobyteus/rpc/config/agent_server_registry.py +152 -0
- autobyteus/rpc/hosting.py +244 -0
- autobyteus/rpc/protocol.py +244 -0
- autobyteus/rpc/server/__init__.py +20 -0
- autobyteus/rpc/server/agent_server_endpoint.py +181 -0
- autobyteus/rpc/server/base_method_handler.py +40 -0
- autobyteus/rpc/server/method_handlers.py +259 -0
- autobyteus/rpc/server/sse_server_handler.py +182 -0
- autobyteus/rpc/server/stdio_server_handler.py +151 -0
- autobyteus/rpc/server_main.py +198 -0
- autobyteus/rpc/transport_type.py +13 -0
- autobyteus/tools/__init__.py +77 -0
- autobyteus/tools/ask_user_input.py +34 -77
- autobyteus/tools/base_tool.py +73 -38
- autobyteus/tools/bash/__init__.py +2 -0
- autobyteus/tools/bash/bash_executor.py +42 -79
- autobyteus/tools/browser/__init__.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +50 -42
- autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +7 -4
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +117 -125
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +75 -22
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +94 -28
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +10 -2
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +18 -2
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +10 -2
- autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +4 -3
- autobyteus/tools/browser/standalone/__init__.py +7 -0
- autobyteus/tools/browser/standalone/factory/google_search_factory.py +17 -2
- autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +17 -2
- autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +10 -2
- autobyteus/tools/browser/standalone/google_search_ui.py +104 -67
- autobyteus/tools/browser/standalone/navigate_to.py +52 -28
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +94 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +146 -61
- autobyteus/tools/browser/standalone/webpage_reader.py +80 -61
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +91 -45
- autobyteus/tools/factory/__init__.py +9 -0
- autobyteus/tools/factory/tool_factory.py +25 -4
- autobyteus/tools/file/file_reader.py +22 -51
- autobyteus/tools/file/file_writer.py +25 -56
- autobyteus/tools/functional_tool.py +249 -0
- autobyteus/tools/image_downloader.py +49 -71
- autobyteus/tools/mcp/__init__.py +47 -0
- autobyteus/tools/mcp/call_handlers/__init__.py +18 -0
- autobyteus/tools/mcp/call_handlers/base_handler.py +40 -0
- autobyteus/tools/mcp/call_handlers/sse_handler.py +22 -0
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +76 -0
- autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +55 -0
- autobyteus/tools/mcp/config_service.py +237 -0
- autobyteus/tools/mcp/factory.py +70 -0
- autobyteus/tools/mcp/registrar.py +323 -0
- autobyteus/tools/mcp/schema_mapper.py +131 -0
- autobyteus/tools/mcp/tool.py +101 -0
- autobyteus/tools/mcp/types.py +98 -0
- autobyteus/tools/parameter_schema.py +268 -0
- autobyteus/tools/pdf_downloader.py +78 -79
- autobyteus/tools/registry/__init__.py +0 -2
- autobyteus/tools/registry/tool_definition.py +113 -34
- autobyteus/tools/registry/tool_registry.py +64 -22
- autobyteus/tools/timer.py +150 -102
- autobyteus/tools/tool_category.py +11 -0
- autobyteus/tools/tool_config.py +117 -0
- autobyteus/tools/tool_meta.py +50 -26
- autobyteus/tools/tool_state.py +20 -0
- autobyteus/tools/usage/__init__.py +6 -0
- autobyteus/tools/usage/formatters/__init__.py +31 -0
- autobyteus/tools/usage/formatters/anthropic_json_example_formatter.py +18 -0
- autobyteus/tools/usage/formatters/anthropic_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/base_formatter.py +42 -0
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +42 -0
- autobyteus/tools/usage/formatters/default_json_schema_formatter.py +28 -0
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +55 -0
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +46 -0
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +34 -0
- autobyteus/tools/usage/formatters/gemini_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +34 -0
- autobyteus/tools/usage/formatters/google_json_schema_formatter.py +25 -0
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +49 -0
- autobyteus/tools/usage/formatters/openai_json_schema_formatter.py +28 -0
- autobyteus/tools/usage/parsers/__init__.py +22 -0
- autobyteus/tools/usage/parsers/anthropic_xml_tool_usage_parser.py +10 -0
- autobyteus/tools/usage/parsers/base_parser.py +41 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +106 -0
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +136 -0
- autobyteus/tools/usage/parsers/exceptions.py +13 -0
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +66 -0
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +196 -0
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +67 -0
- autobyteus/tools/usage/providers/__init__.py +22 -0
- autobyteus/tools/usage/providers/json_example_provider.py +32 -0
- autobyteus/tools/usage/providers/json_schema_provider.py +35 -0
- autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +28 -0
- autobyteus/tools/usage/providers/tool_manifest_provider.py +68 -0
- autobyteus/tools/usage/providers/xml_example_provider.py +28 -0
- autobyteus/tools/usage/providers/xml_schema_provider.py +29 -0
- autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +26 -0
- autobyteus/tools/usage/registries/__init__.py +20 -0
- autobyteus/tools/usage/registries/json_example_formatter_registry.py +51 -0
- autobyteus/tools/usage/registries/json_schema_formatter_registry.py +51 -0
- autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +42 -0
- autobyteus/tools/usage/registries/xml_example_formatter_registry.py +30 -0
- autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +33 -0
- autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +30 -0
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/METADATA +23 -5
- autobyteus-1.1.1.dist-info/RECORD +296 -0
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +1 -1
- autobyteus/agent/async_agent.py +0 -175
- autobyteus/agent/async_group_aware_agent.py +0 -136
- autobyteus/agent/group/async_group_aware_agent.py +0 -122
- autobyteus/agent/group/coordinator_agent.py +0 -36
- autobyteus/agent/group/group_aware_agent.py +0 -121
- autobyteus/agent/orchestrator/__init__.py +0 -0
- autobyteus/agent/orchestrator/base_agent_orchestrator.py +0 -82
- autobyteus/agent/orchestrator/multi_replica_agent_orchestrator.py +0 -72
- autobyteus/agent/orchestrator/single_replica_agent_orchestrator.py +0 -43
- autobyteus/agent/response_parser/__init__.py +0 -0
- autobyteus/agent/response_parser/tool_usage_command_parser.py +0 -100
- autobyteus/agent/status.py +0 -12
- autobyteus/conversation/__init__.py +0 -0
- autobyteus/conversation/conversation.py +0 -54
- autobyteus/conversation/user_message.py +0 -59
- autobyteus/events/decorators.py +0 -29
- autobyteus/prompt/prompt_version_manager.py +0 -58
- autobyteus/prompt/storage/__init__.py +0 -0
- autobyteus/prompt/storage/prompt_version_model.py +0 -29
- autobyteus/prompt/storage/prompt_version_repository.py +0 -83
- autobyteus/tools/bash/factory/__init__.py +0 -0
- autobyteus/tools/bash/factory/bash_executor_factory.py +0 -6
- autobyteus/tools/factory/ask_user_input_factory.py +0 -6
- autobyteus/tools/factory/image_downloader_factory.py +0 -9
- autobyteus/tools/factory/pdf_downloader_factory.py +0 -9
- autobyteus/tools/factory/webpage_image_downloader_factory.py +0 -6
- autobyteus/tools/file/factory/__init__.py +0 -0
- autobyteus/tools/file/factory/file_reader_factory.py +0 -6
- autobyteus/tools/file/factory/file_writer_factory.py +0 -6
- autobyteus/tools/web_page_pdf_generator.py +0 -90
- autobyteus-1.0.6.dist-info/RECORD +0 -157
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.0.6.dist-info → autobyteus-1.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .base_bootstrap_step import BaseBootstrapStep
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.agent.context import AgentContext
|
|
9
|
+
from autobyteus.agent.phases import AgentPhaseManager
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class WorkspaceContextInitializationStep(BaseBootstrapStep):
|
|
14
|
+
"""
|
|
15
|
+
Bootstrap step for injecting the AgentContext into the agent's workspace instance.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
logger.debug("WorkspaceContextInitializationStep initialized.")
|
|
20
|
+
|
|
21
|
+
async def execute(self,
|
|
22
|
+
context: 'AgentContext',
|
|
23
|
+
phase_manager: 'AgentPhaseManager') -> bool:
|
|
24
|
+
agent_id = context.agent_id
|
|
25
|
+
logger.info(f"Agent '{agent_id}': Executing WorkspaceContextInitializationStep.")
|
|
26
|
+
|
|
27
|
+
workspace = context.workspace
|
|
28
|
+
|
|
29
|
+
if not workspace:
|
|
30
|
+
logger.debug(f"Agent '{agent_id}': No workspace configured. Skipping context injection.")
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
if hasattr(workspace, 'set_context') and callable(getattr(workspace, 'set_context')):
|
|
35
|
+
workspace.set_context(context)
|
|
36
|
+
logger.info(f"Agent '{agent_id}': AgentContext successfully injected into workspace instance of type '{type(workspace).__name__}'.")
|
|
37
|
+
else:
|
|
38
|
+
logger.warning(f"Agent '{agent_id}': Configured workspace of type '{type(workspace).__name__}' does not have a 'set_context' method. "
|
|
39
|
+
"Workspace will not have access to the agent's context.")
|
|
40
|
+
|
|
41
|
+
return True
|
|
42
|
+
except Exception as e:
|
|
43
|
+
error_message = f"Agent '{agent_id}': Critical failure during WorkspaceContextInitializationStep: {e}"
|
|
44
|
+
logger.error(error_message, exc_info=True)
|
|
45
|
+
# No easy way to enqueue an error event here if queues aren't even initialized yet.
|
|
46
|
+
# The failure of a bootstrap step is handled by the bootstrapper, which will log and set error phase.
|
|
47
|
+
return False
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/context/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Components related to the agent's runtime context, state, config, and status management.
|
|
4
|
+
"""
|
|
5
|
+
from .agent_config import AgentConfig
|
|
6
|
+
from .agent_runtime_state import AgentRuntimeState
|
|
7
|
+
from .agent_context import AgentContext
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AgentContext",
|
|
11
|
+
"AgentConfig",
|
|
12
|
+
"AgentRuntimeState",
|
|
13
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/context/agent_config.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Optional, Union, Tuple, TYPE_CHECKING, Dict, Any
|
|
4
|
+
|
|
5
|
+
# Correctly import the new master processor and the base class
|
|
6
|
+
from autobyteus.agent.system_prompt_processor import ToolManifestInjectorProcessor, BaseSystemPromptProcessor
|
|
7
|
+
from autobyteus.agent.llm_response_processor import ProviderAwareToolUsageProcessor, BaseLLMResponseProcessor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
12
|
+
from autobyteus.agent.input_processor import BaseAgentUserInputMessageProcessor
|
|
13
|
+
from autobyteus.llm.base_llm import BaseLLM
|
|
14
|
+
from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
|
|
15
|
+
from autobyteus.agent.hooks.base_phase_hook import BasePhaseHook
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
class AgentConfig:
|
|
20
|
+
"""
|
|
21
|
+
Represents the complete, static configuration for an agent instance.
|
|
22
|
+
This is the single source of truth for an agent's definition, including
|
|
23
|
+
its identity, capabilities, and default behaviors.
|
|
24
|
+
"""
|
|
25
|
+
# Use the new ProviderAwareToolUsageProcessor as the default
|
|
26
|
+
DEFAULT_LLM_RESPONSE_PROCESSORS = [ProviderAwareToolUsageProcessor()]
|
|
27
|
+
# Use the new, single, unified processor as the default
|
|
28
|
+
DEFAULT_SYSTEM_PROMPT_PROCESSORS = [ToolManifestInjectorProcessor()]
|
|
29
|
+
|
|
30
|
+
def __init__(self,
|
|
31
|
+
name: str,
|
|
32
|
+
role: str,
|
|
33
|
+
description: str,
|
|
34
|
+
llm_instance: 'BaseLLM',
|
|
35
|
+
system_prompt: str,
|
|
36
|
+
tools: List['BaseTool'],
|
|
37
|
+
auto_execute_tools: bool = True,
|
|
38
|
+
use_xml_tool_format: bool = True,
|
|
39
|
+
input_processors: Optional[List['BaseAgentUserInputMessageProcessor']] = None,
|
|
40
|
+
llm_response_processors: Optional[List['BaseLLMResponseProcessor']] = None,
|
|
41
|
+
system_prompt_processors: Optional[List['BaseSystemPromptProcessor']] = None,
|
|
42
|
+
workspace: Optional['BaseAgentWorkspace'] = None,
|
|
43
|
+
phase_hooks: Optional[List['BasePhaseHook']] = None,
|
|
44
|
+
initial_custom_data: Optional[Dict[str, Any]] = None):
|
|
45
|
+
"""
|
|
46
|
+
Initializes the AgentConfig.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: The agent's name.
|
|
50
|
+
role: The agent's role.
|
|
51
|
+
description: A description of the agent.
|
|
52
|
+
llm_instance: A pre-initialized LLM instance (subclass of BaseLLM).
|
|
53
|
+
The user is responsible for creating and configuring this instance.
|
|
54
|
+
system_prompt: The base system prompt.
|
|
55
|
+
tools: A list of pre-initialized tool instances (subclasses of BaseTool).
|
|
56
|
+
auto_execute_tools: If True, the agent will execute tools without approval.
|
|
57
|
+
use_xml_tool_format: Whether to use XML for tool descriptions and examples.
|
|
58
|
+
input_processors: A list of input processor instances.
|
|
59
|
+
llm_response_processors: A list of LLM response processor instances.
|
|
60
|
+
system_prompt_processors: A list of system prompt processor instances.
|
|
61
|
+
workspace: An optional pre-initialized workspace instance for the agent.
|
|
62
|
+
phase_hooks: An optional list of phase transition hook instances.
|
|
63
|
+
initial_custom_data: An optional dictionary of data to pre-populate
|
|
64
|
+
the agent's runtime state `custom_data`.
|
|
65
|
+
"""
|
|
66
|
+
self.name = name
|
|
67
|
+
self.role = role
|
|
68
|
+
self.description = description
|
|
69
|
+
self.llm_instance = llm_instance
|
|
70
|
+
self.system_prompt = system_prompt
|
|
71
|
+
self.tools = tools
|
|
72
|
+
self.workspace = workspace
|
|
73
|
+
self.auto_execute_tools = auto_execute_tools
|
|
74
|
+
self.use_xml_tool_format = use_xml_tool_format
|
|
75
|
+
self.input_processors = input_processors or []
|
|
76
|
+
self.llm_response_processors = llm_response_processors if llm_response_processors is not None else list(self.DEFAULT_LLM_RESPONSE_PROCESSORS)
|
|
77
|
+
self.system_prompt_processors = system_prompt_processors if system_prompt_processors is not None else list(self.DEFAULT_SYSTEM_PROMPT_PROCESSORS)
|
|
78
|
+
self.phase_hooks = phase_hooks or []
|
|
79
|
+
self.initial_custom_data = initial_custom_data
|
|
80
|
+
|
|
81
|
+
logger.debug(f"AgentConfig created for name '{self.name}', role '{self.role}'.")
|
|
82
|
+
|
|
83
|
+
def __repr__(self) -> str:
|
|
84
|
+
return (f"AgentConfig(name='{self.name}', role='{self.role}', llm_instance='{self.llm_instance.__class__.__name__}', workspace_configured={self.workspace is not None})")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/context/agent_context.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, List, Dict, Any, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .agent_config import AgentConfig
|
|
9
|
+
from .agent_runtime_state import AgentRuntimeState
|
|
10
|
+
from autobyteus.llm.base_llm import BaseLLM
|
|
11
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
12
|
+
from autobyteus.agent.events.agent_input_event_queue_manager import AgentInputEventQueueManager
|
|
13
|
+
from autobyteus.agent.tool_invocation import ToolInvocation
|
|
14
|
+
# LLMConfig no longer needed here
|
|
15
|
+
from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
|
|
16
|
+
from autobyteus.agent.phases import AgentPhaseManager
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
class AgentContext:
|
|
21
|
+
"""
|
|
22
|
+
Represents the complete operational context for a single agent instance.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, agent_id: str, config: 'AgentConfig', state: 'AgentRuntimeState'):
|
|
25
|
+
from .agent_config import AgentConfig as AgentConfigClass
|
|
26
|
+
from .agent_runtime_state import AgentRuntimeState as AgentRuntimeStateClass
|
|
27
|
+
|
|
28
|
+
if not agent_id or not isinstance(agent_id, str):
|
|
29
|
+
raise ValueError("AgentContext requires a non-empty string 'agent_id'.")
|
|
30
|
+
if not isinstance(config, AgentConfigClass):
|
|
31
|
+
raise TypeError(f"AgentContext 'config' must be an AgentConfig instance. Got {type(config)}")
|
|
32
|
+
if not isinstance(state, AgentRuntimeStateClass):
|
|
33
|
+
raise TypeError(f"AgentContext 'state' must be an AgentRuntimeState instance. Got {type(state)}")
|
|
34
|
+
|
|
35
|
+
if agent_id != state.agent_id: # pragma: no cover
|
|
36
|
+
logger.warning(f"AgentContext created with mismatched agent_id ('{agent_id}') and state's ID ('{state.agent_id}'). Using context's ID for logging.")
|
|
37
|
+
|
|
38
|
+
self.agent_id: str = agent_id
|
|
39
|
+
self.config: 'AgentConfig' = config
|
|
40
|
+
self.state: 'AgentRuntimeState' = state
|
|
41
|
+
|
|
42
|
+
logger.info(f"AgentContext composed for agent_id '{self.agent_id}'. Config and State linked.")
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def tool_instances(self) -> Dict[str, 'BaseTool']:
|
|
46
|
+
return self.state.tool_instances if self.state.tool_instances is not None else {}
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def auto_execute_tools(self) -> bool:
|
|
50
|
+
return self.config.auto_execute_tools
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def llm_instance(self) -> Optional['BaseLLM']:
|
|
54
|
+
return self.state.llm_instance
|
|
55
|
+
|
|
56
|
+
@llm_instance.setter
|
|
57
|
+
def llm_instance(self, value: Optional['BaseLLM']):
|
|
58
|
+
self.state.llm_instance = value
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def input_event_queues(self) -> 'AgentInputEventQueueManager':
|
|
62
|
+
if self.state.input_event_queues is None:
|
|
63
|
+
logger.critical(f"AgentContext for '{self.agent_id}': Attempted to access 'input_event_queues' before they were initialized by AgentWorker.")
|
|
64
|
+
raise RuntimeError(f"Agent '{self.agent_id}': Input event queues have not been initialized. This typically occurs during agent bootstrapping.")
|
|
65
|
+
return self.state.input_event_queues
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def current_phase(self) -> 'AgentOperationalPhase':
|
|
69
|
+
return self.state.current_phase
|
|
70
|
+
|
|
71
|
+
@current_phase.setter
|
|
72
|
+
def current_phase(self, value: 'AgentOperationalPhase'):
|
|
73
|
+
if not isinstance(value, AgentOperationalPhase): # pragma: no cover
|
|
74
|
+
raise TypeError(f"current_phase must be an AgentOperationalPhase instance. Got {type(value)}")
|
|
75
|
+
self.state.current_phase = value
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def phase_manager(self) -> Optional['AgentPhaseManager']:
|
|
79
|
+
return self.state.phase_manager_ref
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def conversation_history(self) -> List[Dict[str, Any]]:
|
|
83
|
+
return self.state.conversation_history
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def pending_tool_approvals(self) -> Dict[str, 'ToolInvocation']:
|
|
87
|
+
return self.state.pending_tool_approvals
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def custom_data(self) -> Dict[str, Any]:
|
|
91
|
+
return self.state.custom_data
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def workspace(self) -> Optional['BaseAgentWorkspace']:
|
|
95
|
+
return self.state.workspace
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def processed_system_prompt(self) -> Optional[str]:
|
|
99
|
+
return self.state.processed_system_prompt
|
|
100
|
+
|
|
101
|
+
@processed_system_prompt.setter
|
|
102
|
+
def processed_system_prompt(self, value: Optional[str]):
|
|
103
|
+
self.state.processed_system_prompt = value
|
|
104
|
+
|
|
105
|
+
# final_llm_config_for_creation property removed
|
|
106
|
+
|
|
107
|
+
def add_message_to_history(self, message: Dict[str, Any]) -> None:
|
|
108
|
+
self.state.add_message_to_history(message)
|
|
109
|
+
|
|
110
|
+
def get_tool(self, tool_name: str) -> Optional['BaseTool']:
|
|
111
|
+
tool = self.tool_instances.get(tool_name)
|
|
112
|
+
if not tool: # pragma: no cover
|
|
113
|
+
logger.warning(f"Tool '{tool_name}' not found in AgentContext.state.tool_instances for agent '{self.agent_id}'. "
|
|
114
|
+
f"Available tools: {list(self.tool_instances.keys())}")
|
|
115
|
+
return tool
|
|
116
|
+
|
|
117
|
+
def store_pending_tool_invocation(self, invocation: 'ToolInvocation') -> None:
|
|
118
|
+
self.state.store_pending_tool_invocation(invocation)
|
|
119
|
+
|
|
120
|
+
def retrieve_pending_tool_invocation(self, invocation_id: str) -> Optional['ToolInvocation']:
|
|
121
|
+
return self.state.retrieve_pending_tool_invocation(invocation_id)
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str:
|
|
124
|
+
input_q_status = "Initialized" if self.state.input_event_queues is not None else "Pending Init"
|
|
125
|
+
return (f"AgentContext(agent_id='{self.agent_id}', "
|
|
126
|
+
f"current_phase='{self.state.current_phase.value}', "
|
|
127
|
+
f"llm_initialized={self.state.llm_instance is not None}, "
|
|
128
|
+
f"tools_initialized={self.state.tool_instances is not None}, "
|
|
129
|
+
f"input_queues_status='{input_q_status}')")
|
|
@@ -0,0 +1,264 @@
|
|
|
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")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/context/agent_runtime_state.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Dict, Any, Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent.events.agent_input_event_queue_manager import AgentInputEventQueueManager
|
|
6
|
+
# AgentOutputDataManager is no longer part of AgentRuntimeState
|
|
7
|
+
# from autobyteus.agent.events.agent_output_data_manager import AgentOutputDataManager
|
|
8
|
+
|
|
9
|
+
from autobyteus.llm.base_llm import BaseLLM
|
|
10
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
11
|
+
from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
|
|
12
|
+
from autobyteus.agent.tool_invocation import ToolInvocation
|
|
13
|
+
# LLMConfig is no longer needed here
|
|
14
|
+
# from autobyteus.llm.utils.llm_config import LLMConfig
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from autobyteus.agent.phases import AgentPhaseManager
|
|
18
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
class AgentRuntimeState:
|
|
23
|
+
"""
|
|
24
|
+
Encapsulates the dynamic, stateful data of an agent instance.
|
|
25
|
+
Input event queues are initialized by the AgentWorker via a bootstrap step.
|
|
26
|
+
Output data is now handled by emitting events via AgentExternalEventNotifier.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self,
|
|
29
|
+
agent_id: str,
|
|
30
|
+
workspace: Optional[BaseAgentWorkspace] = None,
|
|
31
|
+
conversation_history: Optional[List[Dict[str, Any]]] = None,
|
|
32
|
+
custom_data: Optional[Dict[str, Any]] = None):
|
|
33
|
+
if not agent_id or not isinstance(agent_id, str):
|
|
34
|
+
raise ValueError("AgentRuntimeState requires a non-empty string 'agent_id'.")
|
|
35
|
+
if workspace is not None and not isinstance(workspace, BaseAgentWorkspace): # pragma: no cover
|
|
36
|
+
raise TypeError(f"AgentRuntimeState 'workspace' must be a BaseAgentWorkspace or None. Got {type(workspace)}")
|
|
37
|
+
|
|
38
|
+
self.agent_id: str = agent_id
|
|
39
|
+
self.current_phase: AgentOperationalPhase = AgentOperationalPhase.UNINITIALIZED
|
|
40
|
+
self.llm_instance: Optional[BaseLLM] = None
|
|
41
|
+
self.tool_instances: Optional[Dict[str, 'BaseTool']] = None
|
|
42
|
+
|
|
43
|
+
self.input_event_queues: Optional[AgentInputEventQueueManager] = None
|
|
44
|
+
# REMOVED: self.output_data_queues attribute
|
|
45
|
+
|
|
46
|
+
self.workspace: Optional[BaseAgentWorkspace] = workspace
|
|
47
|
+
self.conversation_history: List[Dict[str, Any]] = conversation_history or []
|
|
48
|
+
self.pending_tool_approvals: Dict[str, ToolInvocation] = {}
|
|
49
|
+
self.custom_data: Dict[str, Any] = custom_data or {}
|
|
50
|
+
|
|
51
|
+
self.processed_system_prompt: Optional[str] = None
|
|
52
|
+
# self.final_llm_config_for_creation removed
|
|
53
|
+
|
|
54
|
+
self.phase_manager_ref: Optional['AgentPhaseManager'] = None
|
|
55
|
+
|
|
56
|
+
logger.info(f"AgentRuntimeState initialized for agent_id '{self.agent_id}'. Initial phase: {self.current_phase.value}. Workspace linked. InputQueues pending initialization. Output data via notifier.")
|
|
57
|
+
|
|
58
|
+
def add_message_to_history(self, message: Dict[str, Any]) -> None:
|
|
59
|
+
if not isinstance(message, dict) or "role" not in message: # pragma: no cover
|
|
60
|
+
logger.warning(f"Attempted to add malformed message to history for agent '{self.agent_id}': {message}")
|
|
61
|
+
return
|
|
62
|
+
self.conversation_history.append(message)
|
|
63
|
+
logger.debug(f"Message added to history for agent '{self.agent_id}': role={message['role']}")
|
|
64
|
+
|
|
65
|
+
def store_pending_tool_invocation(self, invocation: ToolInvocation) -> None:
|
|
66
|
+
if not isinstance(invocation, ToolInvocation) or not invocation.id: # pragma: no cover
|
|
67
|
+
logger.error(f"Agent '{self.agent_id}': Attempted to store invalid ToolInvocation for approval: {invocation}")
|
|
68
|
+
return
|
|
69
|
+
self.pending_tool_approvals[invocation.id] = invocation
|
|
70
|
+
logger.info(f"Agent '{self.agent_id}': Stored pending tool invocation '{invocation.id}' ({invocation.name}).")
|
|
71
|
+
|
|
72
|
+
def retrieve_pending_tool_invocation(self, invocation_id: str) -> Optional[ToolInvocation]:
|
|
73
|
+
invocation = self.pending_tool_approvals.pop(invocation_id, None)
|
|
74
|
+
if invocation:
|
|
75
|
+
logger.info(f"Agent '{self.agent_id}': Retrieved pending tool invocation '{invocation_id}' ({invocation.name}).")
|
|
76
|
+
else: # pragma: no cover
|
|
77
|
+
logger.warning(f"Agent '{self.agent_id}': Pending tool invocation '{invocation_id}' not found.")
|
|
78
|
+
return invocation
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
phase_repr = self.current_phase.value
|
|
82
|
+
llm_status = "Initialized" if self.llm_instance else "Not Initialized"
|
|
83
|
+
tools_status = f"{len(self.tool_instances)} Initialized" if self.tool_instances is not None else "Not Initialized"
|
|
84
|
+
input_queues_status = "Initialized" if self.input_event_queues else "Not Initialized"
|
|
85
|
+
# REMOVED output_queues_status from repr
|
|
86
|
+
return (f"AgentRuntimeState(agent_id='{self.agent_id}', current_phase='{phase_repr}', "
|
|
87
|
+
f"llm_status='{llm_status}', tools_status='{tools_status}', "
|
|
88
|
+
f"input_queues_status='{input_queues_status}', "
|
|
89
|
+
f"pending_approvals={len(self.pending_tool_approvals)}, history_len={len(self.conversation_history)})")
|