autobyteus 1.1.3__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/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/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 +1 -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/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/tool_registrar.py +3 -1
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +12 -8
- autobyteus/tools/registry/tool_registry.py +50 -2
- 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.3.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -14
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/RECORD +134 -65
- {autobyteus-1.1.3.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/registrar.py +0 -202
- 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 → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ from typing import Dict, Any, List as TypingList, Type, TYPE_CHECKING, Optional,
|
|
|
6
6
|
from autobyteus.llm.providers import LLMProvider
|
|
7
7
|
from autobyteus.tools.tool_config import ToolConfig
|
|
8
8
|
from autobyteus.tools.parameter_schema import ParameterSchema
|
|
9
|
-
from autobyteus.tools.
|
|
9
|
+
from autobyteus.tools.tool_origin import ToolOrigin
|
|
10
10
|
from autobyteus.tools.usage.providers import (
|
|
11
11
|
XmlSchemaProvider,
|
|
12
12
|
JsonSchemaProvider,
|
|
@@ -28,7 +28,8 @@ class ToolDefinition:
|
|
|
28
28
|
name: str,
|
|
29
29
|
description: str,
|
|
30
30
|
argument_schema: Optional['ParameterSchema'],
|
|
31
|
-
|
|
31
|
+
origin: ToolOrigin,
|
|
32
|
+
category: str,
|
|
32
33
|
config_schema: Optional['ParameterSchema'] = None,
|
|
33
34
|
tool_class: Optional[Type['BaseTool']] = None,
|
|
34
35
|
custom_factory: Optional[Callable[['ToolConfig'], 'BaseTool']] = None,
|
|
@@ -55,12 +56,12 @@ class ToolDefinition:
|
|
|
55
56
|
raise TypeError(f"ToolDefinition '{name}' received an invalid 'argument_schema'. Expected ParameterSchema or None.")
|
|
56
57
|
if config_schema is not None and not isinstance(config_schema, ParameterSchema):
|
|
57
58
|
raise TypeError(f"ToolDefinition '{name}' received an invalid 'config_schema'. Expected ParameterSchema or None.")
|
|
58
|
-
if not isinstance(
|
|
59
|
-
raise TypeError(f"ToolDefinition '{name}' requires a
|
|
59
|
+
if not isinstance(origin, ToolOrigin):
|
|
60
|
+
raise TypeError(f"ToolDefinition '{name}' requires a ToolOrigin for 'origin'. Got {type(origin)}")
|
|
60
61
|
|
|
61
62
|
# Validation for MCP-specific metadata
|
|
62
|
-
if
|
|
63
|
-
raise ValueError(f"ToolDefinition '{name}' with
|
|
63
|
+
if origin == ToolOrigin.MCP and not (metadata and metadata.get("mcp_server_id")):
|
|
64
|
+
raise ValueError(f"ToolDefinition '{name}' with origin MCP must provide a 'mcp_server_id' in its metadata.")
|
|
64
65
|
|
|
65
66
|
self._name = name
|
|
66
67
|
self._description = description
|
|
@@ -68,6 +69,7 @@ class ToolDefinition:
|
|
|
68
69
|
self._config_schema: Optional['ParameterSchema'] = config_schema
|
|
69
70
|
self._tool_class = tool_class
|
|
70
71
|
self._custom_factory = custom_factory
|
|
72
|
+
self._origin = origin
|
|
71
73
|
self._category = category
|
|
72
74
|
self._metadata = metadata or {}
|
|
73
75
|
|
|
@@ -87,7 +89,9 @@ class ToolDefinition:
|
|
|
87
89
|
@property
|
|
88
90
|
def config_schema(self) -> Optional['ParameterSchema']: return self._config_schema
|
|
89
91
|
@property
|
|
90
|
-
def
|
|
92
|
+
def origin(self) -> ToolOrigin: return self._origin
|
|
93
|
+
@property
|
|
94
|
+
def category(self) -> str: return self._category
|
|
91
95
|
@property
|
|
92
96
|
def metadata(self) -> Dict[str, Any]: return self._metadata
|
|
93
97
|
|
|
@@ -145,4 +149,4 @@ class ToolDefinition:
|
|
|
145
149
|
def __repr__(self) -> str:
|
|
146
150
|
creator_repr = f"class='{self._tool_class.__name__}'" if self._tool_class else "factory=True"
|
|
147
151
|
metadata_repr = f", metadata={self.metadata}" if self.metadata else ""
|
|
148
|
-
return (f"ToolDefinition(name='{self.name}',
|
|
152
|
+
return (f"ToolDefinition(name='{self.name}', origin='{self.origin.value}', category='{self.category}'{metadata_repr}, {creator_repr})")
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# file: autobyteus/tools/registry/tool_registry.py
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Dict, List, Optional, Type, TYPE_CHECKING
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
|
|
5
6
|
from autobyteus.tools.registry.tool_definition import ToolDefinition
|
|
6
7
|
from autobyteus.utils.singleton import SingletonMeta
|
|
7
8
|
from autobyteus.tools.tool_config import ToolConfig
|
|
8
|
-
from autobyteus.tools.
|
|
9
|
+
from autobyteus.tools.tool_origin import ToolOrigin
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from autobyteus.tools.base_tool import BaseTool
|
|
@@ -141,7 +142,54 @@ class ToolRegistry(metaclass=SingletonMeta):
|
|
|
141
142
|
|
|
142
143
|
return [
|
|
143
144
|
td for td in self._definitions.values()
|
|
144
|
-
if td.
|
|
145
|
+
if td.origin == ToolOrigin.MCP and td.metadata.get("mcp_server_id") == server_id
|
|
145
146
|
]
|
|
146
147
|
|
|
148
|
+
def get_tools_by_category(self, category: str) -> List[ToolDefinition]:
|
|
149
|
+
"""
|
|
150
|
+
Returns a list of all registered tool definitions that match a specific category.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
category: The category string to filter by.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A list of matching ToolDefinition objects, sorted by name.
|
|
157
|
+
"""
|
|
158
|
+
if not category:
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
matching_tools = [
|
|
162
|
+
td for td in self._definitions.values() if td.category == category
|
|
163
|
+
]
|
|
164
|
+
return sorted(matching_tools, key=lambda td: td.name)
|
|
165
|
+
|
|
166
|
+
def get_tools_grouped_by_category(self, origin: Optional[ToolOrigin] = None) -> Dict[str, List[ToolDefinition]]:
|
|
167
|
+
"""
|
|
168
|
+
Returns all registered tool definitions, grouped into a dictionary by their category.
|
|
169
|
+
Can optionally filter by tool origin before grouping.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
origin: If provided, only tools from this origin will be included.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A dictionary where keys are category strings and values are lists
|
|
176
|
+
of ToolDefinition objects belonging to that category. Both the categories
|
|
177
|
+
and the tools within each category are sorted alphabetically.
|
|
178
|
+
"""
|
|
179
|
+
grouped_tools = defaultdict(list)
|
|
180
|
+
|
|
181
|
+
tools_to_process = self._definitions.values()
|
|
182
|
+
if origin:
|
|
183
|
+
tools_to_process = [td for td in tools_to_process if td.origin == origin]
|
|
184
|
+
|
|
185
|
+
for td in tools_to_process:
|
|
186
|
+
grouped_tools[td.category].append(td)
|
|
187
|
+
|
|
188
|
+
# Sort tools within each category and sort the categories themselves for deterministic output
|
|
189
|
+
sorted_grouped_tools = {}
|
|
190
|
+
for category in sorted(grouped_tools.keys()):
|
|
191
|
+
sorted_grouped_tools[category] = sorted(grouped_tools[category], key=lambda td: td.name)
|
|
192
|
+
|
|
193
|
+
return sorted_grouped_tools
|
|
194
|
+
|
|
147
195
|
default_tool_registry = ToolRegistry()
|
autobyteus/tools/timer.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Optional, TYPE_CHECKING, Any
|
|
|
3
3
|
from autobyteus.tools.base_tool import BaseTool
|
|
4
4
|
from autobyteus.tools.tool_config import ToolConfig
|
|
5
5
|
from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
6
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
6
7
|
from autobyteus.events.event_emitter import EventEmitter
|
|
7
8
|
from autobyteus.events.event_types import EventType
|
|
8
9
|
import logging
|
|
@@ -17,6 +18,7 @@ class Timer(BaseTool, EventEmitter):
|
|
|
17
18
|
A tool that provides timer functionality with configurable duration and event emission.
|
|
18
19
|
The timer runs independently after being started and emits TIMER_UPDATE events.
|
|
19
20
|
"""
|
|
21
|
+
CATEGORY = ToolCategory.UTILITY
|
|
20
22
|
|
|
21
23
|
def __init__(self, config: Optional[ToolConfig] = None):
|
|
22
24
|
BaseTool.__init__(self, config=config)
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
|
|
4
4
|
class ToolCategory(str, Enum):
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"""
|
|
6
|
+
Provides standardized string constants for common tool categories.
|
|
7
|
+
While tools can use any string for a category, using these constants
|
|
8
|
+
is recommended to ensure consistency in UI grouping.
|
|
9
|
+
"""
|
|
10
|
+
USER_INTERACTION = "User Interaction"
|
|
11
|
+
FILE_SYSTEM = "File System"
|
|
12
|
+
WEB = "Web"
|
|
13
|
+
SYSTEM = "System"
|
|
14
|
+
UTILITY = "Utility"
|
|
15
|
+
AGENT_COMMUNICATION = "Agent Communication"
|
|
16
|
+
PROMPT_MANAGEMENT = "Prompt Management"
|
|
17
|
+
GENERAL = "General"
|
|
18
|
+
MCP = "MCP"
|
|
9
19
|
|
|
10
20
|
def __str__(self) -> str:
|
|
11
21
|
return self.value
|
autobyteus/tools/tool_meta.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Dict, Any
|
|
|
5
5
|
|
|
6
6
|
from autobyteus.tools.registry import default_tool_registry, ToolDefinition
|
|
7
7
|
from autobyteus.tools.parameter_schema import ParameterSchema
|
|
8
|
+
from autobyteus.tools.tool_origin import ToolOrigin
|
|
8
9
|
from autobyteus.tools.tool_category import ToolCategory
|
|
9
10
|
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
@@ -53,6 +54,9 @@ class ToolMeta(ABCMeta):
|
|
|
53
54
|
instantiation_config_schema = None
|
|
54
55
|
except Exception as e:
|
|
55
56
|
logger.warning(f"Tool class {name} ({tool_name}) has get_config_schema() but it failed: {e}. Assuming no instantiation config.")
|
|
57
|
+
|
|
58
|
+
# Get category from class attribute, defaulting to "General"
|
|
59
|
+
category_str = getattr(cls, 'CATEGORY', ToolCategory.GENERAL)
|
|
56
60
|
|
|
57
61
|
# Create the definition without pre-generating usage strings
|
|
58
62
|
definition = ToolDefinition(
|
|
@@ -62,7 +66,8 @@ class ToolMeta(ABCMeta):
|
|
|
62
66
|
custom_factory=None,
|
|
63
67
|
argument_schema=argument_schema,
|
|
64
68
|
config_schema=instantiation_config_schema,
|
|
65
|
-
|
|
69
|
+
origin=ToolOrigin.LOCAL,
|
|
70
|
+
category=category_str
|
|
66
71
|
)
|
|
67
72
|
default_tool_registry.register_tool(definition)
|
|
68
73
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/tool_origin.py
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
class ToolOrigin(str, Enum):
|
|
5
|
+
"""Enumeration of tool origins to identify their execution model."""
|
|
6
|
+
LOCAL = "local"
|
|
7
|
+
MCP = "mcp"
|
|
8
|
+
|
|
9
|
+
def __str__(self) -> str:
|
|
10
|
+
return self.value
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/agentic_workflow.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
|
|
6
|
+
from autobyteus.workflow.events.workflow_events import ProcessUserMessageEvent, ToolApprovalWorkflowEvent
|
|
7
|
+
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
8
|
+
from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class AgenticWorkflow:
|
|
13
|
+
"""
|
|
14
|
+
User-facing facade for interacting with a managed workflow.
|
|
15
|
+
This class is a lightweight wrapper around a WorkflowRuntime instance
|
|
16
|
+
and is typically created by a WorkflowFactory.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, runtime: WorkflowRuntime):
|
|
19
|
+
"""
|
|
20
|
+
Initializes the AgenticWorkflow facade.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
runtime: The pre-configured and ready-to-use runtime for the workflow.
|
|
24
|
+
"""
|
|
25
|
+
if not isinstance(runtime, WorkflowRuntime):
|
|
26
|
+
raise TypeError(f"AgenticWorkflow requires a WorkflowRuntime instance, got {type(runtime).__name__}")
|
|
27
|
+
|
|
28
|
+
self._runtime = runtime
|
|
29
|
+
self.workflow_id: str = self._runtime.context.workflow_id
|
|
30
|
+
logger.info(f"AgenticWorkflow facade created for workflow ID '{self.workflow_id}'.")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return self._runtime.context.config.name
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def role(self) -> Optional[str]:
|
|
38
|
+
"""The role of the workflow, for when it's used as a sub-workflow."""
|
|
39
|
+
return self._runtime.context.config.role
|
|
40
|
+
|
|
41
|
+
async def post_message(self, message: AgentInputUserMessage, target_agent_name: Optional[str] = None) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Submits a message to the workflow, routing it to a specific node (agent or sub-workflow).
|
|
44
|
+
If `target_agent_name` is not provided, the message is sent to the workflow's coordinator.
|
|
45
|
+
"""
|
|
46
|
+
final_target_name = target_agent_name or self._runtime.context.config.coordinator_node.name
|
|
47
|
+
logger.info(f"Workflow '{self.workflow_id}': post_message called. Target: '{final_target_name}'.")
|
|
48
|
+
|
|
49
|
+
if not self._runtime.is_running:
|
|
50
|
+
self.start()
|
|
51
|
+
|
|
52
|
+
event = ProcessUserMessageEvent(
|
|
53
|
+
user_message=message,
|
|
54
|
+
target_agent_name=final_target_name
|
|
55
|
+
)
|
|
56
|
+
await self._runtime.submit_event(event)
|
|
57
|
+
|
|
58
|
+
async def post_tool_execution_approval(
|
|
59
|
+
self,
|
|
60
|
+
agent_name: str,
|
|
61
|
+
tool_invocation_id: str,
|
|
62
|
+
is_approved: bool,
|
|
63
|
+
reason: Optional[str] = None
|
|
64
|
+
):
|
|
65
|
+
"""Submits a tool execution approval/denial to a specific agent in the workflow."""
|
|
66
|
+
logger.info(f"Workflow '{self.workflow_id}': post_tool_execution_approval called for agent '{agent_name}'. Approved: {is_approved}.")
|
|
67
|
+
if not self._runtime.is_running:
|
|
68
|
+
logger.warning(f"Workflow '{self.workflow_id}' is not running. Cannot post approval.")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
event = ToolApprovalWorkflowEvent(
|
|
72
|
+
agent_name=agent_name,
|
|
73
|
+
tool_invocation_id=tool_invocation_id,
|
|
74
|
+
is_approved=is_approved,
|
|
75
|
+
reason=reason,
|
|
76
|
+
)
|
|
77
|
+
await self._runtime.submit_event(event)
|
|
78
|
+
|
|
79
|
+
def start(self) -> None:
|
|
80
|
+
"""Starts the workflow's background worker thread."""
|
|
81
|
+
self._runtime.start()
|
|
82
|
+
|
|
83
|
+
async def stop(self, timeout: float = 10.0) -> None:
|
|
84
|
+
"""Stops the workflow and all its agents."""
|
|
85
|
+
await self._runtime.stop(timeout)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_running(self) -> bool:
|
|
89
|
+
"""Checks if the workflow's worker is running."""
|
|
90
|
+
return self._runtime.is_running
|
|
91
|
+
|
|
92
|
+
def get_current_phase(self) -> WorkflowOperationalPhase:
|
|
93
|
+
return self._runtime.context.state.current_phase
|
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/base_agentic_workflow.py
|
|
2
2
|
import logging
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Optional, Any,
|
|
4
|
+
from typing import Optional, Any, TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# These are forward declarations that might be used by subclasses.
|
|
7
|
+
# The actual circular dependency is avoided by type-hinting strings.
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.workflow.agentic_workflow import AgenticWorkflow
|
|
10
|
+
from autobyteus.agent.group import AgentGroup
|
|
8
11
|
|
|
9
12
|
logger = logging.getLogger(__name__)
|
|
10
13
|
|
|
11
14
|
class BaseAgenticWorkflow(ABC):
|
|
12
15
|
"""
|
|
13
|
-
|
|
16
|
+
Optional abstract base class for creating domain-specific, type-safe
|
|
14
17
|
APIs for agentic workflows.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
Users can subclass BaseAgenticWorkflow to create a more structured and
|
|
20
|
+
specific interface for their multi-agent tasks, rather than using the
|
|
21
|
+
generic `AgenticWorkflow.process(**kwargs)` method directly.
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
type safety and a clearer API for that particular workflow.
|
|
23
|
+
Subclasses would typically encapsulate an `AgenticWorkflow` or an `AgentGroup`
|
|
24
|
+
instance and define methods that map domain-specific inputs to the
|
|
25
|
+
underlying workflow's execution.
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
28
|
def __init__(self,
|
|
27
29
|
name: str,
|
|
28
|
-
wrapped_workflow_instance: Optional[
|
|
30
|
+
wrapped_workflow_instance: Optional[Any] = None):
|
|
29
31
|
"""
|
|
30
32
|
Initializes the BaseAgenticWorkflow.
|
|
31
33
|
|
|
@@ -37,7 +39,7 @@ class BaseAgenticWorkflow(ABC):
|
|
|
37
39
|
workflow/group instance.
|
|
38
40
|
"""
|
|
39
41
|
self.name: str = name
|
|
40
|
-
self._wrapped_workflow: Optional[
|
|
42
|
+
self._wrapped_workflow: Optional[Any] = wrapped_workflow_instance
|
|
41
43
|
|
|
42
44
|
if self._wrapped_workflow:
|
|
43
45
|
logger.info(f"BaseAgenticWorkflow '{self.name}' initialized, wrapping an instance of "
|
|
@@ -47,7 +49,7 @@ class BaseAgenticWorkflow(ABC):
|
|
|
47
49
|
"Subclass should handle workflow/group setup.")
|
|
48
50
|
|
|
49
51
|
@property
|
|
50
|
-
def wrapped_workflow(self) -> Optional[
|
|
52
|
+
def wrapped_workflow(self) -> Optional[Any]:
|
|
51
53
|
"""Provides access to the wrapped AgenticWorkflow or AgentGroup instance."""
|
|
52
54
|
return self._wrapped_workflow
|
|
53
55
|
|
|
@@ -76,23 +78,13 @@ class BaseAgenticWorkflow(ABC):
|
|
|
76
78
|
"""
|
|
77
79
|
pass
|
|
78
80
|
|
|
79
|
-
# Subclasses will define their own domain-specific process methods, for example:
|
|
80
|
-
# @abstractmethod
|
|
81
|
-
# async def generate_report(self, topic: str, length_words: int) -> Report:
|
|
82
|
-
# pass
|
|
83
|
-
#
|
|
84
|
-
# @abstractmethod
|
|
85
|
-
# async def translate_document(self, document_path: str, target_language: str) -> TranslatedDocument:
|
|
86
|
-
# pass
|
|
87
|
-
|
|
88
81
|
def __repr__(self) -> str:
|
|
89
82
|
running_status = "N/A (not implemented by subclass)"
|
|
90
83
|
try:
|
|
91
84
|
running_status = str(self.is_running)
|
|
92
|
-
except NotImplementedError:
|
|
93
|
-
pass
|
|
85
|
+
except NotImplementedError:
|
|
86
|
+
pass
|
|
94
87
|
|
|
95
88
|
return (f"<{self.__class__.__name__} name='{self.name}', "
|
|
96
89
|
f"wraps='{self._wrapped_workflow.__class__.__name__ if self._wrapped_workflow else 'NoneInternal'}', "
|
|
97
90
|
f"is_running={running_status}>")
|
|
98
|
-
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/bootstrap_steps/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Defines individual, self-contained steps for the workflow bootstrapping process.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from autobyteus.workflow.bootstrap_steps.base_workflow_bootstrap_step import BaseWorkflowBootstrapStep
|
|
7
|
+
from autobyteus.workflow.bootstrap_steps.workflow_runtime_queue_initialization_step import WorkflowRuntimeQueueInitializationStep
|
|
8
|
+
from autobyteus.workflow.bootstrap_steps.coordinator_prompt_preparation_step import CoordinatorPromptPreparationStep
|
|
9
|
+
from autobyteus.workflow.bootstrap_steps.agent_tool_injection_step import AgentToolInjectionStep
|
|
10
|
+
from autobyteus.workflow.bootstrap_steps.coordinator_initialization_step import CoordinatorInitializationStep
|
|
11
|
+
from autobyteus.workflow.bootstrap_steps.workflow_bootstrapper import WorkflowBootstrapper
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BaseWorkflowBootstrapStep",
|
|
15
|
+
"WorkflowRuntimeQueueInitializationStep",
|
|
16
|
+
"CoordinatorPromptPreparationStep",
|
|
17
|
+
"AgentToolInjectionStep",
|
|
18
|
+
"CoordinatorInitializationStep",
|
|
19
|
+
"WorkflowBootstrapper",
|
|
20
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, Set
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.bootstrap_steps.base_workflow_bootstrap_step import BaseWorkflowBootstrapStep
|
|
6
|
+
from autobyteus.agent.context import AgentConfig
|
|
7
|
+
from autobyteus.agent.message.send_message_to import SendMessageTo
|
|
8
|
+
from autobyteus.workflow.context.workflow_node_config import WorkflowNodeConfig
|
|
9
|
+
from autobyteus.tools.registry import default_tool_registry
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
13
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class AgentToolInjectionStep(BaseWorkflowBootstrapStep):
|
|
18
|
+
"""
|
|
19
|
+
Bootstrap step to inject workflow-aware tools like SendMessageTo into
|
|
20
|
+
agent configurations just before they are used. This step is now effectively
|
|
21
|
+
a placeholder as tool injection is handled just-in-time by the TeamManager,
|
|
22
|
+
but it is kept for potential future use and to maintain the bootstrap sequence structure.
|
|
23
|
+
The primary logic of applying the coordinator prompt has been moved to the TeamManager
|
|
24
|
+
to ensure it happens just before the coordinator is created.
|
|
25
|
+
"""
|
|
26
|
+
async def execute(self, context: 'WorkflowContext', phase_manager: 'WorkflowPhaseManager') -> bool:
|
|
27
|
+
workflow_id = context.workflow_id
|
|
28
|
+
logger.info(f"Workflow '{workflow_id}': Executing AgentToolInjectionStep (now a placeholder).")
|
|
29
|
+
# The logic for injecting SendMessageTo and setting the coordinator prompt is now
|
|
30
|
+
# handled just-in-time by the TeamManager to better support lazy-loading of nodes.
|
|
31
|
+
# This step is preserved in the bootstrap sequence for clarity and future expansion.
|
|
32
|
+
logger.debug(f"Workflow '{workflow_id}': Tool injection and prompt setting are deferred to TeamManager.")
|
|
33
|
+
return True
|
|
34
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
8
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class BaseWorkflowBootstrapStep(ABC):
|
|
13
|
+
"""Abstract base class for individual steps in the workflow bootstrapping process."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
async def execute(self, context: 'WorkflowContext', phase_manager: 'WorkflowPhaseManager') -> bool:
|
|
17
|
+
"""
|
|
18
|
+
Executes the bootstrap step.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
True if the step completed successfully, False otherwise.
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError("Subclasses must implement the 'execute' method.")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.bootstrap_steps.base_workflow_bootstrap_step import BaseWorkflowBootstrapStep
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
9
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class CoordinatorInitializationStep(BaseWorkflowBootstrapStep):
|
|
14
|
+
"""
|
|
15
|
+
Bootstrap step that eagerly instantiates and starts the coordinator agent
|
|
16
|
+
using the TeamManager. This ensures the coordinator is ready before the
|
|
17
|
+
workflow becomes idle.
|
|
18
|
+
"""
|
|
19
|
+
async def execute(self, context: 'WorkflowContext', phase_manager: 'WorkflowPhaseManager') -> bool:
|
|
20
|
+
workflow_id = context.workflow_id
|
|
21
|
+
logger.info(f"Workflow '{workflow_id}': Executing CoordinatorInitializationStep.")
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
team_manager = context.team_manager
|
|
25
|
+
if not team_manager:
|
|
26
|
+
raise RuntimeError("TeamManager not found in workflow context. It should be created by the factory.")
|
|
27
|
+
|
|
28
|
+
coordinator_name = context.config.coordinator_node.name
|
|
29
|
+
|
|
30
|
+
# This call now ensures the coordinator agent is fully created and ready.
|
|
31
|
+
coordinator = await team_manager.ensure_coordinator_is_ready(coordinator_name)
|
|
32
|
+
|
|
33
|
+
if not coordinator:
|
|
34
|
+
raise RuntimeError(f"TeamManager failed to return a ready coordinator agent for '{coordinator_name}'.")
|
|
35
|
+
|
|
36
|
+
logger.info(f"Workflow '{workflow_id}': Coordinator '{coordinator_name}' initialized and started via TeamManager.")
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error(f"Workflow '{workflow_id}': Failed to initialize coordinator agent: {e}", exc_info=True)
|
|
41
|
+
return False
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, Set, List
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.bootstrap_steps.base_workflow_bootstrap_step import BaseWorkflowBootstrapStep
|
|
6
|
+
from autobyteus.agent.context import AgentConfig
|
|
7
|
+
from autobyteus.workflow.context.workflow_node_config import WorkflowNodeConfig
|
|
8
|
+
from autobyteus.workflow.context.workflow_config import WorkflowConfig
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
12
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class CoordinatorPromptPreparationStep(BaseWorkflowBootstrapStep):
|
|
17
|
+
"""
|
|
18
|
+
Bootstrap step to dynamically generate the coordinator's system prompt
|
|
19
|
+
based on the workflow's structure and store it in the workflow's state.
|
|
20
|
+
"""
|
|
21
|
+
async def execute(self, context: 'WorkflowContext', phase_manager: 'WorkflowPhaseManager') -> bool:
|
|
22
|
+
workflow_id = context.workflow_id
|
|
23
|
+
logger.info(f"Workflow '{workflow_id}': Executing CoordinatorPromptPreparationStep.")
|
|
24
|
+
try:
|
|
25
|
+
coordinator_node = context.config.coordinator_node
|
|
26
|
+
member_nodes = {node for node in context.config.nodes if node != coordinator_node}
|
|
27
|
+
|
|
28
|
+
member_node_ids = self._generate_unique_node_ids(member_nodes)
|
|
29
|
+
dynamic_prompt = self._generate_prompt(context, member_node_ids)
|
|
30
|
+
|
|
31
|
+
context.state.prepared_coordinator_prompt = dynamic_prompt
|
|
32
|
+
|
|
33
|
+
logger.info(f"Workflow '{workflow_id}': Coordinator prompt prepared successfully and stored in state.")
|
|
34
|
+
return True
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.error(f"Workflow '{workflow_id}': Failed to prepare coordinator prompt: {e}", exc_info=True)
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
def _generate_unique_node_ids(self, member_nodes: Set[WorkflowNodeConfig]) -> Dict[WorkflowNodeConfig, str]:
|
|
40
|
+
id_map: Dict[WorkflowNodeConfig, str] = {}
|
|
41
|
+
name_counts: Dict[str, int] = {}
|
|
42
|
+
sorted_nodes = sorted(list(member_nodes), key=lambda n: n.name)
|
|
43
|
+
for node in sorted_nodes:
|
|
44
|
+
base_name = node.name
|
|
45
|
+
count = name_counts.get(base_name, 0)
|
|
46
|
+
unique_id = f"{base_name}_{count + 1}" if base_name in name_counts else base_name
|
|
47
|
+
id_map[node] = unique_id
|
|
48
|
+
name_counts[base_name] = count + 1
|
|
49
|
+
return id_map
|
|
50
|
+
|
|
51
|
+
def _generate_prompt(self, context: 'WorkflowContext', member_node_ids: Dict[WorkflowNodeConfig, str]) -> str:
|
|
52
|
+
prompt_parts: List[str] = []
|
|
53
|
+
|
|
54
|
+
tools_section = (
|
|
55
|
+
"### Your Tools\n"
|
|
56
|
+
"To accomplish your goal, you have access to the following tools. You should use them as needed.\n"
|
|
57
|
+
"{{tools}}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if member_node_ids:
|
|
61
|
+
role_and_goal = (
|
|
62
|
+
"You are the coordinator of a team of specialist agents and sub-workflows. Your primary goal is to "
|
|
63
|
+
"achieve the following objective by delegating tasks to your team members:\n"
|
|
64
|
+
f"### Goal\n{context.config.description}"
|
|
65
|
+
)
|
|
66
|
+
prompt_parts.append(role_and_goal)
|
|
67
|
+
|
|
68
|
+
team_lines = []
|
|
69
|
+
for node, uid in member_node_ids.items():
|
|
70
|
+
node_def = node.node_definition
|
|
71
|
+
if node.is_subworkflow and isinstance(node_def, WorkflowConfig):
|
|
72
|
+
# For sub-workflows, use its role and description
|
|
73
|
+
role = node_def.role or "(Sub-Workflow)"
|
|
74
|
+
team_lines.append(f"- **{uid}** (Role: {role}): {node_def.description}")
|
|
75
|
+
elif isinstance(node_def, AgentConfig):
|
|
76
|
+
# For agents, use its role and description
|
|
77
|
+
team_lines.append(f"- **{uid}** (Role: {node_def.role}): {node_def.description}")
|
|
78
|
+
|
|
79
|
+
team_manifest = "### Your Team\n" + "\n".join(team_lines)
|
|
80
|
+
prompt_parts.append(team_manifest)
|
|
81
|
+
|
|
82
|
+
rules_list: List[str] = []
|
|
83
|
+
for node, uid in member_node_ids.items():
|
|
84
|
+
if node.dependencies:
|
|
85
|
+
dep_names = [member_node_ids.get(dep, dep.name) for dep in node.dependencies]
|
|
86
|
+
rules_list.append(f"To use '{uid}', you must have already successfully used: {', '.join(f'`{name}`' for name in dep_names)}.")
|
|
87
|
+
|
|
88
|
+
if rules_list:
|
|
89
|
+
rules_section = "### Execution Rules\n" + "\n".join(rules_list)
|
|
90
|
+
prompt_parts.append(rules_section)
|
|
91
|
+
|
|
92
|
+
prompt_parts.append(tools_section)
|
|
93
|
+
|
|
94
|
+
final_instruction = "### Your Task\nAnalyze the user's request, formulate a plan, and use the `SendMessageTo` tool to delegate tasks to your team. Address team members by their unique ID as listed under 'Your Team'."
|
|
95
|
+
prompt_parts.append(final_instruction)
|
|
96
|
+
else:
|
|
97
|
+
role_and_goal = (
|
|
98
|
+
"You are working alone. Your primary goal is to achieve the following objective:\n"
|
|
99
|
+
f"### Goal\n{context.config.description}"
|
|
100
|
+
)
|
|
101
|
+
prompt_parts.append(role_and_goal)
|
|
102
|
+
prompt_parts.append("### Your Team\nYou are working alone on this task.")
|
|
103
|
+
prompt_parts.append(tools_section)
|
|
104
|
+
final_instruction = "### Your Task\nAnalyze the user's request, formulate a plan, and use your available tools to achieve the goal."
|
|
105
|
+
prompt_parts.append(final_instruction)
|
|
106
|
+
|
|
107
|
+
return "\n\n".join(prompt_parts)
|
|
108
|
+
|