autobyteus 1.2.0__py3-none-any.whl → 1.2.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/context/agent_runtime_state.py +4 -0
- autobyteus/agent/events/notifiers.py +5 -1
- autobyteus/agent/message/send_message_to.py +5 -4
- autobyteus/agent/streaming/agent_event_stream.py +5 -0
- autobyteus/agent/streaming/stream_event_payloads.py +25 -0
- autobyteus/agent/streaming/stream_events.py +13 -1
- autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +4 -4
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +12 -12
- autobyteus/agent_team/context/agent_team_runtime_state.py +2 -2
- autobyteus/agent_team/streaming/agent_team_event_notifier.py +4 -4
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +3 -3
- autobyteus/agent_team/streaming/agent_team_stream_events.py +8 -8
- autobyteus/agent_team/task_notification/activation_policy.py +1 -1
- autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +22 -22
- autobyteus/agent_team/task_notification/task_notification_mode.py +1 -1
- autobyteus/cli/agent_team_tui/app.py +4 -4
- autobyteus/cli/agent_team_tui/state.py +8 -8
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +3 -3
- autobyteus/cli/agent_team_tui/widgets/shared.py +1 -1
- autobyteus/cli/agent_team_tui/widgets/{task_board_panel.py → task_plan_panel.py} +5 -5
- autobyteus/events/event_types.py +4 -3
- autobyteus/multimedia/audio/api/__init__.py +3 -2
- autobyteus/multimedia/audio/api/openai_audio_client.py +112 -0
- autobyteus/multimedia/audio/audio_client_factory.py +37 -0
- autobyteus/multimedia/image/image_client_factory.py +1 -1
- autobyteus/task_management/__init__.py +43 -20
- autobyteus/task_management/{base_task_board.py → base_task_plan.py} +16 -13
- autobyteus/task_management/converters/__init__.py +2 -2
- autobyteus/task_management/converters/{task_board_converter.py → task_plan_converter.py} +13 -13
- autobyteus/task_management/events.py +7 -7
- autobyteus/task_management/{in_memory_task_board.py → in_memory_task_plan.py} +34 -22
- autobyteus/task_management/schemas/__init__.py +3 -0
- autobyteus/task_management/schemas/task_status_report.py +2 -2
- autobyteus/task_management/schemas/todo_definition.py +15 -0
- autobyteus/task_management/todo.py +29 -0
- autobyteus/task_management/todo_list.py +75 -0
- autobyteus/task_management/tools/__init__.py +24 -8
- autobyteus/task_management/tools/task_tools/__init__.py +19 -0
- autobyteus/task_management/tools/{assign_task_to.py → task_tools/assign_task_to.py} +18 -18
- autobyteus/task_management/tools/{publish_task.py → task_tools/create_task.py} +16 -18
- autobyteus/task_management/tools/{publish_tasks.py → task_tools/create_tasks.py} +19 -19
- autobyteus/task_management/tools/{get_my_tasks.py → task_tools/get_my_tasks.py} +15 -15
- autobyteus/task_management/tools/{get_task_board_status.py → task_tools/get_task_plan_status.py} +16 -16
- autobyteus/task_management/tools/{update_task_status.py → task_tools/update_task_status.py} +16 -16
- autobyteus/task_management/tools/todo_tools/__init__.py +18 -0
- autobyteus/task_management/tools/todo_tools/add_todo.py +78 -0
- autobyteus/task_management/tools/todo_tools/create_todo_list.py +79 -0
- autobyteus/task_management/tools/todo_tools/get_todo_list.py +55 -0
- autobyteus/task_management/tools/todo_tools/update_todo_status.py +85 -0
- autobyteus/tools/__init__.py +15 -11
- autobyteus/tools/bash/bash_executor.py +3 -3
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +5 -5
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +4 -4
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -3
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -3
- autobyteus/tools/browser/standalone/navigate_to.py +13 -9
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +9 -5
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +10 -6
- autobyteus/tools/browser/standalone/webpage_reader.py +13 -9
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +9 -5
- autobyteus/tools/file/__init__.py +13 -0
- autobyteus/tools/file/{file_editor.py → edit_file.py} +11 -11
- autobyteus/tools/file/list_directory.py +168 -0
- autobyteus/tools/file/{file_reader.py → read_file.py} +3 -3
- autobyteus/tools/file/search_files.py +188 -0
- autobyteus/tools/file/{file_writer.py → write_file.py} +3 -3
- autobyteus/tools/functional_tool.py +10 -8
- autobyteus/tools/mcp/tool.py +3 -3
- autobyteus/tools/mcp/tool_registrar.py +5 -2
- autobyteus/tools/multimedia/__init__.py +2 -1
- autobyteus/tools/multimedia/audio_tools.py +2 -2
- autobyteus/tools/{download_media_tool.py → multimedia/download_media_tool.py} +3 -3
- autobyteus/tools/multimedia/image_tools.py +4 -4
- autobyteus/tools/multimedia/media_reader_tool.py +1 -1
- autobyteus/tools/registry/tool_definition.py +66 -13
- autobyteus/tools/registry/tool_registry.py +29 -0
- autobyteus/tools/search/__init__.py +17 -0
- autobyteus/tools/search/base_strategy.py +35 -0
- autobyteus/tools/search/client.py +24 -0
- autobyteus/tools/search/factory.py +81 -0
- autobyteus/tools/search/google_cse_strategy.py +68 -0
- autobyteus/tools/search/providers.py +10 -0
- autobyteus/tools/search/serpapi_strategy.py +65 -0
- autobyteus/tools/search/serper_strategy.py +87 -0
- autobyteus/tools/search_tool.py +83 -0
- autobyteus/tools/timer.py +4 -0
- autobyteus/tools/tool_meta.py +4 -24
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +1 -2
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.1.dist-info}/METADATA +5 -5
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.1.dist-info}/RECORD +95 -80
- examples/run_agentic_software_engineer.py +239 -0
- examples/run_poem_writer.py +3 -3
- autobyteus/person/__init__.py +0 -0
- autobyteus/person/examples/__init__.py +0 -0
- autobyteus/person/examples/sample_persons.py +0 -14
- autobyteus/person/examples/sample_roles.py +0 -14
- autobyteus/person/person.py +0 -29
- autobyteus/person/role.py +0 -14
- autobyteus/tools/google_search.py +0 -149
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.1.dist-info}/WHEEL +0 -0
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.2.0.dist-info → autobyteus-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from .base_strategy import SearchStrategy
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class SearchClient:
|
|
10
|
+
"""
|
|
11
|
+
A client that uses a configured search strategy to perform searches.
|
|
12
|
+
This acts as the 'Context' in the Strategy pattern.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, strategy: 'SearchStrategy'):
|
|
15
|
+
if not strategy:
|
|
16
|
+
raise ValueError("SearchClient must be initialized with a valid SearchStrategy.")
|
|
17
|
+
self._strategy = strategy
|
|
18
|
+
logger.debug(f"SearchClient initialized with strategy: {type(strategy).__name__}")
|
|
19
|
+
|
|
20
|
+
async def search(self, query: str, num_results: int) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Delegates the search operation to the configured strategy.
|
|
23
|
+
"""
|
|
24
|
+
return await self._strategy.search(query, num_results)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.utils.singleton import SingletonMeta
|
|
6
|
+
from .providers import SearchProvider
|
|
7
|
+
from .client import SearchClient
|
|
8
|
+
from .serper_strategy import SerperSearchStrategy
|
|
9
|
+
from .serpapi_strategy import SerpApiSearchStrategy
|
|
10
|
+
from .google_cse_strategy import GoogleCSESearchStrategy
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class SearchClientFactory(metaclass=SingletonMeta):
|
|
15
|
+
"""
|
|
16
|
+
Factory for creating a SearchClient with the appropriate strategy
|
|
17
|
+
based on environment variable configuration.
|
|
18
|
+
"""
|
|
19
|
+
_instance: Optional[SearchClient] = None
|
|
20
|
+
|
|
21
|
+
def create_search_client(self) -> SearchClient:
|
|
22
|
+
"""
|
|
23
|
+
Creates and returns a singleton instance of the SearchClient, configured
|
|
24
|
+
with the appropriate search strategy.
|
|
25
|
+
"""
|
|
26
|
+
if self._instance:
|
|
27
|
+
return self._instance
|
|
28
|
+
|
|
29
|
+
provider_name = os.getenv("DEFAULT_SEARCH_PROVIDER", "").lower()
|
|
30
|
+
|
|
31
|
+
serper_key = os.getenv("SERPER_API_KEY")
|
|
32
|
+
serpapi_key = os.getenv("SERPAPI_API_KEY")
|
|
33
|
+
google_api_key = os.getenv("GOOGLE_CSE_API_KEY")
|
|
34
|
+
google_cse_id = os.getenv("GOOGLE_CSE_ID")
|
|
35
|
+
|
|
36
|
+
is_serper_configured = bool(serper_key)
|
|
37
|
+
is_serpapi_configured = bool(serpapi_key)
|
|
38
|
+
is_google_cse_configured = bool(google_api_key and google_cse_id)
|
|
39
|
+
|
|
40
|
+
strategy = None
|
|
41
|
+
|
|
42
|
+
if provider_name == SearchProvider.GOOGLE_CSE:
|
|
43
|
+
if is_google_cse_configured:
|
|
44
|
+
logger.info("DEFAULT_SEARCH_PROVIDER is 'google_cse', using Google CSE strategy.")
|
|
45
|
+
strategy = GoogleCSESearchStrategy()
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError("DEFAULT_SEARCH_PROVIDER is 'google_cse', but Google CSE is not configured. "
|
|
48
|
+
"Set GOOGLE_CSE_API_KEY and GOOGLE_CSE_ID.")
|
|
49
|
+
|
|
50
|
+
elif provider_name == SearchProvider.SERPAPI:
|
|
51
|
+
if is_serpapi_configured:
|
|
52
|
+
logger.info("DEFAULT_SEARCH_PROVIDER is 'serpapi', using SerpApi strategy.")
|
|
53
|
+
strategy = SerpApiSearchStrategy()
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError("DEFAULT_SEARCH_PROVIDER is 'serpapi', but SerpApi is not configured. "
|
|
56
|
+
"Set SERPAPI_API_KEY.")
|
|
57
|
+
|
|
58
|
+
# Default to Serper if explicitly set, or if not set and Serper is available.
|
|
59
|
+
# This handles the case where multiple providers are configured but no provider is specified.
|
|
60
|
+
elif provider_name == SearchProvider.SERPER or is_serper_configured:
|
|
61
|
+
if is_serper_configured:
|
|
62
|
+
logger.info("Using Serper search strategy (either as default or as first fallback).")
|
|
63
|
+
strategy = SerperSearchStrategy()
|
|
64
|
+
else:
|
|
65
|
+
# This branch is only taken if provider_name is 'serper' but it's not configured.
|
|
66
|
+
raise ValueError("DEFAULT_SEARCH_PROVIDER is 'serper', but Serper is not configured. Set SERPER_API_KEY.")
|
|
67
|
+
|
|
68
|
+
elif is_serpapi_configured:
|
|
69
|
+
logger.info("Serper not configured, falling back to available SerpApi strategy.")
|
|
70
|
+
strategy = SerpApiSearchStrategy()
|
|
71
|
+
|
|
72
|
+
elif is_google_cse_configured:
|
|
73
|
+
logger.info("Neither Serper nor SerpApi are configured, falling back to available Google CSE strategy.")
|
|
74
|
+
strategy = GoogleCSESearchStrategy()
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError("No search provider is configured. Please set either SERPER_API_KEY, SERPAPI_API_KEY, "
|
|
78
|
+
"or both GOOGLE_CSE_API_KEY and GOOGLE_CSE_ID environment variables.")
|
|
79
|
+
|
|
80
|
+
self._instance = SearchClient(strategy=strategy)
|
|
81
|
+
return self._instance
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import aiohttp
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
from .base_strategy import SearchStrategy
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class GoogleCSESearchStrategy(SearchStrategy):
|
|
11
|
+
"""
|
|
12
|
+
A search strategy that uses the official Google Custom Search Engine (CSE) API.
|
|
13
|
+
"""
|
|
14
|
+
API_URL = "https://www.googleapis.com/customsearch/v1"
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.api_key: Optional[str] = os.getenv("GOOGLE_CSE_API_KEY")
|
|
18
|
+
self.cse_id: Optional[str] = os.getenv("GOOGLE_CSE_ID")
|
|
19
|
+
if not self.api_key or not self.cse_id:
|
|
20
|
+
raise ValueError(
|
|
21
|
+
"GoogleCSESearchStrategy requires both 'GOOGLE_CSE_API_KEY' and 'GOOGLE_CSE_ID' environment variables to be set."
|
|
22
|
+
)
|
|
23
|
+
logger.debug("GoogleCSESearchStrategy initialized.")
|
|
24
|
+
|
|
25
|
+
def _format_results(self, data: Dict[str, Any]) -> str:
|
|
26
|
+
"""Formats the JSON response from Google CSE API into a clean string for an LLM."""
|
|
27
|
+
if "items" not in data or not data["items"]:
|
|
28
|
+
return "No relevant information found for the query via Google CSE."
|
|
29
|
+
|
|
30
|
+
results = data["items"]
|
|
31
|
+
results_str = "\n".join(
|
|
32
|
+
f"{i+1}. {result.get('title', 'No Title')}\n"
|
|
33
|
+
f" Link: {result.get('link', 'No Link')}\n"
|
|
34
|
+
f" Snippet: {result.get('snippet', 'No Snippet')}"
|
|
35
|
+
for i, result in enumerate(results)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return f"Search Results:\n{results_str}"
|
|
39
|
+
|
|
40
|
+
async def search(self, query: str, num_results: int) -> str:
|
|
41
|
+
logger.info(f"Executing search with Google CSE strategy for query: '{query}'")
|
|
42
|
+
|
|
43
|
+
params = {
|
|
44
|
+
'key': self.api_key,
|
|
45
|
+
'cx': self.cse_id,
|
|
46
|
+
'q': query,
|
|
47
|
+
'num': num_results
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
async with aiohttp.ClientSession() as session:
|
|
52
|
+
async with session.get(self.API_URL, params=params) as response:
|
|
53
|
+
if response.status == 200:
|
|
54
|
+
data = await response.json()
|
|
55
|
+
return self._format_results(data)
|
|
56
|
+
else:
|
|
57
|
+
error_text = await response.text()
|
|
58
|
+
logger.error(
|
|
59
|
+
f"Google CSE API returned a non-200 status code: {response.status}. "
|
|
60
|
+
f"Response: {error_text}"
|
|
61
|
+
)
|
|
62
|
+
raise RuntimeError(f"Google CSE API request failed with status {response.status}: {error_text}")
|
|
63
|
+
except aiohttp.ClientError as e:
|
|
64
|
+
logger.error(f"Network error during Google CSE API call: {e}", exc_info=True)
|
|
65
|
+
raise RuntimeError(f"A network error occurred during Google CSE search: {e}")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"An unexpected error occurred in Google CSE strategy: {e}", exc_info=True)
|
|
68
|
+
raise
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import aiohttp
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
from .base_strategy import SearchStrategy
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class SerpApiSearchStrategy(SearchStrategy):
|
|
11
|
+
"""
|
|
12
|
+
A search strategy that uses the SerpApi.com API.
|
|
13
|
+
"""
|
|
14
|
+
API_URL = "https://serpapi.com/search.json"
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.api_key: Optional[str] = os.getenv("SERPAPI_API_KEY")
|
|
18
|
+
if not self.api_key:
|
|
19
|
+
raise ValueError("SerpApiSearchStrategy requires the 'SERPAPI_API_KEY' environment variable to be set.")
|
|
20
|
+
logger.debug("SerpApiSearchStrategy initialized.")
|
|
21
|
+
|
|
22
|
+
def _format_results(self, data: Dict[str, Any]) -> str:
|
|
23
|
+
"""Formats the JSON response from SerpApi into a clean string for an LLM."""
|
|
24
|
+
if "organic_results" not in data or not data["organic_results"]:
|
|
25
|
+
return "No relevant information found for the query via SerpApi."
|
|
26
|
+
|
|
27
|
+
results = data["organic_results"]
|
|
28
|
+
results_str = "\n".join(
|
|
29
|
+
f"{i+1}. {result.get('title', 'No Title')}\n"
|
|
30
|
+
f" Link: {result.get('link', 'No Link')}\n"
|
|
31
|
+
f" Snippet: {result.get('snippet', 'No Snippet')}"
|
|
32
|
+
for i, result in enumerate(results)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return f"Search Results:\n{results_str}"
|
|
36
|
+
|
|
37
|
+
async def search(self, query: str, num_results: int) -> str:
|
|
38
|
+
logger.info(f"Executing search with SerpApi strategy for query: '{query}'")
|
|
39
|
+
|
|
40
|
+
params = {
|
|
41
|
+
'api_key': self.api_key,
|
|
42
|
+
'engine': 'google',
|
|
43
|
+
'q': query,
|
|
44
|
+
'num': num_results
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
async with aiohttp.ClientSession() as session:
|
|
49
|
+
async with session.get(self.API_URL, params=params) as response:
|
|
50
|
+
if response.status == 200:
|
|
51
|
+
data = await response.json()
|
|
52
|
+
return self._format_results(data)
|
|
53
|
+
else:
|
|
54
|
+
error_text = await response.text()
|
|
55
|
+
logger.error(
|
|
56
|
+
f"SerpApi API returned a non-200 status code: {response.status}. "
|
|
57
|
+
f"Response: {error_text}"
|
|
58
|
+
)
|
|
59
|
+
raise RuntimeError(f"SerpApi API request failed with status {response.status}: {error_text}")
|
|
60
|
+
except aiohttp.ClientError as e:
|
|
61
|
+
logger.error(f"Network error during SerpApi API call: {e}", exc_info=True)
|
|
62
|
+
raise RuntimeError(f"A network error occurred during SerpApi search: {e}")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"An unexpected error occurred in SerpApi strategy: {e}", exc_info=True)
|
|
65
|
+
raise
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import aiohttp
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
from .base_strategy import SearchStrategy
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class SerperSearchStrategy(SearchStrategy):
|
|
12
|
+
"""
|
|
13
|
+
A search strategy that uses the Serper.dev API.
|
|
14
|
+
"""
|
|
15
|
+
API_URL = "https://google.serper.dev/search"
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.api_key: Optional[str] = os.getenv("SERPER_API_KEY")
|
|
19
|
+
if not self.api_key:
|
|
20
|
+
raise ValueError("SerperSearchStrategy requires the 'SERPER_API_KEY' environment variable to be set.")
|
|
21
|
+
logger.debug("SerperSearchStrategy initialized.")
|
|
22
|
+
|
|
23
|
+
def _format_results(self, data: Dict[str, Any]) -> str:
|
|
24
|
+
"""Formats the JSON response from Serper into a clean string for an LLM."""
|
|
25
|
+
summary_parts = []
|
|
26
|
+
|
|
27
|
+
# 1. Answer Box (most important for direct questions)
|
|
28
|
+
if "answerBox" in data:
|
|
29
|
+
answer_box = data["answerBox"]
|
|
30
|
+
title = answer_box.get("title", "")
|
|
31
|
+
snippet = answer_box.get("snippet") or answer_box.get("answer")
|
|
32
|
+
summary_parts.append(f"Direct Answer for '{title}':\n{snippet}")
|
|
33
|
+
|
|
34
|
+
# 2. Knowledge Graph (for entity information)
|
|
35
|
+
if "knowledgeGraph" in data:
|
|
36
|
+
kg = data["knowledgeGraph"]
|
|
37
|
+
title = kg.get("title", "")
|
|
38
|
+
description = kg.get("description")
|
|
39
|
+
summary_parts.append(f"Summary for '{title}':\n{description}")
|
|
40
|
+
|
|
41
|
+
# 3. Organic Results (the main search links)
|
|
42
|
+
if "organic" in data and data["organic"]:
|
|
43
|
+
organic_results = data["organic"]
|
|
44
|
+
results_str = "\n".join(
|
|
45
|
+
f"{i+1}. {result.get('title', 'No Title')}\n"
|
|
46
|
+
f" Link: {result.get('link', 'No Link')}\n"
|
|
47
|
+
f" Snippet: {result.get('snippet', 'No Snippet')}"
|
|
48
|
+
for i, result in enumerate(organic_results)
|
|
49
|
+
)
|
|
50
|
+
summary_parts.append(f"Search Results:\n{results_str}")
|
|
51
|
+
|
|
52
|
+
if not summary_parts:
|
|
53
|
+
return "No relevant information found for the query via Serper."
|
|
54
|
+
|
|
55
|
+
return "\n\n---\n\n".join(summary_parts)
|
|
56
|
+
|
|
57
|
+
async def search(self, query: str, num_results: int) -> str:
|
|
58
|
+
logger.info(f"Executing search with Serper strategy for query: '{query}'")
|
|
59
|
+
|
|
60
|
+
headers = {
|
|
61
|
+
'X-API-KEY': self.api_key,
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
}
|
|
64
|
+
payload = json.dumps({
|
|
65
|
+
"q": query,
|
|
66
|
+
"num": num_results
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
async with aiohttp.ClientSession() as session:
|
|
71
|
+
async with session.post(self.API_URL, headers=headers, data=payload) as response:
|
|
72
|
+
if response.status == 200:
|
|
73
|
+
data = await response.json()
|
|
74
|
+
return self._format_results(data)
|
|
75
|
+
else:
|
|
76
|
+
error_text = await response.text()
|
|
77
|
+
logger.error(
|
|
78
|
+
f"Serper API returned a non-200 status code: {response.status}. "
|
|
79
|
+
f"Response: {error_text}"
|
|
80
|
+
)
|
|
81
|
+
raise RuntimeError(f"Serper API request failed with status {response.status}: {error_text}")
|
|
82
|
+
except aiohttp.ClientError as e:
|
|
83
|
+
logger.error(f"Network error during Serper API call: {e}", exc_info=True)
|
|
84
|
+
raise RuntimeError(f"A network error occurred during Serper search: {e}")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"An unexpected error occurred in Serper strategy: {e}", exc_info=True)
|
|
87
|
+
raise
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
5
|
+
from autobyteus.tools.tool_config import ToolConfig
|
|
6
|
+
from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
7
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
8
|
+
from autobyteus.tools.search import SearchClientFactory, SearchClient
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.agent.context import AgentContext
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Search(BaseTool):
|
|
17
|
+
"""
|
|
18
|
+
Performs a web search using a configurable backend provider (e.g., Serper.dev, Google CSE).
|
|
19
|
+
Returns a structured summary of the results.
|
|
20
|
+
Configuration is managed via environment variables (see SearchClientFactory for details).
|
|
21
|
+
"""
|
|
22
|
+
CATEGORY = ToolCategory.WEB
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: Optional[ToolConfig] = None):
|
|
25
|
+
super().__init__(config=config)
|
|
26
|
+
try:
|
|
27
|
+
factory = SearchClientFactory()
|
|
28
|
+
self.search_client: SearchClient = factory.create_search_client()
|
|
29
|
+
except ValueError as e:
|
|
30
|
+
logger.error(f"Failed to initialize search_web tool: {e}", exc_info=True)
|
|
31
|
+
# Re-raise to prevent tool from being used in a misconfigured state.
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
"Could not initialize Search tool. Please check your search provider configuration. "
|
|
34
|
+
f"Error: {e}"
|
|
35
|
+
)
|
|
36
|
+
logger.debug("search_web tool initialized with a configured search client.")
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_name(cls) -> str:
|
|
40
|
+
return "search_web"
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def get_description(cls) -> str:
|
|
44
|
+
return (
|
|
45
|
+
"Searches the web for a given query using the configured search provider. "
|
|
46
|
+
"Returns a concise, structured summary of search results, including direct answers (if available) and top organic links."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
51
|
+
schema = ParameterSchema()
|
|
52
|
+
schema.add_parameter(ParameterDefinition(
|
|
53
|
+
name="query",
|
|
54
|
+
param_type=ParameterType.STRING,
|
|
55
|
+
description="The search query string.",
|
|
56
|
+
required=True
|
|
57
|
+
))
|
|
58
|
+
schema.add_parameter(ParameterDefinition(
|
|
59
|
+
name="num_results",
|
|
60
|
+
param_type=ParameterType.INTEGER,
|
|
61
|
+
description="The number of organic search results to return.",
|
|
62
|
+
required=False,
|
|
63
|
+
default_value=5,
|
|
64
|
+
min_value=1,
|
|
65
|
+
max_value=10
|
|
66
|
+
))
|
|
67
|
+
return schema
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def get_config_schema(cls) -> Optional[ParameterSchema]:
|
|
71
|
+
# Configuration is now handled by the factory via environment variables,
|
|
72
|
+
# so this tool no longer has instance-specific configuration.
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
async def _execute(self, context: 'AgentContext', query: str, num_results: int = 5) -> str:
|
|
76
|
+
logger.info(f"Executing search_web for agent {context.agent_id} with query: '{query}'")
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
return await self.search_client.search(query=query, num_results=num_results)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"An unexpected error occurred in search_web execution: {e}", exc_info=True)
|
|
82
|
+
# Re-raise to ensure the agent is aware of the failure.
|
|
83
|
+
raise
|
autobyteus/tools/timer.py
CHANGED
|
@@ -48,6 +48,10 @@ class Timer(BaseTool, EventEmitter):
|
|
|
48
48
|
self._task: Optional[asyncio.Task] = None
|
|
49
49
|
logger.debug(f"Timer initialized with duration: {self.duration}s, interval: {self.interval}s")
|
|
50
50
|
|
|
51
|
+
@classmethod
|
|
52
|
+
def get_name(cls) -> str:
|
|
53
|
+
return "start_timer"
|
|
54
|
+
|
|
51
55
|
@classmethod
|
|
52
56
|
def get_description(cls) -> str:
|
|
53
57
|
return "Sets and runs a timer. Emits TIMER_UPDATE events with remaining time at specified intervals."
|
autobyteus/tools/tool_meta.py
CHANGED
|
@@ -35,26 +35,8 @@ class ToolMeta(ABCMeta):
|
|
|
35
35
|
logger.error(f"Tool class {name} ({tool_name}) must return a valid string from get_description(). Skipping registration.")
|
|
36
36
|
return
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
argument_schema = cls.get_argument_schema()
|
|
41
|
-
if argument_schema is not None and not isinstance(argument_schema, ParameterSchema):
|
|
42
|
-
logger.error(f"Tool class {name} ({tool_name}) get_argument_schema() must return a ParameterSchema or None. Got {type(argument_schema)}. Skipping registration.")
|
|
43
|
-
return
|
|
44
|
-
except Exception as e:
|
|
45
|
-
logger.error(f"Tool class {name} ({tool_name}) failed to provide argument_schema via get_argument_schema(): {e}. Skipping registration.", exc_info=True)
|
|
46
|
-
return
|
|
38
|
+
# Note: We do not call the schema methods here. We pass them as providers.
|
|
47
39
|
|
|
48
|
-
instantiation_config_schema: ParameterSchema = None
|
|
49
|
-
if hasattr(cls, 'get_config_schema'):
|
|
50
|
-
try:
|
|
51
|
-
instantiation_config_schema = cls.get_config_schema()
|
|
52
|
-
if instantiation_config_schema is not None and not isinstance(instantiation_config_schema, ParameterSchema):
|
|
53
|
-
logger.warning(f"Tool class {name} ({tool_name}) get_config_schema() returned non-ParameterSchema type: {type(instantiation_config_schema)}. Treating as no config schema.")
|
|
54
|
-
instantiation_config_schema = None
|
|
55
|
-
except Exception as e:
|
|
56
|
-
logger.warning(f"Tool class {name} ({tool_name}) has get_config_schema() but it failed: {e}. Assuming no instantiation config.")
|
|
57
|
-
|
|
58
40
|
# Get category from class attribute, defaulting to "General"
|
|
59
41
|
category_str = getattr(cls, 'CATEGORY', ToolCategory.GENERAL)
|
|
60
42
|
|
|
@@ -64,16 +46,14 @@ class ToolMeta(ABCMeta):
|
|
|
64
46
|
description=general_description,
|
|
65
47
|
tool_class=cls,
|
|
66
48
|
custom_factory=None,
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
argument_schema_provider=cls.get_argument_schema,
|
|
50
|
+
config_schema_provider=cls.get_config_schema,
|
|
69
51
|
origin=ToolOrigin.LOCAL,
|
|
70
52
|
category=category_str
|
|
71
53
|
)
|
|
72
54
|
default_tool_registry.register_tool(definition)
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
config_info = f"inst_config: {len(instantiation_config_schema) if instantiation_config_schema else '0'}"
|
|
76
|
-
logger.info(f"Auto-registered tool: '{tool_name}' from class {name} ({arg_schema_info}, {config_info})")
|
|
56
|
+
logger.info(f"Auto-registered tool: '{tool_name}' from class {name}")
|
|
77
57
|
|
|
78
58
|
except AttributeError as e:
|
|
79
59
|
logger.error(f"Tool class {name} is missing a required method ({e}). Skipping registration.")
|
|
@@ -91,7 +91,7 @@ class CoordinatorPromptPreparationStep(BaseWorkflowBootstrapStep):
|
|
|
91
91
|
|
|
92
92
|
prompt_parts.append(tools_section)
|
|
93
93
|
|
|
94
|
-
final_instruction = "### Your Task\nAnalyze the user's request, formulate a plan, and use the `
|
|
94
|
+
final_instruction = "### Your Task\nAnalyze the user's request, formulate a plan, and use the `send_message_to` tool to delegate tasks to your team. Address team members by their unique ID as listed under 'Your Team'."
|
|
95
95
|
prompt_parts.append(final_instruction)
|
|
96
96
|
else:
|
|
97
97
|
role_and_goal = (
|
|
@@ -105,4 +105,3 @@ class CoordinatorPromptPreparationStep(BaseWorkflowBootstrapStep):
|
|
|
105
105
|
prompt_parts.append(final_instruction)
|
|
106
106
|
|
|
107
107
|
return "\n\n".join(prompt_parts)
|
|
108
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autobyteus
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Multi-Agent framework
|
|
5
5
|
Home-page: https://github.com/AutoByteus/autobyteus
|
|
6
6
|
Author: Ryan Zheng
|
|
@@ -99,11 +99,11 @@ Launch and monitor your agent teams with our built-in Textual-based TUI.
|
|
|
99
99
|
- **Hierarchical View**: See the structure of your team, including sub-teams and their agents.
|
|
100
100
|
- **Real-Time Status**: Agent and team statuses are updated live, showing you who is idle, thinking, or executing a tool.
|
|
101
101
|
- **Detailed Logs**: Select any agent to view a detailed, streaming log of their thoughts, actions, and tool interactions.
|
|
102
|
-
- **Live Task
|
|
102
|
+
- **Live Task Plan**: Watch your team's `TaskPlan` update in real-time as the coordinator publishes a plan and agents complete their tasks.
|
|
103
103
|
|
|
104
|
-
| TUI - Detailed Agent Log | TUI - Task
|
|
104
|
+
| TUI - Detailed Agent Log | TUI - Task Plan with Completed Task |
|
|
105
105
|
| :---: | :---: |
|
|
106
|
-
|  |  |  |
|
|
107
107
|
|
|
108
108
|
#### 🏗️ Fluent Team Building
|
|
109
109
|
Define complex agent and team structures with an intuitive, fluent API. The `AgentTeamBuilder` makes composing your team simple and readable.
|
|
@@ -130,7 +130,7 @@ Autobyteus intelligently handles tool communication with LLMs while giving you f
|
|
|
130
130
|
#### 📈 Flexible Communication Protocols
|
|
131
131
|
Choose the collaboration pattern that best fits your use case with configurable `TaskNotificationMode`s.
|
|
132
132
|
- **`AGENT_MANUAL_NOTIFICATION` (Default)**: A traditional approach where a coordinator agent is responsible for creating a plan and then explicitly notifying other agents to begin their work via messages.
|
|
133
|
-
- **`SYSTEM_EVENT_DRIVEN`**: A more automated approach where the coordinator's only job is to publish a plan to the `
|
|
133
|
+
- **`SYSTEM_EVENT_DRIVEN`**: A more automated approach where the coordinator's only job is to publish a plan to the `TaskPlan`. The framework then monitors the board and automatically notifies agents when their tasks become unblocked, enabling parallel execution and reducing coordinator overhead.
|
|
134
134
|
|
|
135
135
|
## Requirements
|
|
136
136
|
|