autobyteus 1.1.2__py3-none-any.whl → 1.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autobyteus/agent/agent.py +1 -1
- autobyteus/agent/bootstrap_steps/__init__.py +2 -0
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
- autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/agent_config.py +36 -5
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
- autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
- autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +68 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_worker.py +25 -34
- autobyteus/agent/shutdown_steps/__init__.py +17 -0
- autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
- autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
- autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
- autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
- autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
- autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
- autobyteus/agent/workspace/base_workspace.py +17 -2
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/cli_display.py +1 -1
- autobyteus/cli/workflow_tui/__init__.py +4 -0
- autobyteus/cli/workflow_tui/app.py +210 -0
- autobyteus/cli/workflow_tui/state.py +189 -0
- autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
- autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
- autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
- autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
- autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
- autobyteus/events/event_types.py +3 -0
- autobyteus/llm/api/lmstudio_llm.py +37 -0
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/llm_factory.py +2 -0
- autobyteus/llm/lmstudio_provider.py +89 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/base_tool.py +2 -0
- autobyteus/tools/bash/bash_executor.py +2 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
- autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
- autobyteus/tools/browser/standalone/navigate_to.py +2 -0
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
- autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
- autobyteus/tools/file/file_reader.py +36 -9
- autobyteus/tools/file/file_writer.py +37 -9
- autobyteus/tools/functional_tool.py +5 -4
- autobyteus/tools/image_downloader.py +2 -0
- autobyteus/tools/mcp/__init__.py +10 -7
- autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
- autobyteus/tools/mcp/config_service.py +1 -6
- autobyteus/tools/mcp/factory.py +12 -26
- autobyteus/tools/mcp/server/__init__.py +16 -0
- autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
- autobyteus/tools/mcp/server/proxy.py +36 -0
- autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
- autobyteus/tools/mcp/server_instance_manager.py +93 -0
- autobyteus/tools/mcp/tool.py +28 -46
- autobyteus/tools/mcp/tool_registrar.py +179 -0
- autobyteus/tools/mcp/types.py +10 -21
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +20 -7
- autobyteus/tools/registry/tool_registry.py +75 -28
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +14 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/workflow/agentic_workflow.py +93 -0
- autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
- autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
- autobyteus/workflow/context/__init__.py +17 -0
- autobyteus/workflow/context/team_manager.py +147 -0
- autobyteus/workflow/context/workflow_config.py +30 -0
- autobyteus/workflow/context/workflow_context.py +61 -0
- autobyteus/workflow/context/workflow_node_config.py +76 -0
- autobyteus/workflow/context/workflow_runtime_state.py +53 -0
- autobyteus/workflow/events/__init__.py +29 -0
- autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
- autobyteus/workflow/events/workflow_events.py +53 -0
- autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
- autobyteus/workflow/exceptions.py +8 -0
- autobyteus/workflow/factory/__init__.py +9 -0
- autobyteus/workflow/factory/workflow_factory.py +99 -0
- autobyteus/workflow/handlers/__init__.py +19 -0
- autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
- autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
- autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
- autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
- autobyteus/workflow/phases/__init__.py +11 -0
- autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
- autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
- autobyteus/workflow/runtime/__init__.py +13 -0
- autobyteus/workflow/runtime/workflow_runtime.py +82 -0
- autobyteus/workflow/runtime/workflow_worker.py +117 -0
- autobyteus/workflow/shutdown_steps/__init__.py +17 -0
- autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
- autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
- autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
- autobyteus/workflow/streaming/__init__.py +26 -0
- autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
- autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
- autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
- autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
- autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
- autobyteus/workflow/utils/__init__.py +9 -0
- autobyteus/workflow/utils/wait_for_idle.py +46 -0
- autobyteus/workflow/workflow_builder.py +151 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +260 -0
- examples/run_google_slides_agent.py +286 -0
- examples/run_mcp_browser_client.py +174 -0
- examples/run_mcp_google_slides_client.py +270 -0
- examples/run_mcp_list_tools.py +189 -0
- examples/run_poem_writer.py +274 -0
- examples/run_sqlite_agent.py +293 -0
- examples/workflow/__init__.py +1 -0
- examples/workflow/run_basic_research_workflow.py +189 -0
- examples/workflow/run_code_review_workflow.py +269 -0
- examples/workflow/run_debate_workflow.py +212 -0
- examples/workflow/run_workflow_with_tui.py +153 -0
- autobyteus/agent/context/agent_phase_manager.py +0 -264
- autobyteus/agent/context/phases.py +0 -49
- autobyteus/agent/group/__init__.py +0 -0
- autobyteus/agent/group/agent_group.py +0 -164
- autobyteus/agent/group/agent_group_context.py +0 -81
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
- autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
- autobyteus/agent/workflow/__init__.py +0 -11
- autobyteus/agent/workflow/agentic_workflow.py +0 -89
- autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
- autobyteus/tools/mcp/registrar.py +0 -323
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
autobyteus/agent/agent.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from typing import AsyncIterator, Optional, List, Any, Dict, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from autobyteus.agent.runtime.agent_runtime import AgentRuntime
|
|
7
|
-
from autobyteus.agent.
|
|
7
|
+
from autobyteus.agent.phases.phase_enum import AgentOperationalPhase
|
|
8
8
|
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
9
9
|
from autobyteus.agent.message.inter_agent_message import InterAgentMessage
|
|
10
10
|
from autobyteus.agent.events import UserMessageReceivedEvent, InterAgentMessageReceivedEvent, ToolExecutionApprovalEvent, BaseEvent
|
|
@@ -9,6 +9,7 @@ from .agent_runtime_queue_initialization_step import AgentRuntimeQueueInitializa
|
|
|
9
9
|
from .workspace_context_initialization_step import WorkspaceContextInitializationStep
|
|
10
10
|
# ToolInitializationStep is no longer a bootstrap step.
|
|
11
11
|
from .system_prompt_processing_step import SystemPromptProcessingStep
|
|
12
|
+
from .mcp_server_prewarming_step import McpServerPrewarmingStep
|
|
12
13
|
# LLMConfigFinalizationStep and LLMInstanceCreationStep removed.
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
@@ -16,4 +17,5 @@ __all__ = [
|
|
|
16
17
|
"AgentRuntimeQueueInitializationStep", # UPDATED
|
|
17
18
|
"WorkspaceContextInitializationStep",
|
|
18
19
|
"SystemPromptProcessingStep",
|
|
20
|
+
"McpServerPrewarmingStep",
|
|
19
21
|
]
|
|
@@ -6,6 +6,7 @@ from .base_bootstrap_step import BaseBootstrapStep
|
|
|
6
6
|
from .agent_runtime_queue_initialization_step import AgentRuntimeQueueInitializationStep
|
|
7
7
|
from .workspace_context_initialization_step import WorkspaceContextInitializationStep
|
|
8
8
|
from .system_prompt_processing_step import SystemPromptProcessingStep
|
|
9
|
+
from .mcp_server_prewarming_step import McpServerPrewarmingStep
|
|
9
10
|
from autobyteus.agent.events import AgentReadyEvent
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
@@ -31,6 +32,7 @@ class AgentBootstrapper:
|
|
|
31
32
|
self.bootstrap_steps: List[BaseBootstrapStep] = [
|
|
32
33
|
AgentRuntimeQueueInitializationStep(),
|
|
33
34
|
WorkspaceContextInitializationStep(),
|
|
35
|
+
McpServerPrewarmingStep(),
|
|
34
36
|
SystemPromptProcessingStep(),
|
|
35
37
|
]
|
|
36
38
|
logger.debug("AgentBootstrapper initialized with default steps.")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Set
|
|
4
|
+
|
|
5
|
+
from .base_bootstrap_step import BaseBootstrapStep
|
|
6
|
+
from autobyteus.tools.mcp.config_service import McpConfigService
|
|
7
|
+
from autobyteus.tools.mcp.server_instance_manager import McpServerInstanceManager
|
|
8
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.agent.context import AgentContext
|
|
12
|
+
from autobyteus.agent.phases import AgentPhaseManager
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class McpServerPrewarmingStep(BaseBootstrapStep):
|
|
17
|
+
"""
|
|
18
|
+
Bootstrap step to eagerly start all MCP servers associated with an agent's tools.
|
|
19
|
+
This ensures servers are running and ready before the agent becomes idle.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self._config_service = McpConfigService()
|
|
24
|
+
self._instance_manager = McpServerInstanceManager()
|
|
25
|
+
logger.debug("McpServerPrewarmingStep initialized.")
|
|
26
|
+
|
|
27
|
+
async def execute(self,
|
|
28
|
+
context: 'AgentContext',
|
|
29
|
+
phase_manager: 'AgentPhaseManager') -> bool:
|
|
30
|
+
agent_id = context.agent_id
|
|
31
|
+
logger.info(f"Agent '{agent_id}': Executing McpServerPrewarmingStep.")
|
|
32
|
+
|
|
33
|
+
# 1. Find all unique server IDs by inspecting tool definitions.
|
|
34
|
+
mcp_server_ids: Set[str] = set()
|
|
35
|
+
for tool in context.config.tools:
|
|
36
|
+
# This is the new, superior check. It relies on abstract metadata, not concrete types.
|
|
37
|
+
if tool.definition and tool.definition.category == ToolCategory.MCP:
|
|
38
|
+
# This is the new, superior way to get the server_id.
|
|
39
|
+
# It does not rely on private attributes of the tool instance.
|
|
40
|
+
server_id = tool.definition.metadata.get("mcp_server_id")
|
|
41
|
+
if server_id:
|
|
42
|
+
mcp_server_ids.add(server_id)
|
|
43
|
+
|
|
44
|
+
if not mcp_server_ids:
|
|
45
|
+
logger.debug(f"Agent '{agent_id}': No MCP tools found. Skipping MCP server pre-warming.")
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
logger.info(f"Agent '{agent_id}': Found {len(mcp_server_ids)} unique MCP server IDs to pre-warm: {mcp_server_ids}")
|
|
49
|
+
|
|
50
|
+
# 2. For each server ID, unconditionally start its server instance for this agent.
|
|
51
|
+
for server_id in mcp_server_ids:
|
|
52
|
+
try:
|
|
53
|
+
config = self._config_service.get_config(server_id)
|
|
54
|
+
if not config:
|
|
55
|
+
logger.warning(f"Agent '{agent_id}': Could not find config for server_id '{server_id}' used by a tool. Cannot pre-warm.")
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
logger.info(f"Agent '{agent_id}': Pre-warming MCP server '{server_id}'.")
|
|
59
|
+
# Get the instance for this agent, which creates it if it doesn't exist.
|
|
60
|
+
server_instance = self._instance_manager.get_server_instance(agent_id, server_id)
|
|
61
|
+
# Explicitly connect to start the server process.
|
|
62
|
+
await server_instance.connect()
|
|
63
|
+
logger.info(f"Agent '{agent_id}': Successfully connected to pre-warmed MCP server '{server_id}'.")
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
error_message = f"Agent '{agent_id}': Failed to pre-warm MCP server '{server_id}': {e}"
|
|
67
|
+
logger.error(error_message, exc_info=True)
|
|
68
|
+
# A failure to pre-warm a server is a critical bootstrap failure.
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
return True
|
|
@@ -34,8 +34,10 @@ class SystemPromptProcessingStep(BaseBootstrapStep):
|
|
|
34
34
|
if not llm_instance:
|
|
35
35
|
raise ValueError("LLM instance not found in agent state. It must be provided in AgentConfig.")
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
# If a specific system_prompt is not provided in AgentConfig, fall back
|
|
38
|
+
# to the default system_message from the LLM's own configuration.
|
|
39
|
+
current_system_prompt = context.config.system_prompt or llm_instance.config.system_message
|
|
40
|
+
logger.debug(f"Agent '{agent_id}': Retrieved base system prompt.")
|
|
39
41
|
|
|
40
42
|
processor_instances = context.config.system_prompt_processors
|
|
41
43
|
tool_instances_for_processor = context.tool_instances
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/agent/context/agent_config.py
|
|
2
2
|
import logging
|
|
3
|
+
import copy
|
|
3
4
|
from typing import List, Optional, Union, Tuple, TYPE_CHECKING, Dict, Any
|
|
4
5
|
|
|
5
6
|
# Correctly import the new master processor and the base class
|
|
@@ -10,6 +11,7 @@ from autobyteus.agent.llm_response_processor import ProviderAwareToolUsageProces
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from autobyteus.tools.base_tool import BaseTool
|
|
12
13
|
from autobyteus.agent.input_processor import BaseAgentUserInputMessageProcessor
|
|
14
|
+
from autobyteus.agent.tool_execution_result_processor import BaseToolExecutionResultProcessor
|
|
13
15
|
from autobyteus.llm.base_llm import BaseLLM
|
|
14
16
|
from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
|
|
15
17
|
from autobyteus.agent.hooks.base_phase_hook import BasePhaseHook
|
|
@@ -32,13 +34,14 @@ class AgentConfig:
|
|
|
32
34
|
role: str,
|
|
33
35
|
description: str,
|
|
34
36
|
llm_instance: 'BaseLLM',
|
|
35
|
-
system_prompt: str,
|
|
36
|
-
tools: List['BaseTool'],
|
|
37
|
+
system_prompt: Optional[str] = None,
|
|
38
|
+
tools: Optional[List['BaseTool']] = None,
|
|
37
39
|
auto_execute_tools: bool = True,
|
|
38
40
|
use_xml_tool_format: bool = True,
|
|
39
41
|
input_processors: Optional[List['BaseAgentUserInputMessageProcessor']] = None,
|
|
40
42
|
llm_response_processors: Optional[List['BaseLLMResponseProcessor']] = None,
|
|
41
43
|
system_prompt_processors: Optional[List['BaseSystemPromptProcessor']] = None,
|
|
44
|
+
tool_execution_result_processors: Optional[List['BaseToolExecutionResultProcessor']] = None,
|
|
42
45
|
workspace: Optional['BaseAgentWorkspace'] = None,
|
|
43
46
|
phase_hooks: Optional[List['BasePhaseHook']] = None,
|
|
44
47
|
initial_custom_data: Optional[Dict[str, Any]] = None):
|
|
@@ -51,13 +54,15 @@ class AgentConfig:
|
|
|
51
54
|
description: A description of the agent.
|
|
52
55
|
llm_instance: A pre-initialized LLM instance (subclass of BaseLLM).
|
|
53
56
|
The user is responsible for creating and configuring this instance.
|
|
54
|
-
system_prompt: The base system prompt.
|
|
55
|
-
|
|
57
|
+
system_prompt: The base system prompt. If None, the system_message from the
|
|
58
|
+
llm_instance's config will be used as the base.
|
|
59
|
+
tools: An optional list of pre-initialized tool instances (subclasses of BaseTool).
|
|
56
60
|
auto_execute_tools: If True, the agent will execute tools without approval.
|
|
57
61
|
use_xml_tool_format: Whether to use XML for tool descriptions and examples.
|
|
58
62
|
input_processors: A list of input processor instances.
|
|
59
63
|
llm_response_processors: A list of LLM response processor instances.
|
|
60
64
|
system_prompt_processors: A list of system prompt processor instances.
|
|
65
|
+
tool_execution_result_processors: A list of tool execution result processor instances.
|
|
61
66
|
workspace: An optional pre-initialized workspace instance for the agent.
|
|
62
67
|
phase_hooks: An optional list of phase transition hook instances.
|
|
63
68
|
initial_custom_data: An optional dictionary of data to pre-populate
|
|
@@ -68,17 +73,43 @@ class AgentConfig:
|
|
|
68
73
|
self.description = description
|
|
69
74
|
self.llm_instance = llm_instance
|
|
70
75
|
self.system_prompt = system_prompt
|
|
71
|
-
self.tools = tools
|
|
76
|
+
self.tools = tools or []
|
|
72
77
|
self.workspace = workspace
|
|
73
78
|
self.auto_execute_tools = auto_execute_tools
|
|
74
79
|
self.use_xml_tool_format = use_xml_tool_format
|
|
75
80
|
self.input_processors = input_processors or []
|
|
76
81
|
self.llm_response_processors = llm_response_processors if llm_response_processors is not None else list(self.DEFAULT_LLM_RESPONSE_PROCESSORS)
|
|
77
82
|
self.system_prompt_processors = system_prompt_processors if system_prompt_processors is not None else list(self.DEFAULT_SYSTEM_PROMPT_PROCESSORS)
|
|
83
|
+
self.tool_execution_result_processors = tool_execution_result_processors or []
|
|
78
84
|
self.phase_hooks = phase_hooks or []
|
|
79
85
|
self.initial_custom_data = initial_custom_data
|
|
80
86
|
|
|
81
87
|
logger.debug(f"AgentConfig created for name '{self.name}', role '{self.role}'.")
|
|
82
88
|
|
|
89
|
+
def copy(self) -> 'AgentConfig':
|
|
90
|
+
"""
|
|
91
|
+
Creates a copy of this AgentConfig. It avoids deep-copying complex objects
|
|
92
|
+
like tools, workspaces, and processors that may contain un-pickleable state.
|
|
93
|
+
Instead, it creates shallow copies of the lists, allowing the lists themselves
|
|
94
|
+
to be modified independently while sharing the object instances within them.
|
|
95
|
+
"""
|
|
96
|
+
return AgentConfig(
|
|
97
|
+
name=self.name,
|
|
98
|
+
role=self.role,
|
|
99
|
+
description=self.description,
|
|
100
|
+
llm_instance=self.llm_instance, # Keep reference, do not copy
|
|
101
|
+
system_prompt=self.system_prompt,
|
|
102
|
+
tools=self.tools.copy(), # Shallow copy the list, but reference the original tool instances
|
|
103
|
+
auto_execute_tools=self.auto_execute_tools,
|
|
104
|
+
use_xml_tool_format=self.use_xml_tool_format,
|
|
105
|
+
input_processors=self.input_processors.copy(), # Shallow copy the list
|
|
106
|
+
llm_response_processors=self.llm_response_processors.copy(), # Shallow copy the list
|
|
107
|
+
system_prompt_processors=self.system_prompt_processors.copy(), # Shallow copy the list
|
|
108
|
+
tool_execution_result_processors=self.tool_execution_result_processors.copy(), # Shallow copy the list
|
|
109
|
+
workspace=self.workspace, # Pass by reference, do not copy
|
|
110
|
+
phase_hooks=self.phase_hooks.copy(), # Shallow copy the list
|
|
111
|
+
initial_custom_data=copy.deepcopy(self.initial_custom_data) # Deep copy for simple data
|
|
112
|
+
)
|
|
113
|
+
|
|
83
114
|
def __repr__(self) -> str:
|
|
84
115
|
return (f"AgentConfig(name='{self.name}', role='{self.role}', llm_instance='{self.llm_instance.__class__.__name__}', workspace_configured={self.workspace is not None})")
|
|
@@ -21,8 +21,7 @@ from autobyteus.agent.events.agent_events import ( # Updated relative import pat
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from autobyteus.agent.context import AgentContext
|
|
23
23
|
from autobyteus.agent.handlers import EventHandlerRegistry
|
|
24
|
-
from autobyteus.agent.
|
|
25
|
-
|
|
24
|
+
from autobyteus.agent.phases import AgentPhaseManager
|
|
26
25
|
logger = logging.getLogger(__name__)
|
|
27
26
|
|
|
28
27
|
class WorkerEventDispatcher:
|
|
@@ -43,7 +43,7 @@ class InterAgentMessageReceivedEventHandler(AgentEventHandler):
|
|
|
43
43
|
logger.info(
|
|
44
44
|
f"Agent '{context.agent_id}' handling InterAgentMessageReceivedEvent from sender "
|
|
45
45
|
f"'{inter_agent_msg.sender_agent_id}', type '{inter_agent_msg.message_type.value}'. "
|
|
46
|
-
f"Content: '{inter_agent_msg.content
|
|
46
|
+
f"Content: '{inter_agent_msg.content}'"
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
content_for_llm = (
|
|
@@ -45,7 +45,7 @@ class LLMUserMessageReadyEventHandler(AgentEventHandler):
|
|
|
45
45
|
raise RuntimeError(error_msg)
|
|
46
46
|
|
|
47
47
|
llm_user_message: LLMUserMessage = event.llm_user_message
|
|
48
|
-
logger.info(f"Agent '{agent_id}' handling LLMUserMessageReadyEvent: '{llm_user_message.content
|
|
48
|
+
logger.info(f"Agent '{agent_id}' handling LLMUserMessageReadyEvent: '{llm_user_message.content}'")
|
|
49
49
|
logger.debug(f"Agent '{agent_id}' preparing to send full message to LLM:\n---\n{llm_user_message.content}\n---")
|
|
50
50
|
|
|
51
51
|
context.state.add_message_to_history({"role": "user", "content": llm_user_message.content})
|
|
@@ -137,4 +137,4 @@ class LLMUserMessageReadyEventHandler(AgentEventHandler):
|
|
|
137
137
|
complete_response=complete_response_obj
|
|
138
138
|
)
|
|
139
139
|
await context.input_event_queues.enqueue_internal_system_event(llm_complete_event)
|
|
140
|
-
logger.info(f"Agent '{agent_id}' enqueued LLMCompleteResponseReceivedEvent from LLMUserMessageReadyEventHandler.")
|
|
140
|
+
logger.info(f"Agent '{agent_id}' enqueued LLMCompleteResponseReceivedEvent from LLMUserMessageReadyEventHandler.")
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional
|
|
|
6
6
|
from autobyteus.agent.handlers.base_event_handler import AgentEventHandler
|
|
7
7
|
from autobyteus.agent.events import ToolResultEvent, LLMUserMessageReadyEvent
|
|
8
8
|
from autobyteus.llm.user_message import LLMUserMessage
|
|
9
|
+
from autobyteus.agent.tool_execution_result_processor import BaseToolExecutionResultProcessor
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from autobyteus.agent.context import AgentContext
|
|
@@ -29,10 +30,37 @@ class ToolResultEventHandler(AgentEventHandler):
|
|
|
29
30
|
logger.warning(f"ToolResultEventHandler received non-ToolResultEvent: {type(event)}. Skipping.")
|
|
30
31
|
return
|
|
31
32
|
|
|
32
|
-
agent_id = context.agent_id
|
|
33
|
-
|
|
33
|
+
agent_id = context.agent_id
|
|
34
|
+
processed_event = event
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
# --- New: Apply Tool Execution Result Processors ---
|
|
37
|
+
processor_instances = context.config.tool_execution_result_processors
|
|
38
|
+
if processor_instances:
|
|
39
|
+
processor_names = [p.get_name() for p in processor_instances]
|
|
40
|
+
logger.debug(f"Agent '{agent_id}': Applying tool execution result processors: {processor_names}")
|
|
41
|
+
for processor_instance in processor_instances:
|
|
42
|
+
processor_name_for_log = "unknown"
|
|
43
|
+
try:
|
|
44
|
+
if not isinstance(processor_instance, BaseToolExecutionResultProcessor):
|
|
45
|
+
logger.error(f"Agent '{agent_id}': Invalid tool result processor type: {type(processor_instance)}. Skipping.")
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
processor_name_for_log = processor_instance.get_name()
|
|
49
|
+
logger.debug(f"Agent '{agent_id}': Applying tool result processor '{processor_name_for_log}'.")
|
|
50
|
+
|
|
51
|
+
event_before_proc = processed_event
|
|
52
|
+
processed_event = await processor_instance.process(event_before_proc, context)
|
|
53
|
+
logger.info(f"Agent '{agent_id}': Tool result processor '{processor_name_for_log}' applied successfully.")
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Agent '{agent_id}': Error applying tool result processor '{processor_name_for_log}': {e}. "
|
|
57
|
+
f"Skipping and continuing with result from before this processor.", exc_info=True)
|
|
58
|
+
processed_event = event_before_proc
|
|
59
|
+
# --- End New ---
|
|
60
|
+
|
|
61
|
+
tool_invocation_id = processed_event.tool_invocation_id if processed_event.tool_invocation_id else 'N/A'
|
|
62
|
+
|
|
63
|
+
logger.info(f"Agent '{agent_id}' handling processed ToolResultEvent from tool: '{processed_event.tool_name}' (Invocation ID: {tool_invocation_id}). Error: {processed_event.error is not None}")
|
|
36
64
|
|
|
37
65
|
notifier: Optional['AgentExternalEventNotifier'] = None
|
|
38
66
|
if context.phase_manager:
|
|
@@ -41,61 +69,61 @@ class ToolResultEventHandler(AgentEventHandler):
|
|
|
41
69
|
if not notifier: # pragma: no cover
|
|
42
70
|
logger.error(f"Agent '{agent_id}': Notifier not available in ToolResultEventHandler. Tool result processing logs will not be emitted.")
|
|
43
71
|
|
|
44
|
-
if
|
|
45
|
-
logger.debug(f"Agent '{agent_id}' tool '{
|
|
72
|
+
if processed_event.error:
|
|
73
|
+
logger.debug(f"Agent '{agent_id}' tool '{processed_event.tool_name}' (ID: {tool_invocation_id}) raw error details: {processed_event.error}")
|
|
46
74
|
else:
|
|
47
75
|
try:
|
|
48
|
-
raw_result_str_for_debug_log = json.dumps(
|
|
76
|
+
raw_result_str_for_debug_log = json.dumps(processed_event.result, indent=2)
|
|
49
77
|
except TypeError: # pragma: no cover
|
|
50
|
-
raw_result_str_for_debug_log = str(
|
|
51
|
-
logger.debug(f"Agent '{agent_id}' tool '{
|
|
78
|
+
raw_result_str_for_debug_log = str(processed_event.result)
|
|
79
|
+
logger.debug(f"Agent '{agent_id}' tool '{processed_event.tool_name}' (ID: {tool_invocation_id}) raw result:\n---\n{raw_result_str_for_debug_log}\n---")
|
|
52
80
|
|
|
53
81
|
|
|
54
82
|
content_for_llm: str
|
|
55
|
-
if
|
|
83
|
+
if processed_event.error:
|
|
56
84
|
content_for_llm = (
|
|
57
|
-
f"The tool '{
|
|
58
|
-
f"Error details: {
|
|
85
|
+
f"The tool '{processed_event.tool_name}' (invocation ID: {tool_invocation_id}) encountered an error.\n"
|
|
86
|
+
f"Error details: {processed_event.error}\n"
|
|
59
87
|
f"Please analyze this error and decide the next course of action."
|
|
60
88
|
)
|
|
61
|
-
log_msg_error_processed = f"[TOOL_RESULT_ERROR_PROCESSED] Agent_ID: {agent_id}, Tool: {
|
|
89
|
+
log_msg_error_processed = f"[TOOL_RESULT_ERROR_PROCESSED] Agent_ID: {agent_id}, Tool: {processed_event.tool_name}, Invocation_ID: {tool_invocation_id}, Error: {processed_event.error}"
|
|
62
90
|
if notifier:
|
|
63
91
|
try:
|
|
64
92
|
log_data = {
|
|
65
93
|
"log_entry": log_msg_error_processed,
|
|
66
94
|
"tool_invocation_id": tool_invocation_id,
|
|
67
|
-
"tool_name":
|
|
95
|
+
"tool_name": processed_event.tool_name,
|
|
68
96
|
}
|
|
69
97
|
notifier.notify_agent_data_tool_log(log_data)
|
|
70
98
|
except Exception as e_notify:
|
|
71
99
|
logger.error(f"Agent '{agent_id}': Error notifying tool result error log: {e_notify}", exc_info=True)
|
|
72
100
|
else:
|
|
73
101
|
try:
|
|
74
|
-
result_str_for_llm = json.dumps(
|
|
102
|
+
result_str_for_llm = json.dumps(processed_event.result, indent=2) if not isinstance(processed_event.result, str) else processed_event.result
|
|
75
103
|
except TypeError: # pragma: no cover
|
|
76
|
-
result_str_for_llm = str(
|
|
104
|
+
result_str_for_llm = str(processed_event.result)
|
|
77
105
|
|
|
78
106
|
content_for_llm = (
|
|
79
|
-
f"The tool '{
|
|
107
|
+
f"The tool '{processed_event.tool_name}' (invocation ID: {tool_invocation_id}) has executed.\n"
|
|
80
108
|
f"Result:\n{result_str_for_llm}\n"
|
|
81
109
|
f"Based on this result, what is the next step or final answer?"
|
|
82
110
|
)
|
|
83
|
-
log_msg_success_processed = f"[TOOL_RESULT_SUCCESS_PROCESSED] Agent_ID: {agent_id}, Tool: {
|
|
111
|
+
log_msg_success_processed = f"[TOOL_RESULT_SUCCESS_PROCESSED] Agent_ID: {agent_id}, Tool: {processed_event.tool_name}, Invocation_ID: {tool_invocation_id}, Result: {str(processed_event.result)}"
|
|
84
112
|
if notifier:
|
|
85
113
|
try:
|
|
86
114
|
log_data = {
|
|
87
115
|
"log_entry": log_msg_success_processed,
|
|
88
116
|
"tool_invocation_id": tool_invocation_id,
|
|
89
|
-
"tool_name":
|
|
117
|
+
"tool_name": processed_event.tool_name,
|
|
90
118
|
}
|
|
91
119
|
notifier.notify_agent_data_tool_log(log_data)
|
|
92
120
|
except Exception as e_notify:
|
|
93
121
|
logger.error(f"Agent '{agent_id}': Error notifying tool result success log: {e_notify}", exc_info=True)
|
|
94
122
|
|
|
95
|
-
logger.debug(f"Agent '{agent_id}' preparing message for LLM based on tool '{
|
|
123
|
+
logger.debug(f"Agent '{agent_id}' preparing message for LLM based on tool '{processed_event.tool_name}' (ID: {tool_invocation_id}) result:\n---\n{content_for_llm}\n---")
|
|
96
124
|
llm_user_message = LLMUserMessage(content=content_for_llm)
|
|
97
125
|
|
|
98
126
|
next_event = LLMUserMessageReadyEvent(llm_user_message=llm_user_message)
|
|
99
127
|
await context.input_event_queues.enqueue_internal_system_event(next_event)
|
|
100
128
|
|
|
101
|
-
logger.info(f"Agent '{agent_id}' enqueued LLMUserMessageReadyEvent for LLM based on tool '{
|
|
129
|
+
logger.info(f"Agent '{agent_id}' enqueued LLMUserMessageReadyEvent for LLM based on tool '{processed_event.tool_name}' (ID: {tool_invocation_id}) result summary.")
|
|
@@ -35,7 +35,7 @@ class UserInputMessageEventHandler(AgentEventHandler):
|
|
|
35
35
|
original_agent_input_user_msg: AgentInputUserMessage = event.agent_input_user_message
|
|
36
36
|
processed_agent_input_user_msg: AgentInputUserMessage = original_agent_input_user_msg
|
|
37
37
|
|
|
38
|
-
logger.info(f"Agent '{context.agent_id}' handling UserMessageReceivedEvent: '{original_agent_input_user_msg.content
|
|
38
|
+
logger.info(f"Agent '{context.agent_id}' handling UserMessageReceivedEvent: '{original_agent_input_user_msg.content}'")
|
|
39
39
|
|
|
40
40
|
processor_instances = context.config.input_processors
|
|
41
41
|
if processor_instances:
|
|
@@ -4,15 +4,9 @@ Components for pre-processing AgentUserMessage objects.
|
|
|
4
4
|
"""
|
|
5
5
|
from .base_user_input_processor import BaseAgentUserInputMessageProcessor
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
from .passthrough_input_processor import PassthroughInputProcessor
|
|
9
|
-
from .metadata_appending_input_processor import MetadataAppendingInputProcessor
|
|
10
|
-
from .content_prefixing_input_processor import ContentPrefixingInputProcessor
|
|
7
|
+
# Concrete processors have been removed. Users can define their own.
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
__all__ = [
|
|
14
11
|
"BaseAgentUserInputMessageProcessor",
|
|
15
|
-
"PassthroughInputProcessor",
|
|
16
|
-
"MetadataAppendingInputProcessor",
|
|
17
|
-
"ContentPrefixingInputProcessor",
|
|
18
12
|
]
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py
|
|
2
2
|
import logging
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
4
|
|
|
5
5
|
from .base_processor import BaseLLMResponseProcessor
|
|
6
|
+
from autobyteus.agent.events import PendingToolInvocationEvent
|
|
7
|
+
from autobyteus.agent.tool_invocation import ToolInvocation
|
|
6
8
|
from autobyteus.tools.usage.parsers import ProviderAwareToolUsageParser
|
|
7
9
|
from autobyteus.tools.usage.parsers.exceptions import ToolUsageParseException
|
|
8
|
-
from autobyteus.agent.events import PendingToolInvocationEvent
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from autobyteus.agent.context import AgentContext
|
|
@@ -17,21 +18,20 @@ logger = logging.getLogger(__name__)
|
|
|
17
18
|
class ProviderAwareToolUsageProcessor(BaseLLMResponseProcessor):
|
|
18
19
|
"""
|
|
19
20
|
A "master" tool usage processor that uses a high-level parser from the
|
|
20
|
-
`tools` module to extract tool invocations
|
|
21
|
-
|
|
21
|
+
`tools` module to extract tool invocations. It then ensures each invocation
|
|
22
|
+
has a session-unique ID before enqueuing the necessary agent events.
|
|
22
23
|
"""
|
|
24
|
+
INVOCATION_COUNTS_KEY = "agent_tool_invocation_counts"
|
|
25
|
+
|
|
23
26
|
def __init__(self):
|
|
24
27
|
self._parser = ProviderAwareToolUsageParser()
|
|
25
28
|
logger.debug("ProviderAwareToolUsageProcessor initialized.")
|
|
26
29
|
|
|
27
|
-
@classmethod
|
|
28
|
-
def get_name(cls) -> str:
|
|
29
|
-
return "provider_aware_tool_usage"
|
|
30
|
-
|
|
31
30
|
async def process_response(self, response: 'CompleteResponse', context: 'AgentContext', triggering_event: 'LLMCompleteResponseReceivedEvent') -> bool:
|
|
32
31
|
"""
|
|
33
|
-
Uses a ProviderAwareToolUsageParser to get
|
|
34
|
-
and then enqueues a
|
|
32
|
+
Uses a ProviderAwareToolUsageParser to get tool invocations, makes their
|
|
33
|
+
IDs unique within the agent's session, and then enqueues a
|
|
34
|
+
PendingToolInvocationEvent for each one.
|
|
35
35
|
Propagates ToolUsageParseException if parsing fails.
|
|
36
36
|
"""
|
|
37
37
|
try:
|
|
@@ -44,9 +44,38 @@ class ProviderAwareToolUsageProcessor(BaseLLMResponseProcessor):
|
|
|
44
44
|
if not tool_invocations:
|
|
45
45
|
return False
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
# --- NEW LOGIC FOR UNIQUE ID GENERATION ---
|
|
48
|
+
|
|
49
|
+
# Ensure the counter map exists in the agent's state's custom data
|
|
50
|
+
if self.INVOCATION_COUNTS_KEY not in context.custom_data:
|
|
51
|
+
context.custom_data[self.INVOCATION_COUNTS_KEY] = {}
|
|
52
|
+
|
|
53
|
+
invocation_counts = context.custom_data[self.INVOCATION_COUNTS_KEY]
|
|
54
|
+
|
|
55
|
+
processed_invocations: List[ToolInvocation] = []
|
|
56
|
+
|
|
48
57
|
for invocation in tool_invocations:
|
|
49
|
-
|
|
58
|
+
base_id = invocation.id
|
|
59
|
+
|
|
60
|
+
# Get the current count for this base ID, default to 0
|
|
61
|
+
count = invocation_counts.get(base_id, 0)
|
|
62
|
+
|
|
63
|
+
# Create the new session-unique ID
|
|
64
|
+
unique_id = f"{base_id}_{count}"
|
|
65
|
+
|
|
66
|
+
# Update the invocation's ID in-place
|
|
67
|
+
invocation.id = unique_id
|
|
68
|
+
|
|
69
|
+
# Increment the counter for the next time this base ID is seen
|
|
70
|
+
invocation_counts[base_id] = count + 1
|
|
71
|
+
|
|
72
|
+
processed_invocations.append(invocation)
|
|
73
|
+
|
|
74
|
+
# --- END NEW LOGIC ---
|
|
75
|
+
|
|
76
|
+
logger.info(f"Agent '{context.agent_id}': Parsed {len(processed_invocations)} tool invocations. Enqueuing events with unique IDs.")
|
|
77
|
+
for invocation in processed_invocations:
|
|
78
|
+
logger.info(f"Agent '{context.agent_id}' ({self.get_name()}) identified tool invocation: {invocation.name} with unique ID {invocation.id}. Enqueuing event.")
|
|
50
79
|
await context.input_event_queues.enqueue_tool_invocation_request(
|
|
51
80
|
PendingToolInvocationEvent(tool_invocation=invocation)
|
|
52
81
|
)
|
|
@@ -17,6 +17,8 @@ class ContextFileType(str, Enum):
|
|
|
17
17
|
HTML = "html" # .html, .htm
|
|
18
18
|
PYTHON = "python" # .py
|
|
19
19
|
JAVASCRIPT = "javascript" # .js
|
|
20
|
+
AUDIO = "audio" # .mp3, .wav, .m4a, .flac, .ogg
|
|
21
|
+
VIDEO = "video" # .mp4, .mov, .avi, .mkv, .webm
|
|
20
22
|
IMAGE = "image" # .png, .jpg, .jpeg, .gif, .webp (when image is for contextual analysis, not direct LLM vision input)
|
|
21
23
|
UNKNOWN = "unknown" # Fallback for unrecognized types
|
|
22
24
|
|
|
@@ -54,6 +56,10 @@ class ContextFileType(str, Enum):
|
|
|
54
56
|
return cls.PYTHON
|
|
55
57
|
elif extension == ".js":
|
|
56
58
|
return cls.JAVASCRIPT
|
|
59
|
+
elif extension in [".mp3", ".wav", ".m4a", ".flac", ".ogg"]:
|
|
60
|
+
return cls.AUDIO
|
|
61
|
+
elif extension in [".mp4", ".mov", ".avi", ".mkv", ".webm"]:
|
|
62
|
+
return cls.VIDEO
|
|
57
63
|
elif extension in [".png", ".jpg", ".jpeg", ".gif", ".webp"]:
|
|
58
64
|
return cls.IMAGE
|
|
59
65
|
else:
|