autobyteus 1.0.0__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.
Files changed (153) hide show
  1. autobyteus/__init__.py +5 -0
  2. autobyteus/agent/__init__.py +0 -0
  3. autobyteus/agent/agent.py +231 -0
  4. autobyteus/agent/async_agent.py +175 -0
  5. autobyteus/agent/async_group_aware_agent.py +136 -0
  6. autobyteus/agent/exceptions.py +6 -0
  7. autobyteus/agent/factory/__init__.py +0 -0
  8. autobyteus/agent/group/__init__.py +0 -0
  9. autobyteus/agent/group/async_group_aware_agent.py +122 -0
  10. autobyteus/agent/group/coordinator_agent.py +36 -0
  11. autobyteus/agent/group/group_aware_agent.py +121 -0
  12. autobyteus/agent/message/__init__.py +0 -0
  13. autobyteus/agent/message/message.py +41 -0
  14. autobyteus/agent/message/message_types.py +29 -0
  15. autobyteus/agent/message/send_message_to.py +47 -0
  16. autobyteus/agent/orchestrator/__init__.py +0 -0
  17. autobyteus/agent/orchestrator/base_agent_orchestrator.py +82 -0
  18. autobyteus/agent/orchestrator/multi_replica_agent_orchestrator.py +72 -0
  19. autobyteus/agent/orchestrator/single_replica_agent_orchestrator.py +43 -0
  20. autobyteus/agent/response_parser/__init__.py +0 -0
  21. autobyteus/agent/response_parser/tool_usage_command_parser.py +100 -0
  22. autobyteus/agent/status.py +12 -0
  23. autobyteus/agent/tool_invocation.py +7 -0
  24. autobyteus/check_requirements.py +33 -0
  25. autobyteus/conversation/__init__.py +0 -0
  26. autobyteus/conversation/conversation.py +54 -0
  27. autobyteus/conversation/user_message.py +59 -0
  28. autobyteus/events/__init__.py +0 -0
  29. autobyteus/events/decorators.py +29 -0
  30. autobyteus/events/event_emitter.py +74 -0
  31. autobyteus/events/event_manager.py +73 -0
  32. autobyteus/events/event_types.py +17 -0
  33. autobyteus/llm/__init__.py +0 -0
  34. autobyteus/llm/api/__init__.py +0 -0
  35. autobyteus/llm/api/autobyteus_llm.py +136 -0
  36. autobyteus/llm/api/bedrock_llm.py +78 -0
  37. autobyteus/llm/api/claude_llm.py +138 -0
  38. autobyteus/llm/api/deepseek_llm.py +226 -0
  39. autobyteus/llm/api/gemini_llm.py +79 -0
  40. autobyteus/llm/api/grok_llm.py +211 -0
  41. autobyteus/llm/api/groq_llm.py +88 -0
  42. autobyteus/llm/api/mistral_llm.py +120 -0
  43. autobyteus/llm/api/nvidia_llm.py +102 -0
  44. autobyteus/llm/api/ollama_llm.py +130 -0
  45. autobyteus/llm/api/openai_llm.py +148 -0
  46. autobyteus/llm/autobyteus_provider.py +172 -0
  47. autobyteus/llm/base_llm.py +222 -0
  48. autobyteus/llm/extensions/__init__.py +0 -0
  49. autobyteus/llm/extensions/base_extension.py +45 -0
  50. autobyteus/llm/extensions/extension_registry.py +37 -0
  51. autobyteus/llm/extensions/token_usage_tracking_extension.py +79 -0
  52. autobyteus/llm/llm_factory.py +292 -0
  53. autobyteus/llm/models.py +73 -0
  54. autobyteus/llm/ollama_provider.py +98 -0
  55. autobyteus/llm/providers.py +14 -0
  56. autobyteus/llm/token_counter/__init__.py +0 -0
  57. autobyteus/llm/token_counter/base_token_counter.py +69 -0
  58. autobyteus/llm/token_counter/claude_token_counter.py +77 -0
  59. autobyteus/llm/token_counter/deepseek_token_counter.py +24 -0
  60. autobyteus/llm/token_counter/mistral_token_counter.py +115 -0
  61. autobyteus/llm/token_counter/openai_token_counter.py +84 -0
  62. autobyteus/llm/token_counter/token_counter_factory.py +40 -0
  63. autobyteus/llm/utils/__init__.py +0 -0
  64. autobyteus/llm/utils/image_payload_formatter.py +89 -0
  65. autobyteus/llm/utils/llm_config.py +90 -0
  66. autobyteus/llm/utils/messages.py +40 -0
  67. autobyteus/llm/utils/rate_limiter.py +41 -0
  68. autobyteus/llm/utils/response_types.py +19 -0
  69. autobyteus/llm/utils/token_pricing_config.py +87 -0
  70. autobyteus/llm/utils/token_usage.py +13 -0
  71. autobyteus/llm/utils/token_usage_tracker.py +98 -0
  72. autobyteus/person/__init__.py +0 -0
  73. autobyteus/person/examples/__init__.py +0 -0
  74. autobyteus/person/examples/sample_persons.py +14 -0
  75. autobyteus/person/examples/sample_roles.py +14 -0
  76. autobyteus/person/person.py +29 -0
  77. autobyteus/person/role.py +14 -0
  78. autobyteus/prompt/__init__.py +0 -0
  79. autobyteus/prompt/prompt_builder.py +64 -0
  80. autobyteus/prompt/prompt_template.py +44 -0
  81. autobyteus/prompt/prompt_version_manager.py +58 -0
  82. autobyteus/prompt/storage/__init__.py +0 -0
  83. autobyteus/prompt/storage/prompt_version_model.py +29 -0
  84. autobyteus/prompt/storage/prompt_version_repository.py +83 -0
  85. autobyteus/tools/__init__.py +0 -0
  86. autobyteus/tools/ask_user_input.py +93 -0
  87. autobyteus/tools/base_tool.py +49 -0
  88. autobyteus/tools/bash/__init__.py +0 -0
  89. autobyteus/tools/bash/bash_executor.py +103 -0
  90. autobyteus/tools/bash/factory/__init__.py +0 -0
  91. autobyteus/tools/bash/factory/bash_executor_factory.py +6 -0
  92. autobyteus/tools/browser/__init__.py +0 -0
  93. autobyteus/tools/browser/session_aware/__init__.py +0 -0
  94. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +61 -0
  95. autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +27 -0
  96. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +202 -0
  97. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +29 -0
  98. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +34 -0
  99. autobyteus/tools/browser/session_aware/factory/__init__.py +0 -0
  100. autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +6 -0
  101. autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +10 -0
  102. autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +6 -0
  103. autobyteus/tools/browser/session_aware/shared_browser_session.py +11 -0
  104. autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +24 -0
  105. autobyteus/tools/browser/session_aware/web_element_action.py +20 -0
  106. autobyteus/tools/browser/standalone/__init__.py +0 -0
  107. autobyteus/tools/browser/standalone/factory/__init__.py +0 -0
  108. autobyteus/tools/browser/standalone/factory/google_search_factory.py +10 -0
  109. autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +10 -0
  110. autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +6 -0
  111. autobyteus/tools/browser/standalone/google_search_ui.py +113 -0
  112. autobyteus/tools/browser/standalone/navigate_to.py +62 -0
  113. autobyteus/tools/browser/standalone/webpage_image_downloader.py +128 -0
  114. autobyteus/tools/browser/standalone/webpage_reader.py +78 -0
  115. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +49 -0
  116. autobyteus/tools/factory/__init__.py +0 -0
  117. autobyteus/tools/factory/ask_user_input_factory.py +6 -0
  118. autobyteus/tools/factory/image_downloader_factory.py +9 -0
  119. autobyteus/tools/factory/pdf_downloader_factory.py +9 -0
  120. autobyteus/tools/factory/tool_factory.py +8 -0
  121. autobyteus/tools/factory/webpage_image_downloader_factory.py +6 -0
  122. autobyteus/tools/file/__init__.py +0 -0
  123. autobyteus/tools/file/factory/__init__.py +0 -0
  124. autobyteus/tools/file/factory/file_reader_factory.py +6 -0
  125. autobyteus/tools/file/factory/file_writer_factory.py +6 -0
  126. autobyteus/tools/file/file_reader.py +60 -0
  127. autobyteus/tools/file/file_writer.py +65 -0
  128. autobyteus/tools/handlers/__init__.py +0 -0
  129. autobyteus/tools/handlers/shell_handler.py +32 -0
  130. autobyteus/tools/image_downloader.py +142 -0
  131. autobyteus/tools/operation/__init__.py +0 -0
  132. autobyteus/tools/operation/file_operation.py +73 -0
  133. autobyteus/tools/operation/file_rename_operation.py +52 -0
  134. autobyteus/tools/operation/operation.py +64 -0
  135. autobyteus/tools/operation/shell_operation.py +65 -0
  136. autobyteus/tools/pdf_downloader.py +94 -0
  137. autobyteus/tools/timer.py +123 -0
  138. autobyteus/tools/utils.py +17 -0
  139. autobyteus/tools/web_page_pdf_generator.py +86 -0
  140. autobyteus/utils/__init__.py +0 -0
  141. autobyteus/utils/dynamic_enum.py +35 -0
  142. autobyteus/utils/file_utils.py +15 -0
  143. autobyteus/utils/html_cleaner.py +240 -0
  144. autobyteus/utils/singleton.py +26 -0
  145. autobyteus/workflow/__init__.py +0 -0
  146. autobyteus/workflow/simple_task.py +98 -0
  147. autobyteus/workflow/task.py +147 -0
  148. autobyteus/workflow/workflow.py +49 -0
  149. autobyteus-1.0.0.dist-info/LICENSE +14 -0
  150. autobyteus-1.0.0.dist-info/METADATA +138 -0
  151. autobyteus-1.0.0.dist-info/RECORD +153 -0
  152. autobyteus-1.0.0.dist-info/WHEEL +5 -0
  153. autobyteus-1.0.0.dist-info/top_level.txt +1 -0
autobyteus/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from pathlib import Path
2
+ import sys
3
+
4
+
5
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
File without changes
@@ -0,0 +1,231 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import List, Optional
4
+ from autobyteus.agent.response_parser.tool_usage_command_parser import ToolUsageCommandParser
5
+ from autobyteus.events.event_emitter import EventEmitter
6
+ from autobyteus.llm.base_llm import BaseLLM
7
+ from autobyteus.tools.base_tool import BaseTool
8
+ from autobyteus.prompt.prompt_builder import PromptBuilder
9
+ from autobyteus.events.event_types import EventType
10
+ from autobyteus.agent.status import AgentStatus
11
+ from autobyteus.conversation.user_message import UserMessage
12
+ from autobyteus.conversation.conversation import Conversation
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class Agent(EventEmitter):
17
+ def __init__(self, role: str, llm: BaseLLM, tools: Optional[List[BaseTool]] = None,
18
+ agent_id=None,
19
+ prompt_builder: Optional[PromptBuilder] = None,
20
+ initial_user_message: Optional[UserMessage] = None):
21
+ super().__init__()
22
+ self.role = role
23
+ self.llm = llm
24
+ self.tools = tools or []
25
+ self.tool_usage_response_parser = ToolUsageCommandParser() if self.tools else None
26
+ self.conversation = None
27
+ self.agent_id = agent_id or f"{self.role}-001"
28
+ self.status = AgentStatus.NOT_STARTED
29
+ self._run_task = None
30
+ self._queues_initialized = False
31
+ self.task_completed = None
32
+ self.prompt_builder = prompt_builder
33
+ self.initial_user_message = initial_user_message
34
+
35
+ if not self.prompt_builder and not self.initial_user_message:
36
+ raise ValueError("Either prompt_builder or initial_user_message must be provided")
37
+
38
+ if self.tools:
39
+ self.set_agent_id_on_tools()
40
+
41
+ logger.info(f"StandaloneAgent initialized with role: {self.role}, agent_id: {self.agent_id}")
42
+
43
+ def _initialize_queues(self):
44
+ if not self._queues_initialized:
45
+ self.tool_result_messages = asyncio.Queue()
46
+ self.user_messages = asyncio.Queue()
47
+ self._queues_initialized = True
48
+ logger.info(f"Queues initialized for agent {self.role}")
49
+
50
+ def _initialize_task_completed(self):
51
+ if self.task_completed is None:
52
+ self.task_completed = asyncio.Event()
53
+ logger.info(f"task_completed Event initialized for agent {self.role}")
54
+
55
+ def get_task_completed(self):
56
+ if self.task_completed is None:
57
+ raise RuntimeError("task_completed Event accessed before initialization")
58
+ return self.task_completed
59
+
60
+ async def run(self):
61
+ try:
62
+ logger.info(f"Starting execution for agent: {self.role}")
63
+ self._initialize_queues()
64
+ self._initialize_task_completed()
65
+ await self.initialize_conversation()
66
+
67
+ user_message_handler = asyncio.create_task(self.handle_user_messages())
68
+ tool_result_handler = asyncio.create_task(self.handle_tool_result_messages())
69
+
70
+ # Once everything is ready, set the status to RUNNING
71
+ self.status = AgentStatus.RUNNING
72
+
73
+ await asyncio.gather(user_message_handler, tool_result_handler)
74
+
75
+ except Exception as e:
76
+ logger.error(f"Error in agent {self.role} execution: {str(e)}")
77
+ self.status = AgentStatus.ERROR
78
+ finally:
79
+ self.status = AgentStatus.ENDED
80
+ await self.cleanup()
81
+
82
+ async def handle_user_messages(self):
83
+ logger.info(f"Agent {self.role} started handling user messages")
84
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
85
+ try:
86
+ user_message: UserMessage = await asyncio.wait_for(self.user_messages.get(), timeout=1.0)
87
+ logger.info(f"Agent {self.role} handling user message")
88
+ response = await self.conversation.send_user_message(user_message.content, user_message.file_paths)
89
+ await self.process_llm_response(response)
90
+ except asyncio.TimeoutError:
91
+ continue
92
+ except asyncio.CancelledError:
93
+ logger.info(f"User message handler for agent {self.role} cancelled")
94
+ break
95
+ except Exception as e:
96
+ logger.error(f"Error handling user message for agent {self.role}: {str(e)}")
97
+
98
+ async def receive_user_message(self, message: UserMessage):
99
+ """
100
+ This method gracefully waits for the agent to become RUNNING
101
+ if it's in the process of starting up, ensuring the queues are
102
+ initialized before we put a message into them.
103
+ """
104
+ logger.info(f"Agent {self.agent_id} received user message")
105
+
106
+ # If the agent is not started (or ended), begin the start process
107
+ if self.status in [AgentStatus.NOT_STARTED, AgentStatus.ENDED]:
108
+ self.start()
109
+
110
+ # If the agent is still starting, wait until it transitions to RUNNING
111
+ while self.status == AgentStatus.STARTING:
112
+ await asyncio.sleep(0.1)
113
+
114
+ if self.status != AgentStatus.RUNNING:
115
+ logger.error(f"Agent is not in a running state: {self.status}")
116
+ return
117
+
118
+ # Now that we are running, safely place the message in the queue
119
+ await self.user_messages.put(message)
120
+
121
+ async def handle_tool_result_messages(self):
122
+ logger.info(f"Agent {self.role} started handling tool result messages")
123
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
124
+ try:
125
+ message = await asyncio.wait_for(self.tool_result_messages.get(), timeout=1.0)
126
+ logger.info(f"Agent {self.role} handling tool result message: {message}")
127
+ response = await self.conversation.send_user_message(f"Tool execution result: {message}")
128
+ await self.process_llm_response(response)
129
+ except asyncio.TimeoutError:
130
+ continue
131
+ except asyncio.CancelledError:
132
+ logger.info(f"Tool result handler for agent {self.role} cancelled")
133
+ break
134
+ except Exception as e:
135
+ logger.error(f"Error handling tool result for agent {self.role}: {str(e)}")
136
+
137
+ async def initialize_conversation(self):
138
+ logger.info(f"Initializing conversation for agent: {self.role}")
139
+ self.conversation = Conversation(self.llm)
140
+
141
+ if self.initial_user_message:
142
+ initial_message = self.initial_user_message
143
+ else:
144
+ prompt_content = self.prompt_builder.set_variable_value(
145
+ "external_tools",
146
+ self._get_external_tools_section()
147
+ ).build()
148
+ initial_message = UserMessage(content=prompt_content)
149
+
150
+ logger.debug(f"Initial user message for agent {self.role}: {initial_message}")
151
+ initial_llm_response = await self.conversation.send_user_message(
152
+ initial_message.content,
153
+ initial_message.file_paths
154
+ )
155
+ await self.process_llm_response(initial_llm_response)
156
+
157
+ async def process_llm_response(self, response: str) -> None:
158
+ self.emit(EventType.ASSISTANT_RESPONSE, response=response)
159
+
160
+ if self.tools and self.tool_usage_response_parser:
161
+ tool_invocation = self.tool_usage_response_parser.parse_response(response)
162
+ if tool_invocation.is_valid():
163
+ await self.execute_tool(tool_invocation)
164
+ return
165
+
166
+ logger.info(f"Assistant response for agent {self.role}: {response}")
167
+
168
+ async def execute_tool(self, tool_invocation):
169
+ name = tool_invocation.name
170
+ arguments = tool_invocation.arguments
171
+ logger.info(f"Agent {self.role} attempting to execute tool: {name}")
172
+
173
+ tool = next((t for t in self.tools if t.get_name() == name), None)
174
+ if tool:
175
+ try:
176
+ result = await tool.execute(**arguments)
177
+ logger.info(f"Tool '{name}' executed successfully by agent {self.role}. Result: {result}")
178
+ await self.tool_result_messages.put(result)
179
+ except Exception as e:
180
+ error_message = str(e)
181
+ logger.error(f"Error executing tool '{name}' by agent {self.role}: {error_message}")
182
+ await self.tool_result_messages.put(f"Error: {error_message}")
183
+ else:
184
+ logger.warning(f"Tool '{name}' not found for agent {self.role}.")
185
+
186
+ def start(self):
187
+ """
188
+ Starts the agent by creating a task that runs the main loop (run).
189
+ Sets the AgentStatus to STARTING to prevent message enqueuing before
190
+ the system is fully initialized.
191
+ """
192
+ if self.status in [AgentStatus.NOT_STARTED, AgentStatus.ENDED]:
193
+ logger.info(f"Starting agent {self.role}")
194
+ self.status = AgentStatus.STARTING
195
+ self._run_task = asyncio.create_task(self.run())
196
+ elif self.status == AgentStatus.STARTING:
197
+ logger.info(f"Agent {self.role} is already in STARTING state.")
198
+ elif self.status == AgentStatus.RUNNING:
199
+ logger.info(f"Agent {self.role} is already running.")
200
+ else:
201
+ logger.warning(f"Agent {self.role} is in an unexpected state: {self.status}")
202
+
203
+ def stop(self):
204
+ if self._run_task and not self._run_task.done():
205
+ self._run_task.cancel()
206
+
207
+ async def cleanup(self):
208
+ while not self.tool_result_messages.empty():
209
+ self.tool_result_messages.get_nowait()
210
+ while not self.user_messages.empty():
211
+ self.user_messages.get_nowait()
212
+ await self.llm.cleanup()
213
+ logger.info(f"Cleanup completed for agent: {self.role}")
214
+
215
+ def set_agent_id_on_tools(self):
216
+ if self.tools:
217
+ for tool in self.tools:
218
+ tool.set_agent_id(self.agent_id)
219
+
220
+ def _get_external_tools_section(self) -> str:
221
+ if not self.tools:
222
+ return ""
223
+
224
+ external_tools_section = ""
225
+ for i, tool in enumerate(self.tools):
226
+ external_tools_section += f" {i + 1} {tool.tool_usage_xml()}\n\n"
227
+ return external_tools_section.strip()
228
+
229
+ def on_task_completed(self, *args, **kwargs):
230
+ logger.info(f"Task completed event received for agent: {self.role}")
231
+ self.task_completed.set()
@@ -0,0 +1,175 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import (
4
+ List,
5
+ Optional,
6
+ AsyncGenerator,
7
+ Any,
8
+ NoReturn,
9
+ Union,
10
+ AsyncIterator
11
+ )
12
+ from autobyteus.agent.agent import Agent
13
+ from autobyteus.llm.base_llm import BaseLLM
14
+ from autobyteus.tools.base_tool import BaseTool
15
+ from autobyteus.prompt.prompt_builder import PromptBuilder
16
+ from autobyteus.events.event_types import EventType
17
+ from autobyteus.agent.status import AgentStatus
18
+ from autobyteus.conversation.user_message import UserMessage
19
+ from autobyteus.conversation.conversation import Conversation
20
+ from autobyteus.agent.tool_invocation import ToolInvocation
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class AsyncAgent(Agent):
25
+ """
26
+ An asynchronous agent that supports streaming LLM responses while maintaining
27
+ compatibility with the base agent functionality.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ role: str,
33
+ llm: BaseLLM,
34
+ tools: Optional[List[BaseTool]] = None,
35
+ agent_id: Optional[str] = None,
36
+ prompt_builder: Optional[PromptBuilder] = None,
37
+ initial_user_message: Optional[UserMessage] = None
38
+ ) -> None:
39
+ """
40
+ Initialize the AsyncAgent with the given parameters.
41
+
42
+ Args:
43
+ role: The role of the agent
44
+ llm: The language model instance
45
+ tools: List of available tools
46
+ use_xml_parser: Whether to use XML parser for responses
47
+ agent_id: Optional unique identifier for the agent
48
+ prompt_builder: Optional prompt builder instance
49
+ initial_user_message: Optional initial message to start the conversation
50
+ """
51
+ super().__init__(
52
+ role,
53
+ llm,
54
+ tools,
55
+ agent_id,
56
+ prompt_builder,
57
+ initial_user_message
58
+ )
59
+
60
+ async def initialize_conversation(self) -> None:
61
+ """Initialize the conversation with initial message or prompt."""
62
+ logger.info(f"Initializing conversation for agent: {self.role}")
63
+ self.conversation = Conversation(self.llm)
64
+
65
+ if self.initial_user_message:
66
+ initial_message = self.initial_user_message
67
+ else:
68
+ prompt_content = self.prompt_builder.set_variable_value(
69
+ "external_tools",
70
+ self._get_external_tools_section()
71
+ ).build()
72
+ initial_message = UserMessage(content=prompt_content)
73
+
74
+ logger.debug(f"Initial user message for agent {self.role}: {initial_message}")
75
+ await self.process_streaming_response(
76
+ self.conversation.stream_user_message(
77
+ initial_message.content,
78
+ initial_message.file_paths
79
+ )
80
+ )
81
+
82
+ async def handle_user_messages(self) -> NoReturn:
83
+ """
84
+ Handle incoming user messages continuously.
85
+ Processes messages using streaming responses.
86
+ """
87
+ logger.info(f"Agent {self.role} started handling user messages")
88
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
89
+ try:
90
+ user_message: UserMessage = await asyncio.wait_for(
91
+ self.user_messages.get(),
92
+ timeout=1.0
93
+ )
94
+ logger.info(f"Agent {self.role} handling user message")
95
+ await self.process_streaming_response(
96
+ self.conversation.stream_user_message(
97
+ user_message.content,
98
+ user_message.file_paths
99
+ )
100
+ )
101
+ except asyncio.TimeoutError:
102
+ continue
103
+ except asyncio.CancelledError:
104
+ logger.info(f"User message handler for agent {self.role} cancelled")
105
+ break
106
+ except Exception as e:
107
+ logger.error(f"Error handling user message for agent {self.role}: {str(e)}")
108
+
109
+ async def handle_tool_result_messages(self) -> NoReturn:
110
+ """
111
+ Handle tool execution result messages continuously.
112
+ Processes messages using streaming responses.
113
+ """
114
+ logger.info(f"Agent {self.role} started handling tool result messages")
115
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
116
+ try:
117
+ message: str = await asyncio.wait_for(
118
+ self.tool_result_messages.get(),
119
+ timeout=1.0
120
+ )
121
+ logger.info(f"Agent {self.role} handling tool result message: {message}")
122
+ await self.process_streaming_response(
123
+ self.conversation.stream_user_message(
124
+ f"Tool execution result: {message}"
125
+ )
126
+ )
127
+ except asyncio.TimeoutError:
128
+ continue
129
+ except asyncio.CancelledError:
130
+ logger.info(f"Tool result handler for agent {self.role} cancelled")
131
+ break
132
+ except Exception as e:
133
+ logger.error(f"Error handling tool result for agent {self.role}: {str(e)}")
134
+
135
+ async def process_streaming_response(
136
+ self,
137
+ response_stream: AsyncIterator[str]
138
+ ) -> None:
139
+ """
140
+ Process streaming responses from the LLM, emitting each chunk and handling
141
+ tool invocations after receiving the complete response.
142
+
143
+ Args:
144
+ response_stream: AsyncIterator yielding response tokens
145
+ """
146
+ complete_response: str = ""
147
+ try:
148
+ async for chunk in response_stream:
149
+ # Emit each chunk as it arrives
150
+ self.emit(
151
+ EventType.ASSISTANT_RESPONSE,
152
+ response=chunk,
153
+ is_complete=False
154
+ )
155
+ complete_response += chunk
156
+ # Emit the complete response
157
+ self.emit(
158
+ EventType.ASSISTANT_RESPONSE,
159
+ response=complete_response,
160
+ is_complete=True
161
+ )
162
+
163
+ if self.tools and self.tool_usage_response_parser:
164
+ tool_invocation: ToolInvocation = self.tool_usage_response_parser.parse_response(complete_response)
165
+ if tool_invocation.is_valid():
166
+ await self.execute_tool(tool_invocation)
167
+ return
168
+
169
+ logger.info(f"Assistant response for agent {self.role}: {complete_response}")
170
+
171
+ except Exception as e:
172
+ logger.error(f"Error processing streaming response for agent {self.role}: {str(e)}")
173
+ self.emit(
174
+ EventType.ERROR,
175
+ error=str(e))
@@ -0,0 +1,136 @@
1
+
2
+ import asyncio
3
+ import logging
4
+ from typing import TYPE_CHECKING, Optional, AsyncIterator
5
+ from autobyteus.agent.async_agent import AsyncAgent
6
+ from autobyteus.agent.message.message import Message
7
+ from autobyteus.agent.message.message_types import MessageType
8
+ from autobyteus.agent.message.send_message_to import SendMessageTo
9
+ from autobyteus.agent.status import AgentStatus
10
+ from autobyteus.events.event_types import EventType
11
+
12
+ if TYPE_CHECKING:
13
+ from autobyteus.agent.orchestrator.base_agent_orchestrator import BaseAgentOrchestrator
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class AsyncGroupAwareAgent(AsyncAgent):
18
+ def __init__(self, *args, **kwargs):
19
+ super().__init__(*args, **kwargs)
20
+ self.agent_orchestrator: Optional['BaseAgentOrchestrator'] = None
21
+ self.incoming_agent_messages: Optional[asyncio.Queue] = None
22
+ logger.info(f"AsyncGroupAwareAgent initialized with role: {self.role}")
23
+
24
+ def _initialize_queues(self):
25
+ if not self._queues_initialized:
26
+ super()._initialize_queues()
27
+ self.incoming_agent_messages = asyncio.Queue()
28
+ logger.info(f"Queues initialized for agent {self.role}")
29
+
30
+ def set_agent_orchestrator(self, agent_orchestrator: 'BaseAgentOrchestrator'):
31
+ self.agent_orchestrator = agent_orchestrator
32
+ if not any(isinstance(tool, SendMessageTo) for tool in self.tools):
33
+ self.tools.append(SendMessageTo(agent_orchestrator))
34
+ self.register_task_completion_listener()
35
+ logger.info(f"Agent orchestrator set for agent {self.role}")
36
+
37
+ def register_task_completion_listener(self):
38
+ """Register to listen for task completion events from the orchestrator specifically"""
39
+ logger.info(f"Registering task completion listener for agent: {self.role}")
40
+ if self.agent_orchestrator:
41
+ self.subscribe_from(
42
+ self.agent_orchestrator,
43
+ EventType.TASK_COMPLETED,
44
+ self.on_task_completed
45
+ )
46
+ else:
47
+ logger.warning(f"Cannot register task completion listener for agent {self.role}: orchestrator not set")
48
+
49
+ async def receive_agent_message(self, message: Message):
50
+ logger.info(f"Agent {self.agent_id} received message from {message.sender_agent_id}")
51
+ if not self._queues_initialized:
52
+ self._initialize_queues()
53
+ await self.incoming_agent_messages.put(message)
54
+ if self.status != AgentStatus.RUNNING:
55
+ self.start()
56
+
57
+ async def run(self):
58
+ try:
59
+ logger.info(f"Agent {self.role} entering running state")
60
+ self._initialize_queues()
61
+ self._initialize_task_completed()
62
+ await self.initialize_conversation()
63
+
64
+ self.status = AgentStatus.RUNNING
65
+
66
+ user_message_handler = asyncio.create_task(self.handle_user_messages())
67
+ tool_result_handler = asyncio.create_task(self.handle_tool_result_messages())
68
+ agent_message_handler = asyncio.create_task(self.handle_agent_messages())
69
+
70
+ done, pending = await asyncio.wait(
71
+ [agent_message_handler, tool_result_handler, user_message_handler, self.task_completed.wait()],
72
+ return_when=asyncio.FIRST_COMPLETED
73
+ )
74
+
75
+ for task in pending:
76
+ task.cancel()
77
+
78
+ await asyncio.gather(*pending, return_exceptions=True)
79
+
80
+ except Exception as e:
81
+ logger.error(f"Error in agent {self.role} execution: {str(e)}")
82
+ self.status = AgentStatus.ERROR
83
+ finally:
84
+ self.status = AgentStatus.ENDED
85
+ await self.cleanup()
86
+
87
+ async def handle_agent_messages(self):
88
+ logger.info(f"Agent {self.role} started handling agent messages")
89
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
90
+ try:
91
+ message = await asyncio.wait_for(self.incoming_agent_messages.get(), timeout=1.0)
92
+ logger.info(f"{self.role} processing message from {message.sender_agent_id}")
93
+
94
+ if message.message_type == MessageType.TASK_RESULT:
95
+ self.agent_orchestrator.handle_task_completed(message.sender_agent_id)
96
+
97
+ await self.process_streaming_response(
98
+ self.conversation.stream_user_message(
99
+ f"Message from sender_agent_id {message.sender_agent_id}, content {message.content}"
100
+ )
101
+ )
102
+ except asyncio.TimeoutError:
103
+ continue
104
+ except asyncio.CancelledError:
105
+ logger.info(f"Agent message handler for agent {self.role} cancelled")
106
+ break
107
+ except Exception as e:
108
+ logger.error(f"Error handling agent message for agent {self.role}: {str(e)}")
109
+
110
+ async def execute_tool(self, tool_invocation):
111
+ name = tool_invocation.name
112
+ arguments = tool_invocation.arguments
113
+ logger.info(f"Agent {self.role} attempting to execute tool: {name}")
114
+
115
+ tool = next((t for t in self.tools if t.get_name() == name), None)
116
+ if tool:
117
+ try:
118
+ result = await tool.execute(**arguments)
119
+ logger.info(f"Tool '{name}' executed successfully by agent {self.role}. Result: {result}")
120
+ if not isinstance(tool, SendMessageTo):
121
+ await self.tool_result_messages.put(result)
122
+ else:
123
+ logger.info(f"SendMessageTo tool executed by agent {self.role}: {result}")
124
+ except Exception as e:
125
+ error_message = str(e)
126
+ logger.error(f"Error executing tool '{name}' by agent {self.role}: {error_message}")
127
+ if not isinstance(tool, SendMessageTo):
128
+ await self.tool_result_messages.put(f"Error: {error_message}")
129
+ else:
130
+ logger.warning(f"Tool '{name}' not found for agent {self.role}.")
131
+
132
+ async def cleanup(self):
133
+ await super().cleanup()
134
+ while not self.incoming_agent_messages.empty():
135
+ self.incoming_agent_messages.get_nowait()
136
+ logger.info(f"Cleanup completed for async group-aware agent: {self.role}")
@@ -0,0 +1,6 @@
1
+ # File: autobyteus/exceptions.py
2
+
3
+ class AgentNotFoundException(Exception):
4
+ def __init__(self, agent_id: str):
5
+ super().__init__(f"Agent with id {agent_id} not found. This is an invalid state.")
6
+ self.agent_id = agent_id
File without changes
File without changes
@@ -0,0 +1,122 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import TYPE_CHECKING, Optional, AsyncIterator
4
+ from autobyteus.agent.async_agent import AsyncAgent
5
+ from autobyteus.agent.message.message import Message
6
+ from autobyteus.agent.message.message_types import MessageType
7
+ from autobyteus.agent.message.send_message_to import SendMessageTo
8
+ from autobyteus.agent.status import AgentStatus
9
+ from autobyteus.events.event_types import EventType
10
+
11
+ if TYPE_CHECKING:
12
+ from autobyteus.agent.orchestrator.base_agent_orchestrator import BaseAgentOrchestrator
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class AsyncGroupAwareAgent(AsyncAgent):
17
+ def __init__(self, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+ self.agent_orchestrator: Optional['BaseAgentOrchestrator'] = None
20
+ self.incoming_agent_messages: Optional[asyncio.Queue] = None
21
+ logger.info(f"AsyncGroupAwareAgent initialized with role: {self.role}")
22
+
23
+ def _initialize_queues(self):
24
+ if not self._queues_initialized:
25
+ super()._initialize_queues()
26
+ self.incoming_agent_messages = asyncio.Queue()
27
+ logger.info(f"Queues initialized for agent {self.role}")
28
+
29
+ def set_agent_orchestrator(self, agent_orchestrator: 'BaseAgentOrchestrator'):
30
+ self.agent_orchestrator = agent_orchestrator
31
+ if not any(isinstance(tool, SendMessageTo) for tool in self.tools):
32
+ self.tools.append(SendMessageTo(agent_orchestrator))
33
+ logger.info(f"Agent orchestrator set for agent {self.role}")
34
+
35
+ async def receive_agent_message(self, message: Message):
36
+ logger.info(f"Agent {self.agent_id} received message from {message.sender_agent_id}")
37
+ if not self._queues_initialized:
38
+ self._initialize_queues()
39
+ await self.incoming_agent_messages.put(message)
40
+ if self.status != AgentStatus.RUNNING:
41
+ self.start()
42
+
43
+ async def run(self):
44
+ try:
45
+ logger.info(f"Agent {self.role} entering running state")
46
+ self._initialize_queues()
47
+ self._initialize_task_completed()
48
+ await self.initialize_conversation()
49
+
50
+ self.status = AgentStatus.RUNNING
51
+
52
+ user_message_handler = asyncio.create_task(self.handle_user_messages())
53
+ tool_result_handler = asyncio.create_task(self.handle_tool_result_messages())
54
+ agent_message_handler = asyncio.create_task(self.handle_agent_messages())
55
+
56
+ done, pending = await asyncio.wait(
57
+ [agent_message_handler, tool_result_handler, user_message_handler, self.task_completed.wait()],
58
+ return_when=asyncio.FIRST_COMPLETED
59
+ )
60
+
61
+ for task in pending:
62
+ task.cancel()
63
+
64
+ await asyncio.gather(*pending, return_exceptions=True)
65
+
66
+ except Exception as e:
67
+ logger.error(f"Error in agent {self.role} execution: {str(e)}")
68
+ self.status = AgentStatus.ERROR
69
+ finally:
70
+ self.status = AgentStatus.ENDED
71
+ await self.cleanup()
72
+
73
+ async def handle_agent_messages(self):
74
+ logger.info(f"Agent {self.role} started handling agent messages")
75
+ while not self.task_completed.is_set() and self.status == AgentStatus.RUNNING:
76
+ try:
77
+ message = await asyncio.wait_for(self.incoming_agent_messages.get(), timeout=1.0)
78
+ logger.info(f"{self.role} processing message from {message.sender_agent_id}")
79
+
80
+ if message.message_type == MessageType.TASK_RESULT:
81
+ self.agent_orchestrator.handle_task_completed(message.sender_agent_id)
82
+
83
+ await self.process_streaming_response(
84
+ self.conversation.stream_user_message(
85
+ f"Message from sender_agent_id {message.sender_agent_id}, content {message.content}"
86
+ )
87
+ )
88
+ except asyncio.TimeoutError:
89
+ continue
90
+ except asyncio.CancelledError:
91
+ logger.info(f"Agent message handler for agent {self.role} cancelled")
92
+ break
93
+ except Exception as e:
94
+ logger.error(f"Error handling agent message for agent {self.role}: {str(e)}")
95
+
96
+ async def execute_tool(self, tool_invocation):
97
+ name = tool_invocation.name
98
+ arguments = tool_invocation.arguments
99
+ logger.info(f"Agent {self.role} attempting to execute tool: {name}")
100
+
101
+ tool = next((t for t in self.tools if t.get_name() == name), None)
102
+ if tool:
103
+ try:
104
+ result = await tool.execute(**arguments)
105
+ logger.info(f"Tool '{name}' executed successfully by agent {self.role}. Result: {result}")
106
+ if not isinstance(tool, SendMessageTo):
107
+ await self.tool_result_messages.put(result)
108
+ else:
109
+ logger.info(f"SendMessageTo tool executed by agent {self.role}: {result}")
110
+ except Exception as e:
111
+ error_message = str(e)
112
+ logger.error(f"Error executing tool '{name}' by agent {self.role}: {error_message}")
113
+ if not isinstance(tool, SendMessageTo):
114
+ await self.tool_result_messages.put(f"Error: {error_message}")
115
+ else:
116
+ logger.warning(f"Tool '{name}' not found for agent {self.role}.")
117
+
118
+ async def cleanup(self):
119
+ await super().cleanup()
120
+ while not self.incoming_agent_messages.empty():
121
+ self.incoming_agent_messages.get_nowait()
122
+ logger.info(f"Cleanup completed for async group-aware agent: {self.role}")