autobyteus 1.1.0__py3-none-any.whl → 1.1.2__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/bootstrap_steps/agent_bootstrapper.py +1 -1
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
- autobyteus/agent/context/__init__.py +0 -5
- autobyteus/agent/context/agent_config.py +6 -2
- autobyteus/agent/context/agent_context.py +2 -5
- autobyteus/agent/context/agent_phase_manager.py +105 -5
- autobyteus/agent/context/agent_runtime_state.py +2 -2
- autobyteus/agent/context/phases.py +2 -0
- autobyteus/agent/events/__init__.py +0 -11
- autobyteus/agent/events/agent_events.py +0 -37
- autobyteus/agent/events/notifiers.py +25 -7
- autobyteus/agent/events/worker_event_dispatcher.py +1 -1
- autobyteus/agent/factory/agent_factory.py +6 -2
- autobyteus/agent/group/agent_group.py +16 -7
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
- autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
- autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
- autobyteus/agent/hooks/__init__.py +7 -0
- autobyteus/agent/hooks/base_phase_hook.py +11 -2
- 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/base_user_input_processor.py +6 -3
- autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
- autobyteus/agent/input_processor/processor_meta.py +1 -1
- autobyteus/agent/input_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/base_processor.py +6 -3
- autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
- autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
- autobyteus/agent/message/context_file_type.py +2 -3
- 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 +1 -1
- autobyteus/agent/runtime/agent_runtime.py +5 -10
- autobyteus/agent/runtime/agent_worker.py +62 -19
- autobyteus/agent/streaming/agent_event_stream.py +58 -5
- autobyteus/agent/streaming/stream_event_payloads.py +24 -13
- autobyteus/agent/streaming/stream_events.py +14 -11
- autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
- autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
- autobyteus/agent/tool_invocation.py +29 -3
- autobyteus/agent/utils/wait_for_idle.py +1 -1
- autobyteus/agent/workspace/__init__.py +2 -0
- autobyteus/agent/workspace/base_workspace.py +33 -11
- 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 +4 -3
- autobyteus/cli/agent_cli.py +25 -207
- autobyteus/cli/cli_display.py +205 -0
- autobyteus/events/event_manager.py +2 -1
- autobyteus/events/event_types.py +3 -1
- autobyteus/llm/api/autobyteus_llm.py +2 -12
- autobyteus/llm/api/deepseek_llm.py +11 -173
- autobyteus/llm/api/grok_llm.py +11 -172
- autobyteus/llm/api/kimi_llm.py +24 -0
- autobyteus/llm/api/mistral_llm.py +4 -4
- autobyteus/llm/api/ollama_llm.py +2 -2
- autobyteus/llm/api/openai_compatible_llm.py +193 -0
- autobyteus/llm/api/openai_llm.py +11 -139
- autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
- autobyteus/llm/llm_factory.py +168 -42
- autobyteus/llm/models.py +25 -29
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
- autobyteus/llm/token_counter/token_counter_factory.py +3 -0
- autobyteus/llm/utils/messages.py +3 -3
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/base_tool.py +7 -1
- autobyteus/tools/functional_tool.py +20 -5
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
- autobyteus/tools/mcp/config_service.py +106 -127
- autobyteus/tools/mcp/registrar.py +247 -59
- autobyteus/tools/mcp/types.py +5 -3
- autobyteus/tools/registry/tool_definition.py +8 -1
- autobyteus/tools/registry/tool_registry.py +18 -0
- autobyteus/tools/tool_category.py +11 -0
- autobyteus/tools/tool_meta.py +3 -1
- autobyteus/tools/tool_state.py +20 -0
- autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ import threading
|
|
|
6
6
|
import concurrent.futures
|
|
7
7
|
from typing import TYPE_CHECKING, Optional, Any, Callable, Awaitable, List
|
|
8
8
|
|
|
9
|
-
from autobyteus.agent.
|
|
9
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
10
10
|
from autobyteus.agent.events import (
|
|
11
11
|
BaseEvent,
|
|
12
12
|
AgentErrorEvent,
|
|
@@ -140,19 +140,19 @@ class AgentWorker:
|
|
|
140
140
|
|
|
141
141
|
async def async_run(self) -> None:
|
|
142
142
|
agent_id = self.context.agent_id
|
|
143
|
-
logger.info(f"AgentWorker '{agent_id}' async_run(): Starting.")
|
|
144
|
-
|
|
145
|
-
# --- Direct Initialization ---
|
|
146
|
-
initialization_successful = await self._initialize()
|
|
147
|
-
if not initialization_successful:
|
|
148
|
-
logger.critical(f"AgentWorker '{agent_id}' failed to initialize. Worker is shutting down.")
|
|
149
|
-
if self._async_stop_event and not self._async_stop_event.is_set():
|
|
150
|
-
self._async_stop_event.set()
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
# --- Main Event Loop ---
|
|
154
|
-
logger.info(f"AgentWorker '{agent_id}' initialized successfully. Entering main event loop.")
|
|
155
143
|
try:
|
|
144
|
+
logger.info(f"AgentWorker '{agent_id}' async_run(): Starting.")
|
|
145
|
+
|
|
146
|
+
# --- Direct Initialization ---
|
|
147
|
+
initialization_successful = await self._initialize()
|
|
148
|
+
if not initialization_successful:
|
|
149
|
+
logger.critical(f"AgentWorker '{agent_id}' failed to initialize. Worker is shutting down.")
|
|
150
|
+
if self._async_stop_event and not self._async_stop_event.is_set():
|
|
151
|
+
self._async_stop_event.set()
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# --- Main Event Loop ---
|
|
155
|
+
logger.info(f"AgentWorker '{agent_id}' initialized successfully. Entering main event loop.")
|
|
156
156
|
while not self._async_stop_event.is_set():
|
|
157
157
|
try:
|
|
158
158
|
queue_event_tuple = await asyncio.wait_for(
|
|
@@ -183,18 +183,61 @@ class AgentWorker:
|
|
|
183
183
|
if self.context.state.input_event_queues:
|
|
184
184
|
await self.context.state.input_event_queues.enqueue_internal_system_event(AgentStoppedEvent())
|
|
185
185
|
|
|
186
|
+
async def _shutdown_sequence(self):
|
|
187
|
+
"""
|
|
188
|
+
The explicit, ordered shutdown sequence for the worker, executed on its own event loop.
|
|
189
|
+
"""
|
|
190
|
+
agent_id = self.context.agent_id
|
|
191
|
+
logger.info(f"AgentWorker '{agent_id}': Running shutdown sequence on worker loop.")
|
|
192
|
+
|
|
193
|
+
# 1. Clean up resources like the LLM instance.
|
|
194
|
+
if self.context.llm_instance and hasattr(self.context.llm_instance, 'cleanup'):
|
|
195
|
+
logger.info(f"AgentWorker '{agent_id}': Running LLM instance cleanup.")
|
|
196
|
+
try:
|
|
197
|
+
cleanup_func = self.context.llm_instance.cleanup
|
|
198
|
+
if asyncio.iscoroutinefunction(cleanup_func):
|
|
199
|
+
await cleanup_func()
|
|
200
|
+
else:
|
|
201
|
+
cleanup_func()
|
|
202
|
+
logger.info(f"AgentWorker '{agent_id}': LLM instance cleanup completed.")
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"AgentWorker '{agent_id}': Error during LLM instance cleanup: {e}", exc_info=True)
|
|
205
|
+
|
|
206
|
+
# 2. Signal the main event loop to stop.
|
|
207
|
+
await self._signal_internal_stop()
|
|
208
|
+
logger.info(f"AgentWorker '{agent_id}': Shutdown sequence completed.")
|
|
209
|
+
|
|
186
210
|
async def stop(self, timeout: float = 10.0) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Gracefully stops the worker by scheduling a final shutdown sequence on its
|
|
213
|
+
event loop, then waiting for the thread to terminate.
|
|
214
|
+
"""
|
|
187
215
|
if not self._is_active or self._stop_initiated:
|
|
188
216
|
return
|
|
217
|
+
|
|
218
|
+
agent_id = self.context.agent_id
|
|
219
|
+
logger.info(f"AgentWorker '{agent_id}': Stop requested.")
|
|
189
220
|
self._stop_initiated = True
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
|
|
222
|
+
# Schedule the explicit shutdown sequence on the worker's loop.
|
|
223
|
+
if self.get_worker_loop():
|
|
224
|
+
future = self.schedule_coroutine_on_worker_loop(self._shutdown_sequence)
|
|
225
|
+
try:
|
|
226
|
+
# Wait for the cleanup and stop signal to be processed.
|
|
227
|
+
future.result(timeout=max(1.0, timeout-1))
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"AgentWorker '{agent_id}': Error during scheduled shutdown sequence: {e}", exc_info=True)
|
|
230
|
+
|
|
231
|
+
# Wait for the main thread future to complete.
|
|
194
232
|
if self._thread_future:
|
|
195
|
-
try:
|
|
196
|
-
|
|
233
|
+
try:
|
|
234
|
+
await asyncio.wait_for(asyncio.wrap_future(self._thread_future), timeout=timeout)
|
|
235
|
+
logger.info(f"AgentWorker '{agent_id}': Worker thread has terminated.")
|
|
236
|
+
except asyncio.TimeoutError:
|
|
237
|
+
logger.warning(f"AgentWorker '{agent_id}': Timeout waiting for worker thread to terminate.")
|
|
238
|
+
|
|
197
239
|
self._is_active = False
|
|
198
240
|
|
|
241
|
+
|
|
199
242
|
def is_alive(self) -> bool:
|
|
200
243
|
return self._thread_future is not None and not self._thread_future.done()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/streaming/agent_event_stream.py
|
|
1
2
|
import asyncio
|
|
2
3
|
import logging
|
|
3
4
|
import traceback
|
|
@@ -14,9 +15,16 @@ from autobyteus.agent.streaming.stream_event_payloads import (
|
|
|
14
15
|
create_agent_operational_phase_transition_data,
|
|
15
16
|
create_error_event_data,
|
|
16
17
|
create_tool_invocation_approval_requested_data,
|
|
18
|
+
create_tool_invocation_auto_executing_data,
|
|
19
|
+
AssistantChunkData,
|
|
20
|
+
AssistantCompleteResponseData,
|
|
21
|
+
ToolInteractionLogEntryData,
|
|
22
|
+
AgentOperationalPhaseTransitionData,
|
|
23
|
+
ToolInvocationApprovalRequestedData,
|
|
24
|
+
ToolInvocationAutoExecutingData,
|
|
25
|
+
ErrorEventData,
|
|
17
26
|
EmptyData,
|
|
18
27
|
StreamDataPayload,
|
|
19
|
-
ErrorEventData,
|
|
20
28
|
)
|
|
21
29
|
from .queue_streamer import stream_queue_items
|
|
22
30
|
from autobyteus.events.event_types import EventType
|
|
@@ -60,8 +68,7 @@ class AgentEventStream(EventEmitter):
|
|
|
60
68
|
all_agent_event_types = [et for et in EventType if et.name.startswith("AGENT_")]
|
|
61
69
|
|
|
62
70
|
for event_type in all_agent_event_types:
|
|
63
|
-
|
|
64
|
-
self.subscribe_from(self._notifier, event_type, handler)
|
|
71
|
+
self.subscribe_from(self._notifier, event_type, self._handle_notifier_event_sync)
|
|
65
72
|
|
|
66
73
|
def _handle_notifier_event_sync(self, event_type: EventType, payload: Optional[Any] = None, object_id: Optional[str] = None, **kwargs):
|
|
67
74
|
event_agent_id = kwargs.get("agent_id", self.agent_id)
|
|
@@ -88,13 +95,15 @@ class AgentEventStream(EventEmitter):
|
|
|
88
95
|
elif event_type == EventType.AGENT_REQUEST_TOOL_INVOCATION_APPROVAL:
|
|
89
96
|
typed_payload_for_stream_event = create_tool_invocation_approval_requested_data(payload)
|
|
90
97
|
stream_event_type_for_generic_stream = StreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED
|
|
98
|
+
elif event_type == EventType.AGENT_TOOL_INVOCATION_AUTO_EXECUTING:
|
|
99
|
+
typed_payload_for_stream_event = create_tool_invocation_auto_executing_data(payload)
|
|
100
|
+
stream_event_type_for_generic_stream = StreamEventType.TOOL_INVOCATION_AUTO_EXECUTING
|
|
91
101
|
elif event_type == EventType.AGENT_ERROR_OUTPUT_GENERATION:
|
|
92
102
|
typed_payload_for_stream_event = create_error_event_data(payload)
|
|
93
103
|
stream_event_type_for_generic_stream = StreamEventType.ERROR_EVENT
|
|
94
104
|
|
|
95
|
-
# The other queues are no longer needed, as `all_events` is the single source of truth.
|
|
96
105
|
elif event_type in [EventType.AGENT_DATA_ASSISTANT_CHUNK_STREAM_END, EventType.AGENT_DATA_TOOL_LOG_STREAM_END]:
|
|
97
|
-
pass
|
|
106
|
+
pass
|
|
98
107
|
else:
|
|
99
108
|
logger.debug(f"AgentEventStream received internal event '{event_type.name}' with no direct stream mapping.")
|
|
100
109
|
|
|
@@ -118,3 +127,47 @@ class AgentEventStream(EventEmitter):
|
|
|
118
127
|
"""The primary method to consume all structured events from the agent."""
|
|
119
128
|
async for event in stream_queue_items(self._generic_stream_event_internal_q, _AES_INTERNAL_SENTINEL, f"agent_{self.agent_id}_all_events"):
|
|
120
129
|
yield event
|
|
130
|
+
|
|
131
|
+
# --- Convenience Stream Methods ---
|
|
132
|
+
|
|
133
|
+
async def stream_assistant_chunks(self) -> AsyncIterator[AssistantChunkData]:
|
|
134
|
+
"""A convenience async generator that yields only assistant content/reasoning chunks."""
|
|
135
|
+
async for event in self.all_events():
|
|
136
|
+
if event.event_type == StreamEventType.ASSISTANT_CHUNK and isinstance(event.data, AssistantChunkData):
|
|
137
|
+
yield event.data
|
|
138
|
+
|
|
139
|
+
async def stream_assistant_final_response(self) -> AsyncIterator[AssistantCompleteResponseData]:
|
|
140
|
+
"""A convenience async generator that yields only the final, complete assistant responses."""
|
|
141
|
+
async for event in self.all_events():
|
|
142
|
+
if event.event_type == StreamEventType.ASSISTANT_COMPLETE_RESPONSE and isinstance(event.data, AssistantCompleteResponseData):
|
|
143
|
+
yield event.data
|
|
144
|
+
|
|
145
|
+
async def stream_tool_logs(self) -> AsyncIterator[ToolInteractionLogEntryData]:
|
|
146
|
+
"""A convenience async generator that yields only tool interaction log entries."""
|
|
147
|
+
async for event in self.all_events():
|
|
148
|
+
if event.event_type == StreamEventType.TOOL_INTERACTION_LOG_ENTRY and isinstance(event.data, ToolInteractionLogEntryData):
|
|
149
|
+
yield event.data
|
|
150
|
+
|
|
151
|
+
async def stream_phase_transitions(self) -> AsyncIterator[AgentOperationalPhaseTransitionData]:
|
|
152
|
+
"""A convenience async generator that yields only agent phase transition data."""
|
|
153
|
+
async for event in self.all_events():
|
|
154
|
+
if event.event_type == StreamEventType.AGENT_OPERATIONAL_PHASE_TRANSITION and isinstance(event.data, AgentOperationalPhaseTransitionData):
|
|
155
|
+
yield event.data
|
|
156
|
+
|
|
157
|
+
async def stream_tool_approval_requests(self) -> AsyncIterator[ToolInvocationApprovalRequestedData]:
|
|
158
|
+
"""A convenience async generator that yields only requests for tool invocation approval."""
|
|
159
|
+
async for event in self.all_events():
|
|
160
|
+
if event.event_type == StreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED and isinstance(event.data, ToolInvocationApprovalRequestedData):
|
|
161
|
+
yield event.data
|
|
162
|
+
|
|
163
|
+
async def stream_tool_auto_executing(self) -> AsyncIterator[ToolInvocationAutoExecutingData]:
|
|
164
|
+
"""A convenience async generator that yields only events for tools being auto-executed."""
|
|
165
|
+
async for event in self.all_events():
|
|
166
|
+
if event.event_type == StreamEventType.TOOL_INVOCATION_AUTO_EXECUTING and isinstance(event.data, ToolInvocationAutoExecutingData):
|
|
167
|
+
yield event.data
|
|
168
|
+
|
|
169
|
+
async def stream_errors(self) -> AsyncIterator[ErrorEventData]:
|
|
170
|
+
"""A convenience async generator that yields only error events."""
|
|
171
|
+
async for event in self.all_events():
|
|
172
|
+
if event.event_type == StreamEventType.ERROR_EVENT and isinstance(event.data, ErrorEventData):
|
|
173
|
+
yield event.data
|
|
@@ -4,7 +4,7 @@ from typing import Dict, Any, Optional, List, Union
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
from autobyteus.llm.utils.token_usage import TokenUsage
|
|
7
|
-
from autobyteus.agent.
|
|
7
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
@@ -29,6 +29,8 @@ class AssistantCompleteResponseData(BaseStreamPayload):
|
|
|
29
29
|
|
|
30
30
|
class ToolInteractionLogEntryData(BaseStreamPayload):
|
|
31
31
|
log_entry: str
|
|
32
|
+
tool_invocation_id: str
|
|
33
|
+
tool_name: str
|
|
32
34
|
|
|
33
35
|
class AgentOperationalPhaseTransitionData(BaseStreamPayload):
|
|
34
36
|
new_phase: AgentOperationalPhase
|
|
@@ -48,17 +50,23 @@ class ToolInvocationApprovalRequestedData(BaseStreamPayload):
|
|
|
48
50
|
tool_name: str
|
|
49
51
|
arguments: Dict[str, Any]
|
|
50
52
|
|
|
53
|
+
class ToolInvocationAutoExecutingData(BaseStreamPayload):
|
|
54
|
+
invocation_id: str
|
|
55
|
+
tool_name: str
|
|
56
|
+
arguments: Dict[str, Any]
|
|
57
|
+
|
|
51
58
|
class EmptyData(BaseStreamPayload):
|
|
52
59
|
pass
|
|
53
60
|
|
|
54
61
|
# Union of all possible data payload types
|
|
55
62
|
StreamDataPayload = Union[
|
|
56
63
|
AssistantChunkData,
|
|
57
|
-
AssistantCompleteResponseData,
|
|
64
|
+
AssistantCompleteResponseData,
|
|
58
65
|
ToolInteractionLogEntryData,
|
|
59
66
|
AgentOperationalPhaseTransitionData,
|
|
60
67
|
ErrorEventData,
|
|
61
68
|
ToolInvocationApprovalRequestedData,
|
|
69
|
+
ToolInvocationAutoExecutingData,
|
|
62
70
|
EmptyData
|
|
63
71
|
]
|
|
64
72
|
|
|
@@ -99,8 +107,7 @@ def create_assistant_chunk_data(chunk_obj: Any) -> AssistantChunkData:
|
|
|
99
107
|
)
|
|
100
108
|
raise ValueError(f"Cannot create AssistantChunkData from {type(chunk_obj)}")
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
def create_assistant_complete_response_data(complete_resp_obj: Any) -> AssistantCompleteResponseData: # RENAMED
|
|
110
|
+
def create_assistant_complete_response_data(complete_resp_obj: Any) -> AssistantCompleteResponseData:
|
|
104
111
|
usage_data = None
|
|
105
112
|
if hasattr(complete_resp_obj, 'usage'):
|
|
106
113
|
usage_data = getattr(complete_resp_obj, 'usage')
|
|
@@ -120,25 +127,24 @@ def create_assistant_complete_response_data(complete_resp_obj: Any) -> Assistant
|
|
|
120
127
|
logger.warning(f"Unsupported usage type {type(usage_data)} for AssistantCompleteResponseData.")
|
|
121
128
|
|
|
122
129
|
if hasattr(complete_resp_obj, 'content'):
|
|
123
|
-
return AssistantCompleteResponseData(
|
|
130
|
+
return AssistantCompleteResponseData(
|
|
124
131
|
content=str(getattr(complete_resp_obj, 'content', '')),
|
|
125
132
|
reasoning=getattr(complete_resp_obj, 'reasoning', None),
|
|
126
133
|
usage=parsed_usage
|
|
127
134
|
)
|
|
128
135
|
elif isinstance(complete_resp_obj, dict):
|
|
129
|
-
return AssistantCompleteResponseData(
|
|
136
|
+
return AssistantCompleteResponseData(
|
|
130
137
|
content=str(complete_resp_obj.get('content', '')),
|
|
131
138
|
reasoning=complete_resp_obj.get('reasoning', None),
|
|
132
139
|
usage=parsed_usage
|
|
133
140
|
)
|
|
134
141
|
raise ValueError(f"Cannot create AssistantCompleteResponseData from {type(complete_resp_obj)}")
|
|
135
142
|
|
|
136
|
-
def create_tool_interaction_log_entry_data(
|
|
137
|
-
if isinstance(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
raise ValueError(f"Cannot create ToolInteractionLogEntryData from {type(log_entry)}")
|
|
143
|
+
def create_tool_interaction_log_entry_data(log_data: Any) -> ToolInteractionLogEntryData:
|
|
144
|
+
if isinstance(log_data, dict):
|
|
145
|
+
if all(k in log_data for k in ['log_entry', 'tool_invocation_id', 'tool_name']):
|
|
146
|
+
return ToolInteractionLogEntryData(**log_data)
|
|
147
|
+
raise ValueError(f"Cannot create ToolInteractionLogEntryData from {type(log_data)}. Expected dict with 'log_entry', 'tool_invocation_id', and 'tool_name' keys.")
|
|
142
148
|
|
|
143
149
|
def create_agent_operational_phase_transition_data(phase_data_dict: Any) -> AgentOperationalPhaseTransitionData:
|
|
144
150
|
if isinstance(phase_data_dict, dict):
|
|
@@ -153,4 +159,9 @@ def create_error_event_data(error_data_dict: Any) -> ErrorEventData:
|
|
|
153
159
|
def create_tool_invocation_approval_requested_data(approval_data_dict: Any) -> ToolInvocationApprovalRequestedData:
|
|
154
160
|
if isinstance(approval_data_dict, dict):
|
|
155
161
|
return ToolInvocationApprovalRequestedData(**approval_data_dict)
|
|
156
|
-
raise ValueError(f"Cannot create ToolInvocationApprovalRequestedData from {type(approval_data_dict)}")
|
|
162
|
+
raise ValueError(f"Cannot create ToolInvocationApprovalRequestedData from {type(approval_data_dict)}")
|
|
163
|
+
|
|
164
|
+
def create_tool_invocation_auto_executing_data(auto_exec_data_dict: Any) -> ToolInvocationAutoExecutingData:
|
|
165
|
+
if isinstance(auto_exec_data_dict, dict):
|
|
166
|
+
return ToolInvocationAutoExecutingData(**auto_exec_data_dict)
|
|
167
|
+
raise ValueError(f"Cannot create ToolInvocationAutoExecutingData from {type(auto_exec_data_dict)}")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import logging
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Dict, Any, Optional, Union, Type
|
|
5
|
-
from pydantic import BaseModel, Field, AwareDatetime,
|
|
5
|
+
from pydantic import BaseModel, Field, AwareDatetime, field_validator, ValidationInfo
|
|
6
6
|
import datetime
|
|
7
7
|
import uuid
|
|
8
8
|
|
|
@@ -10,11 +10,12 @@ import uuid
|
|
|
10
10
|
from .stream_event_payloads import (
|
|
11
11
|
StreamDataPayload,
|
|
12
12
|
AssistantChunkData,
|
|
13
|
-
AssistantCompleteResponseData,
|
|
13
|
+
AssistantCompleteResponseData,
|
|
14
14
|
ToolInteractionLogEntryData,
|
|
15
15
|
AgentOperationalPhaseTransitionData,
|
|
16
16
|
ErrorEventData,
|
|
17
17
|
ToolInvocationApprovalRequestedData,
|
|
18
|
+
ToolInvocationAutoExecutingData,
|
|
18
19
|
EmptyData
|
|
19
20
|
)
|
|
20
21
|
|
|
@@ -26,22 +27,24 @@ class StreamEventType(str, Enum):
|
|
|
26
27
|
provided by AgentEventStream.
|
|
27
28
|
"""
|
|
28
29
|
ASSISTANT_CHUNK = "assistant_chunk"
|
|
29
|
-
ASSISTANT_COMPLETE_RESPONSE = "assistant_complete_response"
|
|
30
|
+
ASSISTANT_COMPLETE_RESPONSE = "assistant_complete_response"
|
|
30
31
|
TOOL_INTERACTION_LOG_ENTRY = "tool_interaction_log_entry"
|
|
31
|
-
AGENT_OPERATIONAL_PHASE_TRANSITION = "agent_operational_phase_transition"
|
|
32
|
+
AGENT_OPERATIONAL_PHASE_TRANSITION = "agent_operational_phase_transition"
|
|
32
33
|
ERROR_EVENT = "error_event"
|
|
33
34
|
TOOL_INVOCATION_APPROVAL_REQUESTED = "tool_invocation_approval_requested"
|
|
34
|
-
|
|
35
|
+
TOOL_INVOCATION_AUTO_EXECUTING = "tool_invocation_auto_executing"
|
|
36
|
+
AGENT_IDLE = "agent_idle"
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
_STREAM_EVENT_TYPE_TO_PAYLOAD_CLASS: Dict[StreamEventType, Type[BaseModel]] = {
|
|
38
40
|
StreamEventType.ASSISTANT_CHUNK: AssistantChunkData,
|
|
39
|
-
StreamEventType.ASSISTANT_COMPLETE_RESPONSE: AssistantCompleteResponseData,
|
|
41
|
+
StreamEventType.ASSISTANT_COMPLETE_RESPONSE: AssistantCompleteResponseData,
|
|
40
42
|
StreamEventType.TOOL_INTERACTION_LOG_ENTRY: ToolInteractionLogEntryData,
|
|
41
|
-
StreamEventType.AGENT_OPERATIONAL_PHASE_TRANSITION: AgentOperationalPhaseTransitionData,
|
|
43
|
+
StreamEventType.AGENT_OPERATIONAL_PHASE_TRANSITION: AgentOperationalPhaseTransitionData,
|
|
42
44
|
StreamEventType.ERROR_EVENT: ErrorEventData,
|
|
43
45
|
StreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED: ToolInvocationApprovalRequestedData,
|
|
44
|
-
StreamEventType.
|
|
46
|
+
StreamEventType.TOOL_INVOCATION_AUTO_EXECUTING: ToolInvocationAutoExecutingData,
|
|
47
|
+
StreamEventType.AGENT_IDLE: AgentOperationalPhaseTransitionData,
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
|
|
@@ -72,9 +75,9 @@ class StreamEvent(BaseModel):
|
|
|
72
75
|
description="Optional ID of the agent that originated this event."
|
|
73
76
|
)
|
|
74
77
|
|
|
75
|
-
@
|
|
76
|
-
def validate_data_based_on_event_type(cls, v,
|
|
77
|
-
event_type_value =
|
|
78
|
+
@field_validator('data', mode='before')
|
|
79
|
+
def validate_data_based_on_event_type(cls, v, info: ValidationInfo):
|
|
80
|
+
event_type_value = info.data.get('event_type')
|
|
78
81
|
if not event_type_value:
|
|
79
82
|
return v
|
|
80
83
|
|
|
@@ -3,23 +3,26 @@ import logging
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from typing import TYPE_CHECKING, Dict
|
|
5
5
|
|
|
6
|
+
from .processor_meta import SystemPromptProcessorMeta
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
from autobyteus.tools.base_tool import BaseTool
|
|
8
10
|
from autobyteus.agent.context import AgentContext
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
12
|
-
class BaseSystemPromptProcessor(ABC):
|
|
14
|
+
class BaseSystemPromptProcessor(ABC, metaclass=SystemPromptProcessorMeta):
|
|
13
15
|
"""
|
|
14
16
|
Abstract base class for system prompt processors.
|
|
15
17
|
Subclasses should be instantiated and passed to the AgentSpecification.
|
|
16
18
|
"""
|
|
17
|
-
|
|
19
|
+
@classmethod
|
|
20
|
+
def get_name(cls) -> str:
|
|
18
21
|
"""
|
|
19
22
|
Returns the unique name for this processor.
|
|
20
23
|
Defaults to the class name. Can be overridden by subclasses.
|
|
21
24
|
"""
|
|
22
|
-
return
|
|
25
|
+
return cls.__name__
|
|
23
26
|
|
|
24
27
|
@abstractmethod
|
|
25
28
|
def process(self,
|
|
@@ -38,7 +38,7 @@ class SystemPromptProcessorMeta(ABCMeta):
|
|
|
38
38
|
# Ensure 'cls' is correctly typed for SystemPromptProcessorDefinition
|
|
39
39
|
definition = SystemPromptProcessorDefinition(name=processor_name, processor_class=cls) # type: ignore
|
|
40
40
|
default_system_prompt_processor_registry.register_processor(definition)
|
|
41
|
-
logger.info(f"Auto-registered system prompt processor: '{processor_name}' from class {name}")
|
|
41
|
+
logger.info(f"Auto-registered system prompt processor: '{processor_name}' from class {name} (no schema).")
|
|
42
42
|
|
|
43
43
|
except AttributeError as e:
|
|
44
44
|
# Catch if get_name is missing
|
|
@@ -5,6 +5,7 @@ from typing import Dict, TYPE_CHECKING, List
|
|
|
5
5
|
from .base_processor import BaseSystemPromptProcessor
|
|
6
6
|
from autobyteus.tools.registry import default_tool_registry, ToolDefinition
|
|
7
7
|
from autobyteus.tools.usage.providers import ToolManifestProvider
|
|
8
|
+
from autobyteus.prompt.prompt_template import PromptTemplate
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from autobyteus.tools.base_tool import BaseTool
|
|
@@ -14,52 +15,65 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
class ToolManifestInjectorProcessor(BaseSystemPromptProcessor):
|
|
16
17
|
"""
|
|
17
|
-
Injects a tool manifest into the system prompt
|
|
18
|
-
It
|
|
18
|
+
Injects a tool manifest into the system prompt using Jinja2-style placeholders.
|
|
19
|
+
It primarily targets the '{{tools}}' variable. It uses PromptTemplate for
|
|
20
|
+
rendering and delegates manifest generation to a ToolManifestProvider.
|
|
19
21
|
"""
|
|
20
|
-
|
|
22
|
+
# The '{{tools}}' placeholder is now handled by Jinja2 via PromptTemplate.
|
|
21
23
|
DEFAULT_PREFIX_FOR_TOOLS_ONLY_PROMPT = "You have access to a set of tools. Use them by outputting the appropriate tool call format. The user can only see the output of the tool, not the call itself. The available tools are:\n\n"
|
|
22
24
|
|
|
23
25
|
def __init__(self):
|
|
24
26
|
self._manifest_provider = ToolManifestProvider()
|
|
25
27
|
logger.debug(f"{self.get_name()} initialized.")
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_name(cls) -> str:
|
|
28
31
|
return "ToolManifestInjector"
|
|
29
32
|
|
|
30
33
|
def process(self, system_prompt: str, tool_instances: Dict[str, 'BaseTool'], agent_id: str, context: 'AgentContext') -> str:
|
|
31
|
-
|
|
34
|
+
try:
|
|
35
|
+
prompt_template = PromptTemplate(template=system_prompt)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Failed to create PromptTemplate from system prompt for agent '{agent_id}'. Error: {e}", exc_info=True)
|
|
38
|
+
# Return original prompt on Jinja2 parsing failure
|
|
32
39
|
return system_prompt
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
logger.info(f"{self.get_name()}: The '{self.PLACEHOLDER}' placeholder is present, but no tools are instantiated. Replacing with 'No tools available.'")
|
|
38
|
-
replacement_text = "No tools available for this agent."
|
|
39
|
-
if is_tools_only_prompt:
|
|
40
|
-
return self.DEFAULT_PREFIX_FOR_TOOLS_ONLY_PROMPT + replacement_text
|
|
41
|
-
return system_prompt.replace(self.PLACEHOLDER, f"\n{replacement_text}")
|
|
41
|
+
# Check if the 'tools' variable is actually in the template
|
|
42
|
+
if "tools" not in prompt_template.required_vars:
|
|
43
|
+
return system_prompt
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
# Generate the manifest string for the 'tools' variable.
|
|
46
|
+
tools_manifest: str
|
|
47
|
+
if not tool_instances:
|
|
48
|
+
logger.info(f"{self.get_name()}: The '{{{{tools}}}}' placeholder is present, but no tools are instantiated. Using 'No tools available.'")
|
|
49
|
+
tools_manifest = "No tools available for this agent."
|
|
50
|
+
else:
|
|
51
|
+
tool_definitions: List[ToolDefinition] = [
|
|
52
|
+
td for name in tool_instances if (td := default_tool_registry.get_tool_definition(name))
|
|
53
|
+
]
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
llm_provider = context.llm_instance.model.provider if context.llm_instance and context.llm_instance.model else None
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
try:
|
|
58
|
+
# Delegate manifest generation to the provider
|
|
59
|
+
tools_manifest = self._manifest_provider.provide(
|
|
60
|
+
tool_definitions=tool_definitions,
|
|
61
|
+
use_xml=context.config.use_xml_tool_format,
|
|
62
|
+
provider=llm_provider
|
|
63
|
+
)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.exception(f"An unexpected error occurred during tool manifest generation for agent '{agent_id}': {e}")
|
|
66
|
+
tools_manifest = "Error: Could not generate tool descriptions."
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
# Check if the prompt *only* contains the 'tools' variable by rendering with an empty string
|
|
69
|
+
rendered_without_tools = prompt_template.fill({"tools": ""})
|
|
70
|
+
is_tools_only_prompt = not rendered_without_tools.strip()
|
|
71
|
+
|
|
61
72
|
if is_tools_only_prompt:
|
|
62
73
|
logger.info(f"{self.get_name()}: Prompt contains only the tools placeholder. Prepending default instructions.")
|
|
63
|
-
return self.DEFAULT_PREFIX_FOR_TOOLS_ONLY_PROMPT +
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
return self.DEFAULT_PREFIX_FOR_TOOLS_ONLY_PROMPT + tools_manifest
|
|
75
|
+
else:
|
|
76
|
+
# For prompts that contain other text, add a newline for better formatting before filling the template.
|
|
77
|
+
tools_description_with_newline = f"\n{tools_manifest}"
|
|
78
|
+
final_prompt = prompt_template.fill({"tools": tools_description_with_newline})
|
|
79
|
+
return final_prompt
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/agent/tool_invocation.py
|
|
2
2
|
import uuid
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
3
5
|
from typing import Optional, Dict, Any
|
|
4
6
|
|
|
5
7
|
class ToolInvocation:
|
|
@@ -11,11 +13,36 @@ class ToolInvocation:
|
|
|
11
13
|
name: The name of the tool to be invoked.
|
|
12
14
|
arguments: A dictionary of arguments for the tool.
|
|
13
15
|
id: Optional. A unique identifier for this tool invocation.
|
|
14
|
-
If None, a
|
|
16
|
+
If None, a deterministic ID will be generated based on the tool name and arguments.
|
|
15
17
|
"""
|
|
16
18
|
self.name: Optional[str] = name
|
|
17
19
|
self.arguments: Optional[Dict[str, Any]] = arguments
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
if id is not None:
|
|
22
|
+
self.id: str = id
|
|
23
|
+
elif self.name is not None and self.arguments is not None:
|
|
24
|
+
self.id: str = self._generate_deterministic_id(self.name, self.arguments)
|
|
25
|
+
else:
|
|
26
|
+
# Fallback to UUID if name/args are not provided during init, though this is an edge case.
|
|
27
|
+
self.id: str = f"call_{uuid.uuid4().hex}"
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _generate_deterministic_id(name: str, arguments: Dict[str, Any]) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Generates a deterministic ID for the tool invocation based on its content.
|
|
33
|
+
"""
|
|
34
|
+
# Create a canonical representation of the arguments
|
|
35
|
+
# sort_keys=True ensures that the order of keys doesn't change the hash
|
|
36
|
+
canonical_args = json.dumps(arguments, sort_keys=True, separators=(',', ':'))
|
|
37
|
+
|
|
38
|
+
# Create a string to hash
|
|
39
|
+
hash_string = f"{name}:{canonical_args}"
|
|
40
|
+
|
|
41
|
+
# Use SHA256 for a robust hash
|
|
42
|
+
sha256_hash = hashlib.sha256(hash_string.encode('utf-8')).hexdigest()
|
|
43
|
+
|
|
44
|
+
# Prepend a prefix for clarity and use the full hash.
|
|
45
|
+
return f"call_{sha256_hash}"
|
|
19
46
|
|
|
20
47
|
def is_valid(self) -> bool:
|
|
21
48
|
"""
|
|
@@ -27,4 +54,3 @@ class ToolInvocation:
|
|
|
27
54
|
def __repr__(self) -> str:
|
|
28
55
|
return (f"ToolInvocation(id='{self.id}', name='{self.name}', "
|
|
29
56
|
f"arguments={self.arguments})")
|
|
30
|
-
|
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
|
6
6
|
from autobyteus.agent.agent import Agent
|
|
7
7
|
from autobyteus.agent.streaming.agent_event_stream import AgentEventStream
|
|
8
8
|
from autobyteus.agent.streaming.stream_events import StreamEventType
|
|
9
|
-
from autobyteus.agent.
|
|
9
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|