autobyteus 1.1.0__py3-none-any.whl → 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autobyteus/agent/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 +4 -6
- autobyteus/agent/runtime/agent_worker.py +1 -1
- 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 +5 -5
- autobyteus/llm/api/grok_llm.py +5 -5
- autobyteus/llm/api/mistral_llm.py +4 -4
- autobyteus/llm/api/ollama_llm.py +2 -2
- autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
- autobyteus/llm/llm_factory.py +106 -42
- autobyteus/llm/models.py +25 -29
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- 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/default_json_tool_usage_parser.py +3 -3
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +2 -1
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +17 -19
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +126 -77
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/METADATA +11 -11
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/RECORD +95 -78
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/top_level.txt +0 -0
|
@@ -54,7 +54,12 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
|
|
|
54
54
|
|
|
55
55
|
if notifier:
|
|
56
56
|
try:
|
|
57
|
-
|
|
57
|
+
log_data = {
|
|
58
|
+
"log_entry": log_msg_call,
|
|
59
|
+
"tool_invocation_id": invocation_id,
|
|
60
|
+
"tool_name": tool_name,
|
|
61
|
+
}
|
|
62
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
58
63
|
except Exception as e_notify:
|
|
59
64
|
logger.error(f"Agent '{agent_id}': Error notifying approved tool call log: {e_notify}", exc_info=True)
|
|
60
65
|
|
|
@@ -71,11 +76,14 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
|
|
|
71
76
|
"name": tool_name,
|
|
72
77
|
"content": f"Error: Approved tool '{tool_name}' execution failed. Reason: {error_message}",
|
|
73
78
|
})
|
|
74
|
-
log_msg_error = f"[APPROVED_TOOL_ERROR]
|
|
79
|
+
log_msg_error = f"[APPROVED_TOOL_ERROR] {error_message}"
|
|
75
80
|
if notifier:
|
|
76
81
|
try:
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
# Log entry
|
|
83
|
+
log_data = { "log_entry": log_msg_error, "tool_invocation_id": invocation_id, "tool_name": tool_name }
|
|
84
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
85
|
+
# Generic output error
|
|
86
|
+
notifier.notify_agent_error_output_generation(
|
|
79
87
|
error_source=f"ApprovedToolExecution.ToolNotFound.{tool_name}",
|
|
80
88
|
error_message=error_message
|
|
81
89
|
)
|
|
@@ -87,11 +95,11 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
|
|
|
87
95
|
execution_result = await tool_instance.execute(context=context, **arguments)
|
|
88
96
|
|
|
89
97
|
try:
|
|
90
|
-
|
|
91
|
-
except TypeError:
|
|
92
|
-
|
|
98
|
+
result_json_for_log = json.dumps(execution_result)
|
|
99
|
+
except (TypeError, ValueError):
|
|
100
|
+
result_json_for_log = json.dumps(str(execution_result))
|
|
93
101
|
|
|
94
|
-
logger.info(f"Approved tool '{tool_name}' (ID: {invocation_id}) executed successfully by agent '{agent_id}'.
|
|
102
|
+
logger.info(f"Approved tool '{tool_name}' (ID: {invocation_id}) executed successfully by agent '{agent_id}'.")
|
|
95
103
|
result_event = ToolResultEvent(tool_name=tool_name, result=execution_result, error=None, tool_invocation_id=invocation_id)
|
|
96
104
|
|
|
97
105
|
history_content = str(execution_result)
|
|
@@ -101,15 +109,18 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
|
|
|
101
109
|
"name": tool_name,
|
|
102
110
|
"content": history_content,
|
|
103
111
|
})
|
|
104
|
-
log_msg_result = f"[APPROVED_TOOL_RESULT]
|
|
112
|
+
log_msg_result = f"[APPROVED_TOOL_RESULT] {result_json_for_log}"
|
|
105
113
|
if notifier:
|
|
106
114
|
try:
|
|
107
|
-
|
|
115
|
+
# Log entry with embedded JSON result
|
|
116
|
+
log_data = { "log_entry": log_msg_result, "tool_invocation_id": invocation_id, "tool_name": tool_name }
|
|
117
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
108
118
|
except Exception as e_notify:
|
|
109
119
|
logger.error(f"Agent '{agent_id}': Error notifying approved tool result log: {e_notify}", exc_info=True)
|
|
110
120
|
|
|
111
121
|
except Exception as e:
|
|
112
122
|
error_message = f"Error executing approved tool '{tool_name}' (ID: {invocation_id}): {str(e)}"
|
|
123
|
+
error_details = traceback.format_exc()
|
|
113
124
|
logger.error(f"Agent '{agent_id}' {error_message}", exc_info=True)
|
|
114
125
|
result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
|
|
115
126
|
context.add_message_to_history({
|
|
@@ -118,14 +129,17 @@ class ApprovedToolInvocationEventHandler(AgentEventHandler):
|
|
|
118
129
|
"name": tool_name,
|
|
119
130
|
"content": f"Error: Approved tool '{tool_name}' execution failed. Reason: {error_message}",
|
|
120
131
|
})
|
|
121
|
-
log_msg_exception = f"[APPROVED_TOOL_EXCEPTION]
|
|
132
|
+
log_msg_exception = f"[APPROVED_TOOL_EXCEPTION] {error_message}\nDetails:\n{error_details}"
|
|
122
133
|
if notifier:
|
|
123
134
|
try:
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
# Log entry
|
|
136
|
+
log_data = { "log_entry": log_msg_exception, "tool_invocation_id": invocation_id, "tool_name": tool_name }
|
|
137
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
138
|
+
# Generic output error
|
|
139
|
+
notifier.notify_agent_error_output_generation(
|
|
126
140
|
error_source=f"ApprovedToolExecution.Exception.{tool_name}",
|
|
127
141
|
error_message=error_message,
|
|
128
|
-
error_details=
|
|
142
|
+
error_details=error_details
|
|
129
143
|
)
|
|
130
144
|
except Exception as e_notify:
|
|
131
145
|
logger.error(f"Agent '{agent_id}': Error notifying approved tool exception log/output error: {e_notify}", exc_info=True)
|
|
@@ -10,7 +10,7 @@ from autobyteus.agent.events import (
|
|
|
10
10
|
AgentErrorEvent,
|
|
11
11
|
LifecycleEvent
|
|
12
12
|
)
|
|
13
|
-
from autobyteus.agent.
|
|
13
|
+
from autobyteus.agent.phases import AgentOperationalPhase # Import new phase enum
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from autobyteus.agent.context import AgentContext
|
|
@@ -42,7 +42,9 @@ class LLMCompleteResponseReceivedEventHandler(AgentEventHandler):
|
|
|
42
42
|
)
|
|
43
43
|
if complete_response_reasoning:
|
|
44
44
|
logger.debug(f"Agent '{agent_id}' received LLM reasoning for processing:\n---\n{complete_response_reasoning}\n---")
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
# Changed from .debug to .info as per user request
|
|
47
|
+
logger.info(f"Agent '{agent_id}' received full LLM content for processing:\n---\n{complete_response_text}\n---")
|
|
46
48
|
|
|
47
49
|
any_processor_took_action = False
|
|
48
50
|
|
|
@@ -133,4 +135,4 @@ class LLMCompleteResponseReceivedEventHandler(AgentEventHandler):
|
|
|
133
135
|
notifier.notify_agent_data_assistant_complete_response(complete_response)
|
|
134
136
|
logger.debug(f"Agent '{agent_id}' emitted AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE event.")
|
|
135
137
|
except Exception as e_notify: # pragma: no cover
|
|
136
|
-
logger.error(f"Agent '{agent_id}': Error emitting AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE: {e_notify}", exc_info=True)
|
|
138
|
+
logger.error(f"Agent '{agent_id}': Error emitting AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE: {e_notify}", exc_info=True)
|
|
@@ -34,6 +34,17 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
34
34
|
tool_name = tool_invocation.name
|
|
35
35
|
arguments = tool_invocation.arguments
|
|
36
36
|
invocation_id = tool_invocation.id
|
|
37
|
+
|
|
38
|
+
if notifier:
|
|
39
|
+
try:
|
|
40
|
+
auto_exec_data = {
|
|
41
|
+
"invocation_id": invocation_id,
|
|
42
|
+
"tool_name": tool_name,
|
|
43
|
+
"arguments": arguments,
|
|
44
|
+
}
|
|
45
|
+
notifier.notify_agent_tool_invocation_auto_executing(auto_exec_data)
|
|
46
|
+
except Exception as e_notify:
|
|
47
|
+
logger.error(f"Agent '{agent_id}': Error notifying tool auto-execution: {e_notify}", exc_info=True)
|
|
37
48
|
|
|
38
49
|
logger.info(f"Agent '{agent_id}' executing tool directly: '{tool_name}' (ID: {invocation_id}) with args: {arguments}")
|
|
39
50
|
|
|
@@ -45,7 +56,12 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
45
56
|
log_msg_call = f"[TOOL_CALL_DIRECT] Agent_ID: {agent_id}, Tool: {tool_name}, Invocation_ID: {invocation_id}, Arguments: {args_str}"
|
|
46
57
|
if notifier:
|
|
47
58
|
try:
|
|
48
|
-
|
|
59
|
+
log_data = {
|
|
60
|
+
"log_entry": log_msg_call,
|
|
61
|
+
"tool_invocation_id": invocation_id,
|
|
62
|
+
"tool_name": tool_name,
|
|
63
|
+
}
|
|
64
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
49
65
|
except Exception as e_notify:
|
|
50
66
|
logger.error(f"Agent '{agent_id}': Error notifying tool call log: {e_notify}", exc_info=True)
|
|
51
67
|
|
|
@@ -62,11 +78,14 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
62
78
|
"name": tool_name,
|
|
63
79
|
"content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
|
|
64
80
|
})
|
|
65
|
-
log_msg_error = f"[TOOL_ERROR_DIRECT]
|
|
81
|
+
log_msg_error = f"[TOOL_ERROR_DIRECT] {error_message}"
|
|
66
82
|
if notifier:
|
|
67
83
|
try:
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
# Log entry
|
|
85
|
+
log_data = { "log_entry": log_msg_error, "tool_invocation_id": invocation_id, "tool_name": tool_name, }
|
|
86
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
87
|
+
# Generic output error
|
|
88
|
+
notifier.notify_agent_error_output_generation(
|
|
70
89
|
error_source=f"ToolExecutionDirect.ToolNotFound.{tool_name}",
|
|
71
90
|
error_message=error_message
|
|
72
91
|
)
|
|
@@ -78,11 +97,11 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
78
97
|
execution_result = await tool_instance.execute(context=context, **arguments)
|
|
79
98
|
|
|
80
99
|
try:
|
|
81
|
-
|
|
82
|
-
except TypeError:
|
|
83
|
-
|
|
100
|
+
result_json_for_log = json.dumps(execution_result)
|
|
101
|
+
except (TypeError, ValueError):
|
|
102
|
+
result_json_for_log = json.dumps(str(execution_result))
|
|
84
103
|
|
|
85
|
-
logger.info(f"Tool '{tool_name}' (ID: {invocation_id}) executed by agent '{agent_id}'.
|
|
104
|
+
logger.info(f"Tool '{tool_name}' (ID: {invocation_id}) executed by agent '{agent_id}'.")
|
|
86
105
|
result_event = ToolResultEvent(tool_name=tool_name, result=execution_result, error=None, tool_invocation_id=invocation_id)
|
|
87
106
|
|
|
88
107
|
history_content = str(execution_result)
|
|
@@ -92,15 +111,18 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
92
111
|
"name": tool_name,
|
|
93
112
|
"content": history_content,
|
|
94
113
|
})
|
|
95
|
-
log_msg_result = f"[TOOL_RESULT_DIRECT]
|
|
114
|
+
log_msg_result = f"[TOOL_RESULT_DIRECT] {result_json_for_log}"
|
|
96
115
|
if notifier:
|
|
97
116
|
try:
|
|
98
|
-
|
|
117
|
+
# Log entry with embedded JSON result
|
|
118
|
+
log_data = { "log_entry": log_msg_result, "tool_invocation_id": invocation_id, "tool_name": tool_name }
|
|
119
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
99
120
|
except Exception as e_notify:
|
|
100
121
|
logger.error(f"Agent '{agent_id}': Error notifying tool result log: {e_notify}", exc_info=True)
|
|
101
122
|
|
|
102
123
|
except Exception as e:
|
|
103
124
|
error_message = f"Error executing tool '{tool_name}' (ID: {invocation_id}): {str(e)}"
|
|
125
|
+
error_details = traceback.format_exc()
|
|
104
126
|
logger.error(f"Agent '{agent_id}' {error_message}", exc_info=True)
|
|
105
127
|
result_event = ToolResultEvent(tool_name=tool_name, result=None, error=error_message, tool_invocation_id=invocation_id)
|
|
106
128
|
context.add_message_to_history({
|
|
@@ -109,14 +131,17 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
109
131
|
"name": tool_name,
|
|
110
132
|
"content": f"Error: Tool '{tool_name}' execution failed. Reason: {error_message}",
|
|
111
133
|
})
|
|
112
|
-
log_msg_exception = f"[TOOL_EXCEPTION_DIRECT]
|
|
134
|
+
log_msg_exception = f"[TOOL_EXCEPTION_DIRECT] {error_message}\nDetails:\n{error_details}"
|
|
113
135
|
if notifier:
|
|
114
136
|
try:
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
# Log entry
|
|
138
|
+
log_data = { "log_entry": log_msg_exception, "tool_invocation_id": invocation_id, "tool_name": tool_name }
|
|
139
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
140
|
+
# Generic output error
|
|
141
|
+
notifier.notify_agent_error_output_generation(
|
|
117
142
|
error_source=f"ToolExecutionDirect.Exception.{tool_name}",
|
|
118
143
|
error_message=error_message,
|
|
119
|
-
error_details=
|
|
144
|
+
error_details=error_details
|
|
120
145
|
)
|
|
121
146
|
except Exception as e_notify:
|
|
122
147
|
logger.error(f"Agent '{agent_id}': Error notifying tool exception log/output error: {e_notify}", exc_info=True)
|
|
@@ -176,7 +201,7 @@ class ToolInvocationRequestEventHandler(AgentEventHandler):
|
|
|
176
201
|
}
|
|
177
202
|
if notifier:
|
|
178
203
|
try:
|
|
179
|
-
notifier.notify_agent_request_tool_invocation_approval(approval_data)
|
|
204
|
+
notifier.notify_agent_request_tool_invocation_approval(approval_data)
|
|
180
205
|
logger.debug(f"Agent '{agent_id}': Emitted AGENT_REQUEST_TOOL_INVOCATION_APPROVAL for '{tool_invocation.name}' (ID: {tool_invocation.id}).")
|
|
181
206
|
except Exception as e_notify:
|
|
182
207
|
logger.error(f"Agent '{agent_id}': Error emitting AGENT_REQUEST_TOOL_INVOCATION_APPROVAL: {e_notify}", exc_info=True)
|
|
@@ -61,7 +61,12 @@ class ToolResultEventHandler(AgentEventHandler):
|
|
|
61
61
|
log_msg_error_processed = f"[TOOL_RESULT_ERROR_PROCESSED] Agent_ID: {agent_id}, Tool: {event.tool_name}, Invocation_ID: {tool_invocation_id}, Error: {event.error}"
|
|
62
62
|
if notifier:
|
|
63
63
|
try:
|
|
64
|
-
|
|
64
|
+
log_data = {
|
|
65
|
+
"log_entry": log_msg_error_processed,
|
|
66
|
+
"tool_invocation_id": tool_invocation_id,
|
|
67
|
+
"tool_name": event.tool_name,
|
|
68
|
+
}
|
|
69
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
65
70
|
except Exception as e_notify:
|
|
66
71
|
logger.error(f"Agent '{agent_id}': Error notifying tool result error log: {e_notify}", exc_info=True)
|
|
67
72
|
else:
|
|
@@ -70,11 +75,6 @@ class ToolResultEventHandler(AgentEventHandler):
|
|
|
70
75
|
except TypeError: # pragma: no cover
|
|
71
76
|
result_str_for_llm = str(event.result)
|
|
72
77
|
|
|
73
|
-
max_len = 2000
|
|
74
|
-
if len(result_str_for_llm) > max_len: # pragma: no cover
|
|
75
|
-
original_len = len(str(event.result))
|
|
76
|
-
result_str_for_llm = result_str_for_llm[:max_len] + f"... (result truncated, original length {original_len})"
|
|
77
|
-
|
|
78
78
|
content_for_llm = (
|
|
79
79
|
f"The tool '{event.tool_name}' (invocation ID: {tool_invocation_id}) has executed.\n"
|
|
80
80
|
f"Result:\n{result_str_for_llm}\n"
|
|
@@ -83,7 +83,12 @@ class ToolResultEventHandler(AgentEventHandler):
|
|
|
83
83
|
log_msg_success_processed = f"[TOOL_RESULT_SUCCESS_PROCESSED] Agent_ID: {agent_id}, Tool: {event.tool_name}, Invocation_ID: {tool_invocation_id}, Result (first 200 chars of stringified): {str(event.result)[:200]}"
|
|
84
84
|
if notifier:
|
|
85
85
|
try:
|
|
86
|
-
|
|
86
|
+
log_data = {
|
|
87
|
+
"log_entry": log_msg_success_processed,
|
|
88
|
+
"tool_invocation_id": tool_invocation_id,
|
|
89
|
+
"tool_name": event.tool_name,
|
|
90
|
+
}
|
|
91
|
+
notifier.notify_agent_data_tool_log(log_data)
|
|
87
92
|
except Exception as e_notify:
|
|
88
93
|
logger.error(f"Agent '{agent_id}': Error notifying tool result success log: {e_notify}", exc_info=True)
|
|
89
94
|
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
Components for defining and running lifecycle hooks based on agent phase transitions.
|
|
4
4
|
"""
|
|
5
5
|
from .base_phase_hook import BasePhaseHook
|
|
6
|
+
from .hook_definition import PhaseHookDefinition
|
|
7
|
+
from .hook_meta import PhaseHookMeta
|
|
8
|
+
from .hook_registry import PhaseHookRegistry, default_phase_hook_registry
|
|
6
9
|
|
|
7
10
|
__all__ = [
|
|
8
11
|
"BasePhaseHook",
|
|
12
|
+
"PhaseHookDefinition",
|
|
13
|
+
"PhaseHookMeta",
|
|
14
|
+
"PhaseHookRegistry",
|
|
15
|
+
"default_phase_hook_registry",
|
|
9
16
|
]
|
|
@@ -3,14 +3,15 @@ import logging
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
from autobyteus.agent.
|
|
6
|
+
from autobyteus.agent.phases import AgentOperationalPhase
|
|
7
|
+
from .hook_meta import PhaseHookMeta
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from autobyteus.agent.context import AgentContext
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
|
-
class BasePhaseHook(ABC):
|
|
14
|
+
class BasePhaseHook(ABC, metaclass=PhaseHookMeta):
|
|
14
15
|
"""
|
|
15
16
|
Abstract base class for creating hooks that execute on specific agent
|
|
16
17
|
phase transitions.
|
|
@@ -20,6 +21,14 @@ class BasePhaseHook(ABC):
|
|
|
20
21
|
method for their custom logic.
|
|
21
22
|
"""
|
|
22
23
|
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_name(cls) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Returns the unique registration name for this hook.
|
|
28
|
+
Defaults to the class name. Can be overridden by subclasses.
|
|
29
|
+
"""
|
|
30
|
+
return cls.__name__
|
|
31
|
+
|
|
23
32
|
@property
|
|
24
33
|
@abstractmethod
|
|
25
34
|
def source_phase(self) -> AgentOperationalPhase:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/hooks/hook_definition.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Type, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .base_phase_hook import BasePhaseHook
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class PhaseHookDefinition:
|
|
11
|
+
"""
|
|
12
|
+
Represents the definition of a phase hook.
|
|
13
|
+
Contains its registered name and the class itself.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, name: str, hook_class: Type['BasePhaseHook']):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the PhaseHookDefinition.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: The unique registered name of the hook.
|
|
21
|
+
hook_class: The class of the phase hook.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If name is empty or hook_class is not a type.
|
|
25
|
+
"""
|
|
26
|
+
if not name or not isinstance(name, str):
|
|
27
|
+
raise ValueError("Hook name must be a non-empty string.")
|
|
28
|
+
if not isinstance(hook_class, type):
|
|
29
|
+
raise ValueError("hook_class must be a class type.")
|
|
30
|
+
|
|
31
|
+
self.name: str = name
|
|
32
|
+
self.hook_class: Type['BasePhaseHook'] = hook_class
|
|
33
|
+
logger.debug(f"PhaseHookDefinition created: name='{name}', class='{hook_class.__name__}'.")
|
|
34
|
+
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return f"<PhaseHookDefinition name='{self.name}', class='{self.hook_class.__name__}'>"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/hooks/hook_meta.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABCMeta
|
|
4
|
+
|
|
5
|
+
from .hook_registry import default_phase_hook_registry
|
|
6
|
+
from .hook_definition import PhaseHookDefinition
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class PhaseHookMeta(ABCMeta):
|
|
11
|
+
"""
|
|
12
|
+
Metaclass for BasePhaseHook that automatically registers concrete
|
|
13
|
+
hook subclasses with the default_phase_hook_registry.
|
|
14
|
+
Registration uses the name obtained from the class method `get_name()`.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(cls, name, bases, dct):
|
|
17
|
+
super().__init__(name, bases, dct)
|
|
18
|
+
|
|
19
|
+
if name == 'BasePhaseHook' or getattr(cls, "__abstractmethods__", None):
|
|
20
|
+
logger.debug(f"Skipping registration for abstract phase hook class: {name}")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
hook_name = cls.get_name()
|
|
25
|
+
|
|
26
|
+
if not hook_name or not isinstance(hook_name, str):
|
|
27
|
+
logger.error(f"Phase hook class {name} must return a valid string from static get_name(). Skipping registration.")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
definition = PhaseHookDefinition(name=hook_name, hook_class=cls)
|
|
31
|
+
default_phase_hook_registry.register_hook(definition)
|
|
32
|
+
logger.info(f"Auto-registered phase hook: '{hook_name}' from class {name} (no schema).")
|
|
33
|
+
|
|
34
|
+
except AttributeError as e:
|
|
35
|
+
logger.error(f"Phase hook class {name} is missing required static/class method 'get_name' ({e}). Skipping registration.")
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Failed to auto-register phase hook class {name}: {e}", exc_info=True)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/hooks/hook_registry.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.utils.singleton import SingletonMeta
|
|
6
|
+
from .hook_definition import PhaseHookDefinition
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .base_phase_hook import BasePhaseHook
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class PhaseHookRegistry(metaclass=SingletonMeta):
|
|
14
|
+
"""
|
|
15
|
+
A singleton registry for PhaseHookDefinition objects.
|
|
16
|
+
Hooks are typically auto-registered via PhaseHookMeta.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
"""Initializes the PhaseHookRegistry with an empty store."""
|
|
21
|
+
self._definitions: Dict[str, PhaseHookDefinition] = {}
|
|
22
|
+
logger.info("PhaseHookRegistry initialized.")
|
|
23
|
+
|
|
24
|
+
def register_hook(self, definition: PhaseHookDefinition) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Registers a phase hook definition.
|
|
27
|
+
If a definition with the same name already exists, it will be overwritten,
|
|
28
|
+
and a warning will be logged.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
definition: The PhaseHookDefinition object to register.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
TypeError: If the definition is not an instance of PhaseHookDefinition.
|
|
35
|
+
"""
|
|
36
|
+
if not isinstance(definition, PhaseHookDefinition):
|
|
37
|
+
raise TypeError(f"Expected PhaseHookDefinition instance, got {type(definition).__name__}.")
|
|
38
|
+
|
|
39
|
+
hook_name = definition.name
|
|
40
|
+
if hook_name in self._definitions:
|
|
41
|
+
logger.warning(f"Overwriting existing phase hook definition for name: '{hook_name}'.")
|
|
42
|
+
|
|
43
|
+
self._definitions[hook_name] = definition
|
|
44
|
+
logger.info(f"Phase hook definition '{hook_name}' (class: '{definition.hook_class.__name__}') registered successfully.")
|
|
45
|
+
|
|
46
|
+
def get_hook_definition(self, name: str) -> Optional[PhaseHookDefinition]:
|
|
47
|
+
"""
|
|
48
|
+
Retrieves a phase hook definition by its name.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
name: The name of the phase hook definition to retrieve.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The PhaseHookDefinition object if found, otherwise None.
|
|
55
|
+
"""
|
|
56
|
+
if not isinstance(name, str):
|
|
57
|
+
logger.warning(f"Attempted to retrieve hook definition with non-string name: {type(name).__name__}.")
|
|
58
|
+
return None
|
|
59
|
+
definition = self._definitions.get(name)
|
|
60
|
+
if not definition:
|
|
61
|
+
logger.debug(f"Phase hook definition with name '{name}' not found in registry.")
|
|
62
|
+
return definition
|
|
63
|
+
|
|
64
|
+
def get_hook(self, name: str) -> Optional['BasePhaseHook']:
|
|
65
|
+
"""
|
|
66
|
+
Retrieves an instance of a phase hook by its name.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
name: The name of the phase hook to retrieve.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
An instance of the BasePhaseHook if found and instantiable, otherwise None.
|
|
73
|
+
"""
|
|
74
|
+
definition = self.get_hook_definition(name)
|
|
75
|
+
if definition:
|
|
76
|
+
try:
|
|
77
|
+
return definition.hook_class()
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Failed to instantiate phase hook '{name}' from class '{definition.hook_class.__name__}': {e}", exc_info=True)
|
|
80
|
+
return None
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def list_hook_names(self) -> List[str]:
|
|
84
|
+
"""
|
|
85
|
+
Returns a list of names of all registered phase hook definitions.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
A list of strings, where each string is a registered hook name.
|
|
89
|
+
"""
|
|
90
|
+
return list(self._definitions.keys())
|
|
91
|
+
|
|
92
|
+
def get_all_definitions(self) -> Dict[str, PhaseHookDefinition]:
|
|
93
|
+
"""
|
|
94
|
+
Returns a shallow copy of the dictionary containing all registered phase hook definitions.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
A dictionary where keys are hook names and values are PhaseHookDefinition objects.
|
|
98
|
+
"""
|
|
99
|
+
return dict(self._definitions)
|
|
100
|
+
|
|
101
|
+
def clear(self) -> None:
|
|
102
|
+
"""Removes all definitions from the registry."""
|
|
103
|
+
count = len(self._definitions)
|
|
104
|
+
self._definitions.clear()
|
|
105
|
+
logger.info(f"Cleared {count} definitions from the PhaseHookRegistry.")
|
|
106
|
+
|
|
107
|
+
def __len__(self) -> int:
|
|
108
|
+
"""Returns the number of registered hook definitions."""
|
|
109
|
+
return len(self._definitions)
|
|
110
|
+
|
|
111
|
+
def __contains__(self, name: str) -> bool:
|
|
112
|
+
"""Checks if a hook definition is in the registry by name."""
|
|
113
|
+
if isinstance(name, str):
|
|
114
|
+
return name in self._definitions
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# Default instance of the registry
|
|
118
|
+
default_phase_hook_registry = PhaseHookRegistry()
|
|
@@ -3,6 +3,8 @@ import logging
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from typing import TYPE_CHECKING, Optional
|
|
5
5
|
|
|
6
|
+
from .processor_meta import AgentUserInputMessageProcessorMeta
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
8
10
|
from autobyteus.agent.context import AgentContext # Composite AgentContext
|
|
@@ -10,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
12
14
|
|
|
13
|
-
class BaseAgentUserInputMessageProcessor(ABC):
|
|
15
|
+
class BaseAgentUserInputMessageProcessor(ABC, metaclass=AgentUserInputMessageProcessorMeta):
|
|
14
16
|
"""
|
|
15
17
|
Abstract base class for agent user input message processors.
|
|
16
18
|
These processors can modify an AgentInputUserMessage, specifically from a user,
|
|
@@ -18,12 +20,13 @@ class BaseAgentUserInputMessageProcessor(ABC):
|
|
|
18
20
|
Subclasses should be instantiated and passed to the AgentSpecification.
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
@classmethod
|
|
24
|
+
def get_name(cls) -> str:
|
|
22
25
|
"""
|
|
23
26
|
Returns the unique registration name for this processor.
|
|
24
27
|
Defaults to the class name. Can be overridden by subclasses.
|
|
25
28
|
"""
|
|
26
|
-
return
|
|
29
|
+
return cls.__name__
|
|
27
30
|
|
|
28
31
|
@abstractmethod
|
|
29
32
|
async def process(self,
|
|
@@ -16,7 +16,8 @@ class PassthroughInputProcessor(BaseAgentUserInputMessageProcessor):
|
|
|
16
16
|
A processor that returns the message unchanged.
|
|
17
17
|
Can be used as a default or for testing.
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
@classmethod
|
|
20
|
+
def get_name(cls) -> str:
|
|
20
21
|
return "PassthroughInputProcessor"
|
|
21
22
|
|
|
22
23
|
async def process(self,
|
|
@@ -37,7 +37,7 @@ class AgentUserInputMessageProcessorMeta(ABCMeta):
|
|
|
37
37
|
# Create definition using name and the class itself
|
|
38
38
|
definition = AgentUserInputMessageProcessorDefinition(name=processor_name, processor_class=cls)
|
|
39
39
|
default_input_processor_registry.register_processor(definition)
|
|
40
|
-
logger.info(f"Auto-registered input processor: '{processor_name}' from class {name}")
|
|
40
|
+
logger.info(f"Auto-registered input processor: '{processor_name}' from class {name} (no schema).")
|
|
41
41
|
|
|
42
42
|
except AttributeError as e:
|
|
43
43
|
# Catch if get_name is missing
|
|
@@ -60,6 +60,25 @@ class AgentUserInputMessageProcessorRegistry(metaclass=SingletonMeta):
|
|
|
60
60
|
logger.debug(f"Input processor definition with name '{name}' not found in registry.")
|
|
61
61
|
return definition
|
|
62
62
|
|
|
63
|
+
def get_processor(self, name: str) -> Optional['BaseAgentUserInputMessageProcessor']:
|
|
64
|
+
"""
|
|
65
|
+
Retrieves an instance of an input processor by its name.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: The name of the input processor to retrieve.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
An instance of the BaseAgentUserInputMessageProcessor if found and instantiable, otherwise None.
|
|
72
|
+
"""
|
|
73
|
+
definition = self.get_processor_definition(name)
|
|
74
|
+
if definition:
|
|
75
|
+
try:
|
|
76
|
+
return definition.processor_class()
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Failed to instantiate input processor '{name}' from class '{definition.processor_class.__name__}': {e}", exc_info=True)
|
|
79
|
+
return None
|
|
80
|
+
return None
|
|
81
|
+
|
|
63
82
|
def list_processor_names(self) -> List[str]:
|
|
64
83
|
"""
|
|
65
84
|
Returns a list of names of all registered input processor definitions.
|
|
@@ -3,6 +3,8 @@ import logging
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from .processor_meta import LLMResponseProcessorMeta
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
from autobyteus.agent.context import AgentContext # MODIFIED IMPORT
|
|
8
10
|
from autobyteus.agent.events import LLMCompleteResponseReceivedEvent
|
|
@@ -10,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
12
14
|
|
|
13
|
-
class BaseLLMResponseProcessor(ABC):
|
|
15
|
+
class BaseLLMResponseProcessor(ABC, metaclass=LLMResponseProcessorMeta):
|
|
14
16
|
"""
|
|
15
17
|
Abstract base class for LLM response processors.
|
|
16
18
|
These processors analyze the LLM's textual response. If they identify a specific
|
|
@@ -19,13 +21,14 @@ class BaseLLMResponseProcessor(ABC):
|
|
|
19
21
|
Subclasses should be instantiated and passed to the AgentSpecification.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_name(cls) -> str:
|
|
23
26
|
"""
|
|
24
27
|
Returns the unique registration name for this processor.
|
|
25
28
|
Defaults to the class name. Should be overridden by subclasses
|
|
26
29
|
to provide a stable, user-friendly name (e.g., "xml_tool_usage").
|
|
27
30
|
"""
|
|
28
|
-
return
|
|
31
|
+
return cls.__name__
|
|
29
32
|
|
|
30
33
|
@abstractmethod
|
|
31
34
|
async def process_response(self, response: 'CompleteResponse', context: 'AgentContext', triggering_event: 'LLMCompleteResponseReceivedEvent') -> bool:
|
|
@@ -29,7 +29,7 @@ class LLMResponseProcessorMeta(ABCMeta):
|
|
|
29
29
|
|
|
30
30
|
definition = LLMResponseProcessorDefinition(name=processor_name, processor_class=cls)
|
|
31
31
|
default_llm_response_processor_registry.register_processor(definition)
|
|
32
|
-
logger.info(f"Auto-registered LLM response processor: '{processor_name}' from class {name}")
|
|
32
|
+
logger.info(f"Auto-registered LLM response processor: '{processor_name}' from class {name} (no schema).")
|
|
33
33
|
|
|
34
34
|
except AttributeError as e:
|
|
35
35
|
logger.error(f"LLM response processor class {name} is missing required static/class method 'get_name' ({e}). Skipping registration.")
|