autobyteus 1.1.9__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.
Files changed (126) hide show
  1. autobyteus/agent/context/agent_runtime_state.py +4 -0
  2. autobyteus/agent/events/notifiers.py +5 -1
  3. autobyteus/agent/message/send_message_to.py +5 -4
  4. autobyteus/agent/streaming/agent_event_stream.py +5 -0
  5. autobyteus/agent/streaming/stream_event_payloads.py +25 -0
  6. autobyteus/agent/streaming/stream_events.py +13 -1
  7. autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +4 -4
  8. autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +12 -12
  9. autobyteus/agent_team/context/agent_team_runtime_state.py +2 -2
  10. autobyteus/agent_team/streaming/agent_team_event_notifier.py +4 -4
  11. autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +3 -3
  12. autobyteus/agent_team/streaming/agent_team_stream_events.py +8 -8
  13. autobyteus/agent_team/task_notification/activation_policy.py +1 -1
  14. autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +22 -22
  15. autobyteus/agent_team/task_notification/task_notification_mode.py +1 -1
  16. autobyteus/cli/agent_team_tui/app.py +4 -4
  17. autobyteus/cli/agent_team_tui/state.py +8 -8
  18. autobyteus/cli/agent_team_tui/widgets/focus_pane.py +3 -3
  19. autobyteus/cli/agent_team_tui/widgets/shared.py +1 -1
  20. autobyteus/cli/agent_team_tui/widgets/{task_board_panel.py → task_plan_panel.py} +5 -5
  21. autobyteus/clients/__init__.py +10 -0
  22. autobyteus/clients/autobyteus_client.py +318 -0
  23. autobyteus/clients/cert_utils.py +105 -0
  24. autobyteus/clients/certificates/cert.pem +34 -0
  25. autobyteus/events/event_types.py +4 -3
  26. autobyteus/llm/api/autobyteus_llm.py +1 -1
  27. autobyteus/llm/api/zhipu_llm.py +26 -0
  28. autobyteus/llm/autobyteus_provider.py +1 -1
  29. autobyteus/llm/llm_factory.py +23 -0
  30. autobyteus/llm/ollama_provider_resolver.py +1 -0
  31. autobyteus/llm/providers.py +1 -0
  32. autobyteus/llm/token_counter/token_counter_factory.py +3 -0
  33. autobyteus/llm/token_counter/zhipu_token_counter.py +24 -0
  34. autobyteus/multimedia/audio/api/__init__.py +3 -2
  35. autobyteus/multimedia/audio/api/autobyteus_audio_client.py +1 -1
  36. autobyteus/multimedia/audio/api/openai_audio_client.py +112 -0
  37. autobyteus/multimedia/audio/audio_client_factory.py +37 -0
  38. autobyteus/multimedia/audio/autobyteus_audio_provider.py +1 -1
  39. autobyteus/multimedia/image/api/autobyteus_image_client.py +1 -1
  40. autobyteus/multimedia/image/autobyteus_image_provider.py +1 -1
  41. autobyteus/multimedia/image/image_client_factory.py +1 -1
  42. autobyteus/task_management/__init__.py +44 -20
  43. autobyteus/task_management/{base_task_board.py → base_task_plan.py} +16 -13
  44. autobyteus/task_management/converters/__init__.py +2 -2
  45. autobyteus/task_management/converters/{task_board_converter.py → task_plan_converter.py} +13 -13
  46. autobyteus/task_management/events.py +7 -7
  47. autobyteus/task_management/{in_memory_task_board.py → in_memory_task_plan.py} +34 -22
  48. autobyteus/task_management/schemas/__init__.py +3 -0
  49. autobyteus/task_management/schemas/task_definition.py +1 -1
  50. autobyteus/task_management/schemas/task_status_report.py +3 -3
  51. autobyteus/task_management/schemas/todo_definition.py +15 -0
  52. autobyteus/task_management/todo.py +29 -0
  53. autobyteus/task_management/todo_list.py +75 -0
  54. autobyteus/task_management/tools/__init__.py +25 -7
  55. autobyteus/task_management/tools/task_tools/__init__.py +19 -0
  56. autobyteus/task_management/tools/task_tools/assign_task_to.py +125 -0
  57. autobyteus/task_management/tools/{publish_task.py → task_tools/create_task.py} +16 -18
  58. autobyteus/task_management/tools/{publish_tasks.py → task_tools/create_tasks.py} +19 -19
  59. autobyteus/task_management/tools/{get_my_tasks.py → task_tools/get_my_tasks.py} +15 -15
  60. autobyteus/task_management/tools/{get_task_board_status.py → task_tools/get_task_plan_status.py} +16 -16
  61. autobyteus/task_management/tools/{update_task_status.py → task_tools/update_task_status.py} +16 -16
  62. autobyteus/task_management/tools/todo_tools/__init__.py +18 -0
  63. autobyteus/task_management/tools/todo_tools/add_todo.py +78 -0
  64. autobyteus/task_management/tools/todo_tools/create_todo_list.py +79 -0
  65. autobyteus/task_management/tools/todo_tools/get_todo_list.py +55 -0
  66. autobyteus/task_management/tools/todo_tools/update_todo_status.py +85 -0
  67. autobyteus/tools/__init__.py +61 -21
  68. autobyteus/tools/bash/bash_executor.py +3 -3
  69. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +5 -5
  70. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +4 -4
  71. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -3
  72. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -3
  73. autobyteus/tools/browser/standalone/navigate_to.py +13 -9
  74. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +9 -5
  75. autobyteus/tools/browser/standalone/webpage_image_downloader.py +10 -6
  76. autobyteus/tools/browser/standalone/webpage_reader.py +13 -9
  77. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +9 -5
  78. autobyteus/tools/file/__init__.py +13 -0
  79. autobyteus/tools/file/edit_file.py +200 -0
  80. autobyteus/tools/file/list_directory.py +168 -0
  81. autobyteus/tools/file/{file_reader.py → read_file.py} +3 -3
  82. autobyteus/tools/file/search_files.py +188 -0
  83. autobyteus/tools/file/{file_writer.py → write_file.py} +3 -3
  84. autobyteus/tools/functional_tool.py +10 -8
  85. autobyteus/tools/mcp/tool.py +3 -3
  86. autobyteus/tools/mcp/tool_registrar.py +5 -2
  87. autobyteus/tools/multimedia/__init__.py +2 -1
  88. autobyteus/tools/multimedia/audio_tools.py +2 -2
  89. autobyteus/tools/multimedia/download_media_tool.py +136 -0
  90. autobyteus/tools/multimedia/image_tools.py +4 -4
  91. autobyteus/tools/multimedia/media_reader_tool.py +1 -1
  92. autobyteus/tools/registry/tool_definition.py +66 -13
  93. autobyteus/tools/registry/tool_registry.py +29 -0
  94. autobyteus/tools/search/__init__.py +17 -0
  95. autobyteus/tools/search/base_strategy.py +35 -0
  96. autobyteus/tools/search/client.py +24 -0
  97. autobyteus/tools/search/factory.py +81 -0
  98. autobyteus/tools/search/google_cse_strategy.py +68 -0
  99. autobyteus/tools/search/providers.py +10 -0
  100. autobyteus/tools/search/serpapi_strategy.py +65 -0
  101. autobyteus/tools/search/serper_strategy.py +87 -0
  102. autobyteus/tools/search_tool.py +83 -0
  103. autobyteus/tools/timer.py +4 -0
  104. autobyteus/tools/tool_meta.py +4 -24
  105. autobyteus/tools/usage/parsers/_string_decoders.py +18 -0
  106. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +9 -1
  107. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +15 -1
  108. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +4 -1
  109. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +4 -1
  110. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +1 -2
  111. {autobyteus-1.1.9.dist-info → autobyteus-1.2.1.dist-info}/METADATA +7 -6
  112. {autobyteus-1.1.9.dist-info → autobyteus-1.2.1.dist-info}/RECORD +117 -94
  113. examples/run_agentic_software_engineer.py +239 -0
  114. examples/run_poem_writer.py +3 -3
  115. autobyteus/person/__init__.py +0 -0
  116. autobyteus/person/examples/__init__.py +0 -0
  117. autobyteus/person/examples/sample_persons.py +0 -14
  118. autobyteus/person/examples/sample_roles.py +0 -14
  119. autobyteus/person/person.py +0 -29
  120. autobyteus/person/role.py +0 -14
  121. autobyteus/tools/google_search.py +0 -149
  122. autobyteus/tools/image_downloader.py +0 -99
  123. autobyteus/tools/pdf_downloader.py +0 -89
  124. {autobyteus-1.1.9.dist-info → autobyteus-1.2.1.dist-info}/WHEEL +0 -0
  125. {autobyteus-1.1.9.dist-info → autobyteus-1.2.1.dist-info}/licenses/LICENSE +0 -0
  126. {autobyteus-1.1.9.dist-info → autobyteus-1.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,239 @@
1
+ # file: autobyteus/examples/run_agentic_software_engineer.py
2
+ import asyncio
3
+ import logging
4
+ import argparse
5
+ from pathlib import Path
6
+ import sys
7
+ import os
8
+
9
+ # --- Boilerplate to make the script runnable from the project root ---
10
+ SCRIPT_DIR = Path(__file__).resolve().parent
11
+ PACKAGE_ROOT = SCRIPT_DIR.parent
12
+ if str(PACKAGE_ROOT) not in sys.path:
13
+ sys.path.insert(0, str(PACKAGE_ROOT))
14
+
15
+ # Load environment variables from .env file in the project root
16
+ try:
17
+ from dotenv import load_dotenv
18
+ env_file_path = PACKAGE_ROOT / ".env"
19
+ if env_file_path.exists():
20
+ load_dotenv(env_file_path)
21
+ print(f"Loaded environment variables from: {env_file_path}")
22
+ else:
23
+ print(f"Info: No .env file found at: {env_file_path}. Relying on exported environment variables.")
24
+ except ImportError:
25
+ print("Warning: python-dotenv not installed. Cannot load .env file.")
26
+
27
+ # --- Imports for the Agentic Software Engineer Example ---
28
+ try:
29
+ # Tool related imports
30
+ from autobyteus.tools.registry import default_tool_registry
31
+ from autobyteus.tools.tool_origin import ToolOrigin
32
+ # Import local tools to ensure they are registered
33
+ import autobyteus.tools.local_tools
34
+
35
+ # Workspace imports
36
+ from autobyteus.agent.workspace.local_workspace import LocalWorkspace
37
+ from autobyteus.agent.workspace.workspace_config import WorkspaceConfig
38
+
39
+ # For Agent creation
40
+ from autobyteus.agent.context.agent_config import AgentConfig
41
+ from autobyteus.llm.models import LLMModel
42
+ from autobyteus.llm.llm_factory import default_llm_factory, LLMFactory
43
+ from autobyteus.agent.factory.agent_factory import AgentFactory
44
+ from autobyteus.cli import agent_cli
45
+ except ImportError as e:
46
+ print(f"Error importing autobyteus components: {e}", file=sys.stderr)
47
+ print("Please ensure that the autobyteus library is installed and accessible.", file=sys.stderr)
48
+ sys.exit(1)
49
+
50
+ # --- Logging Setup ---
51
+ logger = logging.getLogger("agentic_swe_example")
52
+ interactive_logger = logging.getLogger("autobyteus.cli.interactive")
53
+
54
+ def setup_logging(args: argparse.Namespace):
55
+ """
56
+ Configures logging for the interactive session.
57
+ """
58
+ loggers_to_clear = [
59
+ logging.getLogger(),
60
+ logging.getLogger("autobyteus"),
61
+ logging.getLogger("autobyteus.cli"),
62
+ interactive_logger,
63
+ ]
64
+ for l in loggers_to_clear:
65
+ if l.hasHandlers():
66
+ for handler in l.handlers[:]:
67
+ l.removeHandler(handler)
68
+ if hasattr(handler, 'close'): handler.close()
69
+
70
+ script_log_level = logging.DEBUG if args.debug else logging.INFO
71
+
72
+ # 1. Handler for unformatted interactive output
73
+ interactive_handler = logging.StreamHandler(sys.stdout)
74
+ interactive_logger.addHandler(interactive_handler)
75
+ interactive_logger.setLevel(logging.INFO)
76
+ interactive_logger.propagate = False
77
+
78
+ # 2. Handler for formatted console logs
79
+ console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
80
+
81
+ class FormattedConsoleFilter(logging.Filter):
82
+ def filter(self, record):
83
+ if record.name.startswith("agentic_swe_example") or record.name.startswith("autobyteus.cli"):
84
+ return True
85
+ if record.levelno >= logging.CRITICAL:
86
+ return True
87
+ return False
88
+
89
+ formatted_console_handler = logging.StreamHandler(sys.stdout)
90
+ formatted_console_handler.setFormatter(console_formatter)
91
+ formatted_console_handler.addFilter(FormattedConsoleFilter())
92
+
93
+ root_logger = logging.getLogger()
94
+ root_logger.addHandler(formatted_console_handler)
95
+ root_logger.setLevel(script_log_level)
96
+
97
+ # 3. Handler for the main agent log file
98
+ log_file_path = Path(args.agent_log_file).resolve()
99
+ log_file_path.parent.mkdir(parents=True, exist_ok=True)
100
+ agent_file_handler = logging.FileHandler(log_file_path, mode='w')
101
+ agent_file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s')
102
+ agent_file_handler.setFormatter(agent_file_formatter)
103
+ file_log_level = logging.DEBUG if args.debug else logging.INFO
104
+
105
+ autobyteus_logger = logging.getLogger("autobyteus")
106
+ autobyteus_logger.addHandler(agent_file_handler)
107
+ autobyteus_logger.setLevel(file_log_level)
108
+ autobyteus_logger.propagate = True
109
+
110
+ # 4. Configure `autobyteus.cli` package logging
111
+ cli_logger = logging.getLogger("autobyteus.cli")
112
+ cli_logger.setLevel(script_log_level)
113
+ cli_logger.propagate = True
114
+
115
+ logger.info(f"Core library logs (excluding CLI) redirected to: {log_file_path} (level: {logging.getLevelName(file_log_level)})")
116
+
117
+ # --- Environment Variable Checks ---
118
+ def check_required_env_vars():
119
+ """Checks for environment variables required by this example. None are strictly required."""
120
+ logger.info("No specific environment variables are required, but ensure your chosen LLM provider's API key is set (e.g., GOOGLE_API_KEY).")
121
+ return {}
122
+
123
+ async def main(args: argparse.Namespace):
124
+ """Main function to configure and run the Agentic Software Engineer."""
125
+ logger.info("--- Starting Agentic Software Engineer Example ---")
126
+ check_required_env_vars()
127
+
128
+ try:
129
+ # 1. Create a workspace for the agent.
130
+ workspace_path = Path(args.workspace_path).resolve()
131
+ workspace_path.mkdir(parents=True, exist_ok=True)
132
+ logger.info(f"Agent workspace initialized at: {workspace_path}")
133
+ workspace_config = WorkspaceConfig(params={"root_path": str(workspace_path)})
134
+ workspace = LocalWorkspace(config=workspace_config)
135
+
136
+ # 2. Get all available local tools.
137
+ tool_registry = default_tool_registry
138
+ local_tool_defs = tool_registry.get_tools_by_origin(ToolOrigin.LOCAL)
139
+ local_tool_names = [tool_def.name for tool_def in local_tool_defs]
140
+
141
+ if not local_tool_names:
142
+ logger.error("No local tools were found in the registry. Cannot create agent.")
143
+ return
144
+
145
+ logger.info(f"Creating instances for registered local tools: {local_tool_names}")
146
+ tools_for_agent = [tool_registry.create_tool(name) for name in local_tool_names]
147
+
148
+ # 3. Configure and create the agent.
149
+ try:
150
+ _ = LLMModel[args.llm_model]
151
+ except (KeyError, ValueError):
152
+ logger.error(f"LLM Model '{args.llm_model}' is not valid or ambiguous.", file=sys.stderr)
153
+ try:
154
+ LLMFactory.ensure_initialized()
155
+ print("\nAvailable LLM Models (use the 'Identifier' with --llm-model):")
156
+ all_models = sorted(list(LLMModel), key=lambda m: m.model_identifier)
157
+ if not all_models:
158
+ print(" No models found.")
159
+ for model in all_models:
160
+ print(f" - Display Name: {model.name:<30} Identifier: {model.model_identifier}")
161
+ except Exception as e:
162
+ print(f"Additionally, an error occurred while listing models: {e}", file=sys.stderr)
163
+ sys.exit(1)
164
+
165
+ logger.info(f"Creating LLM instance for model: {args.llm_model}")
166
+ llm_instance = default_llm_factory.create_llm(model_identifier=args.llm_model)
167
+
168
+ # Load system prompt from file
169
+ prompt_path = SCRIPT_DIR / "prompts" / "agentic_software_engineer.prompt"
170
+ if not prompt_path.exists():
171
+ logger.error(f"System prompt file not found at: {prompt_path}")
172
+ sys.exit(1)
173
+ with open(prompt_path, "r", encoding="utf-8") as f:
174
+ system_prompt = f.read()
175
+ logger.info(f"Loaded system prompt from: {prompt_path}")
176
+
177
+ agent_config = AgentConfig(
178
+ name="AgenticSoftwareDeveloper",
179
+ role="SoftwareEngineer",
180
+ description="An AI agent that can reason, plan, and execute software development tasks.",
181
+ llm_instance=llm_instance,
182
+ system_prompt=system_prompt,
183
+ tools=tools_for_agent,
184
+ workspace=workspace,
185
+ use_xml_tool_format=True, # As specified in the prompt
186
+ auto_execute_tools=False # Require user approval for safety
187
+ )
188
+
189
+ agent = AgentFactory().create_agent(config=agent_config)
190
+ logger.info(f"Agentic Software Engineer instance created: {agent.agent_id}")
191
+
192
+ # 4. Run the agent in an interactive CLI session.
193
+ logger.info(f"Starting interactive session for agent {agent.agent_id}...")
194
+ initial_prompt = f"Hello! I'm ready to work. My current working directory is `{workspace_path}`. What's the first task?"
195
+ await agent_cli.run(agent=agent, initial_prompt=initial_prompt, show_tool_logs=not args.no_tool_logs)
196
+ logger.info(f"Interactive session for agent {agent.agent_id} finished.")
197
+
198
+ except Exception as e:
199
+ logger.error(f"An error occurred during the agent workflow: {e}", exc_info=True)
200
+
201
+ logger.info("--- Agentic Software Engineer Example Finished ---")
202
+
203
+ if __name__ == "__main__":
204
+ parser = argparse.ArgumentParser(description="Run the Agentic Software Engineer interactively.")
205
+ parser.add_argument("--llm-model", type=str, default="gemini-2.0-flash-", help=f"The LLM model identifier to use. Call --help-models for list.")
206
+ parser.add_argument("--workspace-path", type=str, default="./agent_workspace", help="Path to the agent's working directory. (Default: ./agent_workspace)")
207
+ parser.add_argument("--help-models", action="store_true", help="Display available LLM models and exit.")
208
+ parser.add_argument("--debug", action="store_true", help="Enable debug logging.")
209
+ parser.add_argument("--agent-log-file", type=str, default="./agent_logs_swe.txt",
210
+ help="Path to the log file for autobyteus.* library logs. (Default: ./agent_logs_swe.txt)")
211
+ parser.add_argument("--no-tool-logs", action="store_true",
212
+ help="Disable display of [Tool Log (...)] messages on the console by the agent_cli.")
213
+
214
+ if "--help-models" in sys.argv:
215
+ try:
216
+ LLMFactory.ensure_initialized()
217
+ print("Available LLM Models (use the 'Identifier' with --llm-model):")
218
+ all_models = sorted(list(LLMModel), key=lambda m: m.model_identifier)
219
+ if not all_models:
220
+ print(" No models found.")
221
+ for model in all_models:
222
+ print(f" - Display Name: {model.name:<30} Identifier: {model.model_identifier}")
223
+ except Exception as e:
224
+ print(f"Error listing models: {e}")
225
+ sys.exit(0)
226
+
227
+ parsed_args = parser.parse_args()
228
+
229
+ setup_logging(parsed_args)
230
+ check_required_env_vars()
231
+
232
+ try:
233
+ asyncio.run(main(parsed_args))
234
+ except (KeyboardInterrupt, SystemExit):
235
+ logger.info("Script interrupted by user. Exiting.")
236
+ except Exception as e:
237
+ logger.error(f"An unhandled error occurred at the top level: {e}", exc_info=True)
238
+ finally:
239
+ logger.info("Exiting script.")
@@ -39,7 +39,7 @@ try:
39
39
  from autobyteus.llm.llm_factory import default_llm_factory, LLMFactory
40
40
  from autobyteus.agent.factory.agent_factory import AgentFactory
41
41
  from autobyteus.cli import agent_cli
42
- from autobyteus.tools.file.file_writer import file_writer
42
+ from autobyteus.tools.file.write_file import write_file
43
43
  # Import core workspace and schema components from the library
44
44
  from autobyteus.agent.workspace import BaseAgentWorkspace, WorkspaceConfig
45
45
  from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
@@ -176,8 +176,8 @@ async def main(args: argparse.Namespace):
176
176
  workspace_base_path.mkdir(parents=True, exist_ok=True)
177
177
  logger.info(f"Agent will be configured with a local workspace at: {workspace_base_path}")
178
178
 
179
- # The file_writer tool is an instance ready to be used
180
- tools_for_agent = [file_writer]
179
+ # The write_file tool is an instance ready to be used
180
+ tools_for_agent = [write_file]
181
181
 
182
182
  # UPDATED: The system prompt now provides context about the workspace.
183
183
  system_prompt = (
File without changes
File without changes
@@ -1,14 +0,0 @@
1
- from autobyteus.person.examples.sample_roles import RESEARCHER_ROLE, WRITER_ROLE
2
- from autobyteus.person.person import Person
3
-
4
- ANNA = Person(
5
- name="Anna",
6
- role=RESEARCHER_ROLE,
7
- characteristics=["detail-oriented", "analytical", "curious"]
8
- )
9
-
10
- RYAN = Person(
11
- name="Ryan",
12
- role=WRITER_ROLE,
13
- characteristics=["creative", "empathetic", "articulate"]
14
- )
@@ -1,14 +0,0 @@
1
- from autobyteus.person.role import Role
2
-
3
-
4
- RESEARCHER_ROLE = Role(
5
- name="Researcher",
6
- skills=["data analysis", "literature review", "critical thinking"],
7
- responsibilities=["gather information", "analyze data", "report findings"]
8
- )
9
-
10
- WRITER_ROLE = Role(
11
- name="Writer",
12
- skills=["content creation", "editing", "storytelling"],
13
- responsibilities=["draft articles", "revise content", "adapt to different styles"]
14
- )
@@ -1,29 +0,0 @@
1
- from typing import List
2
- from autobyteus.person.role import Role
3
-
4
- class Person:
5
- def __init__(self, name: str, role: Role, characteristics: List[str]):
6
- self.name = name
7
- self.role = role
8
- self.characteristics = characteristics
9
- self.tasks = []
10
-
11
- def get_description(self) -> str:
12
- characteristics_str = ", ".join(self.characteristics)
13
- return (f"Person: {self.name}\n"
14
- f"{self.role.get_description()}\n"
15
- f"Characteristics: {characteristics_str}")
16
-
17
- def __str__(self) -> str:
18
- return f"{self.name} ({self.role.name})"
19
-
20
- def assign_task(self, task):
21
- if task not in self.tasks:
22
- self.tasks.append(task)
23
-
24
- def unassign_task(self, task):
25
- if task in self.tasks:
26
- self.tasks.remove(task)
27
-
28
- def get_tasks(self):
29
- return self.tasks
autobyteus/person/role.py DELETED
@@ -1,14 +0,0 @@
1
- from typing import List
2
-
3
- class Role:
4
- def __init__(self, name: str, skills: List[str], responsibilities: List[str]):
5
- self.name = name
6
- self.skills = skills
7
- self.responsibilities = responsibilities
8
-
9
- def get_description(self) -> str:
10
- skills_str = ", ".join(self.skills)
11
- responsibilities_str = ", ".join(self.responsibilities)
12
- return (f"Role: {self.name}\n"
13
- f"Skills: {skills_str}\n"
14
- f"Responsibilities: {responsibilities_str}")
@@ -1,149 +0,0 @@
1
- import os
2
- import json
3
- import logging
4
- import aiohttp
5
- from typing import Optional, TYPE_CHECKING, Any, Dict, List
6
-
7
- from autobyteus.tools.base_tool import BaseTool
8
- from autobyteus.tools.tool_config import ToolConfig
9
- from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
- from autobyteus.tools.tool_category import ToolCategory
11
-
12
- if TYPE_CHECKING:
13
- from autobyteus.agent.context import AgentContext
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
- class GoogleSearch(BaseTool):
18
- """
19
- Performs a Google search using the Serper.dev API and returns a structured summary of the results.
20
- This tool requires a Serper API key, which should be set in the SERPER_API_KEY environment variable.
21
- """
22
- CATEGORY = ToolCategory.WEB
23
- API_URL = "https://google.serper.dev/search"
24
-
25
- def __init__(self, config: Optional[ToolConfig] = None):
26
- super().__init__(config=config)
27
- self.api_key: Optional[str] = None
28
-
29
- if config:
30
- self.api_key = config.get('api_key')
31
-
32
- if not self.api_key:
33
- self.api_key = os.getenv("SERPER_API_KEY")
34
-
35
- if not self.api_key:
36
- raise ValueError(
37
- "GoogleSearch tool requires a Serper API key. "
38
- "Please provide it via the 'api_key' config parameter or set the 'SERPER_API_KEY' environment variable."
39
- )
40
- logger.debug("GoogleSearch (API-based) tool initialized.")
41
-
42
- @classmethod
43
- def get_name(cls) -> str:
44
- return "GoogleSearch"
45
-
46
- @classmethod
47
- def get_description(cls) -> str:
48
- return (
49
- "Searches Google for a given query using the Serper API. "
50
- "Returns a concise, structured summary of search results, including direct answers and top organic links."
51
- )
52
-
53
- @classmethod
54
- def get_argument_schema(cls) -> Optional[ParameterSchema]:
55
- schema = ParameterSchema()
56
- schema.add_parameter(ParameterDefinition(
57
- name="query",
58
- param_type=ParameterType.STRING,
59
- description="The search query string.",
60
- required=True
61
- ))
62
- schema.add_parameter(ParameterDefinition(
63
- name="num_results",
64
- param_type=ParameterType.INTEGER,
65
- description="The number of organic search results to return.",
66
- required=False,
67
- default_value=5,
68
- min_value=1,
69
- max_value=10
70
- ))
71
- return schema
72
-
73
- @classmethod
74
- def get_config_schema(cls) -> Optional[ParameterSchema]:
75
- schema = ParameterSchema()
76
- schema.add_parameter(ParameterDefinition(
77
- name="api_key",
78
- param_type=ParameterType.STRING,
79
- description="The API key for the Serper.dev service. Overrides the SERPER_API_KEY environment variable.",
80
- required=False
81
- ))
82
- return schema
83
-
84
- def _format_results(self, data: Dict[str, Any]) -> str:
85
- """Formats the JSON response from Serper into a clean string for an LLM."""
86
- summary_parts = []
87
-
88
- # 1. Answer Box (most important for direct questions)
89
- if "answerBox" in data:
90
- answer_box = data["answerBox"]
91
- title = answer_box.get("title", "")
92
- snippet = answer_box.get("snippet") or answer_box.get("answer")
93
- summary_parts.append(f"Direct Answer for '{title}':\n{snippet}")
94
-
95
- # 2. Knowledge Graph (for entity information)
96
- if "knowledgeGraph" in data:
97
- kg = data["knowledgeGraph"]
98
- title = kg.get("title", "")
99
- description = kg.get("description")
100
- summary_parts.append(f"Summary for '{title}':\n{description}")
101
-
102
- # 3. Organic Results (the main search links)
103
- if "organic" in data and data["organic"]:
104
- organic_results = data["organic"]
105
- results_str = "\n".join(
106
- f"{i+1}. {result.get('title', 'No Title')}\n"
107
- f" Link: {result.get('link', 'No Link')}\n"
108
- f" Snippet: {result.get('snippet', 'No Snippet')}"
109
- for i, result in enumerate(organic_results)
110
- )
111
- summary_parts.append(f"Search Results:\n{results_str}")
112
-
113
- if not summary_parts:
114
- return "No relevant information found for the query."
115
-
116
- return "\n\n---\n\n".join(summary_parts)
117
-
118
-
119
- async def _execute(self, context: 'AgentContext', query: str, num_results: int = 5) -> str:
120
- logger.info(f"Executing GoogleSearch (API) for agent {context.agent_id} with query: '{query}'")
121
-
122
- headers = {
123
- 'X-API-KEY': self.api_key,
124
- 'Content-Type': 'application/json'
125
- }
126
- payload = json.dumps({
127
- "q": query,
128
- "num": num_results
129
- })
130
-
131
- try:
132
- async with aiohttp.ClientSession() as session:
133
- async with session.post(self.API_URL, headers=headers, data=payload) as response:
134
- if response.status == 200:
135
- data = await response.json()
136
- return self._format_results(data)
137
- else:
138
- error_text = await response.text()
139
- logger.error(
140
- f"Serper API returned a non-200 status code: {response.status}. "
141
- f"Response: {error_text}"
142
- )
143
- raise RuntimeError(f"API request failed with status {response.status}: {error_text}")
144
- except aiohttp.ClientError as e:
145
- logger.error(f"Network error during GoogleSearch API call: {e}", exc_info=True)
146
- raise RuntimeError(f"A network error occurred: {e}")
147
- except Exception as e:
148
- logger.error(f"An unexpected error occurred in GoogleSearch tool: {e}", exc_info=True)
149
- raise
@@ -1,99 +0,0 @@
1
- import os
2
- import aiohttp
3
- import logging
4
- from datetime import datetime
5
- from typing import Optional, TYPE_CHECKING, Any
6
-
7
- from autobyteus.tools.base_tool import BaseTool
8
- from autobyteus.tools.tool_config import ToolConfig
9
- from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
- from autobyteus.tools.tool_category import ToolCategory
11
- from PIL import Image
12
- from io import BytesIO
13
- from autobyteus.utils.file_utils import get_default_download_folder
14
- from autobyteus.events.event_types import EventType
15
-
16
- if TYPE_CHECKING:
17
- from autobyteus.agent.context import AgentContext
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
- class ImageDownloader(BaseTool):
22
- CATEGORY = ToolCategory.WEB
23
- supported_formats = ['.jpeg', '.jpg', '.gif', '.png', '.webp']
24
-
25
- def __init__(self, config: Optional[ToolConfig] = None):
26
- super().__init__(config=config)
27
-
28
- custom_download_folder = None
29
- if config:
30
- custom_download_folder = config.get('custom_download_folder')
31
-
32
- self.default_download_folder = get_default_download_folder()
33
- self.download_folder = custom_download_folder or self.default_download_folder
34
- self.last_downloaded_image = None
35
-
36
- # Explicitly subscribe the handler in the constructor
37
- self.subscribe(EventType.WEIBO_POST_COMPLETED, self.on_weibo_post_completed)
38
-
39
- logger.debug(f"ImageDownloader initialized with download_folder: {self.download_folder}")
40
-
41
- @classmethod
42
- def get_description(cls) -> str:
43
- return f"Downloads an image from a given URL. Supported formats: {', '.join(format.upper()[1:] for format in cls.supported_formats)}."
44
-
45
- @classmethod
46
- def get_argument_schema(cls) -> Optional[ParameterSchema]:
47
- schema = ParameterSchema()
48
- schema.add_parameter(ParameterDefinition(name="url", param_type=ParameterType.STRING, description=f"A direct URL to an image file (must end with {', '.join(cls.supported_formats)}).", required=True))
49
- schema.add_parameter(ParameterDefinition(name="folder", param_type=ParameterType.STRING, description="Optional. Custom directory path to save this specific image. Overrides instance default.", required=False))
50
- return schema
51
-
52
- @classmethod
53
- def get_config_schema(cls) -> Optional[ParameterSchema]:
54
- schema = ParameterSchema()
55
- schema.add_parameter(ParameterDefinition(name="custom_download_folder", param_type=ParameterType.STRING, description="Custom directory path where downloaded images will be saved by default.", required=False, default_value=None))
56
- return schema
57
-
58
- async def _execute(self, context: 'AgentContext', url: str, folder: Optional[str] = None) -> str:
59
- current_download_folder = folder or self.download_folder
60
- if not any(url.lower().endswith(fmt) for fmt in self.supported_formats):
61
- raise ValueError(f"Unsupported image format. URL must end with one of: {', '.join(self.supported_formats)}.")
62
-
63
- try:
64
- async with aiohttp.ClientSession() as session:
65
- async with session.get(url) as response:
66
- response.raise_for_status()
67
- image_bytes = await response.read()
68
-
69
- with Image.open(BytesIO(image_bytes)) as img:
70
- img.verify()
71
-
72
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
73
- extension = os.path.splitext(url)[1].lower() or ".png"
74
-
75
- filename = f"downloaded_image_{timestamp}{extension}"
76
- filepath = os.path.join(current_download_folder, filename)
77
-
78
- os.makedirs(current_download_folder, exist_ok=True)
79
- with open(filepath, 'wb') as f:
80
- f.write(image_bytes)
81
-
82
- self.last_downloaded_image = filepath
83
- logger.info(f"The image is downloaded and stored at: {filepath}")
84
- self.emit(EventType.IMAGE_DOWNLOADED, image_path=filepath)
85
- return f"The image is downloaded and stored at: {filepath}"
86
- except Exception as e:
87
- logger.error(f"Error processing image from {url}: {str(e)}", exc_info=True)
88
- raise ValueError(f"Error processing image from {url}: {str(e)}")
89
-
90
- def on_weibo_post_completed(self): # No **kwargs needed due to intelligent dispatch
91
- if self.last_downloaded_image and os.path.exists(self.last_downloaded_image):
92
- try:
93
- os.remove(self.last_downloaded_image)
94
- logger.info(f"Removed downloaded image: {self.last_downloaded_image} after Weibo post.")
95
- except Exception as e:
96
- logger.error(f"Failed to remove downloaded image: {self.last_downloaded_image}. Error: {str(e)}", exc_info=True)
97
- else:
98
- logger.debug("No last downloaded image to remove or image file not found.")
99
- self.last_downloaded_image = None
@@ -1,89 +0,0 @@
1
- # This was top-level, keep it there.
2
- import os
3
- import logging
4
- import asyncio
5
- import requests
6
- from datetime import datetime
7
- from typing import TYPE_CHECKING, Optional
8
-
9
- from autobyteus.tools import tool
10
- from autobyteus.tools.tool_category import ToolCategory
11
- from autobyteus.utils.file_utils import get_default_download_folder
12
-
13
- if TYPE_CHECKING:
14
- from autobyteus.agent.context import AgentContext
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
- @tool(name="PDFDownloader", category=ToolCategory.WEB)
19
- async def pdf_downloader( # function name can be pdf_downloader
20
- context: 'AgentContext',
21
- url: str,
22
- folder: Optional[str] = None
23
- ) -> str:
24
- """
25
- Downloads a PDF file from a given URL and saves it locally.
26
- 'url' is the URL of the PDF.
27
- 'folder' (optional) is a custom directory to save the PDF. If not given,
28
- uses the system's default download folder. Validates Content-Type.
29
- """
30
- logger.debug(f"Functional PDFDownloader tool for agent {context.agent_id}, URL: {url}, Folder: {folder}")
31
-
32
- current_download_folder = folder if folder else get_default_download_folder()
33
-
34
- try:
35
- loop = asyncio.get_event_loop()
36
- response = await loop.run_in_executor(None, lambda: requests.get(url, stream=True, timeout=30))
37
- response.raise_for_status()
38
-
39
- content_type = response.headers.get('Content-Type', '').lower()
40
- if 'application/pdf' not in content_type:
41
- response.close()
42
- raise ValueError(f"The URL does not point to a PDF file. Content-Type: {content_type}")
43
-
44
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
45
- filename_from_header = None
46
- if 'Content-Disposition' in response.headers:
47
- import re
48
- match = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', response.headers['Content-Disposition'])
49
- if match: filename_from_header = match.group(1)
50
-
51
- if filename_from_header and filename_from_header.lower().endswith(".pdf"):
52
- import string
53
- valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
54
- filename_from_header = ''.join(c for c in filename_from_header if c in valid_chars)[:200]
55
- filename = f"{timestamp}_{filename_from_header}"
56
- else:
57
- filename = f"downloaded_pdf_{timestamp}.pdf"
58
-
59
- save_path = os.path.join(current_download_folder, filename)
60
- os.makedirs(current_download_folder, exist_ok=True)
61
-
62
- def download_and_save_sync():
63
- with open(save_path, 'wb') as file_handle:
64
- for chunk in response.iter_content(chunk_size=8192):
65
- file_handle.write(chunk)
66
- response.close()
67
-
68
- await loop.run_in_executor(None, download_and_save_sync)
69
-
70
- logger.info(f"PDF successfully downloaded and saved to {save_path}")
71
- return f"PDF successfully downloaded and saved to {save_path}"
72
- except requests.exceptions.Timeout:
73
- logger.error(f"Timeout downloading PDF from {url}", exc_info=True)
74
- return f"Error downloading PDF: Timeout occurred for URL {url}"
75
- except requests.exceptions.RequestException as e:
76
- logger.error(f"Error downloading PDF from {url}: {str(e)}", exc_info=True)
77
- return f"Error downloading PDF: {str(e)}"
78
- except ValueError as e:
79
- logger.error(f"Content type error for PDF from {url}: {str(e)}", exc_info=True)
80
- return str(e)
81
- except IOError as e:
82
- logger.error(f"Error saving PDF to {current_download_folder}: {str(e)}", exc_info=True)
83
- return f"Error saving PDF: {str(e)}"
84
- except Exception as e:
85
- logger.error(f"Unexpected error downloading PDF from {url}: {str(e)}", exc_info=True)
86
- return f"An unexpected error occurred: {str(e)}"
87
- finally:
88
- if 'response' in locals() and hasattr(response, 'close') and response.raw and not response.raw.closed:
89
- response.close()