From 3a034476f4b976624ab950a5bd4f62dc583a04cc Mon Sep 17 00:00:00 2001 From: ascender1729 Date: Sun, 30 Mar 2025 10:27:13 +0530 Subject: [PATCH 01/73] Improve tool agent error handling with custom exceptions and retry mechanism --- swarms/agents/exceptions.py | 32 +++ swarms/agents/tool_agent.py | 339 ++++++++++++++++++++------------ tests/agents/test_tool_agent.py | 127 ++++++++++++ 3 files changed, 372 insertions(+), 126 deletions(-) create mode 100644 swarms/agents/exceptions.py diff --git a/swarms/agents/exceptions.py b/swarms/agents/exceptions.py new file mode 100644 index 00000000..a07fa88f --- /dev/null +++ b/swarms/agents/exceptions.py @@ -0,0 +1,32 @@ +from typing import Any, Dict, Optional + +class ToolAgentError(Exception): + """Base exception for all tool agent errors.""" + def __init__(self, message: str, details: Optional[Dict[str, Any]] = None): + self.message = message + self.details = details or {} + super().__init__(self.message) + +class ToolExecutionError(ToolAgentError): + """Raised when a tool fails to execute.""" + def __init__(self, tool_name: str, error: Exception, details: Optional[Dict[str, Any]] = None): + message = f"Failed to execute tool '{tool_name}': {str(error)}" + super().__init__(message, details) + +class ToolValidationError(ToolAgentError): + """Raised when tool parameters fail validation.""" + def __init__(self, tool_name: str, param_name: str, error: str, details: Optional[Dict[str, Any]] = None): + message = f"Validation error for tool '{tool_name}' parameter '{param_name}': {error}" + super().__init__(message, details) + +class ToolNotFoundError(ToolAgentError): + """Raised when a requested tool is not found.""" + def __init__(self, tool_name: str, details: Optional[Dict[str, Any]] = None): + message = f"Tool '{tool_name}' not found" + super().__init__(message, details) + +class ToolParameterError(ToolAgentError): + """Raised when tool parameters are invalid.""" + def __init__(self, tool_name: str, error: str, details: Optional[Dict[str, Any]] = None): + message = f"Invalid parameters for tool '{tool_name}': {error}" + super().__init__(message, details) \ No newline at end of file diff --git a/swarms/agents/tool_agent.py b/swarms/agents/tool_agent.py index 2d19ec26..d69d6c2f 100644 --- a/swarms/agents/tool_agent.py +++ b/swarms/agents/tool_agent.py @@ -1,156 +1,243 @@ -from typing import Any, Optional, Callable -from swarms.tools.json_former import Jsonformer -from swarms.utils.loguru_logger import initialize_logger - -logger = initialize_logger(log_folder="tool_agent") - +from typing import List, Optional, Dict, Any, Callable +from loguru import logger +from swarms.agents.exceptions import ( + ToolAgentError, + ToolExecutionError, + ToolValidationError, + ToolNotFoundError, + ToolParameterError +) class ToolAgent: """ - Represents a tool agent that performs a specific task using a model and tokenizer. - - Args: - name (str): The name of the tool agent. - description (str): A description of the tool agent. - model (Any): The model used by the tool agent. - tokenizer (Any): The tokenizer used by the tool agent. - json_schema (Any): The JSON schema used by the tool agent. - *args: Variable length arguments. - **kwargs: Keyword arguments. - - Attributes: - name (str): The name of the tool agent. - description (str): A description of the tool agent. - model (Any): The model used by the tool agent. - tokenizer (Any): The tokenizer used by the tool agent. - json_schema (Any): The JSON schema used by the tool agent. - - Methods: - run: Runs the tool agent for a specific task. - - Raises: - Exception: If an error occurs while running the tool agent. - - - Example: - from transformers import AutoModelForCausalLM, AutoTokenizer - from swarms import ToolAgent - - - model = AutoModelForCausalLM.from_pretrained("databricks/dolly-v2-12b") - tokenizer = AutoTokenizer.from_pretrained("databricks/dolly-v2-12b") - - json_schema = { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "number"}, - "is_student": {"type": "boolean"}, - "courses": { - "type": "array", - "items": {"type": "string"} - } - } - } - - task = "Generate a person's information based on the following schema:" - agent = ToolAgent(model=model, tokenizer=tokenizer, json_schema=json_schema) - generated_data = agent.run(task) - - print(generated_data) + A wrapper class for vLLM that provides a similar interface to LiteLLM. + This class handles model initialization and inference using vLLM. """ def __init__( self, - name: str = "Function Calling Agent", - description: str = "Generates a function based on the input json schema and the task", - model: Any = None, - tokenizer: Any = None, - json_schema: Any = None, - max_number_tokens: int = 500, - parsing_function: Optional[Callable] = None, - llm: Any = None, + model_name: str = "meta-llama/Llama-2-7b-chat-hf", + system_prompt: Optional[str] = None, + stream: bool = False, + temperature: float = 0.5, + max_tokens: int = 4000, + max_completion_tokens: int = 4000, + tools_list_dictionary: Optional[List[Dict[str, Any]]] = None, + tool_choice: str = "auto", + parallel_tool_calls: bool = False, + retry_attempts: int = 3, + retry_interval: float = 1.0, *args, **kwargs, ): - super().__init__( - agent_name=name, - agent_description=description, - llm=llm, - **kwargs, + """ + Initialize the vLLM wrapper with the given parameters. + Args: + model_name (str): The name of the model to use. Defaults to "meta-llama/Llama-2-7b-chat-hf". + system_prompt (str, optional): The system prompt to use. Defaults to None. + stream (bool): Whether to stream the output. Defaults to False. + temperature (float): The temperature for sampling. Defaults to 0.5. + max_tokens (int): The maximum number of tokens to generate. Defaults to 4000. + max_completion_tokens (int): The maximum number of completion tokens. Defaults to 4000. + tools_list_dictionary (List[Dict[str, Any]], optional): List of available tools. Defaults to None. + tool_choice (str): How to choose tools. Defaults to "auto". + parallel_tool_calls (bool): Whether to allow parallel tool calls. Defaults to False. + retry_attempts (int): Number of retry attempts for failed operations. Defaults to 3. + retry_interval (float): Time to wait between retries in seconds. Defaults to 1.0. + """ + self.model_name = model_name + self.system_prompt = system_prompt + self.stream = stream + self.temperature = temperature + self.max_tokens = max_tokens + self.max_completion_tokens = max_completion_tokens + self.tools_list_dictionary = tools_list_dictionary + self.tool_choice = tool_choice + self.parallel_tool_calls = parallel_tool_calls + self.retry_attempts = retry_attempts + self.retry_interval = retry_interval + + # Initialize vLLM + try: + self.llm = LLM(model=model_name, **kwargs) + self.sampling_params = SamplingParams( + temperature=temperature, + max_tokens=max_tokens, + ) + except Exception as e: + raise ToolExecutionError( + "model_initialization", + e, + {"model_name": model_name, "kwargs": kwargs} + ) + + def _validate_tool(self, tool_name: str, parameters: Dict[str, Any]) -> None: + """ + Validate tool parameters before execution. + Args: + tool_name (str): Name of the tool to validate + parameters (Dict[str, Any]): Parameters to validate + Raises: + ToolValidationError: If validation fails + """ + if not self.tools_list_dictionary: + raise ToolValidationError( + tool_name, + "parameters", + "No tools available for validation" + ) + + tool_spec = next( + (tool for tool in self.tools_list_dictionary if tool["name"] == tool_name), + None ) - self.name = name - self.description = description - self.model = model - self.tokenizer = tokenizer - self.json_schema = json_schema - self.max_number_tokens = max_number_tokens - self.parsing_function = parsing_function - - def run(self, task: str, *args, **kwargs): + + if not tool_spec: + raise ToolNotFoundError(tool_name) + + required_params = { + param["name"] for param in tool_spec["parameters"] + if param.get("required", True) + } + + missing_params = required_params - set(parameters.keys()) + if missing_params: + raise ToolParameterError( + tool_name, + f"Missing required parameters: {', '.join(missing_params)}" + ) + + def _execute_with_retry(self, func: Callable, *args, **kwargs) -> Any: """ - Run the tool agent for the specified task. + Execute a function with retry logic. + Args: + func (Callable): Function to execute + *args: Positional arguments for the function + **kwargs: Keyword arguments for the function + Returns: + Any: Result of the function execution + Raises: + ToolExecutionError: If all retry attempts fail + """ + last_error = None + for attempt in range(self.retry_attempts): + try: + return func(*args, **kwargs) + except Exception as e: + last_error = e + logger.warning( + f"Attempt {attempt + 1}/{self.retry_attempts} failed: {str(e)}" + ) + if attempt < self.retry_attempts - 1: + time.sleep(self.retry_interval) + raise ToolExecutionError( + func.__name__, + last_error, + {"attempts": self.retry_attempts} + ) + + def run(self, task: str, *args, **kwargs) -> str: + """ + Run the tool agent for the specified task. Args: task (str): The task to be performed by the tool agent. *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. - Returns: The output of the tool agent. - Raises: - Exception: If an error occurs during the execution of the tool agent. + ToolExecutionError: If an error occurs during execution. """ try: - if self.model: - logger.info(f"Running {self.name} for task: {task}") - self.toolagent = Jsonformer( - model=self.model, - tokenizer=self.tokenizer, - json_schema=self.json_schema, - llm=self.llm, - prompt=task, - max_number_tokens=self.max_number_tokens, - *args, - **kwargs, + if not self.llm: + raise ToolExecutionError( + "run", + Exception("LLM not initialized"), + {"task": task} ) - if self.parsing_function: - out = self.parsing_function(self.toolagent()) - else: - out = self.toolagent() - - return out - elif self.llm: - logger.info(f"Running {self.name} for task: {task}") - self.toolagent = Jsonformer( - json_schema=self.json_schema, - llm=self.llm, - prompt=task, - max_number_tokens=self.max_number_tokens, - *args, - **kwargs, - ) + logger.info(f"Running task: {task}") + + # Prepare the prompt + prompt = self._prepare_prompt(task) + + # Execute with retry logic + outputs = self._execute_with_retry( + self.llm.generate, + prompt, + self.sampling_params + ) + + response = outputs[0].outputs[0].text.strip() + return response - if self.parsing_function: - out = self.parsing_function(self.toolagent()) - else: - out = self.toolagent() + except Exception as error: + logger.error(f"Error running task: {error}") + raise ToolExecutionError( + "run", + error, + {"task": task, "args": args, "kwargs": kwargs} + ) - return out + def _prepare_prompt(self, task: str) -> str: + """ + Prepare the prompt for the given task. + Args: + task (str): The task to prepare the prompt for. + Returns: + str: The prepared prompt. + """ + if self.system_prompt: + return f"{self.system_prompt}\n\nUser: {task}\nAssistant:" + return f"User: {task}\nAssistant:" - else: - raise Exception( - "Either model or llm should be provided to the" - " ToolAgent" - ) + def __call__(self, task: str, *args, **kwargs) -> str: + """ + Call the model for the given task. + Args: + task (str): The task to run the model for. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + Returns: + str: The model's response. + """ + return self.run(task, *args, **kwargs) + + def batched_run(self, tasks: List[str], batch_size: int = 10) -> List[str]: + """ + Run the model for multiple tasks in batches. + Args: + tasks (List[str]): List of tasks to run. + batch_size (int): Size of each batch. Defaults to 10. + Returns: + List[str]: List of model responses. + Raises: + ToolExecutionError: If an error occurs during batch execution. + """ + logger.info(f"Running tasks in batches of size {batch_size}. Total tasks: {len(tasks)}") + results = [] + + try: + for i in range(0, len(tasks), batch_size): + batch = tasks[i:i + batch_size] + for task in batch: + logger.info(f"Running task: {task}") + try: + result = self.run(task) + results.append(result) + except ToolExecutionError as e: + logger.error(f"Failed to execute task '{task}': {e}") + results.append(f"Error: {str(e)}") + continue + + logger.info("Completed all tasks.") + return results except Exception as error: - logger.error( - f"Error running {self.name} for task: {task}" + logger.error(f"Error in batch execution: {error}") + raise ToolExecutionError( + "batched_run", + error, + {"tasks": tasks, "batch_size": batch_size} ) - raise error - - def __call__(self, task: str, *args, **kwargs): - return self.run(task, *args, **kwargs) diff --git a/tests/agents/test_tool_agent.py b/tests/agents/test_tool_agent.py index 691489c0..9f8344d0 100644 --- a/tests/agents/test_tool_agent.py +++ b/tests/agents/test_tool_agent.py @@ -1,8 +1,15 @@ from unittest.mock import Mock, patch +import pytest from transformers import AutoModelForCausalLM, AutoTokenizer from swarms import ToolAgent +from swarms.agents.exceptions import ( + ToolExecutionError, + ToolValidationError, + ToolNotFoundError, + ToolParameterError +) def test_tool_agent_init(): @@ -99,3 +106,123 @@ def test_tool_agent_init_with_kwargs(): agent.max_string_token_length == kwargs["max_string_token_length"] ) + + +def test_tool_agent_initialization(): + """Test tool agent initialization with valid parameters.""" + agent = ToolAgent( + model_name="test-model", + temperature=0.7, + max_tokens=1000 + ) + assert agent.model_name == "test-model" + assert agent.temperature == 0.7 + assert agent.max_tokens == 1000 + assert agent.retry_attempts == 3 + assert agent.retry_interval == 1.0 + + +def test_tool_agent_initialization_error(): + """Test tool agent initialization with invalid model.""" + with pytest.raises(ToolExecutionError) as exc_info: + ToolAgent(model_name="invalid-model") + assert "model_initialization" in str(exc_info.value) + + +def test_tool_validation(): + """Test tool parameter validation.""" + tools_list = [{ + "name": "test_tool", + "parameters": [ + {"name": "required_param", "required": True}, + {"name": "optional_param", "required": False} + ] + }] + + agent = ToolAgent(tools_list_dictionary=tools_list) + + # Test missing required parameter + with pytest.raises(ToolParameterError) as exc_info: + agent._validate_tool("test_tool", {}) + assert "Missing required parameters" in str(exc_info.value) + + # Test valid parameters + agent._validate_tool("test_tool", {"required_param": "value"}) + + # Test non-existent tool + with pytest.raises(ToolNotFoundError) as exc_info: + agent._validate_tool("non_existent_tool", {}) + assert "Tool 'non_existent_tool' not found" in str(exc_info.value) + + +def test_retry_mechanism(): + """Test retry mechanism for failed operations.""" + mock_llm = Mock() + mock_llm.generate.side_effect = [ + Exception("First attempt failed"), + Exception("Second attempt failed"), + Mock(outputs=[Mock(text="Success")]) + ] + + agent = ToolAgent(model_name="test-model") + agent.llm = mock_llm + + # Test successful retry + result = agent.run("test task") + assert result == "Success" + assert mock_llm.generate.call_count == 3 + + # Test all retries failing + mock_llm.generate.side_effect = Exception("All attempts failed") + with pytest.raises(ToolExecutionError) as exc_info: + agent.run("test task") + assert "All attempts failed" in str(exc_info.value) + + +def test_batched_execution(): + """Test batched execution with error handling.""" + mock_llm = Mock() + mock_llm.generate.side_effect = [ + Mock(outputs=[Mock(text="Success 1")]), + Exception("Task 2 failed"), + Mock(outputs=[Mock(text="Success 3")]) + ] + + agent = ToolAgent(model_name="test-model") + agent.llm = mock_llm + + tasks = ["Task 1", "Task 2", "Task 3"] + results = agent.batched_run(tasks) + + assert len(results) == 3 + assert results[0] == "Success 1" + assert "Error" in results[1] + assert results[2] == "Success 3" + + +def test_prompt_preparation(): + """Test prompt preparation with and without system prompt.""" + # Test without system prompt + agent = ToolAgent() + prompt = agent._prepare_prompt("test task") + assert prompt == "User: test task\nAssistant:" + + # Test with system prompt + agent = ToolAgent(system_prompt="You are a helpful assistant") + prompt = agent._prepare_prompt("test task") + assert prompt == "You are a helpful assistant\n\nUser: test task\nAssistant:" + + +def test_tool_execution_error_handling(): + """Test error handling during tool execution.""" + agent = ToolAgent(model_name="test-model") + agent.llm = None # Simulate uninitialized LLM + + with pytest.raises(ToolExecutionError) as exc_info: + agent.run("test task") + assert "LLM not initialized" in str(exc_info.value) + + # Test with invalid parameters + with pytest.raises(ToolExecutionError) as exc_info: + agent.run("test task", invalid_param="value") + assert "Error running task" in str(exc_info.value) From 310302619b980fdcb8033ac398fea2c8b24eded8 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Thu, 24 Jul 2025 23:15:36 +0530 Subject: [PATCH 02/73] updated footer link with button --- docs/mkdocs.yml | 94 +++++++++++++++++++++++------------- docs/overrides/main.html | 100 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 35 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 4027d032..21d5039b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -55,16 +55,6 @@ extra: link: https://www.linkedin.com/company/swarms-corp/ footer_links: - "Getting Started": - - title: "Installation" - url: "https://docs.swarms.world/en/latest/swarms/install/install/" - - title: "Quickstart" - url: "https://docs.swarms.world/en/latest/quickstart/" - - title: "Environment Setup" - url: "https://docs.swarms.world/en/latest/swarms/install/env/" - - title: "Basic Agent Example" - url: "https://docs.swarms.world/en/latest/swarms/examples/basic_agent/" - "Core Capabilities": - title: "Agents" @@ -82,29 +72,67 @@ extra: - title: "Swarm Router" url: "https://docs.swarms.world/en/latest/swarms/structs/swarm_router/" - - "Templates & Applications": - - title: "Examples Overview" - url: "https://docs.swarms.world/en/latest/examples/index/" - - title: "Cookbook" - url: "https://docs.swarms.world/en/latest/examples/cookbook_index/" - - title: "Templates" - url: "https://docs.swarms.world/en/latest/examples/templates/" - - title: "Paper Implementations" - url: "https://docs.swarms.world/en/latest/examples/paper_implementations/" + "Popular Multi-Agent Patterns": + - title: "Mixture of Agents (MoA)" + url: "https://docs.swarms.world/en/latest/swarms/structs/moa/" + - title: "Sequential Workflow" + url: "https://docs.swarms.world/en/latest/swarms/examples/sequential_example/" + - title: "Concurrent Workflow" + url: "https://docs.swarms.world/en/latest/swarms/examples/concurrent_workflow/" + - title: "Hierarchical Swarm" + url: "https://docs.swarms.world/en/latest/swarms/examples/hierarchical_swarm_example/" + - title: "Group Chat" + url: "https://docs.swarms.world/en/latest/swarms/examples/groupchat_example/" + - title: "Agent Rearrange" + url: "https://docs.swarms.world/en/latest/swarms/structs/agent_rearrange/" + - title: "Deep Research Swarm" + url: "https://docs.swarms.world/en/latest/swarms/structs/deep_research_swarm/" + - title: "MALT Framework" + url: "https://docs.swarms.world/en/latest/swarms/structs/malt/" + + "Popular Tools & Integrations": + - title: "MCP (Model Context Protocol)" + url: "https://docs.swarms.world/en/latest/swarms/examples/agent_with_mcp/" + - title: "OpenAI Tools & Function Calling" + url: "https://docs.swarms.world/en/latest/swarms/examples/agent_structured_outputs/" + - title: "Web Search (Exa, Serper)" + url: "https://docs.swarms.world/en/latest/swarms_tools/search/" + - title: "Browser Automation" + url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_of_browser_agents/" + - title: "Vision & Image Processing" + url: "https://docs.swarms.world/en/latest/swarms/examples/vision_processing/" + - title: "Crypto APIs (CoinGecko, HTX)" + url: "https://docs.swarms.world/en/latest/swarms/examples/agent_with_tools/" + - title: "Yahoo Finance" + url: "https://docs.swarms.world/en/latest/swarms/examples/yahoo_finance/" + - title: "Structured Outputs" + url: "https://docs.swarms.world/en/latest/swarms/agents/structured_outputs/" - - "Contributors": - - title: "Contributing" - url: "https://docs.swarms.world/en/latest/contributors/main/" - - title: "Code Style Guide" - url: "https://docs.swarms.world/en/latest/swarms/framework/code_cleanliness/" - - title: "Adding Documentation" - url: "https://docs.swarms.world/en/latest/contributors/docs/" - - title: "Bounty Program" - url: "https://docs.swarms.world/en/latest/corporate/bounty_program/" - - title: "Support" - url: "https://docs.swarms.world/en/latest/swarms/support/" + "Paper Implementations": + - title: "Paper Implementations Overview" + url: "https://docs.swarms.world/en/latest/examples/paper_implementations/" + - title: "MALT (Multi-Agent Learning Task)" + url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/malt_example.py" + - title: "MAI-DxO (Medical AI Diagnosis)" + url: "https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator" + - title: "AI-CoScientist Research Framework" + url: "https://github.com/The-Swarm-Corporation/AI-CoScientist" + - title: "Mixture of Agents (MoA)" + url: "https://github.com/kyegomez/swarms/blob/master/examples/multi_agent/mixture_of_agents_example.py" + - title: "Agent-as-a-Judge Evaluation" + url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/agent_judge_example.py" + - title: "Swarms of Browser Agents" + url: "https://docs.swarms.world/en/latest/examples/swarms_of_browser_agents/" + - title: "Swarms DAO Governance" + url: "https://docs.swarms.world/en/latest/examples/swarms_dao/" + - title: "Deep Research Swarm Examples" + url: "https://github.com/kyegomez/swarms/tree/master/examples/multi_agent/deep_research_examples" + - title: "Hierarchical Swarm Examples" + url: "https://github.com/kyegomez/swarms/tree/master/examples/multi_agent/hiearchical_swarm" + - title: "All Examples Repository" + url: "https://github.com/kyegomez/swarms/tree/master/examples" + - title: "Research Papers Collection" + url: "https://github.com/kyegomez/awesome-multi-agent-papers" "Community": - title: "Twitter" @@ -122,8 +150,6 @@ extra: - title: "Onboarding Session" url: "https://cal.com/swarms/swarms-onboarding-session" - - analytics: provider: google property: G-MPE9C65596 diff --git a/docs/overrides/main.html b/docs/overrides/main.html index c2828415..dd69212e 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -19,13 +19,22 @@ + {% if links|length > 4 %} + + {% endif %} {% endfor %} @@ -114,6 +123,45 @@ color: var(--md-accent-fg-color); } + /* Hidden footer items */ + .md-footer-links__item--hidden { + display: none; + } + + /* Toggle button styles */ + .md-footer-links__toggle { + background: none; + border: 0.05rem solid; + border-radius: 0.2rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 0.64rem; + font-weight: 500; + margin-top: 0.8rem; + padding: 0.4rem 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all 150ms ease; + width: auto; + min-width: fit-content; + } + + .md-footer-links__toggle:hover { + transform: translateY(-1px); + } + + .md-footer-links__toggle-icon { + font-size: 0.5rem; + transition: transform 200ms ease; + line-height: 1; + } + + .md-footer-links__toggle--expanded .md-footer-links__toggle-icon { + transform: rotate(180deg); + } + /* Light Mode (Default) */ [data-md-color-scheme="default"] .md-footer-custom { background: #ffffff; @@ -134,6 +182,18 @@ color: #1976d2; } + [data-md-color-scheme="default"] .md-footer-links__toggle { + border-color: #e1e5e9; + color: #636c76; + background: #ffffff; + } + + [data-md-color-scheme="default"] .md-footer-links__toggle:hover { + border-color: #1976d2; + color: #1976d2; + background: #f8f9fa; + } + /* Dark Mode (Slate) */ [data-md-color-scheme="slate"] .md-footer-custom { background: #1F2129; @@ -154,6 +214,18 @@ color: #42a5f5; } + [data-md-color-scheme="slate"] .md-footer-links__toggle { + border-color: #404040; + color: #9ca3af; + background: #1F2129; + } + + [data-md-color-scheme="slate"] .md-footer-links__toggle:hover { + border-color: #42a5f5; + color: #42a5f5; + background: #2a2d38; + } + /* Company Information Section - Base */ .md-footer-company { padding: 1.5rem 0; @@ -292,4 +364,30 @@ } } + + {% endblock %} \ No newline at end of file From 7de6a03640a99863f60a9cebc12a6321e56f6d5d Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Thu, 24 Jul 2025 23:21:48 +0530 Subject: [PATCH 03/73] updates to the footer layout ! --- docs/overrides/main.html | 65 ++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/docs/overrides/main.html b/docs/overrides/main.html index dd69212e..5acae004 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -79,8 +79,8 @@ .md-footer-links { display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 2rem; + grid-template-columns: repeat(5, 1fr); + gap: 1.2rem; max-width: 1220px; margin: 0 auto; } @@ -90,12 +90,12 @@ } .md-footer-links__title { - font-size: 0.64rem; + font-size: 0.6rem; font-weight: 700; - margin: 0 0 1rem; + margin: 0 0 0.8rem; text-transform: uppercase; letter-spacing: 0.1em; - padding-bottom: 0.4rem; + padding-bottom: 0.3rem; } .md-footer-links__list { @@ -106,14 +106,14 @@ .md-footer-links__item { margin: 0; - line-height: 1.8; + line-height: 1.6; } .md-footer-links__link { text-decoration: none; - font-size: 0.7rem; + font-size: 0.65rem; display: block; - padding: 0.1rem 0; + padding: 0.08rem 0; transition: color 125ms; border-radius: 0.1rem; } @@ -132,15 +132,15 @@ .md-footer-links__toggle { background: none; border: 0.05rem solid; - border-radius: 0.2rem; + border-radius: 0.15rem; cursor: pointer; display: flex; align-items: center; - gap: 0.3rem; - font-size: 0.64rem; + gap: 0.25rem; + font-size: 0.58rem; font-weight: 500; - margin-top: 0.8rem; - padding: 0.4rem 0.8rem; + margin-top: 0.6rem; + padding: 0.3rem 0.6rem; text-transform: uppercase; letter-spacing: 0.05em; transition: all 150ms ease; @@ -153,7 +153,7 @@ } .md-footer-links__toggle-icon { - font-size: 0.5rem; + font-size: 0.45rem; transition: transform 200ms ease; line-height: 1; } @@ -312,28 +312,45 @@ } /* Responsive Design */ - @media screen and (max-width: 76.1875em) { + @media screen and (min-width: 90em) { .md-footer-links { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + max-width: 1400px; gap: 1.5rem; } + } + + @media screen and (max-width: 76.1875em) { + .md-footer-links { + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + } .md-footer-custom { - padding: 2rem 0 1rem; + padding: 1.8rem 0 1rem; } } @media screen and (max-width: 59.9375em) { .md-footer-links { grid-template-columns: repeat(2, 1fr); - gap: 1.5rem; + gap: 1rem; + } + + .md-footer-links__title { + font-size: 0.62rem; + margin: 0 0 0.9rem; + } + + .md-footer-links__link { + font-size: 0.68rem; + padding: 0.1rem 0; } } @media screen and (max-width: 44.9375em) { .md-footer-links { grid-template-columns: 1fr; - gap: 1.5rem; + gap: 1.2rem; } .md-footer-custom { @@ -344,6 +361,16 @@ padding: 0 1rem; } + .md-footer-links__title { + font-size: 0.65rem; + margin: 0 0 1rem; + } + + .md-footer-links__link { + font-size: 0.7rem; + padding: 0.12rem 0; + } + /* Company section mobile styles */ .md-footer-company__content { flex-direction: column; From dfcd608525ee23699466dea9c0fb76a1005ba9c4 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 25 Jul 2025 10:29:14 +0530 Subject: [PATCH 04/73] fixed the links --- docs/mkdocs.yml | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 94d57d8d..df7314db 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -63,12 +63,6 @@ extra: url: "https://docs.swarms.world/en/latest/swarms/tools/tools_examples/" - title: "Multi-Agent Architectures" url: "https://docs.swarms.world/en/latest/swarms/concept/swarm_architectures/" - - title: "Sequential Workflow" - url: "https://docs.swarms.world/en/latest/swarms/structs/sequential_workflow/" - - title: "Concurrent Workflow" - url: "https://docs.swarms.world/en/latest/swarms/structs/concurrentworkflow/" - - title: "Hierarchical Swarm" - url: "https://docs.swarms.world/en/latest/swarms/structs/hierarchical_swarm/" - title: "Swarm Router" url: "https://docs.swarms.world/en/latest/swarms/structs/swarm_router/" @@ -85,10 +79,7 @@ extra: url: "https://docs.swarms.world/en/latest/swarms/examples/groupchat_example/" - title: "Agent Rearrange" url: "https://docs.swarms.world/en/latest/swarms/structs/agent_rearrange/" - - title: "Deep Research Swarm" - url: "https://docs.swarms.world/en/latest/swarms/structs/deep_research_swarm/" - - title: "MALT Framework" - url: "https://docs.swarms.world/en/latest/swarms/structs/malt/" + "Popular Tools & Integrations": - title: "MCP (Model Context Protocol)" @@ -109,28 +100,14 @@ extra: url: "https://docs.swarms.world/en/latest/swarms/agents/structured_outputs/" "Paper Implementations": - - title: "Paper Implementations Overview" - url: "https://docs.swarms.world/en/latest/examples/paper_implementations/" - title: "MALT (Multi-Agent Learning Task)" url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/malt_example.py" - title: "MAI-DxO (Medical AI Diagnosis)" url: "https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator" - title: "AI-CoScientist Research Framework" url: "https://github.com/The-Swarm-Corporation/AI-CoScientist" - - title: "Mixture of Agents (MoA)" - url: "https://github.com/kyegomez/swarms/blob/master/examples/multi_agent/mixture_of_agents_example.py" - title: "Agent-as-a-Judge Evaluation" url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/agent_judge_example.py" - - title: "Swarms of Browser Agents" - url: "https://docs.swarms.world/en/latest/examples/swarms_of_browser_agents/" - - title: "Swarms DAO Governance" - url: "https://docs.swarms.world/en/latest/examples/swarms_dao/" - - title: "Deep Research Swarm Examples" - url: "https://github.com/kyegomez/swarms/tree/master/examples/multi_agent/deep_research_examples" - - title: "Hierarchical Swarm Examples" - url: "https://github.com/kyegomez/swarms/tree/master/examples/multi_agent/hiearchical_swarm" - - title: "All Examples Repository" - url: "https://github.com/kyegomez/swarms/tree/master/examples" - title: "Research Papers Collection" url: "https://github.com/kyegomez/awesome-multi-agent-papers" From a5e39f289f6d298274a56d3d9da11e85bae7a4ce Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 25 Jul 2025 18:54:37 +0530 Subject: [PATCH 05/73] updates !! --- docs/mkdocs.yml | 94 +++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index df7314db..2ac7a052 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -56,76 +56,72 @@ extra: footer_links: + "Quick Start": + - title: "Installation" + url: "https://docs.swarms.world/en/latest/swarms/install/install/" + - title: "Quickstart Guide" + url: "https://docs.swarms.world/en/latest/quickstart/" + - title: "Environment Setup" + url: "https://docs.swarms.world/en/latest/swarms/install/env/" + - title: "Basic Agent Example" + url: "https://docs.swarms.world/en/latest/swarms/examples/basic_agent/" + "Core Capabilities": - title: "Agents" url: "https://docs.swarms.world/en/latest/swarms/structs/agent/" - - title: "Tools and MCP" - url: "https://docs.swarms.world/en/latest/swarms/tools/tools_examples/" - title: "Multi-Agent Architectures" url: "https://docs.swarms.world/en/latest/swarms/concept/swarm_architectures/" + - title: "LLM Providers" + url: "https://docs.swarms.world/en/latest/swarms/examples/model_providers/" - title: "Swarm Router" url: "https://docs.swarms.world/en/latest/swarms/structs/swarm_router/" - "Popular Multi-Agent Patterns": - - title: "Mixture of Agents (MoA)" - url: "https://docs.swarms.world/en/latest/swarms/structs/moa/" - - title: "Sequential Workflow" - url: "https://docs.swarms.world/en/latest/swarms/examples/sequential_example/" - - title: "Concurrent Workflow" - url: "https://docs.swarms.world/en/latest/swarms/examples/concurrent_workflow/" - - title: "Hierarchical Swarm" - url: "https://docs.swarms.world/en/latest/swarms/examples/hierarchical_swarm_example/" - - title: "Group Chat" - url: "https://docs.swarms.world/en/latest/swarms/examples/groupchat_example/" - - title: "Agent Rearrange" - url: "https://docs.swarms.world/en/latest/swarms/structs/agent_rearrange/" - - - "Popular Tools & Integrations": + "Advanced Concepts": + - title: "MALT (Multi-Agent Learning Task)" + url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/malt_example.py" + - title: "MAI-DxO (Medical AI Diagnosis)" + url: "https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator" + - title: "AI-CoScientist Research Framework" + url: "https://github.com/The-Swarm-Corporation/AI-CoScientist" + - title: "Agent-as-a-Judge Evaluation" + url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/agent_judge_example.py" + - title: "Research Papers Collection" + url: "https://github.com/kyegomez/awesome-multi-agent-papers" + + + "Popular Tools Integration": + - title: "Tools and MCP" + url: "https://docs.swarms.world/en/latest/swarms/tools/tools_examples/" - title: "MCP (Model Context Protocol)" url: "https://docs.swarms.world/en/latest/swarms/examples/agent_with_mcp/" - title: "OpenAI Tools & Function Calling" url: "https://docs.swarms.world/en/latest/swarms/examples/agent_structured_outputs/" - title: "Web Search (Exa, Serper)" url: "https://docs.swarms.world/en/latest/swarms_tools/search/" - - title: "Browser Automation" - url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_of_browser_agents/" - title: "Vision & Image Processing" url: "https://docs.swarms.world/en/latest/swarms/examples/vision_processing/" + - title: "Browser Automation" + url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_of_browser_agents/" - title: "Crypto APIs (CoinGecko, HTX)" url: "https://docs.swarms.world/en/latest/swarms/examples/agent_with_tools/" - title: "Yahoo Finance" url: "https://docs.swarms.world/en/latest/swarms/examples/yahoo_finance/" - - title: "Structured Outputs" - url: "https://docs.swarms.world/en/latest/swarms/agents/structured_outputs/" - - "Paper Implementations": - - title: "MALT (Multi-Agent Learning Task)" - url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/malt_example.py" - - title: "MAI-DxO (Medical AI Diagnosis)" - url: "https://github.com/The-Swarm-Corporation/Open-MAI-Dx-Orchestrator" - - title: "AI-CoScientist Research Framework" - url: "https://github.com/The-Swarm-Corporation/AI-CoScientist" - - title: "Agent-as-a-Judge Evaluation" - url: "https://github.com/kyegomez/swarms/blob/master/examples/single_agent/reasoning_agent_examples/agent_judge_example.py" - - title: "Research Papers Collection" - url: "https://github.com/kyegomez/awesome-multi-agent-papers" - "Community": - - title: "Twitter" - url: "https://twitter.com/swarms_corp" - - title: "Discord" - url: "https://discord.gg/jM3Z6M9uMq" - - title: "YouTube" - url: "https://www.youtube.com/channel/UC9yXyitkbU_WSy7bd_41SqQ" - - title: "LinkedIn" - url: "https://www.linkedin.com/company/the-swarm-corporation" - - title: "Blog" - url: "https://medium.com/@kyeg" - - title: "Events" - url: "https://lu.ma/5p2jnc2v" - - title: "Onboarding Session" - url: "https://cal.com/swarms/swarms-onboarding-session" + "Applications Use Case": + - title: "Examples Overview" + url: "https://docs.swarms.world/en/latest/examples/index/" + - title: "Templates & Applications" + url: "https://docs.swarms.world/en/latest/examples/templates/" + - title: "Financial Analysis Swarms" + url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_api_finance/" + - title: "Deep Research Swarm" + url: "https://docs.swarms.world/en/latest/swarms/structs/deep_research_swarm/" + - title: "Medical Diagnosis Systems" + url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_api_medical/" + - title: "DAO Governance" + url: "https://docs.swarms.world/en/latest/swarms/examples/swarms_dao/" + - title: "All Examples Repository" + url: "https://github.com/kyegomez/swarms/tree/master/examples" analytics: provider: google From cf0c19943e0ddb7a91020ba6b13d6d00e4d3aa5f Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:19:34 +0300 Subject: [PATCH 06/73] Add files via upload --- .../board_of_directors_decision_making.md | 885 +++++++++++++ .../board_of_directors_example.md | 466 +++++++ .../board_of_directors_roles.md | 1151 +++++++++++++++++ .../board_of_directors_swarm.md | 704 ++++++++++ .../board_of_directors_workflow.md | 908 +++++++++++++ .../structs/board_of_directors/index.md | 291 +++++ 6 files changed, 4405 insertions(+) create mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md create mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_example.md create mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_roles.md create mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_swarm.md create mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_workflow.md create mode 100644 docs/swarms/structs/board_of_directors/index.md diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md b/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md new file mode 100644 index 00000000..22a3cb35 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md @@ -0,0 +1,885 @@ +# Board of Directors Decision Making + +The Board of Directors decision-making process is a sophisticated, multi-layered system that ensures comprehensive analysis, balanced consideration, and effective consensus building. This process combines democratic principles with hierarchical authority to achieve optimal outcomes. + +## Decision-Making Framework + +### Overview of the Decision Process + +```mermaid +graph TD + A[Task Analysis] --> B[Expertise Assignment] + B --> C[Individual Analysis] + C --> D[Group Discussion] + D --> E[Proposal Development] + E --> F[Voting Process] + F --> G[Consensus Building] + G --> H{Consensus Achieved?} + H -->|Yes| I[Decision Finalization] + H -->|No| J[Conflict Resolution] + J --> K[Proposal Refinement] + K --> F + I --> L[Execution Planning] + L --> M[Implementation Oversight] + + style A fill:#e3f2fd + style I fill:#c8e6c9 + style H fill:#fff3e0 +``` + +**Diagram Explanation:** +This comprehensive decision-making framework shows the complete process from initial task analysis to final implementation oversight. The process begins with task analysis and expertise assignment, followed by individual analysis where each board member contributes their specialized knowledge. Group discussion facilitates information sharing and debate, leading to proposal development. The voting process and consensus building ensure democratic decision-making, with conflict resolution mechanisms for when consensus cannot be reached. Once consensus is achieved, decisions are finalized and execution planning begins, followed by implementation oversight. + +**Technical Implementation:** +```python +# Example: Decision-making framework implementation +class DecisionMakingFramework: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.decision_history = [] + self.consensus_threshold = config.get("consensus_threshold", 0.7) + self.max_voting_rounds = config.get("max_voting_rounds", 3) + + async def execute_decision_process(self, task): + """Execute the complete decision-making process""" + decision_result = { + "task": task, + "phases": [], + "final_decision": None, + "consensus_achieved": False, + "execution_plan": None + } + + # Phase 1: Task Analysis and Expertise Assignment + analysis_phase = await self.analyze_task_and_assign_expertise(task) + decision_result["phases"].append(analysis_phase) + + # Phase 2: Individual Analysis + individual_analysis = await self.conduct_individual_analysis(task, analysis_phase["expertise_assignments"]) + decision_result["phases"].append(individual_analysis) + + # Phase 3: Group Discussion + group_discussion = await self.facilitate_group_discussion(individual_analysis) + decision_result["phases"].append(group_discussion) + + # Phase 4: Proposal Development + proposal_development = await self.develop_proposals(group_discussion) + decision_result["phases"].append(proposal_development) + + # Phase 5: Voting and Consensus Building + consensus_result = await self.build_consensus(proposal_development["proposals"]) + decision_result["phases"].append(consensus_result) + + # Phase 6: Decision Finalization + if consensus_result["consensus_achieved"]: + finalization = await self.finalize_decision(consensus_result) + decision_result["phases"].append(finalization) + decision_result["final_decision"] = finalization["decision"] + decision_result["consensus_achieved"] = True + else: + # Handle conflict resolution + conflict_resolution = await self.resolve_conflicts(consensus_result) + decision_result["phases"].append(conflict_resolution) + decision_result["final_decision"] = conflict_resolution["decision"] + decision_result["consensus_achieved"] = False + + # Phase 7: Execution Planning + execution_planning = await self.plan_execution(decision_result["final_decision"]) + decision_result["phases"].append(execution_planning) + decision_result["execution_plan"] = execution_planning["plan"] + + # Store decision in history + self.decision_history.append(decision_result) + + return decision_result + + async def analyze_task_and_assign_expertise(self, task): + """Analyze task and assign expertise areas to board members""" + # Analyze task complexity and requirements + task_analysis = await self.analyze_task_complexity(task) + + # Identify required expertise areas + required_expertise = await self.identify_required_expertise(task_analysis) + + # Assign expertise areas to board members + expertise_assignments = await self.assign_expertise_to_members(required_expertise) + + return { + "phase": "task_analysis_and_expertise_assignment", + "task_analysis": task_analysis, + "required_expertise": required_expertise, + "expertise_assignments": expertise_assignments, + "timestamp": datetime.now().isoformat() + } + + async def conduct_individual_analysis(self, task, expertise_assignments): + """Conduct individual analysis by each board member""" + individual_analyses = {} + + for member_role, expertise_areas in expertise_assignments.items(): + member = self.board_members[member_role] + + # Conduct analysis based on assigned expertise + analysis = await member.analyze_task_areas(task, expertise_areas) + + individual_analyses[member_role] = { + "expertise_areas": expertise_areas, + "analysis": analysis, + "recommendations": analysis.get("recommendations", []), + "concerns": analysis.get("concerns", []), + "proposals": analysis.get("proposals", []) + } + + return { + "phase": "individual_analysis", + "analyses": individual_analyses, + "timestamp": datetime.now().isoformat() + } +``` + +## Voting Mechanisms + +### Weighted Voting System + +```mermaid +graph TD + A[Proposal Submission] --> B[Vote Collection] + B --> C[Weight Application] + C --> D[Score Calculation] + D --> E[Threshold Check] + E -->|Above Threshold| F[Consensus Achieved] + E -->|Below Threshold| G[Additional Rounds] + G --> H[Proposal Refinement] + H --> B + + subgraph "Voting Components" + I[Individual Votes] + J[Role Weights] + K[Consensus Threshold] + L[Conflict Resolution] + end + + B --> I + C --> J + E --> K + G --> L +``` + +**Diagram Explanation:** +This diagram illustrates the weighted voting system used by the Board of Directors. The process begins with proposal submission, followed by vote collection from all board members. Each vote is weighted according to the member's role and authority level. Scores are calculated using the weighted voting formula, and results are checked against the consensus threshold. If the threshold is met, consensus is achieved. If not, additional voting rounds with proposal refinement are conducted until consensus is reached or maximum rounds are exceeded. + +**Technical Implementation:** +```python +# Example: Weighted voting system implementation +class WeightedVotingSystem: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.voting_weights = { + "CHAIRMAN": 1.5, + "VICE_CHAIRMAN": 1.2, + "EXECUTIVE_DIRECTOR": 1.5, + "SECRETARY": 1.0, + "TREASURER": 1.0, + "MEMBER": 1.0 + } + self.consensus_threshold = config.get("consensus_threshold", 0.7) + self.max_voting_rounds = config.get("max_voting_rounds", 3) + + async def conduct_weighted_voting(self, proposals): + """Conduct weighted voting on proposals""" + voting_result = { + "rounds": [], + "final_decision": None, + "consensus_achieved": False, + "voting_summary": {} + } + + current_proposals = proposals + round_number = 1 + + while round_number <= self.max_voting_rounds: + # Collect votes from all board members + votes = await self.collect_votes(current_proposals) + + # Apply voting weights + weighted_votes = self.apply_voting_weights(votes) + + # Calculate scores + scores = self.calculate_weighted_scores(weighted_votes, current_proposals) + + # Check consensus threshold + consensus_check = self.check_consensus_threshold(scores) + + round_result = { + "round": round_number, + "votes": votes, + "weighted_votes": weighted_votes, + "scores": scores, + "consensus_achieved": consensus_check["achieved"], + "winning_proposal": consensus_check["winning_proposal"] + } + + voting_result["rounds"].append(round_result) + + if consensus_check["achieved"]: + voting_result["final_decision"] = consensus_check["winning_proposal"] + voting_result["consensus_achieved"] = True + break + + # Refine proposals for next round + current_proposals = await self.refine_proposals(current_proposals, round_result) + round_number += 1 + + # Generate voting summary + voting_result["voting_summary"] = self.generate_voting_summary(voting_result["rounds"]) + + return voting_result + + async def collect_votes(self, proposals): + """Collect votes from all board members""" + votes = {} + + for member_role, member in self.board_members.items(): + # Each member votes on all proposals + member_votes = await member.vote_on_proposals(proposals) + + votes[member_role] = { + "proposal_scores": member_votes["scores"], + "rationale": member_votes["rationale"], + "confidence_level": member_votes.get("confidence_level", 1.0), + "timestamp": datetime.now().isoformat() + } + + return votes + + def apply_voting_weights(self, votes): + """Apply voting weights to member votes""" + weighted_votes = {} + + for member_role, vote_data in votes.items(): + weight = self.voting_weights.get(member_role, 1.0) + + weighted_scores = {} + for proposal_id, score in vote_data["proposal_scores"].items(): + weighted_scores[proposal_id] = score * weight + + weighted_votes[member_role] = { + "original_scores": vote_data["proposal_scores"], + "weighted_scores": weighted_scores, + "weight": weight, + "rationale": vote_data["rationale"], + "confidence_level": vote_data["confidence_level"] + } + + return weighted_votes + + def calculate_weighted_scores(self, weighted_votes, proposals): + """Calculate final weighted scores for each proposal""" + proposal_scores = {} + + for proposal in proposals: + proposal_id = proposal["id"] + total_weighted_score = 0 + total_weight = 0 + vote_count = 0 + + for member_role, vote_data in weighted_votes.items(): + if proposal_id in vote_data["weighted_scores"]: + weighted_score = vote_data["weighted_scores"][proposal_id] + weight = vote_data["weight"] + + total_weighted_score += weighted_score + total_weight += weight + vote_count += 1 + + # Calculate average weighted score + if total_weight > 0: + final_score = total_weighted_score / total_weight + else: + final_score = 0 + + proposal_scores[proposal_id] = { + "final_score": final_score, + "total_weight": total_weight, + "vote_count": vote_count, + "consensus_percentage": final_score + } + + return proposal_scores + + def check_consensus_threshold(self, scores): + """Check if any proposal meets the consensus threshold""" + best_proposal = None + best_score = 0 + + for proposal_id, score_data in scores.items(): + if score_data["final_score"] > best_score: + best_score = score_data["final_score"] + best_proposal = proposal_id + + consensus_achieved = best_score >= self.consensus_threshold + + return { + "achieved": consensus_achieved, + "winning_proposal": best_proposal if consensus_achieved else None, + "best_score": best_score, + "threshold": self.consensus_threshold + } +``` + +## Consensus Building Process + +### Multi-Round Consensus Building + +```mermaid +flowchart TD + A[Initial Proposals] --> B[Round 1 Voting] + B --> C[Score Calculation] + C --> D{Consensus?} + D -->|Yes| E[Consensus Achieved] + D -->|No| F[Proposal Refinement] + F --> G[Round 2 Voting] + G --> H[Score Calculation] + H --> I{Consensus?} + I -->|Yes| J[Consensus Achieved] + I -->|No| K[Final Round] + K --> L[Round 3 Voting] + L --> M[Score Calculation] + M --> N{Consensus?} + N -->|Yes| O[Consensus Achieved] + N -->|No| P[Authority Decision] + + subgraph "Consensus Building Elements" + Q[Discussion Facilitation] + R[Conflict Resolution] + S[Proposal Synthesis] + T[Mediation Process] + end + + F --> Q + F --> R + F --> S + K --> T +``` + +**Diagram Explanation:** +This flowchart shows the multi-round consensus building process used by the Board of Directors. The process begins with initial proposals and proceeds through multiple voting rounds. After each round, scores are calculated and consensus is checked. If consensus is not achieved, proposals are refined through discussion facilitation, conflict resolution, proposal synthesis, and mediation processes. The process continues for up to three rounds, after which authority decision-making is used if consensus still cannot be reached. + +**Technical Implementation:** +```python +# Example: Consensus building system +class ConsensusBuildingSystem: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.max_rounds = config.get("max_consensus_rounds", 3) + self.consensus_threshold = config.get("consensus_threshold", 0.7) + self.discussion_facilitator = board_members.get("CHAIRMAN") + + async def build_consensus(self, initial_proposals): + """Build consensus through multiple rounds""" + consensus_result = { + "rounds": [], + "consensus_achieved": False, + "final_proposal": None, + "authority_decision": None + } + + current_proposals = initial_proposals + round_number = 1 + + while round_number <= self.max_rounds: + # Conduct voting round + voting_result = await self.conduct_voting_round(current_proposals, round_number) + + # Check consensus + if voting_result["consensus_achieved"]: + consensus_result["rounds"].append(voting_result) + consensus_result["consensus_achieved"] = True + consensus_result["final_proposal"] = voting_result["winning_proposal"] + break + + # If no consensus and not final round, refine proposals + if round_number < self.max_rounds: + refinement_result = await self.refine_proposals(current_proposals, voting_result) + consensus_result["rounds"].append(voting_result) + current_proposals = refinement_result["refined_proposals"] + else: + # Final round - use authority decision + authority_decision = await self.make_authority_decision(voting_result) + consensus_result["rounds"].append(voting_result) + consensus_result["authority_decision"] = authority_decision + consensus_result["final_proposal"] = authority_decision["selected_proposal"] + + round_number += 1 + + return consensus_result + + async def conduct_voting_round(self, proposals, round_number): + """Conduct a single voting round""" + round_result = { + "round": round_number, + "proposals": proposals, + "votes": {}, + "scores": {}, + "consensus_achieved": False, + "winning_proposal": None + } + + # Collect votes from all board members + for member_role, member in self.board_members.items(): + vote = await member.vote_on_proposals(proposals, round_number) + round_result["votes"][member_role] = vote + + # Calculate weighted scores + weighted_scores = self.calculate_weighted_scores(round_result["votes"], proposals) + round_result["scores"] = weighted_scores + + # Check consensus + consensus_check = self.check_consensus(weighted_scores) + round_result["consensus_achieved"] = consensus_check["achieved"] + round_result["winning_proposal"] = consensus_check["winning_proposal"] + + return round_result + + async def refine_proposals(self, current_proposals, voting_result): + """Refine proposals based on voting results and discussion""" + refinement_result = { + "refined_proposals": [], + "discussion_summary": "", + "conflicts_resolved": [] + } + + # Analyze voting patterns + voting_analysis = self.analyze_voting_patterns(voting_result) + + # Identify areas of disagreement + disagreements = self.identify_disagreements(voting_result) + + # Facilitate discussion to resolve conflicts + discussion_result = await self.facilitate_discussion(disagreements, current_proposals) + refinement_result["discussion_summary"] = discussion_result["summary"] + refinement_result["conflicts_resolved"] = discussion_result["resolved_conflicts"] + + # Synthesize refined proposals + refined_proposals = await self.synthesize_proposals(current_proposals, discussion_result) + refinement_result["refined_proposals"] = refined_proposals + + return refinement_result + + async def facilitate_discussion(self, disagreements, proposals): + """Facilitate discussion to resolve disagreements""" + discussion_result = { + "summary": "", + "resolved_conflicts": [], + "new_insights": [] + } + + # Chairman facilitates discussion + if self.discussion_facilitator: + facilitation_result = await self.discussion_facilitator.facilitate_discussion( + disagreements, proposals + ) + + discussion_result["summary"] = facilitation_result["summary"] + discussion_result["resolved_conflicts"] = facilitation_result["resolved_conflicts"] + discussion_result["new_insights"] = facilitation_result["new_insights"] + + return discussion_result + + async def make_authority_decision(self, final_voting_result): + """Make authority decision when consensus cannot be reached""" + # Chairman makes final decision based on best available information + authority_decision = { + "decision_maker": "CHAIRMAN", + "decision_method": "authority_decision", + "selected_proposal": None, + "rationale": "", + "board_support_level": 0.0 + } + + # Analyze all proposals and select the best one + best_proposal = self.select_best_proposal(final_voting_result["scores"]) + authority_decision["selected_proposal"] = best_proposal["proposal_id"] + authority_decision["rationale"] = best_proposal["rationale"] + authority_decision["board_support_level"] = best_proposal["support_level"] + + return authority_decision +``` + +## Conflict Resolution Mechanisms + +### Structured Conflict Resolution + +```mermaid +graph TD + A[Conflict Identification] --> B[Conflict Analysis] + B --> C[Stakeholder Mapping] + C --> D[Root Cause Analysis] + D --> E[Resolution Strategy] + E --> F[Mediation Process] + F --> G[Compromise Facilitation] + G --> H[Agreement Building] + H --> I[Resolution Implementation] + + subgraph "Conflict Resolution Tools" + J[Mediation Techniques] + K[Compromise Strategies] + L[Consensus Building] + M[Authority Intervention] + end + + F --> J + G --> K + H --> L + I --> M +``` + +**Diagram Explanation:** +This diagram illustrates the structured conflict resolution process used by the Board of Directors. The process begins with conflict identification and proceeds through systematic analysis including stakeholder mapping and root cause analysis. A resolution strategy is developed, followed by mediation processes and compromise facilitation. The process culminates in agreement building and resolution implementation, using various tools including mediation techniques, compromise strategies, consensus building, and authority intervention when necessary. + +**Technical Implementation:** +```python +# Example: Conflict resolution system +class ConflictResolutionSystem: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.mediation_techniques = [ + "active_listening", + "interest_based_negotiation", + "brainstorming", + "consensus_building" + ] + self.resolution_strategies = [ + "compromise", + "collaboration", + "accommodation", + "authority_decision" + ] + + async def resolve_conflicts(self, conflicts, context): + """Resolve conflicts using structured approach""" + resolution_result = { + "conflicts": conflicts, + "resolution_process": [], + "final_resolution": None, + "implementation_plan": None + } + + for conflict in conflicts: + # Step 1: Analyze conflict + conflict_analysis = await self.analyze_conflict(conflict, context) + resolution_result["resolution_process"].append({ + "step": "conflict_analysis", + "conflict_id": conflict["id"], + "analysis": conflict_analysis + }) + + # Step 2: Map stakeholders + stakeholder_mapping = await self.map_stakeholders(conflict, context) + resolution_result["resolution_process"].append({ + "step": "stakeholder_mapping", + "conflict_id": conflict["id"], + "mapping": stakeholder_mapping + }) + + # Step 3: Analyze root causes + root_cause_analysis = await self.analyze_root_causes(conflict, context) + resolution_result["resolution_process"].append({ + "step": "root_cause_analysis", + "conflict_id": conflict["id"], + "analysis": root_cause_analysis + }) + + # Step 4: Develop resolution strategy + resolution_strategy = await self.develop_resolution_strategy( + conflict, conflict_analysis, stakeholder_mapping, root_cause_analysis + ) + resolution_result["resolution_process"].append({ + "step": "resolution_strategy", + "conflict_id": conflict["id"], + "strategy": resolution_strategy + }) + + # Step 5: Implement resolution + resolution_implementation = await self.implement_resolution( + conflict, resolution_strategy + ) + resolution_result["resolution_process"].append({ + "step": "resolution_implementation", + "conflict_id": conflict["id"], + "implementation": resolution_implementation + }) + + # Generate final resolution + resolution_result["final_resolution"] = await self.generate_final_resolution( + resolution_result["resolution_process"] + ) + + # Create implementation plan + resolution_result["implementation_plan"] = await self.create_implementation_plan( + resolution_result["final_resolution"] + ) + + return resolution_result + + async def analyze_conflict(self, conflict, context): + """Analyze the nature and scope of a conflict""" + analysis = { + "conflict_type": self.categorize_conflict(conflict), + "severity_level": self.assess_severity(conflict), + "scope": self.define_scope(conflict), + "impact_assessment": await self.assess_impact(conflict, context), + "urgency_level": self.assess_urgency(conflict) + } + + return analysis + + async def map_stakeholders(self, conflict, context): + """Map stakeholders involved in the conflict""" + stakeholders = { + "primary_stakeholders": [], + "secondary_stakeholders": [], + "influencers": [], + "decision_makers": [] + } + + # Identify stakeholders based on conflict type + if conflict["type"] == "resource_allocation": + stakeholders["primary_stakeholders"] = self.identify_resource_stakeholders(conflict) + elif conflict["type"] == "strategic_direction": + stakeholders["primary_stakeholders"] = self.identify_strategic_stakeholders(conflict) + elif conflict["type"] == "implementation_approach": + stakeholders["primary_stakeholders"] = self.identify_implementation_stakeholders(conflict) + + # Map stakeholder interests and positions + for stakeholder in stakeholders["primary_stakeholders"]: + stakeholder["interests"] = await self.identify_stakeholder_interests(stakeholder, conflict) + stakeholder["position"] = await self.identify_stakeholder_position(stakeholder, conflict) + stakeholder["influence_level"] = self.assess_influence_level(stakeholder) + + return stakeholders + + async def analyze_root_causes(self, conflict, context): + """Analyze root causes of the conflict""" + root_causes = { + "structural_causes": [], + "communication_causes": [], + "resource_causes": [], + "process_causes": [] + } + + # Analyze based on conflict type + if conflict["type"] == "resource_allocation": + root_causes["resource_causes"] = await self.analyze_resource_causes(conflict) + elif conflict["type"] == "strategic_direction": + root_causes["structural_causes"] = await self.analyze_structural_causes(conflict) + elif conflict["type"] == "implementation_approach": + root_causes["process_causes"] = await self.analyze_process_causes(conflict) + + # Identify communication issues + root_causes["communication_causes"] = await self.analyze_communication_causes(conflict) + + return root_causes + + async def develop_resolution_strategy(self, conflict, analysis, stakeholders, root_causes): + """Develop appropriate resolution strategy""" + strategy = { + "approach": self.select_resolution_approach(analysis, stakeholders), + "techniques": self.select_mediation_techniques(conflict, stakeholders), + "timeline": self.estimate_resolution_timeline(analysis), + "resources": self.identify_resolution_resources(analysis), + "success_criteria": self.define_success_criteria(conflict) + } + + return strategy + + async def implement_resolution(self, conflict, strategy): + """Implement the resolution strategy""" + implementation = { + "mediation_process": await self.conduct_mediation(conflict, strategy), + "compromise_facilitation": await self.facilitate_compromise(conflict, strategy), + "agreement_building": await self.build_agreement(conflict, strategy), + "implementation_oversight": await self.oversee_implementation(conflict, strategy) + } + + return implementation +``` + +## Decision Quality Assurance + +### Quality Assessment Framework + +```mermaid +graph TD + A[Decision Made] --> B[Quality Assessment] + B --> C[Completeness Check] + C --> D[Accuracy Verification] + D --> E[Feasibility Analysis] + E --> F[Risk Assessment] + F --> G[Stakeholder Impact] + G --> H[Quality Score] + H --> I{Quality Threshold?} + I -->|Yes| J[Decision Approved] + I -->|No| K[Decision Refinement] + K --> L[Additional Analysis] + L --> B + + subgraph "Quality Metrics" + M[Completeness Score] + N[Accuracy Score] + O[Feasibility Score] + P[Risk Score] + Q[Impact Score] + end + + C --> M + D --> N + E --> O + F --> P + G --> Q +``` + +**Diagram Explanation:** +This diagram shows the quality assessment framework used to ensure high-quality decisions. After a decision is made, it undergoes comprehensive quality assessment including completeness checks, accuracy verification, feasibility analysis, risk assessment, and stakeholder impact evaluation. A quality score is calculated based on these metrics, and the decision is either approved if it meets the quality threshold or sent back for refinement and additional analysis. + +**Technical Implementation:** +```python +# Example: Decision quality assurance system +class DecisionQualityAssurance: + def __init__(self, config): + self.config = config + self.quality_threshold = config.get("quality_threshold", 0.8) + self.quality_metrics = { + "completeness": {"weight": 0.2, "threshold": 0.8}, + "accuracy": {"weight": 0.25, "threshold": 0.85}, + "feasibility": {"weight": 0.2, "threshold": 0.8}, + "risk": {"weight": 0.15, "threshold": 0.7}, + "impact": {"weight": 0.2, "threshold": 0.8} + } + + async def assess_decision_quality(self, decision, context): + """Assess the quality of a decision""" + quality_assessment = { + "decision": decision, + "metrics": {}, + "overall_score": 0.0, + "threshold_met": False, + "recommendations": [] + } + + # Assess completeness + completeness_score = await self.assess_completeness(decision, context) + quality_assessment["metrics"]["completeness"] = completeness_score + + # Assess accuracy + accuracy_score = await self.assess_accuracy(decision, context) + quality_assessment["metrics"]["accuracy"] = accuracy_score + + # Assess feasibility + feasibility_score = await self.assess_feasibility(decision, context) + quality_assessment["metrics"]["feasibility"] = feasibility_score + + # Assess risk + risk_score = await self.assess_risk(decision, context) + quality_assessment["metrics"]["risk"] = risk_score + + # Assess stakeholder impact + impact_score = await self.assess_stakeholder_impact(decision, context) + quality_assessment["metrics"]["impact"] = impact_score + + # Calculate overall score + overall_score = self.calculate_overall_score(quality_assessment["metrics"]) + quality_assessment["overall_score"] = overall_score + + # Check if threshold is met + quality_assessment["threshold_met"] = overall_score >= self.quality_threshold + + # Generate recommendations + quality_assessment["recommendations"] = await self.generate_quality_recommendations( + quality_assessment["metrics"], overall_score + ) + + return quality_assessment + + async def assess_completeness(self, decision, context): + """Assess the completeness of the decision""" + completeness_factors = { + "all_aspects_covered": self.check_aspect_coverage(decision, context), + "stakeholder_consideration": self.check_stakeholder_consideration(decision, context), + "implementation_details": self.check_implementation_details(decision), + "resource_allocation": self.check_resource_allocation(decision), + "timeline_definition": self.check_timeline_definition(decision) + } + + # Calculate completeness score + completeness_score = sum(completeness_factors.values()) / len(completeness_factors) + + return { + "score": completeness_score, + "factors": completeness_factors, + "threshold_met": completeness_score >= self.quality_metrics["completeness"]["threshold"] + } + + async def assess_accuracy(self, decision, context): + """Assess the accuracy of the decision""" + accuracy_factors = { + "data_quality": self.assess_data_quality(decision, context), + "analysis_quality": self.assess_analysis_quality(decision, context), + "assumption_validity": self.assess_assumption_validity(decision, context), + "conclusion_soundness": self.assess_conclusion_soundness(decision, context) + } + + # Calculate accuracy score + accuracy_score = sum(accuracy_factors.values()) / len(accuracy_factors) + + return { + "score": accuracy_score, + "factors": accuracy_factors, + "threshold_met": accuracy_score >= self.quality_metrics["accuracy"]["threshold"] + } + + def calculate_overall_score(self, metrics): + """Calculate overall quality score""" + weighted_score = 0.0 + total_weight = 0.0 + + for metric_name, metric_data in metrics.items(): + weight = self.quality_metrics[metric_name]["weight"] + score = metric_data["score"] + + weighted_score += score * weight + total_weight += weight + + return weighted_score / total_weight if total_weight > 0 else 0.0 +``` + +## Best Practices for Decision Making + +### 1. Structured Approach +- Follow the defined decision-making framework +- Ensure all phases are completed thoroughly +- Document decisions and rationale + +### 2. Inclusive Participation +- Encourage all board members to contribute +- Value diverse perspectives and expertise +- Ensure fair representation in voting + +### 3. Quality Assurance +- Implement quality checkpoints throughout the process +- Assess decision quality before implementation +- Continuously monitor and improve decision-making processes + +### 4. Conflict Management +- Address conflicts promptly and constructively +- Use appropriate resolution strategies +- Maintain focus on organizational objectives + +### 5. Continuous Improvement +- Learn from previous decisions +- Refine decision-making processes based on outcomes +- Adapt to changing circumstances and requirements \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_example.md b/docs/swarms/structs/board_of_directors/board_of_directors_example.md new file mode 100644 index 00000000..34c5edc3 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/board_of_directors_example.md @@ -0,0 +1,466 @@ +# Board of Directors Example + +This example demonstrates how to use the Board of Directors swarm feature for democratic decision-making and collective intelligence in multi-agent systems. + +## Overview + +The Board of Directors Swarm provides a sophisticated alternative to single-director architectures by implementing collective decision-making through voting, consensus, and role-based leadership. This example shows how to create and configure a board for strategic decision-making scenarios. + +## Basic Setup + +### 1. Import Required Modules + +```python +from swarms import Agent +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, + BoardMember, + BoardMemberRole +) +from swarms.config.board_config import ( + enable_board_feature, + set_decision_threshold, + get_default_board_template +) +``` + +### 2. Enable Board Feature + +```python +# Enable the Board of Directors feature globally +enable_board_feature() + +# Set global decision threshold +set_decision_threshold(0.7) # 70% majority required +``` + +### 3. Create Board Members + +```python +# Create Chairman +chairman = Agent( + agent_name="Chairman", + agent_description="Chairman of the Board responsible for leading meetings and making final decisions", + model_name="gpt-4o-mini", + system_prompt="""You are the Chairman of the Board. Your responsibilities include: +1. Leading board meetings and discussions +2. Facilitating consensus among board members +3. Making final decisions when consensus cannot be reached +4. Ensuring all board members have an opportunity to contribute +5. Maintaining focus on the organization's goals and objectives + +You should be diplomatic, fair, and decisive in your leadership.""" +) + +# Create Vice Chairman +vice_chairman = Agent( + agent_name="Vice-Chairman", + agent_description="Vice Chairman who supports the Chairman and coordinates operations", + model_name="gpt-4o-mini", + system_prompt="""You are the Vice Chairman of the Board. Your responsibilities include: +1. Supporting the Chairman in leading board meetings +2. Coordinating operational activities and implementation +3. Ensuring effective communication between board members +4. Managing day-to-day board operations +5. Stepping in when the Chairman is unavailable + +You should be collaborative, organized, and supportive of the Chairman's leadership.""" +) + +# Create Secretary +secretary = Agent( + agent_name="Secretary", + agent_description="Secretary responsible for documentation and record keeping", + model_name="gpt-4o-mini", + system_prompt="""You are the Secretary of the Board. Your responsibilities include: +1. Documenting all board meetings and decisions +2. Maintaining accurate records and meeting minutes +3. Ensuring proper communication and notifications +4. Managing board documentation and archives +5. Supporting compliance and governance requirements + +You should be detail-oriented, organized, and thorough in your documentation.""" +) + +# Create Treasurer +treasurer = Agent( + agent_name="Treasurer", + agent_description="Treasurer responsible for financial oversight and resource management", + model_name="gpt-4o-mini", + system_prompt="""You are the Treasurer of the Board. Your responsibilities include: +1. Overseeing financial planning and budgeting +2. Monitoring resource allocation and utilization +3. Ensuring financial compliance and accountability +4. Providing financial insights for decision-making +5. Managing financial risk and controls + +You should be financially astute, analytical, and focused on value creation.""" +) + +# Create Executive Director +executive_director = Agent( + agent_name="Executive-Director", + agent_description="Executive Director responsible for strategic planning and operational oversight", + model_name="gpt-4o-mini", + system_prompt="""You are the Executive Director of the Board. Your responsibilities include: +1. Developing and implementing strategic plans +2. Overseeing operational performance and efficiency +3. Leading innovation and continuous improvement +4. Managing stakeholder relationships +5. Ensuring organizational effectiveness + +You should be strategic, results-oriented, and focused on organizational success.""" +) +``` + +### 4. Create BoardMember Objects + +```python +# Create BoardMember objects with roles, voting weights, and expertise areas +board_members = [ + BoardMember( + agent=chairman, + role=BoardMemberRole.CHAIRMAN, + voting_weight=1.5, + expertise_areas=["leadership", "strategy", "governance", "decision_making"] + ), + BoardMember( + agent=vice_chairman, + role=BoardMemberRole.VICE_CHAIRMAN, + voting_weight=1.2, + expertise_areas=["operations", "coordination", "communication", "implementation"] + ), + BoardMember( + agent=secretary, + role=BoardMemberRole.SECRETARY, + voting_weight=1.0, + expertise_areas=["documentation", "compliance", "record_keeping", "communication"] + ), + BoardMember( + agent=treasurer, + role=BoardMemberRole.TREASURER, + voting_weight=1.0, + expertise_areas=["finance", "budgeting", "risk_management", "resource_allocation"] + ), + BoardMember( + agent=executive_director, + role=BoardMemberRole.EXECUTIVE_DIRECTOR, + voting_weight=1.5, + expertise_areas=["strategy", "operations", "innovation", "performance_management"] + ) +] +``` + +### 5. Create Specialized Worker Agents + +```python +# Create specialized worker agents for different types of analysis +research_agent = Agent( + agent_name="Research-Specialist", + agent_description="Expert in market research, data analysis, and trend identification", + model_name="gpt-4o", + system_prompt="""You are a Research Specialist. Your responsibilities include: +1. Conducting comprehensive market research and analysis +2. Identifying trends, opportunities, and risks +3. Gathering and analyzing relevant data +4. Providing evidence-based insights and recommendations +5. Supporting strategic decision-making with research findings + +You should be thorough, analytical, and objective in your research.""" +) + +financial_agent = Agent( + agent_name="Financial-Analyst", + agent_description="Specialist in financial analysis, valuation, and investment assessment", + model_name="gpt-4o", + system_prompt="""You are a Financial Analyst. Your responsibilities include: +1. Conducting financial analysis and valuation +2. Assessing investment opportunities and risks +3. Analyzing financial performance and metrics +4. Providing financial insights and recommendations +5. Supporting financial decision-making + +You should be financially astute, analytical, and focused on value creation.""" +) + +technical_agent = Agent( + agent_name="Technical-Specialist", + agent_description="Expert in technical analysis, feasibility assessment, and implementation planning", + model_name="gpt-4o", + system_prompt="""You are a Technical Specialist. Your responsibilities include: +1. Conducting technical feasibility analysis +2. Assessing implementation requirements and challenges +3. Providing technical insights and recommendations +4. Supporting technical decision-making +5. Planning and coordinating technical implementations + +You should be technically proficient, practical, and solution-oriented.""" +) + +strategy_agent = Agent( + agent_name="Strategy-Specialist", + agent_description="Expert in strategic planning, competitive analysis, and business development", + model_name="gpt-4o", + system_prompt="""You are a Strategy Specialist. Your responsibilities include: +1. Developing strategic plans and initiatives +2. Conducting competitive analysis and market positioning +3. Identifying strategic opportunities and threats +4. Providing strategic insights and recommendations +5. Supporting strategic decision-making + +You should be strategic, forward-thinking, and focused on long-term success.""" +) +``` + +### 6. Initialize the Board of Directors Swarm + +```python +# Initialize the Board of Directors swarm with comprehensive configuration +board_swarm = BoardOfDirectorsSwarm( + name="Executive_Board_Swarm", + description="Executive board with specialized roles for strategic decision-making and collective intelligence", + board_members=board_members, + agents=[research_agent, financial_agent, technical_agent, strategy_agent], + max_loops=3, # Allow multiple iterations for complex analysis + verbose=True, # Enable detailed logging + decision_threshold=0.7, # 70% consensus required + enable_voting=True, # Enable voting mechanisms + enable_consensus=True, # Enable consensus building + max_workers=4, # Maximum parallel workers + output_type="dict" # Return results as dictionary +) +``` + +## Advanced Configuration + +### Custom Board Templates + +You can use pre-configured board templates for common use cases: + +```python +# Get a financial analysis board template +financial_board_template = get_default_board_template("financial_analysis") + +# Get a strategic planning board template +strategic_board_template = get_default_board_template("strategic_planning") + +# Get a technology assessment board template +tech_board_template = get_default_board_template("technology_assessment") +``` + +### Dynamic Role Assignment + +Automatically assign roles based on task requirements: + +```python +# Board members are automatically assigned roles based on expertise +board_swarm = BoardOfDirectorsSwarm( + board_members=board_members, + agents=agents, + auto_assign_roles=True, + role_mapping={ + "financial_analysis": ["Treasurer", "Financial_Member"], + "strategic_planning": ["Chairman", "Executive_Director"], + "technical_assessment": ["Technical_Member", "Executive_Director"], + "research_analysis": ["Research_Member", "Secretary"] + } +) +``` + +### Consensus Optimization + +Configure advanced consensus-building mechanisms: + +```python +# Enable advanced consensus features +board_swarm = BoardOfDirectorsSwarm( + board_members=board_members, + agents=agents, + enable_consensus=True, + consensus_timeout=300, # 5 minutes timeout + min_participation_rate=0.8, # 80% minimum participation + auto_fallback_to_chairman=True, # Chairman can make final decisions + consensus_rounds=3 # Maximum consensus building rounds +) +``` + +## Example Use Cases + +### 1. Strategic Investment Analysis + +```python +# Execute a complex strategic investment analysis +investment_task = """ +Analyze the strategic investment opportunity for a $50M Series B funding round in a +fintech startup. Consider market conditions, competitive landscape, financial projections, +technical feasibility, and strategic fit. Provide comprehensive recommendations including: +1. Investment recommendation (proceed/hold/decline) +2. Valuation analysis and suggested terms +3. Risk assessment and mitigation strategies +4. Strategic value and synergies +5. Implementation timeline and milestones +""" + +result = board_swarm.run(task=investment_task) +print("Investment Analysis Results:") +print(json.dumps(result, indent=2)) +``` + +### 2. Technology Strategy Development + +```python +# Develop a comprehensive technology strategy +tech_strategy_task = """ +Develop a comprehensive technology strategy for a mid-size manufacturing company +looking to digitize operations and implement Industry 4.0 technologies. Consider: +1. Current technology assessment and gaps +2. Technology roadmap and implementation plan +3. Investment requirements and ROI analysis +4. Risk assessment and mitigation strategies +5. Change management and training requirements +6. Competitive positioning and market advantages +""" + +result = board_swarm.run(task=tech_strategy_task) +print("Technology Strategy Results:") +print(json.dumps(result, indent=2)) +``` + +### 3. Market Entry Strategy + +```python +# Develop a market entry strategy for a new product +market_entry_task = """ +Develop a comprehensive market entry strategy for a new AI-powered productivity +software targeting the enterprise market. Consider: +1. Market analysis and opportunity assessment +2. Competitive landscape and positioning +3. Go-to-market strategy and channels +4. Pricing strategy and revenue model +5. Resource requirements and investment needs +6. Risk assessment and mitigation strategies +7. Success metrics and KPIs +""" + +result = board_swarm.run(task=market_entry_task) +print("Market Entry Strategy Results:") +print(json.dumps(result, indent=2)) +``` + +## Monitoring and Analysis + +### Board Performance Metrics + +```python +# Get board performance metrics +board_summary = board_swarm.get_board_summary() +print("Board Summary:") +print(f"Board Name: {board_summary['board_name']}") +print(f"Total Board Members: {board_summary['total_members']}") +print(f"Total Worker Agents: {board_summary['total_agents']}") +print(f"Decision Threshold: {board_summary['decision_threshold']}") +print(f"Max Loops: {board_summary['max_loops']}") + +# Display board member details +print("\nBoard Members:") +for member in board_summary['members']: + print(f"- {member['name']} (Role: {member['role']}, Weight: {member['voting_weight']})") + print(f" Expertise: {', '.join(member['expertise_areas'])}") + +# Display worker agent details +print("\nWorker Agents:") +for agent in board_summary['agents']: + print(f"- {agent['name']}: {agent['description']}") +``` + +### Decision Analysis + +```python +# Analyze decision-making patterns +if hasattr(result, 'get') and callable(result.get): + conversation_history = result.get('conversation_history', []) + + print(f"\nDecision Analysis:") + print(f"Total Messages: {len(conversation_history)}") + + # Count board member contributions + board_contributions = {} + for msg in conversation_history: + if 'Board' in msg.get('role', ''): + member_name = msg.get('agent_name', 'Unknown') + board_contributions[member_name] = board_contributions.get(member_name, 0) + 1 + + print(f"Board Member Contributions:") + for member, count in board_contributions.items(): + print(f"- {member}: {count} contributions") + + # Count agent executions + agent_executions = {} + for msg in conversation_history: + if any(agent.agent_name in msg.get('role', '') for agent in [research_agent, financial_agent, technical_agent, strategy_agent]): + agent_name = msg.get('agent_name', 'Unknown') + agent_executions[agent_name] = agent_executions.get(agent_name, 0) + 1 + + print(f"\nAgent Executions:") + for agent, count in agent_executions.items(): + print(f"- {agent}: {count} executions") +``` + +## Best Practices + +### 1. Role Definition +- Clearly define responsibilities for each board member +- Ensure expertise areas align with organizational needs +- Balance voting weights based on role importance + +### 2. Task Formulation +- Provide clear, specific task descriptions +- Include relevant context and constraints +- Specify expected outputs and deliverables + +### 3. Consensus Building +- Allow adequate time for discussion and consensus +- Encourage diverse perspectives and viewpoints +- Use structured decision-making processes + +### 4. Performance Monitoring +- Track decision quality and outcomes +- Monitor board member participation +- Analyze agent utilization and effectiveness + +### 5. Continuous Improvement +- Learn from each execution cycle +- Refine board composition and roles +- Optimize decision thresholds and processes + +## Troubleshooting + +### Common Issues + +1. **Consensus Failures**: Lower decision threshold or increase timeout +2. **Role Conflicts**: Ensure clear role definitions and responsibilities +3. **Agent Coordination**: Verify agent communication and task distribution +4. **Performance Issues**: Monitor resource usage and optimize configurations + +### Debug Commands + +```python +# Enable detailed logging +import logging +logging.basicConfig(level=logging.DEBUG) + +# Check board configuration +print(board_swarm.get_board_summary()) + +# Test individual components +for member in board_members: + print(f"Testing {member.agent.agent_name}...") + response = member.agent.run("Test message") + print(f"Response: {response[:100]}...") +``` + +## Conclusion + +This example demonstrates the comprehensive capabilities of the Board of Directors Swarm for democratic decision-making and collective intelligence. The feature provides a sophisticated alternative to single-director architectures, enabling more robust and well-considered decisions through voting, consensus, and role-based leadership. + +For more information, see the [Board of Directors Documentation](index.md) and [Configuration Guide](../config/board_config.md). \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_roles.md b/docs/swarms/structs/board_of_directors/board_of_directors_roles.md new file mode 100644 index 00000000..6f607c37 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/board_of_directors_roles.md @@ -0,0 +1,1151 @@ +# Board of Directors Roles + +The Board of Directors system implements a hierarchical structure with clearly defined roles, responsibilities, and voting weights. Each role is designed to contribute specific expertise and authority to the decision-making process, ensuring comprehensive analysis and balanced decision-making. + +## Role Hierarchy + +```mermaid +graph TD + A[Chairman
Voting Weight: 1.5
Final Authority] --> B[Vice Chairman
Voting Weight: 1.2
Operational Support] + A --> C[Executive Director
Voting Weight: 1.5
Strategic Planning] + A --> D[Secretary
Voting Weight: 1.0
Documentation] + A --> E[Treasurer
Voting Weight: 1.0
Financial Oversight] + A --> F[Member
Voting Weight: 1.0
Expertise Contribution] + + B --> G[Operational Coordination] + C --> H[Strategic Initiatives] + D --> I[Record Keeping] + E --> J[Resource Management] + F --> K[Specialized Input] + + style A fill:#ffeb3b + style B fill:#2196f3 + style C fill:#4caf50 + style D fill:#ff9800 + style E fill:#9c27b0 + style F fill:#607d8b +``` + +**Diagram Explanation:** +This hierarchical diagram shows the organizational structure of the Board of Directors, with the Chairman at the top having final authority and the highest voting weight (1.5). The Chairman directly supervises all other board members, each with specific responsibilities and voting weights. The Vice Chairman and Executive Director have elevated voting weights (1.2 and 1.5 respectively) due to their senior positions, while the Secretary, Treasurer, and general Members have standard voting weights (1.0). Each role contributes specialized expertise to different aspects of the decision-making process. + +**Technical Implementation:** +```python +# Example: Role hierarchy implementation +class BoardRoleHierarchy: + def __init__(self): + self.roles = { + "CHAIRMAN": { + "voting_weight": 1.5, + "authority_level": "FINAL", + "supervises": ["VICE_CHAIRMAN", "EXECUTIVE_DIRECTOR", "SECRETARY", "TREASURER", "MEMBER"], + "responsibilities": ["leadership", "final_decision", "consensus_facilitation"], + "override_capability": True + }, + "VICE_CHAIRMAN": { + "voting_weight": 1.2, + "authority_level": "SENIOR", + "supervises": ["MEMBER"], + "responsibilities": ["operational_support", "coordination", "implementation"], + "backup_for": "CHAIRMAN" + }, + "EXECUTIVE_DIRECTOR": { + "voting_weight": 1.5, + "authority_level": "SENIOR", + "supervises": ["MEMBER"], + "responsibilities": ["strategic_planning", "execution_oversight", "performance_management"], + "strategic_authority": True + }, + "SECRETARY": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["documentation", "record_keeping", "communication"], + "administrative_authority": True + }, + "TREASURER": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["financial_oversight", "resource_management", "budget_control"], + "financial_authority": True + }, + "MEMBER": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["expertise_contribution", "analysis", "voting"], + "specialized_expertise": True + } + } + + def get_role_info(self, role_name): + """Get detailed information about a specific role""" + return self.roles.get(role_name, {}) + + def get_voting_weight(self, role_name): + """Get voting weight for a specific role""" + role_info = self.get_role_info(role_name) + return role_info.get("voting_weight", 1.0) + + def get_authority_level(self, role_name): + """Get authority level for a specific role""" + role_info = self.get_role_info(role_name) + return role_info.get("authority_level", "STANDARD") + + def can_override_decision(self, role_name): + """Check if a role can override board decisions""" + role_info = self.get_role_info(role_name) + return role_info.get("override_capability", False) + + def get_supervision_chain(self, role_name): + """Get the supervision chain for a specific role""" + supervision_chain = [] + current_role = role_name + + while current_role: + role_info = self.get_role_info(current_role) + if role_info: + supervision_chain.append(current_role) + # Find who supervises this role + current_role = None + for supervisor, info in self.roles.items(): + if current_role in info.get("supervises", []): + current_role = supervisor + break + else: + break + + return supervision_chain +``` + +## Chairman Role + +### Primary Responsibilities + +```mermaid +graph TD + A[Chairman] --> B[Meeting Leadership] + A --> C[Final Decision Authority] + A --> D[Consensus Facilitation] + A --> E[Strategic Direction] + A --> F[Stakeholder Communication] + + B --> G[Agenda Setting] + B --> H[Discussion Management] + B --> I[Time Management] + + C --> J[Approval Authority] + C --> K[Override Capability] + C --> L[Final Sign-off] + + D --> M[Conflict Resolution] + D --> N[Mediation] + D --> O[Compromise Facilitation] + + E --> P[Vision Setting] + E --> Q[Goal Definition] + E --> R[Priority Establishment] + + F --> S[External Relations] + F --> T[Stakeholder Updates] + F --> U[Public Communication] +``` + +**Diagram Explanation:** +This diagram illustrates the comprehensive responsibilities of the Chairman role, showing how the Chairman serves as the central authority figure with five main areas of responsibility. Meeting Leadership involves agenda setting, discussion management, and time management. Final Decision Authority includes approval authority, override capability, and final sign-off responsibilities. Consensus Facilitation covers conflict resolution, mediation, and compromise facilitation. Strategic Direction encompasses vision setting, goal definition, and priority establishment. Stakeholder Communication includes external relations, stakeholder updates, and public communication. + +**Technical Implementation:** +```python +# Example: Chairman role implementation +class Chairman: + def __init__(self, name, config): + self.name = name + self.config = config + self.authority_level = "FINAL" + self.voting_weight = 1.5 + self.override_capability = True + self.meeting_history = [] + self.decision_history = [] + + async def lead_meeting(self, task, board_members): + """Lead a board meeting for task discussion and decision-making""" + meeting_result = { + "meeting_id": self.generate_meeting_id(), + "task": task, + "participants": list(board_members.keys()), + "phases": [], + "decisions": [], + "consensus_achieved": False + } + + # Phase 1: Meeting Opening + opening_phase = await self.open_meeting(task, board_members) + meeting_result["phases"].append(opening_phase) + + # Phase 2: Agenda Review and Task Presentation + presentation_phase = await self.present_task(task, board_members) + meeting_result["phases"].append(presentation_phase) + + # Phase 3: Discussion Facilitation + discussion_phase = await self.facilitate_discussion(task, board_members) + meeting_result["phases"].append(discussion_phase) + + # Phase 4: Consensus Building + consensus_phase = await self.build_consensus(discussion_phase["proposals"], board_members) + meeting_result["phases"].append(consensus_phase) + + # Phase 5: Decision Making + decision_phase = await self.make_final_decision(consensus_phase, board_members) + meeting_result["phases"].append(decision_phase) + + meeting_result["decisions"] = decision_phase["decisions"] + meeting_result["consensus_achieved"] = consensus_phase["consensus_achieved"] + + # Record meeting in history + self.meeting_history.append(meeting_result) + + return meeting_result + + async def open_meeting(self, task, board_members): + """Open the board meeting and set the agenda""" + agenda = await self.create_agenda(task) + + opening_statement = f""" + Meeting called to order by Chairman {self.name}. + + Task: {task['description']} + Priority: {task.get('priority', 'Normal')} + Timeline: {task.get('timeline', 'Not specified')} + + Agenda: + {self.format_agenda(agenda)} + + Board members present: {', '.join(board_members.keys())} + """ + + return { + "phase": "meeting_opening", + "opening_statement": opening_statement, + "agenda": agenda, + "participants": list(board_members.keys()), + "timestamp": datetime.now().isoformat() + } + + async def create_agenda(self, task): + """Create a structured agenda for the meeting""" + agenda = { + "items": [ + { + "item": "Task Presentation", + "duration": "10 minutes", + "responsible": "Chairman", + "description": "Present task details and requirements" + }, + { + "item": "Expertise Assignment", + "duration": "5 minutes", + "responsible": "Chairman", + "description": "Assign analysis areas to board members" + }, + { + "item": "Individual Analysis", + "duration": "15 minutes", + "responsible": "All Members", + "description": "Board members analyze assigned areas" + }, + { + "item": "Group Discussion", + "duration": "20 minutes", + "responsible": "All Members", + "description": "Open discussion and debate" + }, + { + "item": "Proposal Development", + "duration": "15 minutes", + "responsible": "All Members", + "description": "Develop and refine proposals" + }, + { + "item": "Voting and Consensus", + "duration": "10 minutes", + "responsible": "All Members", + "description": "Vote on proposals and reach consensus" + }, + { + "item": "Decision Finalization", + "duration": "5 minutes", + "responsible": "Chairman", + "description": "Finalize decisions and assign execution" + } + ], + "total_duration": "80 minutes", + "break_time": "10 minutes" + } + + return agenda + + async def facilitate_discussion(self, task, board_members): + """Facilitate discussion among board members""" + discussion_result = { + "phase": "discussion_facilitation", + "discussion_points": [], + "conflicts": [], + "resolutions": [], + "proposals": [] + } + + # Guide discussion through structured phases + for member_role, member in board_members.items(): + # Get member's analysis + analysis = await member.analyze_task(task) + discussion_result["discussion_points"].append({ + "member": member_role, + "analysis": analysis, + "timestamp": datetime.now().isoformat() + }) + + # Identify conflicts + conflicts = await self.identify_conflicts(analysis, discussion_result["discussion_points"]) + discussion_result["conflicts"].extend(conflicts) + + # Facilitate conflict resolution + for conflict in conflicts: + resolution = await self.resolve_conflict(conflict, board_members) + discussion_result["resolutions"].append(resolution) + + # Develop proposals based on discussion + proposals = await self.develop_proposals(discussion_result["discussion_points"]) + discussion_result["proposals"] = proposals + + return discussion_result + + async def build_consensus(self, proposals, board_members): + """Build consensus among board members on proposals""" + consensus_result = { + "phase": "consensus_building", + "voting_rounds": [], + "consensus_achieved": False, + "final_proposal": None + } + + current_proposals = proposals + round_number = 1 + + while round_number <= self.config.get("max_consensus_rounds", 3): + # Conduct voting round + voting_result = await self.conduct_voting_round(current_proposals, board_members, round_number) + consensus_result["voting_rounds"].append(voting_result) + + # Check if consensus achieved + if voting_result["consensus_achieved"]: + consensus_result["consensus_achieved"] = True + consensus_result["final_proposal"] = voting_result["winning_proposal"] + break + + # Refine proposals for next round + current_proposals = await self.refine_proposals(current_proposals, voting_result) + round_number += 1 + + return consensus_result + + async def make_final_decision(self, consensus_result, board_members): + """Make final decision based on consensus or exercise authority""" + if consensus_result["consensus_achieved"]: + # Consensus reached, approve the decision + decision = { + "type": "consensus_decision", + "proposal": consensus_result["final_proposal"], + "approval_method": "consensus", + "board_support": "unanimous" + } + else: + # No consensus, exercise chairman authority + decision = await self.exercise_authority(consensus_result, board_members) + + decision_result = { + "phase": "final_decision", + "decision": decision, + "execution_plan": await self.create_execution_plan(decision), + "timestamp": datetime.now().isoformat() + } + + # Record decision in history + self.decision_history.append(decision_result) + + return decision_result + + async def exercise_authority(self, consensus_result, board_members): + """Exercise chairman authority when consensus cannot be reached""" + # Analyze all proposals and voting results + proposal_analysis = await self.analyze_proposals(consensus_result["voting_rounds"]) + + # Make decision based on best interests and available information + final_decision = await self.select_best_proposal(proposal_analysis) + + return { + "type": "authority_decision", + "proposal": final_decision, + "approval_method": "chairman_authority", + "rationale": final_decision["rationale"], + "board_support": final_decision["support_level"] + } +``` + +### Chairman's Decision Flow + +```mermaid +flowchart TD + A[Task Received] --> B[Assess Complexity] + B --> C[Determine Board Composition] + C --> D[Set Meeting Agenda] + D --> E[Lead Discussion] + E --> F[Facilitate Consensus] + F --> G{Consensus Reached?} + G -->|Yes| H[Approve Decision] + G -->|No| I[Exercise Authority] + I --> J[Make Final Decision] + H --> K[Oversee Execution] + J --> K + K --> L[Monitor Progress] + L --> M[Review Results] + M --> N[Final Approval] +``` + +**Diagram Explanation:** +This flowchart shows the Chairman's decision-making process from task reception to final approval. The process begins with task assessment and board composition determination, followed by agenda setting and discussion leadership. The Chairman then facilitates consensus building, and if consensus is reached, approves the decision. If consensus cannot be achieved, the Chairman exercises their authority to make a final decision. The Chairman then oversees execution, monitors progress, reviews results, and provides final approval. + +**Technical Implementation:** +```python +# Example: Chairman decision flow implementation +class ChairmanDecisionFlow: + def __init__(self, chairman): + self.chairman = chairman + self.decision_states = { + "TASK_RECEIVED": "Initial state when task is received", + "COMPLEXITY_ASSESSED": "Task complexity has been evaluated", + "BOARD_COMPOSED": "Board members have been selected", + "AGENDA_SET": "Meeting agenda has been created", + "DISCUSSION_LEAD": "Discussion has been facilitated", + "CONSENSUS_FACILITATED": "Consensus building has been attempted", + "DECISION_MADE": "Final decision has been made", + "EXECUTION_OVERSIGHT": "Execution is being monitored", + "PROGRESS_MONITORED": "Progress is being tracked", + "RESULTS_REVIEWED": "Results have been reviewed", + "FINAL_APPROVAL": "Final approval has been given" + } + + async def execute_decision_flow(self, task): + """Execute the complete chairman decision flow""" + flow_state = { + "current_state": "TASK_RECEIVED", + "task": task, + "transitions": [], + "decisions": [], + "timestamps": {} + } + + # Step 1: Assess Complexity + complexity_assessment = await self.assess_task_complexity(task) + flow_state["transitions"].append({ + "from": "TASK_RECEIVED", + "to": "COMPLEXITY_ASSESSED", + "assessment": complexity_assessment + }) + flow_state["current_state"] = "COMPLEXITY_ASSESSED" + flow_state["timestamps"]["complexity_assessed"] = datetime.now().isoformat() + + # Step 2: Determine Board Composition + board_composition = await self.determine_board_composition(complexity_assessment) + flow_state["transitions"].append({ + "from": "COMPLEXITY_ASSESSED", + "to": "BOARD_COMPOSED", + "composition": board_composition + }) + flow_state["current_state"] = "BOARD_COMPOSED" + flow_state["timestamps"]["board_composed"] = datetime.now().isoformat() + + # Step 3: Set Meeting Agenda + meeting_agenda = await self.set_meeting_agenda(task, board_composition) + flow_state["transitions"].append({ + "from": "BOARD_COMPOSED", + "to": "AGENDA_SET", + "agenda": meeting_agenda + }) + flow_state["current_state"] = "AGENDA_SET" + flow_state["timestamps"]["agenda_set"] = datetime.now().isoformat() + + # Step 4: Lead Discussion + discussion_result = await self.lead_discussion(task, board_composition, meeting_agenda) + flow_state["transitions"].append({ + "from": "AGENDA_SET", + "to": "DISCUSSION_LEAD", + "discussion": discussion_result + }) + flow_state["current_state"] = "DISCUSSION_LEAD" + flow_state["timestamps"]["discussion_led"] = datetime.now().isoformat() + + # Step 5: Facilitate Consensus + consensus_result = await self.facilitate_consensus(discussion_result) + flow_state["transitions"].append({ + "from": "DISCUSSION_LEAD", + "to": "CONSENSUS_FACILITATED", + "consensus": consensus_result + }) + flow_state["current_state"] = "CONSENSUS_FACILITATED" + flow_state["timestamps"]["consensus_facilitated"] = datetime.now().isoformat() + + # Step 6: Make Decision + if consensus_result["consensus_achieved"]: + decision = await self.approve_consensus_decision(consensus_result) + else: + decision = await self.exercise_authority(consensus_result) + + flow_state["decisions"].append(decision) + flow_state["transitions"].append({ + "from": "CONSENSUS_FACILITATED", + "to": "DECISION_MADE", + "decision": decision + }) + flow_state["current_state"] = "DECISION_MADE" + flow_state["timestamps"]["decision_made"] = datetime.now().isoformat() + + # Step 7: Oversee Execution + execution_oversight = await self.oversee_execution(decision) + flow_state["transitions"].append({ + "from": "DECISION_MADE", + "to": "EXECUTION_OVERSIGHT", + "oversight": execution_oversight + }) + flow_state["current_state"] = "EXECUTION_OVERSIGHT" + flow_state["timestamps"]["execution_oversight"] = datetime.now().isoformat() + + # Step 8: Monitor Progress + progress_monitoring = await self.monitor_progress(execution_oversight) + flow_state["transitions"].append({ + "from": "EXECUTION_OVERSIGHT", + "to": "PROGRESS_MONITORED", + "progress": progress_monitoring + }) + flow_state["current_state"] = "PROGRESS_MONITORED" + flow_state["timestamps"]["progress_monitored"] = datetime.now().isoformat() + + # Step 9: Review Results + results_review = await self.review_results(progress_monitoring) + flow_state["transitions"].append({ + "from": "PROGRESS_MONITORED", + "to": "RESULTS_REVIEWED", + "review": results_review + }) + flow_state["current_state"] = "RESULTS_REVIEWED" + flow_state["timestamps"]["results_reviewed"] = datetime.now().isoformat() + + # Step 10: Final Approval + final_approval = await self.give_final_approval(results_review) + flow_state["transitions"].append({ + "from": "RESULTS_REVIEWED", + "to": "FINAL_APPROVAL", + "approval": final_approval + }) + flow_state["current_state"] = "FINAL_APPROVAL" + flow_state["timestamps"]["final_approval"] = datetime.now().isoformat() + + return flow_state +``` + +### Key Competencies + +- **Leadership**: Ability to guide and inspire board members +- **Decision Making**: Strong analytical and judgment skills +- **Communication**: Excellent verbal and written communication +- **Conflict Resolution**: Skills in mediating disagreements +- **Strategic Thinking**: Long-term vision and planning ability +- **Stakeholder Management**: Relationship building and management + +## Vice Chairman Role + +### Supporting Responsibilities + +```mermaid +graph TD + A[Vice Chairman] --> B[Operational Support] + A --> C[Chairman Backup] + A --> D[Coordination] + A --> E[Implementation Oversight] + + B --> F[Meeting Support] + B --> G[Documentation Assistance] + B --> H[Logistics Management] + + C --> I[Acting Chairman] + C --> J[Decision Delegation] + C --> K[Authority Exercise] + + D --> L[Inter-Department Coordination] + D --> M[Resource Allocation] + D --> N[Timeline Management] + + E --> O[Execution Monitoring] + E --> P[Quality Control] + E --> Q[Performance Tracking] +``` + +**Diagram Explanation:** +This diagram shows the Vice Chairman's supporting role structure, highlighting four main areas of responsibility. Operational Support includes meeting support, documentation assistance, and logistics management. Chairman Backup involves acting as chairman when needed, decision delegation, and authority exercise. Coordination covers inter-department coordination, resource allocation, and timeline management. Implementation Oversight includes execution monitoring, quality control, and performance tracking. + +**Technical Implementation:** +```python +# Example: Vice Chairman role implementation +class ViceChairman: + def __init__(self, name, config): + self.name = name + self.config = config + self.authority_level = "SENIOR" + self.voting_weight = 1.2 + self.backup_for = "CHAIRMAN" + self.operational_areas = [] + self.coordination_history = [] + + async def provide_operational_support(self, chairman, task): + """Provide operational support to the chairman""" + support_areas = { + "meeting_support": await self.support_meeting_operations(chairman, task), + "documentation_assistance": await self.assist_with_documentation(task), + "logistics_management": await self.manage_logistics(task) + } + + return { + "support_provided": support_areas, + "timestamp": datetime.now().isoformat() + } + + async def act_as_chairman(self, chairman, reason): + """Act as chairman when the chairman is unavailable""" + acting_authority = { + "acting_chairman": self.name, + "original_chairman": chairman.name, + "reason": reason, + "authority_delegated": True, + "delegation_timestamp": datetime.now().isoformat() + } + + # Assume chairman responsibilities + acting_authority["capabilities"] = [ + "meeting_leadership", + "decision_making", + "consensus_facilitation", + "final_approval" + ] + + return acting_authority + + async def coordinate_operations(self, task, board_members): + """Coordinate operations across different areas""" + coordination_plan = { + "inter_department_coordination": await self.coordinate_departments(task), + "resource_allocation": await self.allocate_resources(task), + "timeline_management": await self.manage_timeline(task) + } + + self.coordination_history.append({ + "task": task, + "coordination_plan": coordination_plan, + "timestamp": datetime.now().isoformat() + }) + + return coordination_plan + + async def oversee_implementation(self, execution_plan): + """Oversee the implementation of board decisions""" + oversight_result = { + "execution_monitoring": await self.monitor_execution(execution_plan), + "quality_control": await self.control_quality(execution_plan), + "performance_tracking": await self.track_performance(execution_plan) + } + + return oversight_result +``` + +## Executive Director Role + +### Strategic Responsibilities + +```mermaid +graph TD + A[Executive Director] --> B[Strategic Planning] + A --> C[Execution Oversight] + A --> D[Performance Management] + A --> E[Strategic Initiatives] + + B --> F[Vision Development] + B --> G[Goal Setting] + B --> H[Strategy Formulation] + + C --> I[Implementation Monitoring] + C --> J[Progress Tracking] + C --> K[Issue Resolution] + + D --> L[KPI Definition] + D --> M[Performance Measurement] + D --> N[Improvement Planning] + + E --> O[Innovation Leadership] + E --> P[Change Management] + E --> Q[Strategic Partnerships] +``` + +**Diagram Explanation:** +This diagram illustrates the Executive Director's strategic role, showing four main areas of responsibility. Strategic Planning includes vision development, goal setting, and strategy formulation. Execution Oversight involves implementation monitoring, progress tracking, and issue resolution. Performance Management covers KPI definition, performance measurement, and improvement planning. Strategic Initiatives includes innovation leadership, change management, and strategic partnerships. + +**Technical Implementation:** +```python +# Example: Executive Director role implementation +class ExecutiveDirector: + def __init__(self, name, config): + self.name = name + self.config = config + self.authority_level = "SENIOR" + self.voting_weight = 1.5 + self.strategic_authority = True + self.performance_metrics = {} + self.strategic_initiatives = [] + + async def develop_strategic_plan(self, task, board_context): + """Develop strategic plan for task execution""" + strategic_plan = { + "vision": await self.develop_vision(task), + "goals": await self.set_goals(task), + "strategy": await self.formulate_strategy(task, board_context), + "timeline": await self.create_strategic_timeline(task), + "resources": await self.plan_strategic_resources(task) + } + + return strategic_plan + + async def oversee_execution(self, execution_plan): + """Oversee execution of strategic plans""" + oversight_result = { + "implementation_monitoring": await self.monitor_implementation(execution_plan), + "progress_tracking": await self.track_progress(execution_plan), + "issue_resolution": await self.resolve_issues(execution_plan) + } + + return oversight_result + + async def manage_performance(self, execution_results): + """Manage performance and define KPIs""" + performance_management = { + "kpi_definition": await self.define_kpis(execution_results), + "performance_measurement": await self.measure_performance(execution_results), + "improvement_planning": await self.plan_improvements(execution_results) + } + + return performance_management + + async def lead_strategic_initiatives(self, task): + """Lead strategic initiatives and innovation""" + strategic_initiatives = { + "innovation_leadership": await self.lead_innovation(task), + "change_management": await self.manage_change(task), + "strategic_partnerships": await self.develop_partnerships(task) + } + + self.strategic_initiatives.append({ + "task": task, + "initiatives": strategic_initiatives, + "timestamp": datetime.now().isoformat() + }) + + return strategic_initiatives +``` + +## Secretary Role + +### Administrative Responsibilities + +```mermaid +graph TD + A[Secretary] --> B[Documentation] + A --> C[Record Keeping] + A --> D[Communication] + A --> E[Administrative Support] + + B --> F[Meeting Minutes] + B --> G[Decision Records] + B --> H[Policy Documentation] + + C --> I[File Management] + C --> J[Record Maintenance] + C --> K[Archive Management] + + D --> L[Internal Communication] + D --> M[External Communication] + D --> N[Stakeholder Updates] + + E --> O[Meeting Coordination] + E --> P[Agenda Preparation] + E --> Q[Follow-up Actions] +``` + +**Diagram Explanation:** +This diagram shows the Secretary's administrative role structure, highlighting four main areas of responsibility. Documentation includes meeting minutes, decision records, and policy documentation. Record Keeping involves file management, record maintenance, and archive management. Communication covers internal communication, external communication, and stakeholder updates. Administrative Support includes meeting coordination, agenda preparation, and follow-up actions. + +**Technical Implementation:** +```python +# Example: Secretary role implementation +class Secretary: + def __init__(self, name, config): + self.name = name + self.config = config + self.authority_level = "STANDARD" + self.voting_weight = 1.0 + self.administrative_authority = True + self.documentation_repository = {} + self.communication_log = [] + + async def document_meeting(self, meeting_data): + """Document board meeting proceedings""" + meeting_documentation = { + "meeting_id": meeting_data["meeting_id"], + "date": meeting_data["timestamp"], + "participants": meeting_data["participants"], + "agenda": meeting_data["agenda"], + "minutes": await self.create_meeting_minutes(meeting_data), + "decisions": meeting_data["decisions"], + "action_items": await self.extract_action_items(meeting_data) + } + + # Store in documentation repository + self.documentation_repository[meeting_data["meeting_id"]] = meeting_documentation + + return meeting_documentation + + async def maintain_records(self, record_type, data): + """Maintain various types of records""" + record_entry = { + "type": record_type, + "data": data, + "timestamp": datetime.now().isoformat(), + "secretary": self.name + } + + if record_type not in self.documentation_repository: + self.documentation_repository[record_type] = [] + + self.documentation_repository[record_type].append(record_entry) + + return record_entry + + async def manage_communication(self, communication_type, content, recipients): + """Manage internal and external communication""" + communication = { + "type": communication_type, + "content": content, + "recipients": recipients, + "timestamp": datetime.now().isoformat(), + "secretary": self.name + } + + self.communication_log.append(communication) + + # Send communication based on type + if communication_type == "internal": + await self.send_internal_communication(communication) + elif communication_type == "external": + await self.send_external_communication(communication) + elif communication_type == "stakeholder_update": + await self.send_stakeholder_update(communication) + + return communication + + async def provide_administrative_support(self, board_members, task): + """Provide administrative support to board members""" + administrative_support = { + "meeting_coordination": await self.coordinate_meetings(board_members, task), + "agenda_preparation": await self.prepare_agendas(task), + "follow_up_actions": await self.manage_follow_up_actions(task) + } + + return administrative_support +``` + +## Treasurer Role + +### Financial Responsibilities + +```mermaid +graph TD + A[Treasurer] --> B[Financial Oversight] + A --> C[Resource Management] + A --> D[Budget Control] + A --> E[Financial Reporting] + + B --> F[Financial Analysis] + B --> G[Risk Assessment] + B --> H[Compliance Monitoring] + + C --> I[Resource Allocation] + C --> J[Cost Management] + C --> K[Efficiency Optimization] + + D --> L[Budget Planning] + D --> M[Expense Monitoring] + D --> N[Budget Adjustments] + + E --> O[Financial Statements] + E --> P[Performance Reports] + E --> Q[Stakeholder Reports] +``` + +**Diagram Explanation:** +This diagram illustrates the Treasurer's financial role structure, showing four main areas of responsibility. Financial Oversight includes financial analysis, risk assessment, and compliance monitoring. Resource Management involves resource allocation, cost management, and efficiency optimization. Budget Control covers budget planning, expense monitoring, and budget adjustments. Financial Reporting includes financial statements, performance reports, and stakeholder reports. + +**Technical Implementation:** +```python +# Example: Treasurer role implementation +class Treasurer: + def __init__(self, name, config): + self.name = name + self.config = config + self.authority_level = "STANDARD" + self.voting_weight = 1.0 + self.financial_authority = True + self.financial_records = {} + self.budget_tracking = {} + + async def provide_financial_oversight(self, task, budget): + """Provide financial oversight for task execution""" + financial_oversight = { + "financial_analysis": await self.analyze_financial_implications(task, budget), + "risk_assessment": await self.assess_financial_risks(task, budget), + "compliance_monitoring": await self.monitor_compliance(task, budget) + } + + return financial_oversight + + async def manage_resources(self, task, available_resources): + """Manage resource allocation and optimization""" + resource_management = { + "resource_allocation": await self.allocate_resources(task, available_resources), + "cost_management": await self.manage_costs(task), + "efficiency_optimization": await self.optimize_efficiency(task) + } + + return resource_management + + async def control_budget(self, task, budget_limits): + """Control budget and monitor expenses""" + budget_control = { + "budget_planning": await self.plan_budget(task, budget_limits), + "expense_monitoring": await self.monitor_expenses(task), + "budget_adjustments": await self.adjust_budget(task) + } + + # Track budget usage + self.budget_tracking[task["id"]] = budget_control + + return budget_control + + async def generate_financial_reports(self, task, financial_data): + """Generate financial reports and statements""" + financial_reports = { + "financial_statements": await self.create_financial_statements(financial_data), + "performance_reports": await self.create_performance_reports(financial_data), + "stakeholder_reports": await self.create_stakeholder_reports(financial_data) + } + + # Store financial records + self.financial_records[task["id"]] = financial_reports + + return financial_reports +``` + +## Member Role + +### General Responsibilities + +```mermaid +graph TD + A[Member] --> B[Expertise Contribution] + A --> C[Analysis] + A --> D[Voting] + A --> E[Specialized Input] + + B --> F[Domain Knowledge] + B --> G[Technical Expertise] + B --> H[Industry Experience] + + C --> I[Data Analysis] + C --> J[Impact Assessment] + C --> K[Feasibility Study] + + D --> L[Informed Voting] + D --> M[Proposal Evaluation] + D --> N[Consensus Building] + + E --> O[Specialized Recommendations] + E --> P[Risk Identification] + E --> Q[Opportunity Assessment] +``` + +**Diagram Explanation:** +This diagram shows the general Member role structure, highlighting four main areas of responsibility. Expertise Contribution includes domain knowledge, technical expertise, and industry experience. Analysis involves data analysis, impact assessment, and feasibility study. Voting covers informed voting, proposal evaluation, and consensus building. Specialized Input includes specialized recommendations, risk identification, and opportunity assessment. + +**Technical Implementation:** +```python +# Example: Member role implementation +class BoardMember: + def __init__(self, name, expertise_areas, config): + self.name = name + self.expertise_areas = expertise_areas + self.config = config + self.authority_level = "STANDARD" + self.voting_weight = 1.0 + self.specialized_expertise = True + self.analysis_history = [] + self.voting_history = [] + + async def contribute_expertise(self, task, expertise_areas): + """Contribute specialized expertise to task analysis""" + expertise_contribution = { + "domain_knowledge": await self.apply_domain_knowledge(task, expertise_areas), + "technical_expertise": await self.apply_technical_expertise(task, expertise_areas), + "industry_experience": await self.apply_industry_experience(task, expertise_areas) + } + + return expertise_contribution + + async def perform_analysis(self, task, data): + """Perform comprehensive analysis of task and data""" + analysis_result = { + "data_analysis": await self.analyze_data(task, data), + "impact_assessment": await self.assess_impact(task, data), + "feasibility_study": await self.study_feasibility(task, data) + } + + # Store analysis in history + self.analysis_history.append({ + "task": task, + "analysis": analysis_result, + "timestamp": datetime.now().isoformat() + }) + + return analysis_result + + async def vote_on_proposals(self, proposals, context): + """Vote on proposals based on analysis and expertise""" + voting_decision = { + "proposal_evaluations": await self.evaluate_proposals(proposals, context), + "informed_vote": await self.make_informed_vote(proposals, context), + "rationale": await self.provide_voting_rationale(proposals, context) + } + + # Store voting decision in history + self.voting_history.append({ + "proposals": proposals, + "decision": voting_decision, + "timestamp": datetime.now().isoformat() + }) + + return voting_decision + + async def provide_specialized_input(self, task, context): + """Provide specialized input based on expertise areas""" + specialized_input = { + "specialized_recommendations": await self.make_specialized_recommendations(task, context), + "risk_identification": await self.identify_risks(task, context), + "opportunity_assessment": await self.assess_opportunities(task, context) + } + + return specialized_input +``` + +## Role Interaction Patterns + +### Communication Flow + +```mermaid +graph TD + A[Chairman] --> B[Vice Chairman] + A --> C[Executive Director] + A --> D[Secretary] + A --> E[Treasurer] + A --> F[Members] + + B --> G[Operational Coordination] + C --> H[Strategic Alignment] + D --> I[Documentation Flow] + E --> J[Financial Oversight] + F --> K[Expertise Input] + + G --> L[Implementation] + H --> M[Strategic Execution] + I --> N[Record Keeping] + J --> O[Budget Control] + K --> P[Decision Support] +``` + +**Diagram Explanation:** +This diagram shows the communication flow and interaction patterns between different board roles. The Chairman serves as the central communication hub, coordinating with all other board members. Each role has specific communication channels and responsibilities: Vice Chairman handles operational coordination, Executive Director manages strategic alignment, Secretary manages documentation flow, Treasurer oversees financial matters, and Members provide expertise input. These interactions support implementation, strategic execution, record keeping, budget control, and decision support. + +**Technical Implementation:** +```python +# Example: Role interaction system +class RoleInteractionSystem: + def __init__(self, board_members): + self.board_members = board_members + self.communication_channels = {} + self.interaction_patterns = {} + + async def establish_communication_flow(self): + """Establish communication flow between board members""" + communication_flow = { + "chairman_communications": await self.setup_chairman_communications(), + "operational_coordination": await self.setup_operational_coordination(), + "strategic_alignment": await self.setup_strategic_alignment(), + "documentation_flow": await self.setup_documentation_flow(), + "financial_oversight": await self.setup_financial_oversight(), + "expertise_input": await self.setup_expertise_input() + } + + return communication_flow + + async def coordinate_role_interactions(self, task): + """Coordinate interactions between different roles""" + interactions = { + "chairman_vice_chairman": await self.coordinate_chairman_vice_chairman(task), + "chairman_executive_director": await self.coordinate_chairman_executive_director(task), + "secretary_documentation": await self.coordinate_secretary_documentation(task), + "treasurer_financial": await self.coordinate_treasurer_financial(task), + "member_expertise": await self.coordinate_member_expertise(task) + } + + return interactions +``` + +## Best Practices for Role Management + +### 1. Clear Role Definition +- Define specific responsibilities for each role +- Establish clear authority boundaries +- Document role interactions and communication protocols + +### 2. Balanced Voting Weights +- Ensure fair representation while maintaining leadership hierarchy +- Consider expertise and experience in weight assignment +- Regularly review and adjust weights based on performance + +### 3. Effective Communication +- Establish clear communication channels +- Implement regular reporting mechanisms +- Ensure transparency in decision-making processes + +### 4. Continuous Improvement +- Regularly assess role effectiveness +- Gather feedback from board members +- Implement improvements based on lessons learned + +### 5. Conflict Resolution +- Establish clear conflict resolution procedures +- Provide mediation mechanisms +- Ensure fair and impartial resolution processes \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md b/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md new file mode 100644 index 00000000..20223c49 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md @@ -0,0 +1,704 @@ +# `BoardOfDirectorsSwarm` + +The `BoardOfDirectorsSwarm` is a sophisticated multi-agent orchestration system that implements a collective decision-making approach as an alternative to the single Director pattern. It consists of a board of directors that convenes to discuss, vote, and reach consensus on task distribution and execution strategies. + +## Overview + +The Board of Directors Swarm follows a democratic workflow pattern that mimics real-world corporate governance structures: + +1. **Task Reception**: User provides a task to the swarm +2. **Board Meeting**: Board of Directors convenes to discuss and create a plan +3. **Voting & Consensus**: Board members vote and reach consensus on task distribution +4. **Order Distribution**: Board distributes orders to specialized worker agents +5. **Execution**: Individual agents execute their assigned tasks +6. **Feedback Loop**: Board evaluates results and issues new orders if needed (up to `max_loops`) +7. **Context Preservation**: All conversation history and context is maintained throughout the process + +## Architecture + +### High-Level Workflow + +```mermaid +graph TD + A[User Task] --> B[Board of Directors] + B --> C[Board Meeting & Discussion] + C --> D[Voting & Consensus] + D --> E[Create Plan & Orders] + E --> F[Distribute to Agents] + F --> G[Agent 1] + F --> H[Agent 2] + F --> I[Agent N] + G --> J[Execute Task] + H --> J + I --> J + J --> K[Report Results] + K --> L[Board Evaluation] + L --> M{More Loops?} + M -->|Yes| C + M -->|No| N[Final Output] +``` + +**Diagram Explanation:** +This diagram illustrates the complete lifecycle of a task through the Board of Directors Swarm system. The workflow begins when a user submits a task, which triggers the board to convene for discussion and planning. The board then votes on the approach and creates detailed execution orders. These orders are distributed to multiple specialized agents who execute their tasks in parallel. Results are collected and evaluated by the board, which can trigger additional refinement loops if needed. The process continues until the board is satisfied with the results or the maximum number of loops is reached. + +**Key Technical Points:** +- **Parallel Execution**: Multiple agents can work simultaneously on different aspects of the task +- **Iterative Refinement**: The system supports multiple feedback loops for continuous improvement +- **Centralized Coordination**: The board maintains control over the entire process while delegating execution +- **Result Aggregation**: All agent outputs are collected and evaluated before proceeding + +### Detailed Decision-Making Process + +```mermaid +flowchart TD + A[Task Received] --> B[Board Convenes] + B --> C[Chairman Opens Meeting] + C --> D[Task Analysis Phase] + D --> E[Expertise Assignment] + E --> F[Individual Member Analysis] + F --> G[Discussion & Debate] + G --> H[Proposal Generation] + H --> I[Voting Process] + I --> J{Consensus Reached?} + J -->|No| K[Reconciliation Phase] + K --> G + J -->|Yes| L[Plan Finalization] + L --> M[Order Creation] + M --> N[Agent Assignment] + N --> O[Execution Phase] + O --> P[Result Collection] + P --> Q[Board Review] + Q --> R{Approval?} + R -->|No| S[Refinement Loop] + S --> G + R -->|Yes| T[Final Delivery] +``` + +**Diagram Explanation:** +This detailed flowchart shows the internal decision-making process within the board. The process begins with task reception and board convening, followed by a structured analysis phase where each board member contributes based on their expertise. The discussion and debate phase allows for thorough consideration of different approaches, leading to proposal generation and voting. If consensus isn't reached, the system enters a reconciliation phase to resolve conflicts. Once consensus is achieved, the plan is finalized and orders are created for agent assignment. The execution phase is followed by result collection and board review, with the option to enter refinement loops if the results don't meet approval criteria. + +**Technical Implementation Details:** +- **Expertise Assignment**: Board members are assigned tasks based on their specialized knowledge areas +- **Voting Mechanisms**: Configurable voting weights and decision thresholds ensure fair representation +- **Conflict Resolution**: Built-in reconciliation mechanisms handle disagreements among board members +- **Quality Gates**: Approval checkpoints ensure output quality before final delivery + +### Board Member Interaction Flow + +```mermaid +sequenceDiagram + participant User + participant Chairman + participant ViceChair + participant Secretary + participant Treasurer + participant ExecDir + participant Agents + + User->>Chairman: Submit Task + Chairman->>ViceChair: Notify Board Meeting + Chairman->>Secretary: Request Meeting Setup + Chairman->>Treasurer: Resource Assessment + Chairman->>ExecDir: Strategic Planning + + Note over Chairman,ExecDir: Board Discussion Phase + + Chairman->>ViceChair: Lead Discussion + ViceChair->>Secretary: Document Decisions + Secretary->>Treasurer: Budget Considerations + Treasurer->>ExecDir: Resource Allocation + ExecDir->>Chairman: Strategic Recommendations + + Note over Chairman,ExecDir: Voting & Consensus + + Chairman->>ViceChair: Call for Vote + ViceChair->>Secretary: Record Votes + Secretary->>Treasurer: Financial Approval + Treasurer->>ExecDir: Resource Approval + ExecDir->>Chairman: Final Decision + + Note over Chairman,Agents: Execution Phase + + Chairman->>Agents: Distribute Orders + Agents->>Chairman: Execute Tasks + Agents->>ViceChair: Progress Reports + Agents->>Secretary: Documentation + Agents->>Treasurer: Resource Usage + Agents->>ExecDir: Strategic Updates + + Note over Chairman,ExecDir: Review & Feedback + + Chairman->>User: Deliver Results +``` + +**Diagram Explanation:** +This sequence diagram shows the detailed interaction patterns between different board members and agents throughout the entire process. The interaction begins with the Chairman receiving a task from the user and immediately coordinating with other board members. Each board member has specific responsibilities: the Vice Chairman leads discussions, the Secretary documents decisions, the Treasurer handles resource assessment, and the Executive Director provides strategic planning. During the voting phase, each member contributes their expertise and votes are recorded systematically. In the execution phase, the Chairman distributes orders to agents while maintaining communication channels with all board members for progress monitoring and strategic updates. + +**Technical Communication Patterns:** +- **Hierarchical Communication**: Chairman serves as the central coordinator +- **Specialized Reporting**: Each agent reports to the appropriate board member based on their role +- **Parallel Coordination**: Multiple board members can work simultaneously on different aspects +- **Feedback Channels**: Continuous communication ensures real-time monitoring and adjustment + +### Agent Execution and Feedback Loop + +```mermaid +graph LR + subgraph "Board of Directors" + A[Chairman] + B[Vice Chairman] + C[Secretary] + D[Treasurer] + E[Executive Director] + end + + subgraph "Worker Agents" + F[Research Agent] + G[Analysis Agent] + H[Technical Agent] + I[Financial Agent] + J[Strategy Agent] + end + + subgraph "Execution Flow" + K[Task Distribution] + L[Parallel Execution] + M[Result Aggregation] + N[Quality Assessment] + O[Feedback Loop] + end + + A --> K + B --> K + C --> K + D --> K + E --> K + + K --> L + L --> F + L --> G + L --> H + L --> I + L --> J + + F --> M + G --> M + H --> M + I --> M + J --> M + + M --> N + N --> O + O --> K +``` + +**Diagram Explanation:** +This diagram illustrates the relationship between the Board of Directors and Worker Agents, showing how tasks flow from decision-making to execution and back through feedback loops. The Board of Directors collectively participates in task distribution, ensuring that all perspectives are considered. Worker agents execute tasks in parallel, each specializing in different areas (research, analysis, technical implementation, financial considerations, and strategic planning). Results are aggregated and assessed for quality before determining whether additional feedback loops are needed. + +**Technical Architecture Benefits:** +- **Separation of Concerns**: Clear distinction between decision-making (Board) and execution (Agents) +- **Scalability**: Additional agents can be added without changing the board structure +- **Fault Tolerance**: If one agent fails, others can continue working +- **Quality Control**: Centralized assessment ensures consistent output quality + +## Technical Implementation + +### Core Components + +The `BoardOfDirectorsSwarm` consists of several key components: + +1. **Board Members**: Specialized agents with specific roles and voting weights +2. **Worker Agents**: Execution agents that perform the actual tasks +3. **Voting System**: Configurable voting mechanisms for decision-making +4. **Communication Protocol**: Structured communication between board members and agents +5. **Feedback Loop Controller**: Manages iterative refinement processes + +### Configuration Options + +```python +# Example configuration for BoardOfDirectorsSwarm +board_config = { + "max_loops": 3, # Maximum number of refinement loops + "voting_threshold": 0.7, # Consensus threshold (70%) + "enable_voting_weights": True, # Use weighted voting + "consensus_method": "majority", # Voting method: "majority" or "unanimous" + "board_size": 5, # Number of board members + "enable_feedback_loops": True, # Enable iterative refinement + "output_format": "structured", # Output format: "structured", "text", "json" + "enable_logging": True, # Enable detailed logging + "parallel_execution": True, # Enable parallel agent execution + "quality_gates": True, # Enable quality checkpoints +} +``` + +### Voting Mechanisms + +The system supports multiple voting mechanisms: + +1. **Majority Voting**: Simple majority of votes required +2. **Weighted Voting**: Votes weighted by board member importance +3. **Unanimous Consensus**: All board members must agree +4. **Threshold-based**: Configurable percentage of agreement required + +```python +# Example voting configuration +voting_config = { + "method": "weighted_majority", + "threshold": 0.75, + "weights": { + "CHAIRMAN": 1.5, + "VICE_CHAIRMAN": 1.2, + "SECRETARY": 1.0, + "TREASURER": 1.0, + "EXECUTIVE_DIRECTOR": 1.5 + }, + "tie_breaker": "CHAIRMAN", # Chairman breaks ties + "allow_abstention": True, # Allow board members to abstain +} +``` + +### Communication Protocols + +The system uses structured communication protocols: + +1. **Task Distribution Protocol**: Standardized format for distributing tasks to agents +2. **Progress Reporting Protocol**: Regular status updates from agents to board +3. **Decision Communication Protocol**: Clear communication of board decisions +4. **Feedback Protocol**: Structured feedback for iterative improvement + +```python +# Example communication message format +message_format = { + "sender": "CHAIRMAN", + "recipient": "RESEARCH_AGENT", + "message_type": "TASK_DISTRIBUTION", + "content": { + "task_id": "task_123", + "task_description": "Research market trends for Q4", + "deadline": "2024-01-15T10:00:00Z", + "priority": "HIGH", + "resources": ["market_data", "analytics_tools"], + "expected_output": "structured_report" + }, + "metadata": { + "timestamp": "2024-01-10T09:00:00Z", + "session_id": "session_456", + "board_decision_id": "decision_789" + } +} +``` + +## Usage Examples + +### Basic Usage + +```python +from swarms import BoardOfDirectorsSwarm, Agent + +# Create specialized agents +research_agent = Agent( + name="Research Agent", + system_prompt="You are a research specialist focused on market analysis and data gathering.", + llm="gpt-4" +) + +analysis_agent = Agent( + name="Analysis Agent", + system_prompt="You are an analysis expert who interprets data and provides insights.", + llm="gpt-4" +) + +technical_agent = Agent( + name="Technical Agent", + system_prompt="You are a technical specialist who handles implementation and technical details.", + llm="gpt-4" +) + +# Create the Board of Directors Swarm +board_swarm = BoardOfDirectorsSwarm( + agents=[research_agent, analysis_agent, technical_agent], + max_loops=3, + voting_threshold=0.7, + enable_voting_weights=True +) + +# Execute a task +task = "Analyze the current market trends in AI and provide strategic recommendations for our company." +result = board_swarm.run(task) +print(result) +``` + +### Advanced Configuration + +```python +from swarms import BoardOfDirectorsSwarm, Agent +from swarms.structs.board_of_directors import BoardConfig + +# Create a custom board configuration +custom_config = BoardConfig( + max_loops=5, + voting_threshold=0.8, + consensus_method="unanimous", + enable_feedback_loops=True, + output_format="structured", + board_roles={ + "CHAIRMAN": {"weight": 1.5, "responsibilities": ["leadership", "final_decision"]}, + "VICE_CHAIRMAN": {"weight": 1.2, "responsibilities": ["operations", "coordination"]}, + "SECRETARY": {"weight": 1.0, "responsibilities": ["documentation", "record_keeping"]}, + "TREASURER": {"weight": 1.0, "responsibilities": ["financial_oversight", "resource_allocation"]}, + "EXECUTIVE_DIRECTOR": {"weight": 1.5, "responsibilities": ["strategic_planning", "execution"]} + } +) + +# Create specialized agents with custom prompts +agents = [ + Agent( + name="Market Research Agent", + system_prompt="""You are a market research specialist with expertise in: + - Competitive analysis + - Market sizing and segmentation + - Customer behavior analysis + - Industry trend identification + Provide detailed, data-driven insights.""", + llm="gpt-4" + ), + Agent( + name="Financial Analysis Agent", + system_prompt="""You are a financial analyst specializing in: + - Financial modeling and forecasting + - Risk assessment and mitigation + - Investment analysis + - Cost-benefit analysis + Provide comprehensive financial insights.""", + llm="gpt-4" + ), + Agent( + name="Technical Strategy Agent", + system_prompt="""You are a technical strategist focused on: + - Technology roadmap planning + - Technical feasibility assessment + - Implementation strategy + - Technology risk evaluation + Provide strategic technical guidance.""", + llm="gpt-4" + ) +] + +# Create the swarm with custom configuration +board_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=custom_config +) + +# Execute a complex task +complex_task = """ +Analyze the feasibility of launching a new AI-powered product in the healthcare sector. +Consider: +1. Market opportunity and competitive landscape +2. Financial viability and investment requirements +3. Technical implementation challenges and timeline +4. Regulatory compliance requirements +5. Risk assessment and mitigation strategies + +Provide a comprehensive report with recommendations for next steps. +""" + +result = board_swarm.run(complex_task) +print(result) +``` + +### Real-World Use Cases + +#### 1. Strategic Business Planning + +```python +# Example: Strategic business planning with multiple stakeholders +business_planning_task = """ +Develop a comprehensive 5-year strategic plan for our technology company. +Include: +- Market analysis and competitive positioning +- Product development roadmap +- Financial projections and funding requirements +- Operational scaling strategy +- Risk management framework +- Success metrics and KPIs +""" + +# Configure board for strategic planning +strategic_config = BoardConfig( + max_loops=4, + voting_threshold=0.8, + consensus_method="weighted_majority", + enable_feedback_loops=True, + output_format="structured" +) + +strategic_swarm = BoardOfDirectorsSwarm( + agents=[market_agent, financial_agent, technical_agent, strategy_agent], + config=strategic_config +) + +strategic_plan = strategic_swarm.run(business_planning_task) +``` + +#### 2. Product Development Decision Making + +```python +# Example: Product development decision making +product_decision_task = """ +Evaluate the feasibility of developing a new AI-powered customer service chatbot. +Consider: +- Technical requirements and development timeline +- Market demand and competitive analysis +- Cost-benefit analysis and ROI projections +- Implementation challenges and resource requirements +- Success criteria and measurement metrics + +Provide a go/no-go recommendation with detailed rationale. +""" + +# Configure board for product decisions +product_config = BoardConfig( + max_loops=3, + voting_threshold=0.75, + consensus_method="majority", + enable_feedback_loops=True, + output_format="structured" +) + +product_swarm = BoardOfDirectorsSwarm( + agents=[technical_agent, market_agent, financial_agent], + config=product_config +) + +product_recommendation = product_swarm.run(product_decision_task) +``` + +#### 3. Crisis Management and Response + +```python +# Example: Crisis management and response planning +crisis_task = """ +Our company is facing a major data breach. Develop an immediate response plan. +Include: +- Immediate containment and mitigation steps +- Communication strategy for stakeholders +- Legal and regulatory compliance requirements +- Financial impact assessment +- Long-term recovery and prevention measures +- Timeline and resource allocation +""" + +# Configure board for crisis management +crisis_config = BoardConfig( + max_loops=2, # Faster response needed + voting_threshold=0.6, # Lower threshold for urgent decisions + consensus_method="majority", + enable_feedback_loops=False, # No time for multiple iterations + output_format="structured" +) + +crisis_swarm = BoardOfDirectorsSwarm( + agents=[security_agent, legal_agent, communications_agent, financial_agent], + config=crisis_config +) + +crisis_response = crisis_swarm.run(crisis_task) +``` + +## Performance Optimization + +### Parallel Execution Strategies + +The Board of Directors Swarm supports various parallel execution strategies: + +1. **Task-Level Parallelism**: Multiple agents work on different aspects simultaneously +2. **Board-Level Parallelism**: Board members can analyze different aspects in parallel +3. **Iteration-Level Parallelism**: Multiple refinement loops can run concurrently + +```python +# Example: Optimizing for parallel execution +parallel_config = BoardConfig( + max_loops=3, + parallel_execution=True, + max_concurrent_agents=5, + enable_async_processing=True, + timeout_per_agent=300, # 5 minutes per agent + enable_agent_pooling=True +) + +# Create agent pool for better resource utilization +agent_pool = [ + Agent(name=f"Research_Agent_{i}", system_prompt="...", llm="gpt-4") + for i in range(3) +] + [ + Agent(name=f"Analysis_Agent_{i}", system_prompt="...", llm="gpt-4") + for i in range(2) +] + +parallel_swarm = BoardOfDirectorsSwarm( + agents=agent_pool, + config=parallel_config +) +``` + +### Quality Control Mechanisms + +```python +# Example: Quality control configuration +quality_config = BoardConfig( + max_loops=3, + quality_gates=True, + quality_threshold=0.8, + enable_peer_review=True, + review_required=True, + output_validation=True, + enable_metrics_tracking=True +) + +# Define quality metrics +quality_metrics = { + "completeness": 0.9, # 90% completeness required + "accuracy": 0.85, # 85% accuracy required + "relevance": 0.9, # 90% relevance required + "clarity": 0.8 # 80% clarity required +} + +quality_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=quality_config, + quality_metrics=quality_metrics +) +``` + +## Monitoring and Debugging + +### Logging and Observability + +```python +import logging +from swarms import BoardOfDirectorsSwarm + +# Configure detailed logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Create swarm with logging enabled +logging_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=BoardConfig( + enable_logging=True, + log_level="DEBUG", + enable_metrics=True, + enable_tracing=True + ) +) + +# Execute with detailed logging +result = logging_swarm.run(task) +``` + +### Performance Metrics + +```python +# Example: Performance monitoring +from swarms.metrics import SwarmMetrics + +# Create metrics collector +metrics = SwarmMetrics() + +# Configure swarm with metrics +monitored_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=BoardConfig(enable_metrics=True), + metrics_collector=metrics +) + +# Execute and collect metrics +result = monitored_swarm.run(task) + +# Analyze performance +performance_report = metrics.generate_report() +print(f"Total execution time: {performance_report['total_time']}") +print(f"Average agent response time: {performance_report['avg_agent_time']}") +print(f"Number of voting rounds: {performance_report['voting_rounds']}") +print(f"Consensus achieved: {performance_report['consensus_achieved']}") +``` + +## Best Practices + +### 1. Agent Design + +- **Specialized Expertise**: Design agents with specific, complementary expertise +- **Clear Responsibilities**: Define clear boundaries for each agent's responsibilities +- **Consistent Communication**: Use standardized communication protocols +- **Error Handling**: Implement robust error handling and recovery mechanisms + +### 2. Board Configuration + +- **Appropriate Voting Thresholds**: Set thresholds based on decision criticality +- **Balanced Voting Weights**: Ensure fair representation while maintaining leadership hierarchy +- **Flexible Consensus Methods**: Choose consensus methods based on decision type +- **Reasonable Loop Limits**: Balance quality with efficiency + +### 3. Task Design + +- **Clear Objectives**: Define clear, measurable objectives +- **Structured Requirements**: Provide structured, detailed requirements +- **Appropriate Scope**: Ensure tasks are appropriately scoped for the board size +- **Context Provision**: Provide sufficient context for informed decision-making + +### 4. Performance Optimization + +- **Parallel Execution**: Leverage parallel execution where possible +- **Resource Management**: Monitor and optimize resource usage +- **Quality Gates**: Implement appropriate quality control mechanisms +- **Continuous Monitoring**: Monitor performance and adjust configurations as needed + +## Troubleshooting + +### Common Issues and Solutions + +1. **Consensus Not Reached** + - **Issue**: Board cannot reach consensus within loop limit + - **Solution**: Lower voting threshold, increase max_loops, or adjust voting weights + +2. **Agent Timeout** + - **Issue**: Individual agents take too long to respond + - **Solution**: Increase timeout settings or optimize agent prompts + +3. **Poor Quality Output** + - **Issue**: Final output doesn't meet quality standards + - **Solution**: Enable quality gates, increase max_loops, or improve agent prompts + +4. **Resource Exhaustion** + - **Issue**: System runs out of resources during execution + - **Solution**: Implement resource limits, use agent pooling, or optimize parallel execution + +### Debugging Techniques + +```python +# Example: Debugging configuration +debug_config = BoardConfig( + max_loops=1, # Limit loops for debugging + enable_logging=True, + log_level="DEBUG", + enable_tracing=True, + debug_mode=True +) + +# Create debug swarm +debug_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=debug_config +) + +# Execute with debugging +try: + result = debug_swarm.run(task) +except Exception as e: + print(f"Error: {e}") + print(f"Debug info: {debug_swarm.get_debug_info()}") +``` \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md b/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md new file mode 100644 index 00000000..3e913ea7 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md @@ -0,0 +1,908 @@ +# Board of Directors Workflow + +The Board of Directors workflow is a sophisticated multi-stage process that ensures comprehensive task analysis, collaborative decision-making, and effective execution through specialized agents. This workflow implements a corporate governance model that balances efficiency with thoroughness, ensuring high-quality outcomes through structured collaboration. + +## Workflow Overview + +```mermaid +graph TD + A[Task Input] --> B[Initial Assessment] + B --> C[Board Assembly] + C --> D[Meeting Phase] + D --> E[Decision Phase] + E --> F[Execution Phase] + F --> G[Review Phase] + G --> H{Approval?} + H -->|No| I[Refinement] + I --> D + H -->|Yes| J[Final Delivery] + + style A fill:#e1f5fe + style J fill:#c8e6c9 + style H fill:#fff3e0 +``` + +**Diagram Explanation:** +This high-level workflow diagram shows the complete lifecycle of a task through the Board of Directors system. The process begins with task input, followed by an initial assessment to understand requirements and complexity. The board then assembles with appropriate members, conducts a structured meeting phase for analysis and discussion, makes decisions through voting and consensus, executes the plan through specialized agents, reviews results, and either approves for final delivery or returns to refinement loops for improvement. + +**Technical Implementation Details:** +- **Task Input Validation**: System validates task format, requirements, and constraints +- **Dynamic Board Assembly**: Board members are selected based on task requirements and availability +- **Structured Meeting Protocol**: Follows corporate governance best practices +- **Iterative Refinement**: Quality gates ensure output meets standards before final delivery + +## Phase 1: Initial Assessment + +### Task Analysis and Board Preparation + +```mermaid +flowchart LR + A[Task Received] --> B[Complexity Assessment] + B --> C[Resource Requirements] + C --> D[Expertise Mapping] + D --> E[Board Member Selection] + E --> F[Meeting Scheduling] + + subgraph "Assessment Criteria" + G[Task Complexity] + H[Time Constraints] + I[Resource Availability] + J[Expertise Requirements] + end + + B --> G + B --> H + C --> I + D --> J +``` + +**Diagram Explanation:** +This flowchart illustrates the systematic approach to task analysis and board preparation. When a task is received, the system performs a comprehensive assessment including complexity evaluation, resource requirement analysis, expertise mapping, and board member selection. The assessment criteria include task complexity, time constraints, resource availability, and specific expertise requirements needed for successful completion. + +**Technical Implementation:** +```python +# Example: Task assessment implementation +class TaskAssessment: + def __init__(self): + self.complexity_metrics = { + "scope": 0.0, # Task scope (0-1) + "technical_depth": 0.0, # Technical complexity (0-1) + "stakeholder_count": 0, # Number of stakeholders + "timeline_constraints": 0.0, # Time pressure (0-1) + "resource_intensity": 0.0 # Resource requirements (0-1) + } + + def assess_task(self, task_description): + """Assess task complexity and requirements""" + assessment = { + "complexity_score": self.calculate_complexity(), + "required_expertise": self.identify_expertise_needs(), + "resource_requirements": self.estimate_resources(), + "timeline_estimate": self.estimate_timeline(), + "recommended_board_size": self.calculate_board_size() + } + return assessment + + def calculate_complexity(self): + """Calculate overall task complexity score""" + weights = { + "scope": 0.25, + "technical_depth": 0.3, + "stakeholder_count": 0.15, + "timeline_constraints": 0.2, + "resource_intensity": 0.1 + } + + complexity_score = sum( + self.complexity_metrics[key] * weights[key] + for key in weights + ) + return min(complexity_score, 1.0) + + def identify_expertise_needs(self): + """Identify required expertise areas""" + expertise_areas = [] + + if self.complexity_metrics["technical_depth"] > 0.5: + expertise_areas.append("technical") + + if self.complexity_metrics["stakeholder_count"] > 3: + expertise_areas.append("stakeholder_management") + + if self.complexity_metrics["resource_intensity"] > 0.5: + expertise_areas.append("resource_management") + + return expertise_areas +``` + +### Board Member Activation + +```mermaid +sequenceDiagram + participant System + participant Chairman + participant Members + participant Agents + + System->>Chairman: Notify New Task + Chairman->>System: Assess Task Requirements + System->>Members: Activate Relevant Members + Members->>Chairman: Confirm Availability + Chairman->>Agents: Prepare Agent Pool + Agents->>Chairman: Confirm Readiness + Chairman->>System: Board Ready for Meeting +``` + +**Diagram Explanation:** +This sequence diagram shows the systematic process of board member activation and preparation. The system notifies the Chairman of a new task, who then assesses requirements and determines which board members are needed. The system activates relevant members, who confirm their availability. The Chairman prepares the agent pool and confirms readiness before declaring the board ready for the meeting. + +**Technical Implementation:** +```python +# Example: Board member activation system +class BoardActivation: + def __init__(self, board_members, agents): + self.board_members = board_members + self.agents = agents + self.activation_status = {} + + async def activate_board(self, task_assessment): + """Activate board members based on task requirements""" + # Determine required members + required_members = self.select_required_members(task_assessment) + + # Activate members + activation_results = [] + for member in required_members: + status = await self.activate_member(member, task_assessment) + activation_results.append(status) + + # Prepare agent pool + agent_pool = self.prepare_agent_pool(task_assessment) + + # Confirm readiness + readiness_status = await self.confirm_readiness(activation_results, agent_pool) + + return { + "board_ready": readiness_status["ready"], + "active_members": readiness_status["active_members"], + "agent_pool": readiness_status["agent_pool"], + "estimated_start_time": readiness_status["start_time"] + } + + def select_required_members(self, assessment): + """Select board members based on task requirements""" + required_members = ["CHAIRMAN"] # Chairman always required + + if assessment["complexity_score"] > 0.7: + required_members.extend(["VICE_CHAIRMAN", "EXECUTIVE_DIRECTOR"]) + + if "technical" in assessment["required_expertise"]: + required_members.append("TECHNICAL_DIRECTOR") + + if "financial" in assessment["required_expertise"]: + required_members.append("TREASURER") + + if assessment["stakeholder_count"] > 5: + required_members.append("SECRETARY") + + return list(set(required_members)) # Remove duplicates + + async def activate_member(self, member_role, assessment): + """Activate individual board member""" + member = self.board_members.get(member_role) + if not member: + return {"status": "error", "message": f"Member {member_role} not found"} + + # Check availability + availability = await member.check_availability() + if not availability["available"]: + return {"status": "unavailable", "member": member_role, "reason": availability["reason"]} + + # Prepare member for task + preparation_status = await member.prepare_for_task(assessment) + + return { + "status": "activated", + "member": member_role, + "preparation_time": preparation_status["preparation_time"], + "expertise_areas": preparation_status["expertise_areas"] + } +``` + +## Phase 2: Board Meeting + +### Meeting Structure + +```mermaid +graph TD + A[Meeting Opens] --> B[Agenda Review] + B --> C[Task Presentation] + C --> D[Expertise Assignment] + D --> E[Individual Analysis] + E --> F[Group Discussion] + F --> G[Proposal Development] + G --> H[Voting Process] + H --> I[Consensus Building] + I --> J[Plan Finalization] + + subgraph "Meeting Components" + K[Time Management] + L[Documentation] + M[Conflict Resolution] + N[Decision Recording] + end + + A --> K + F --> L + I --> M + J --> N +``` + +**Diagram Explanation:** +This diagram shows the structured meeting process that follows corporate governance best practices. The meeting begins with agenda review and task presentation, followed by expertise assignment where board members are given specific areas to analyze. Individual analysis leads to group discussion, proposal development, voting, consensus building, and plan finalization. Throughout the process, time management, documentation, conflict resolution, and decision recording ensure effective governance. + +**Technical Implementation:** +```python +# Example: Meeting management system +class BoardMeeting: + def __init__(self, board_members, task, config): + self.board_members = board_members + self.task = task + self.config = config + self.meeting_phases = [] + self.decisions = [] + self.documentation = {} + + async def conduct_meeting(self): + """Conduct the board meeting following structured phases""" + meeting_result = { + "phases": [], + "decisions": [], + "documentation": {}, + "consensus_achieved": False, + "final_plan": None + } + + # Phase 1: Meeting Opening and Agenda Review + opening_phase = await self.conduct_opening_phase() + meeting_result["phases"].append(opening_phase) + + # Phase 2: Task Presentation and Expertise Assignment + presentation_phase = await self.conduct_presentation_phase() + meeting_result["phases"].append(presentation_phase) + + # Phase 3: Individual Analysis + analysis_phase = await self.conduct_analysis_phase() + meeting_result["phases"].append(analysis_phase) + + # Phase 4: Group Discussion + discussion_phase = await self.conduct_discussion_phase() + meeting_result["phases"].append(discussion_phase) + + # Phase 5: Proposal Development + proposal_phase = await self.conduct_proposal_phase() + meeting_result["phases"].append(proposal_phase) + + # Phase 6: Voting and Consensus + voting_phase = await self.conduct_voting_phase() + meeting_result["phases"].append(voting_phase) + + # Phase 7: Plan Finalization + finalization_phase = await self.conduct_finalization_phase() + meeting_result["phases"].append(finalization_phase) + + meeting_result["decisions"] = self.decisions + meeting_result["documentation"] = self.documentation + meeting_result["consensus_achieved"] = voting_phase["consensus_achieved"] + meeting_result["final_plan"] = finalization_phase["final_plan"] + + return meeting_result + + async def conduct_opening_phase(self): + """Conduct meeting opening and agenda review""" + chairman = self.board_members["CHAIRMAN"] + + # Open meeting + opening_statement = await chairman.open_meeting(self.task) + + # Review agenda + agenda_review = await chairman.review_agenda(self.task) + + # Set meeting parameters + meeting_params = { + "time_limit": self.config.get("meeting_time_limit", 3600), # 1 hour default + "voting_threshold": self.config.get("voting_threshold", 0.7), + "consensus_method": self.config.get("consensus_method", "majority") + } + + return { + "phase": "opening", + "opening_statement": opening_statement, + "agenda_review": agenda_review, + "meeting_params": meeting_params, + "timestamp": datetime.now().isoformat() + } + + async def conduct_presentation_phase(self): + """Conduct task presentation and expertise assignment""" + chairman = self.board_members["CHAIRMAN"] + + # Present task details + task_presentation = await chairman.present_task(self.task) + + # Assign expertise areas + expertise_assignments = await chairman.assign_expertise_areas( + self.board_members, self.task + ) + + return { + "phase": "presentation", + "task_presentation": task_presentation, + "expertise_assignments": expertise_assignments, + "timestamp": datetime.now().isoformat() + } +``` + +### Discussion and Debate Process + +```mermaid +flowchart TD + A[Discussion Opens] --> B[Expertise-Based Input] + B --> C[Cross-Examination] + C --> D[Alternative Proposals] + D --> E[Impact Analysis] + E --> F[Risk Assessment] + F --> G[Stakeholder Consideration] + G --> H[Resource Evaluation] + H --> I[Timeline Assessment] + I --> J[Consensus Check] + J -->|No Consensus| K[Conflict Resolution] + K --> L[Mediation Process] + L --> J + J -->|Consensus| M[Proposal Finalization] + + subgraph "Discussion Elements" + N[Evidence-Based Arguments] + O[Stakeholder Perspectives] + P[Risk-Benefit Analysis] + Q[Implementation Feasibility] + end + + B --> N + G --> O + E --> P + H --> Q +``` + +**Diagram Explanation:** +This flowchart details the discussion and debate process that ensures thorough consideration of all aspects before decision-making. The process begins with expertise-based input from each board member, followed by cross-examination to validate claims. Alternative proposals are considered, and comprehensive impact analysis, risk assessment, stakeholder consideration, resource evaluation, and timeline assessment are conducted. If consensus isn't reached, conflict resolution and mediation processes are employed until agreement is achieved. + +**Technical Implementation:** +```python +# Example: Discussion and debate management +class DiscussionManager: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.discussion_points = [] + self.conflicts = [] + self.consensus_status = False + + async def conduct_discussion(self, task, expertise_assignments): + """Conduct structured discussion and debate""" + discussion_result = { + "phases": [], + "consensus_achieved": False, + "final_proposal": None, + "conflicts_resolved": [] + } + + # Phase 1: Expertise-based input + expertise_input = await self.gather_expertise_input(task, expertise_assignments) + discussion_result["phases"].append(expertise_input) + + # Phase 2: Cross-examination + cross_examination = await self.conduct_cross_examination(expertise_input) + discussion_result["phases"].append(cross_examination) + + # Phase 3: Alternative proposals + alternatives = await self.generate_alternatives(task, expertise_input) + discussion_result["phases"].append(alternatives) + + # Phase 4: Comprehensive analysis + analysis = await self.conduct_comprehensive_analysis(task, alternatives) + discussion_result["phases"].append(analysis) + + # Phase 5: Consensus building + consensus = await self.build_consensus(analysis) + discussion_result["phases"].append(consensus) + + discussion_result["consensus_achieved"] = consensus["achieved"] + discussion_result["final_proposal"] = consensus["final_proposal"] + discussion_result["conflicts_resolved"] = consensus["conflicts_resolved"] + + return discussion_result + + async def gather_expertise_input(self, task, expertise_assignments): + """Gather input from each board member based on their expertise""" + expertise_inputs = {} + + for member_role, expertise_areas in expertise_assignments.items(): + member = self.board_members[member_role] + + # Generate expertise-based analysis + analysis = await member.analyze_task_areas(task, expertise_areas) + + expertise_inputs[member_role] = { + "expertise_areas": expertise_areas, + "analysis": analysis, + "recommendations": analysis.get("recommendations", []), + "concerns": analysis.get("concerns", []), + "proposals": analysis.get("proposals", []) + } + + return { + "phase": "expertise_input", + "inputs": expertise_inputs, + "timestamp": datetime.now().isoformat() + } + + async def conduct_cross_examination(self, expertise_input): + """Conduct cross-examination of expertise inputs""" + cross_examination_results = {} + + for member_role, input_data in expertise_input["inputs"].items(): + member = self.board_members[member_role] + + # Other members examine this member's input + examinations = [] + for examiner_role, examiner in self.board_members.items(): + if examiner_role != member_role: + examination = await examiner.examine_input( + input_data, member_role + ) + examinations.append({ + "examiner": examiner_role, + "examination": examination + }) + + cross_examination_results[member_role] = { + "original_input": input_data, + "examinations": examinations, + "validation_status": self.assess_validation_status(examinations) + } + + return { + "phase": "cross_examination", + "results": cross_examination_results, + "timestamp": datetime.now().isoformat() + } + + async def generate_alternatives(self, task, expertise_input): + """Generate alternative proposals based on expertise input""" + alternatives = [] + + # Generate alternatives from each expertise area + for member_role, input_data in expertise_input["inputs"].items(): + member = self.board_members[member_role] + + member_alternatives = await member.generate_alternatives( + task, input_data + ) + + alternatives.extend(member_alternatives) + + # Combine and synthesize alternatives + synthesized_alternatives = await self.synthesize_alternatives(alternatives) + + return { + "phase": "alternatives", + "individual_alternatives": alternatives, + "synthesized_alternatives": synthesized_alternatives, + "timestamp": datetime.now().isoformat() + } +``` + +## Phase 3: Decision Making + +### Voting and Consensus Process + +```mermaid +graph TD + A[Proposal Review] --> B[Voting Preparation] + B --> C[Individual Voting] + C --> D[Vote Aggregation] + D --> E[Threshold Check] + E -->|Below Threshold| F[Consensus Building] + F --> G[Proposal Refinement] + G --> C + E -->|Above Threshold| H[Decision Finalization] + H --> I[Plan Creation] + I --> J[Execution Orders] + + subgraph "Voting Components" + K[Weighted Voting] + L[Secret Ballot] + M[Transparent Process] + N[Conflict Resolution] + end + + C --> K + C --> L + D --> M + F --> N +``` + +**Diagram Explanation:** +This diagram shows the structured voting and consensus process that ensures fair and transparent decision-making. The process begins with proposal review and voting preparation, followed by individual voting where each board member casts their vote. Votes are aggregated and checked against the consensus threshold. If the threshold isn't met, consensus building and proposal refinement processes are initiated. Once the threshold is achieved, the decision is finalized, a plan is created, and execution orders are generated. + +**Technical Implementation:** +```python +# Example: Voting and consensus system +class VotingSystem: + def __init__(self, board_members, config): + self.board_members = board_members + self.config = config + self.voting_history = [] + self.consensus_threshold = config.get("voting_threshold", 0.7) + self.voting_weights = config.get("voting_weights", {}) + + async def conduct_voting(self, proposals): + """Conduct voting on proposals""" + voting_result = { + "rounds": [], + "final_decision": None, + "consensus_achieved": False, + "voting_summary": {} + } + + current_proposals = proposals + round_number = 1 + + while round_number <= self.config.get("max_voting_rounds", 3): + # Conduct voting round + round_result = await self.conduct_voting_round( + current_proposals, round_number + ) + voting_result["rounds"].append(round_result) + + # Check if consensus achieved + if round_result["consensus_achieved"]: + voting_result["final_decision"] = round_result["winning_proposal"] + voting_result["consensus_achieved"] = True + break + + # Refine proposals for next round + current_proposals = await self.refine_proposals( + current_proposals, round_result + ) + + round_number += 1 + + # Generate voting summary + voting_result["voting_summary"] = self.generate_voting_summary( + voting_result["rounds"] + ) + + return voting_result + + async def conduct_voting_round(self, proposals, round_number): + """Conduct a single voting round""" + round_result = { + "round": round_number, + "proposals": proposals, + "votes": {}, + "aggregated_results": {}, + "consensus_achieved": False, + "winning_proposal": None + } + + # Collect votes from each board member + for member_role, member in self.board_members.items(): + vote = await member.vote_on_proposals(proposals, round_number) + round_result["votes"][member_role] = vote + + # Aggregate votes + aggregated_results = self.aggregate_votes( + round_result["votes"], proposals + ) + round_result["aggregated_results"] = aggregated_results + + # Check consensus + consensus_check = self.check_consensus(aggregated_results) + round_result["consensus_achieved"] = consensus_check["achieved"] + round_result["winning_proposal"] = consensus_check["winning_proposal"] + + return round_result + + def aggregate_votes(self, votes, proposals): + """Aggregate votes using weighted voting system""" + aggregated_results = {} + + for proposal_id in [p["id"] for p in proposals]: + total_weighted_score = 0 + total_weight = 0 + vote_counts = {} + + for member_role, vote in votes.items(): + member_weight = self.voting_weights.get(member_role, 1.0) + + if proposal_id in vote["scores"]: + score = vote["scores"][proposal_id] + weighted_score = score * member_weight + total_weighted_score += weighted_score + total_weight += member_weight + + # Track vote distribution + vote_counts[member_role] = { + "score": score, + "weight": member_weight, + "weighted_score": weighted_score + } + + # Calculate final score + final_score = total_weighted_score / total_weight if total_weight > 0 else 0 + + aggregated_results[proposal_id] = { + "final_score": final_score, + "total_weight": total_weight, + "vote_counts": vote_counts, + "consensus_percentage": final_score + } + + return aggregated_results + + def check_consensus(self, aggregated_results): + """Check if consensus threshold is met""" + best_proposal = None + best_score = 0 + + for proposal_id, result in aggregated_results.items(): + if result["final_score"] > best_score: + best_score = result["final_score"] + best_proposal = proposal_id + + consensus_achieved = best_score >= self.consensus_threshold + + return { + "achieved": consensus_achieved, + "winning_proposal": best_proposal if consensus_achieved else None, + "best_score": best_score, + "threshold": self.consensus_threshold + } +``` + +## Phase 4: Execution and Monitoring + +### Agent Execution Management + +```mermaid +graph TD + A[Execution Orders] --> B[Agent Assignment] + B --> C[Task Distribution] + C --> D[Parallel Execution] + D --> E[Progress Monitoring] + E --> F[Result Collection] + F --> G[Quality Assessment] + G --> H{Quality Met?} + H -->|No| I[Refinement Request] + I --> J[Agent Re-execution] + J --> E + H -->|Yes| K[Result Aggregation] + K --> L[Board Review] + + subgraph "Execution Components" + M[Resource Allocation] + N[Deadline Management] + O[Error Handling] + P[Performance Tracking] + end + + B --> M + C --> N + D --> O + E --> P +``` + +**Diagram Explanation:** +This diagram illustrates the execution and monitoring phase where the board's decisions are implemented through specialized agents. Execution orders are created and distributed to appropriate agents, who execute tasks in parallel while being monitored for progress. Results are collected, assessed for quality, and either approved or sent back for refinement. Once quality standards are met, results are aggregated and presented to the board for final review. + +**Technical Implementation:** +```python +# Example: Execution management system +class ExecutionManager: + def __init__(self, agents, config): + self.agents = agents + self.config = config + self.execution_status = {} + self.performance_metrics = {} + + async def execute_plan(self, execution_plan): + """Execute the board's approved plan""" + execution_result = { + "phases": [], + "final_results": None, + "quality_metrics": {}, + "performance_summary": {} + } + + # Phase 1: Agent assignment and task distribution + assignment_phase = await self.assign_agents(execution_plan) + execution_result["phases"].append(assignment_phase) + + # Phase 2: Parallel execution with monitoring + execution_phase = await self.execute_tasks(assignment_phase["assignments"]) + execution_result["phases"].append(execution_phase) + + # Phase 3: Result collection and quality assessment + assessment_phase = await self.assess_results(execution_phase["results"]) + execution_result["phases"].append(assessment_phase) + + # Phase 4: Result aggregation and board review + review_phase = await self.prepare_board_review(assessment_phase) + execution_result["phases"].append(review_phase) + + execution_result["final_results"] = review_phase["final_results"] + execution_result["quality_metrics"] = assessment_phase["quality_metrics"] + execution_result["performance_summary"] = execution_phase["performance_summary"] + + return execution_result + + async def assign_agents(self, execution_plan): + """Assign agents to tasks based on execution plan""" + assignments = {} + + for task_id, task_details in execution_plan["tasks"].items(): + # Select appropriate agent based on task requirements + selected_agent = await self.select_agent_for_task(task_details) + + # Prepare task assignment + assignment = { + "task_id": task_id, + "agent": selected_agent, + "task_details": task_details, + "deadline": task_details.get("deadline"), + "priority": task_details.get("priority", "normal"), + "resources": task_details.get("resources", []), + "expected_output": task_details.get("expected_output") + } + + assignments[task_id] = assignment + + return { + "phase": "agent_assignment", + "assignments": assignments, + "timestamp": datetime.now().isoformat() + } + + async def execute_tasks(self, assignments): + """Execute tasks in parallel with monitoring""" + execution_tasks = [] + execution_results = {} + performance_metrics = {} + + # Create execution tasks + for task_id, assignment in assignments.items(): + task = self.create_execution_task(assignment) + execution_tasks.append(task) + + # Execute tasks in parallel + results = await asyncio.gather(*execution_tasks, return_exceptions=True) + + # Process results + for i, result in enumerate(results): + task_id = list(assignments.keys())[i] + + if isinstance(result, Exception): + execution_results[task_id] = { + "status": "error", + "error": str(result), + "retry_count": 0 + } + else: + execution_results[task_id] = { + "status": "completed", + "result": result, + "execution_time": result.get("execution_time"), + "quality_score": result.get("quality_score") + } + + # Collect performance metrics + performance_metrics[task_id] = self.collect_performance_metrics( + task_id, result + ) + + return { + "phase": "task_execution", + "results": execution_results, + "performance_summary": self.summarize_performance(performance_metrics), + "timestamp": datetime.now().isoformat() + } + + async def assess_results(self, execution_results): + """Assess quality of execution results""" + quality_metrics = {} + overall_quality_score = 0 + total_tasks = len(execution_results) + + for task_id, result in execution_results.items(): + if result["status"] == "completed": + # Assess individual task quality + task_quality = await self.assess_task_quality(result) + quality_metrics[task_id] = task_quality + overall_quality_score += task_quality["overall_score"] + else: + quality_metrics[task_id] = { + "overall_score": 0, + "completeness": 0, + "accuracy": 0, + "relevance": 0, + "issues": ["Task failed to execute"] + } + + # Calculate overall quality + overall_quality_score = overall_quality_score / total_tasks if total_tasks > 0 else 0 + + return { + "phase": "quality_assessment", + "quality_metrics": quality_metrics, + "overall_quality_score": overall_quality_score, + "quality_threshold_met": overall_quality_score >= self.config.get("quality_threshold", 0.8), + "timestamp": datetime.now().isoformat() + } +``` + +## Best Practices and Optimization + +### Performance Optimization Strategies + +1. **Parallel Execution**: Maximize parallel agent execution for faster results +2. **Resource Pooling**: Implement agent pooling for better resource utilization +3. **Caching**: Cache common analysis results to avoid redundant computation +4. **Load Balancing**: Distribute tasks evenly across available agents + +### Quality Assurance + +1. **Quality Gates**: Implement quality checkpoints throughout the process +2. **Peer Review**: Enable peer review mechanisms for critical decisions +3. **Validation**: Validate outputs against predefined criteria +4. **Continuous Improvement**: Learn from previous executions to improve future performance + +### Monitoring and Analytics + +```python +# Example: Performance monitoring system +class PerformanceMonitor: + def __init__(self): + self.metrics = { + "execution_times": [], + "quality_scores": [], + "consensus_rounds": [], + "error_rates": [] + } + + def track_execution_time(self, phase, duration): + """Track execution time for different phases""" + self.metrics["execution_times"].append({ + "phase": phase, + "duration": duration, + "timestamp": datetime.now().isoformat() + }) + + def track_quality_score(self, score): + """Track quality scores""" + self.metrics["quality_scores"].append({ + "score": score, + "timestamp": datetime.now().isoformat() + }) + + def generate_performance_report(self): + """Generate comprehensive performance report""" + return { + "average_execution_time": self.calculate_average_execution_time(), + "quality_trends": self.analyze_quality_trends(), + "consensus_efficiency": self.analyze_consensus_efficiency(), + "error_analysis": self.analyze_errors(), + "recommendations": self.generate_recommendations() + } +``` \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/index.md b/docs/swarms/structs/board_of_directors/index.md new file mode 100644 index 00000000..136fdea8 --- /dev/null +++ b/docs/swarms/structs/board_of_directors/index.md @@ -0,0 +1,291 @@ +# Board of Directors - Multi-Agent Architecture + +The Board of Directors is a sophisticated multi-agent architecture that implements collective decision-making through democratic processes, voting mechanisms, and role-based leadership. This architecture provides an alternative to single-director patterns by enabling collaborative intelligence through structured governance. + +## 🏛️ Overview + +The Board of Directors architecture follows a democratic workflow pattern: + +1. **Task Reception**: User provides a task to the swarm +2. **Board Meeting**: Board of Directors convenes to discuss and create a plan +3. **Voting & Consensus**: Board members vote and reach consensus on task distribution +4. **Order Distribution**: Board distributes orders to specialized worker agents +5. **Execution**: Individual agents execute their assigned tasks +6. **Feedback Loop**: Board evaluates results and issues new orders if needed (up to `max_loops`) +7. **Context Preservation**: All conversation history and context is maintained throughout the process + +## 🏗️ Architecture Components + +### Core Components + +| Component | Description | Purpose | +|-----------|-------------|---------| +| **[BoardOfDirectorsSwarm](board_of_directors_swarm.md)** | Main orchestration class | Manages the entire board workflow and agent coordination | +| **[Board Member Roles](board_of_directors_roles.md)** | Role definitions and hierarchy | Defines responsibilities and voting weights for each board member | +| **[Decision Making Process](board_of_directors_decision_making.md)** | Voting and consensus mechanisms | Implements democratic decision-making with weighted voting | +| **[Workflow Management](board_of_directors_workflow.md)** | Process orchestration | Manages the complete lifecycle from task reception to final delivery | + +### Architecture Diagram + +```mermaid +graph TD + A[User Task] --> B[Board of Directors] + B --> C[Board Meeting & Discussion] + C --> D[Voting & Consensus] + D --> E[Create Plan & Orders] + E --> F[Distribute to Agents] + F --> G[Agent 1] + F --> H[Agent 2] + F --> I[Agent N] + G --> J[Execute Task] + H --> J + I --> J + J --> K[Report Results] + K --> L[Board Evaluation] + L --> M{More Loops?} + M -->|Yes| C + M -->|No| N[Final Output] +``` + +## 🎯 Key Features + +### Democratic Decision Making +- **Collective Intelligence**: Multiple perspectives through board member collaboration +- **Weighted Voting**: Different voting weights based on roles and expertise +- **Consensus Building**: Support for both majority voting and consensus approaches +- **Conflict Resolution**: Structured processes for handling disagreements + +### Role-Based Leadership +- **Hierarchical Structure**: Clear roles with defined responsibilities +- **Specialized Expertise**: Each board member brings unique domain knowledge +- **Authority Distribution**: Balanced power distribution across roles +- **Accountability**: Clear lines of responsibility and decision ownership + +### Operational Excellence +- **Iterative Refinement**: Multiple feedback loops for improved results +- **Context Preservation**: Full conversation history maintained +- **Flexible Output Formats**: Support for various output types (dict, str, list) +- **Comprehensive Logging**: Detailed logging for debugging and monitoring + +## 👥 Board Member Roles + +The Board of Directors supports various roles with different responsibilities and voting weights: + +| Role | Description | Voting Weight | Responsibilities | +|------|-------------|---------------|------------------| +| `CHAIRMAN` | Primary leader responsible for board meetings and final decisions | 1.5 | Leading meetings, facilitating consensus, making final decisions | +| `VICE_CHAIRMAN` | Secondary leader who supports the chairman | 1.2 | Supporting chairman, coordinating operations | +| `SECRETARY` | Responsible for documentation and meeting minutes | 1.0 | Documenting meetings, maintaining records | +| `TREASURER` | Manages financial aspects and resource allocation | 1.0 | Financial oversight, resource management | +| `EXECUTIVE_DIRECTOR` | Executive-level board member with operational authority | 1.5 | Strategic planning, operational oversight | +| `MEMBER` | General board member with specific expertise | 1.0 | Contributing expertise, participating in decisions | + +## 🚀 Quick Start + +### Basic Setup + +```python +from swarms import Agent +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, + BoardMember, + BoardMemberRole +) +from swarms.config.board_config import enable_board_feature + +# Enable the Board of Directors feature +enable_board_feature() + +# Create board members with specific roles +chairman = Agent( + agent_name="Chairman", + agent_description="Chairman of the Board responsible for leading meetings", + model_name="gpt-4o-mini", + system_prompt="You are the Chairman of the Board..." +) + +vice_chairman = Agent( + agent_name="Vice-Chairman", + agent_description="Vice Chairman who supports the Chairman", + model_name="gpt-4o-mini", + system_prompt="You are the Vice Chairman..." +) + +# Create BoardMember objects with roles and expertise +board_members = [ + BoardMember(chairman, BoardMemberRole.CHAIRMAN, 1.5, ["leadership", "strategy"]), + BoardMember(vice_chairman, BoardMemberRole.VICE_CHAIRMAN, 1.2, ["operations", "coordination"]), +] + +# Create worker agents +research_agent = Agent( + agent_name="Research-Specialist", + agent_description="Expert in market research and analysis", + model_name="gpt-4o", +) + +financial_agent = Agent( + agent_name="Financial-Analyst", + agent_description="Specialist in financial analysis and valuation", + model_name="gpt-4o", +) + +# Initialize the Board of Directors swarm +board_swarm = BoardOfDirectorsSwarm( + name="Executive_Board_Swarm", + description="Executive board with specialized roles for strategic decision-making", + board_members=board_members, + agents=[research_agent, financial_agent], + max_loops=2, + verbose=True, + decision_threshold=0.6, + enable_voting=True, + enable_consensus=True, +) + +# Execute a complex task with democratic decision-making +result = board_swarm.run(task="Analyze the market potential for Tesla (TSLA) stock") +print(result) +``` + +## 📋 Use Cases + +### Corporate Governance +- **Strategic Planning**: Long-term business strategy development +- **Risk Management**: Comprehensive risk assessment and mitigation +- **Resource Allocation**: Optimal distribution of company resources +- **Performance Oversight**: Monitoring and evaluating organizational performance + +### Financial Analysis +- **Portfolio Management**: Investment portfolio optimization and rebalancing +- **Market Analysis**: Comprehensive market research and trend analysis +- **Risk Assessment**: Financial risk evaluation and management +- **Compliance Monitoring**: Regulatory compliance and audit preparation + +### Research & Development +- **Technology Assessment**: Evaluation of emerging technologies +- **Product Development**: Strategic product planning and development +- **Innovation Management**: Managing innovation pipelines and initiatives +- **Quality Assurance**: Ensuring high standards across development processes + +### Project Management +- **Complex Project Planning**: Multi-faceted project strategy development +- **Resource Optimization**: Efficient allocation of project resources +- **Stakeholder Management**: Coordinating diverse stakeholder interests +- **Risk Mitigation**: Identifying and addressing project risks + +## ⚙️ Configuration + +### Decision Thresholds +- **Default Threshold**: 60% consensus required for decisions +- **Configurable**: Adjustable based on organizational needs +- **Role-Based**: Different thresholds for different types of decisions +- **Fallback Mechanisms**: Chairman can make final decisions when consensus fails + +### Voting Mechanisms +- **Weighted Voting**: Different voting weights based on roles +- **Consensus Building**: Support for both majority and consensus approaches +- **Conflict Resolution**: Structured processes for handling disagreements +- **Transparency**: Clear visibility into decision-making processes + +### Operational Settings +- **Meeting Duration**: Configurable board meeting time limits +- **Participation Requirements**: Minimum participation rates for valid decisions +- **Feedback Loops**: Multiple iterations for complex problem-solving +- **Documentation**: Comprehensive record-keeping of all decisions + +## 🔧 Advanced Features + +### Custom Board Templates +Pre-configured board templates for common use cases: + +```python +from swarms.config.board_config import get_default_board_template + +# Get a financial analysis board template +financial_board = get_default_board_template("financial_analysis") + +# Get a strategic planning board template +strategic_board = get_default_board_template("strategic_planning") +``` + +### Dynamic Role Assignment +Automatically assign roles based on task requirements: + +```python +# Board members are automatically assigned roles based on expertise +board_swarm = BoardOfDirectorsSwarm( + board_members=board_members, + auto_assign_roles=True, + role_mapping={ + "financial_analysis": ["Treasurer", "Financial_Member"], + "strategic_planning": ["Chairman", "Executive_Director"] + } +) +``` + +### Consensus Optimization +Advanced consensus-building mechanisms: + +```python +# Enable advanced consensus features +board_swarm = BoardOfDirectorsSwarm( + enable_consensus=True, + consensus_timeout=300, # 5 minutes + min_participation_rate=0.5, # 50% minimum participation + auto_fallback_to_chairman=True +) +``` + +## 📊 Performance Monitoring + +### Decision Metrics +- **Consensus Rate**: Percentage of decisions reached by consensus +- **Voting Participation**: Board member participation rates +- **Decision Quality**: Assessment of decision outcomes +- **Execution Efficiency**: Time from decision to implementation + +### Operational Metrics +- **Meeting Duration**: Average board meeting length +- **Agent Utilization**: How effectively worker agents are utilized +- **Feedback Loop Efficiency**: Speed of iterative improvements +- **Resource Optimization**: Efficient use of computational resources + +## 🛠️ Troubleshooting + +### Common Issues + +1. **Consensus Failures**: Lower decision threshold or increase timeout +2. **Role Conflicts**: Ensure clear role definitions and responsibilities +3. **Agent Coordination**: Verify agent communication and task distribution +4. **Performance Issues**: Monitor resource usage and optimize configurations + +### Best Practices + +1. **Role Clarity**: Define clear responsibilities for each board member +2. **Expertise Alignment**: Match board member expertise to task requirements +3. **Consensus Building**: Allow adequate time for discussion and consensus +4. **Documentation**: Maintain comprehensive records of all decisions +5. **Continuous Improvement**: Learn from each execution cycle + +## 📚 Documentation Sections + +- **[BoardOfDirectorsSwarm](board_of_directors_swarm.md)**: Complete API reference and implementation details +- **[Board Member Roles](board_of_directors_roles.md)**: Detailed role definitions and responsibilities +- **[Decision Making Process](board_of_directors_decision_making.md)**: Voting mechanisms and consensus building +- **[Workflow Management](board_of_directors_workflow.md)**: Complete process orchestration guide + +## 🎯 Success Criteria + +A successful Board of Directors implementation should demonstrate: + +- ✅ **Democratic Decision Making**: All board members contribute to decisions +- ✅ **Consensus Achievement**: Decisions reached through collaborative processes +- ✅ **Role Effectiveness**: Each board member fulfills their responsibilities +- ✅ **Agent Coordination**: Worker agents execute tasks efficiently +- ✅ **Quality Output**: High-quality results through collective intelligence +- ✅ **Process Transparency**: Clear visibility into decision-making processes + +--- + +The Board of Directors architecture represents a sophisticated approach to multi-agent collaboration, enabling organizations to leverage collective intelligence through structured governance and democratic decision-making processes. \ No newline at end of file From 8d48dcaf2ae4a49b8e7490085681b707080a37c3 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:48:43 +0300 Subject: [PATCH 07/73] Delete docs/swarms/structs/board_of_directors directory --- .../board_of_directors_decision_making.md | 885 ------------- .../board_of_directors_example.md | 466 ------- .../board_of_directors_roles.md | 1151 ----------------- .../board_of_directors_swarm.md | 704 ---------- .../board_of_directors_workflow.md | 908 ------------- .../structs/board_of_directors/index.md | 291 ----- 6 files changed, 4405 deletions(-) delete mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md delete mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_example.md delete mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_roles.md delete mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_swarm.md delete mode 100644 docs/swarms/structs/board_of_directors/board_of_directors_workflow.md delete mode 100644 docs/swarms/structs/board_of_directors/index.md diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md b/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md deleted file mode 100644 index 22a3cb35..00000000 --- a/docs/swarms/structs/board_of_directors/board_of_directors_decision_making.md +++ /dev/null @@ -1,885 +0,0 @@ -# Board of Directors Decision Making - -The Board of Directors decision-making process is a sophisticated, multi-layered system that ensures comprehensive analysis, balanced consideration, and effective consensus building. This process combines democratic principles with hierarchical authority to achieve optimal outcomes. - -## Decision-Making Framework - -### Overview of the Decision Process - -```mermaid -graph TD - A[Task Analysis] --> B[Expertise Assignment] - B --> C[Individual Analysis] - C --> D[Group Discussion] - D --> E[Proposal Development] - E --> F[Voting Process] - F --> G[Consensus Building] - G --> H{Consensus Achieved?} - H -->|Yes| I[Decision Finalization] - H -->|No| J[Conflict Resolution] - J --> K[Proposal Refinement] - K --> F - I --> L[Execution Planning] - L --> M[Implementation Oversight] - - style A fill:#e3f2fd - style I fill:#c8e6c9 - style H fill:#fff3e0 -``` - -**Diagram Explanation:** -This comprehensive decision-making framework shows the complete process from initial task analysis to final implementation oversight. The process begins with task analysis and expertise assignment, followed by individual analysis where each board member contributes their specialized knowledge. Group discussion facilitates information sharing and debate, leading to proposal development. The voting process and consensus building ensure democratic decision-making, with conflict resolution mechanisms for when consensus cannot be reached. Once consensus is achieved, decisions are finalized and execution planning begins, followed by implementation oversight. - -**Technical Implementation:** -```python -# Example: Decision-making framework implementation -class DecisionMakingFramework: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.decision_history = [] - self.consensus_threshold = config.get("consensus_threshold", 0.7) - self.max_voting_rounds = config.get("max_voting_rounds", 3) - - async def execute_decision_process(self, task): - """Execute the complete decision-making process""" - decision_result = { - "task": task, - "phases": [], - "final_decision": None, - "consensus_achieved": False, - "execution_plan": None - } - - # Phase 1: Task Analysis and Expertise Assignment - analysis_phase = await self.analyze_task_and_assign_expertise(task) - decision_result["phases"].append(analysis_phase) - - # Phase 2: Individual Analysis - individual_analysis = await self.conduct_individual_analysis(task, analysis_phase["expertise_assignments"]) - decision_result["phases"].append(individual_analysis) - - # Phase 3: Group Discussion - group_discussion = await self.facilitate_group_discussion(individual_analysis) - decision_result["phases"].append(group_discussion) - - # Phase 4: Proposal Development - proposal_development = await self.develop_proposals(group_discussion) - decision_result["phases"].append(proposal_development) - - # Phase 5: Voting and Consensus Building - consensus_result = await self.build_consensus(proposal_development["proposals"]) - decision_result["phases"].append(consensus_result) - - # Phase 6: Decision Finalization - if consensus_result["consensus_achieved"]: - finalization = await self.finalize_decision(consensus_result) - decision_result["phases"].append(finalization) - decision_result["final_decision"] = finalization["decision"] - decision_result["consensus_achieved"] = True - else: - # Handle conflict resolution - conflict_resolution = await self.resolve_conflicts(consensus_result) - decision_result["phases"].append(conflict_resolution) - decision_result["final_decision"] = conflict_resolution["decision"] - decision_result["consensus_achieved"] = False - - # Phase 7: Execution Planning - execution_planning = await self.plan_execution(decision_result["final_decision"]) - decision_result["phases"].append(execution_planning) - decision_result["execution_plan"] = execution_planning["plan"] - - # Store decision in history - self.decision_history.append(decision_result) - - return decision_result - - async def analyze_task_and_assign_expertise(self, task): - """Analyze task and assign expertise areas to board members""" - # Analyze task complexity and requirements - task_analysis = await self.analyze_task_complexity(task) - - # Identify required expertise areas - required_expertise = await self.identify_required_expertise(task_analysis) - - # Assign expertise areas to board members - expertise_assignments = await self.assign_expertise_to_members(required_expertise) - - return { - "phase": "task_analysis_and_expertise_assignment", - "task_analysis": task_analysis, - "required_expertise": required_expertise, - "expertise_assignments": expertise_assignments, - "timestamp": datetime.now().isoformat() - } - - async def conduct_individual_analysis(self, task, expertise_assignments): - """Conduct individual analysis by each board member""" - individual_analyses = {} - - for member_role, expertise_areas in expertise_assignments.items(): - member = self.board_members[member_role] - - # Conduct analysis based on assigned expertise - analysis = await member.analyze_task_areas(task, expertise_areas) - - individual_analyses[member_role] = { - "expertise_areas": expertise_areas, - "analysis": analysis, - "recommendations": analysis.get("recommendations", []), - "concerns": analysis.get("concerns", []), - "proposals": analysis.get("proposals", []) - } - - return { - "phase": "individual_analysis", - "analyses": individual_analyses, - "timestamp": datetime.now().isoformat() - } -``` - -## Voting Mechanisms - -### Weighted Voting System - -```mermaid -graph TD - A[Proposal Submission] --> B[Vote Collection] - B --> C[Weight Application] - C --> D[Score Calculation] - D --> E[Threshold Check] - E -->|Above Threshold| F[Consensus Achieved] - E -->|Below Threshold| G[Additional Rounds] - G --> H[Proposal Refinement] - H --> B - - subgraph "Voting Components" - I[Individual Votes] - J[Role Weights] - K[Consensus Threshold] - L[Conflict Resolution] - end - - B --> I - C --> J - E --> K - G --> L -``` - -**Diagram Explanation:** -This diagram illustrates the weighted voting system used by the Board of Directors. The process begins with proposal submission, followed by vote collection from all board members. Each vote is weighted according to the member's role and authority level. Scores are calculated using the weighted voting formula, and results are checked against the consensus threshold. If the threshold is met, consensus is achieved. If not, additional voting rounds with proposal refinement are conducted until consensus is reached or maximum rounds are exceeded. - -**Technical Implementation:** -```python -# Example: Weighted voting system implementation -class WeightedVotingSystem: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.voting_weights = { - "CHAIRMAN": 1.5, - "VICE_CHAIRMAN": 1.2, - "EXECUTIVE_DIRECTOR": 1.5, - "SECRETARY": 1.0, - "TREASURER": 1.0, - "MEMBER": 1.0 - } - self.consensus_threshold = config.get("consensus_threshold", 0.7) - self.max_voting_rounds = config.get("max_voting_rounds", 3) - - async def conduct_weighted_voting(self, proposals): - """Conduct weighted voting on proposals""" - voting_result = { - "rounds": [], - "final_decision": None, - "consensus_achieved": False, - "voting_summary": {} - } - - current_proposals = proposals - round_number = 1 - - while round_number <= self.max_voting_rounds: - # Collect votes from all board members - votes = await self.collect_votes(current_proposals) - - # Apply voting weights - weighted_votes = self.apply_voting_weights(votes) - - # Calculate scores - scores = self.calculate_weighted_scores(weighted_votes, current_proposals) - - # Check consensus threshold - consensus_check = self.check_consensus_threshold(scores) - - round_result = { - "round": round_number, - "votes": votes, - "weighted_votes": weighted_votes, - "scores": scores, - "consensus_achieved": consensus_check["achieved"], - "winning_proposal": consensus_check["winning_proposal"] - } - - voting_result["rounds"].append(round_result) - - if consensus_check["achieved"]: - voting_result["final_decision"] = consensus_check["winning_proposal"] - voting_result["consensus_achieved"] = True - break - - # Refine proposals for next round - current_proposals = await self.refine_proposals(current_proposals, round_result) - round_number += 1 - - # Generate voting summary - voting_result["voting_summary"] = self.generate_voting_summary(voting_result["rounds"]) - - return voting_result - - async def collect_votes(self, proposals): - """Collect votes from all board members""" - votes = {} - - for member_role, member in self.board_members.items(): - # Each member votes on all proposals - member_votes = await member.vote_on_proposals(proposals) - - votes[member_role] = { - "proposal_scores": member_votes["scores"], - "rationale": member_votes["rationale"], - "confidence_level": member_votes.get("confidence_level", 1.0), - "timestamp": datetime.now().isoformat() - } - - return votes - - def apply_voting_weights(self, votes): - """Apply voting weights to member votes""" - weighted_votes = {} - - for member_role, vote_data in votes.items(): - weight = self.voting_weights.get(member_role, 1.0) - - weighted_scores = {} - for proposal_id, score in vote_data["proposal_scores"].items(): - weighted_scores[proposal_id] = score * weight - - weighted_votes[member_role] = { - "original_scores": vote_data["proposal_scores"], - "weighted_scores": weighted_scores, - "weight": weight, - "rationale": vote_data["rationale"], - "confidence_level": vote_data["confidence_level"] - } - - return weighted_votes - - def calculate_weighted_scores(self, weighted_votes, proposals): - """Calculate final weighted scores for each proposal""" - proposal_scores = {} - - for proposal in proposals: - proposal_id = proposal["id"] - total_weighted_score = 0 - total_weight = 0 - vote_count = 0 - - for member_role, vote_data in weighted_votes.items(): - if proposal_id in vote_data["weighted_scores"]: - weighted_score = vote_data["weighted_scores"][proposal_id] - weight = vote_data["weight"] - - total_weighted_score += weighted_score - total_weight += weight - vote_count += 1 - - # Calculate average weighted score - if total_weight > 0: - final_score = total_weighted_score / total_weight - else: - final_score = 0 - - proposal_scores[proposal_id] = { - "final_score": final_score, - "total_weight": total_weight, - "vote_count": vote_count, - "consensus_percentage": final_score - } - - return proposal_scores - - def check_consensus_threshold(self, scores): - """Check if any proposal meets the consensus threshold""" - best_proposal = None - best_score = 0 - - for proposal_id, score_data in scores.items(): - if score_data["final_score"] > best_score: - best_score = score_data["final_score"] - best_proposal = proposal_id - - consensus_achieved = best_score >= self.consensus_threshold - - return { - "achieved": consensus_achieved, - "winning_proposal": best_proposal if consensus_achieved else None, - "best_score": best_score, - "threshold": self.consensus_threshold - } -``` - -## Consensus Building Process - -### Multi-Round Consensus Building - -```mermaid -flowchart TD - A[Initial Proposals] --> B[Round 1 Voting] - B --> C[Score Calculation] - C --> D{Consensus?} - D -->|Yes| E[Consensus Achieved] - D -->|No| F[Proposal Refinement] - F --> G[Round 2 Voting] - G --> H[Score Calculation] - H --> I{Consensus?} - I -->|Yes| J[Consensus Achieved] - I -->|No| K[Final Round] - K --> L[Round 3 Voting] - L --> M[Score Calculation] - M --> N{Consensus?} - N -->|Yes| O[Consensus Achieved] - N -->|No| P[Authority Decision] - - subgraph "Consensus Building Elements" - Q[Discussion Facilitation] - R[Conflict Resolution] - S[Proposal Synthesis] - T[Mediation Process] - end - - F --> Q - F --> R - F --> S - K --> T -``` - -**Diagram Explanation:** -This flowchart shows the multi-round consensus building process used by the Board of Directors. The process begins with initial proposals and proceeds through multiple voting rounds. After each round, scores are calculated and consensus is checked. If consensus is not achieved, proposals are refined through discussion facilitation, conflict resolution, proposal synthesis, and mediation processes. The process continues for up to three rounds, after which authority decision-making is used if consensus still cannot be reached. - -**Technical Implementation:** -```python -# Example: Consensus building system -class ConsensusBuildingSystem: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.max_rounds = config.get("max_consensus_rounds", 3) - self.consensus_threshold = config.get("consensus_threshold", 0.7) - self.discussion_facilitator = board_members.get("CHAIRMAN") - - async def build_consensus(self, initial_proposals): - """Build consensus through multiple rounds""" - consensus_result = { - "rounds": [], - "consensus_achieved": False, - "final_proposal": None, - "authority_decision": None - } - - current_proposals = initial_proposals - round_number = 1 - - while round_number <= self.max_rounds: - # Conduct voting round - voting_result = await self.conduct_voting_round(current_proposals, round_number) - - # Check consensus - if voting_result["consensus_achieved"]: - consensus_result["rounds"].append(voting_result) - consensus_result["consensus_achieved"] = True - consensus_result["final_proposal"] = voting_result["winning_proposal"] - break - - # If no consensus and not final round, refine proposals - if round_number < self.max_rounds: - refinement_result = await self.refine_proposals(current_proposals, voting_result) - consensus_result["rounds"].append(voting_result) - current_proposals = refinement_result["refined_proposals"] - else: - # Final round - use authority decision - authority_decision = await self.make_authority_decision(voting_result) - consensus_result["rounds"].append(voting_result) - consensus_result["authority_decision"] = authority_decision - consensus_result["final_proposal"] = authority_decision["selected_proposal"] - - round_number += 1 - - return consensus_result - - async def conduct_voting_round(self, proposals, round_number): - """Conduct a single voting round""" - round_result = { - "round": round_number, - "proposals": proposals, - "votes": {}, - "scores": {}, - "consensus_achieved": False, - "winning_proposal": None - } - - # Collect votes from all board members - for member_role, member in self.board_members.items(): - vote = await member.vote_on_proposals(proposals, round_number) - round_result["votes"][member_role] = vote - - # Calculate weighted scores - weighted_scores = self.calculate_weighted_scores(round_result["votes"], proposals) - round_result["scores"] = weighted_scores - - # Check consensus - consensus_check = self.check_consensus(weighted_scores) - round_result["consensus_achieved"] = consensus_check["achieved"] - round_result["winning_proposal"] = consensus_check["winning_proposal"] - - return round_result - - async def refine_proposals(self, current_proposals, voting_result): - """Refine proposals based on voting results and discussion""" - refinement_result = { - "refined_proposals": [], - "discussion_summary": "", - "conflicts_resolved": [] - } - - # Analyze voting patterns - voting_analysis = self.analyze_voting_patterns(voting_result) - - # Identify areas of disagreement - disagreements = self.identify_disagreements(voting_result) - - # Facilitate discussion to resolve conflicts - discussion_result = await self.facilitate_discussion(disagreements, current_proposals) - refinement_result["discussion_summary"] = discussion_result["summary"] - refinement_result["conflicts_resolved"] = discussion_result["resolved_conflicts"] - - # Synthesize refined proposals - refined_proposals = await self.synthesize_proposals(current_proposals, discussion_result) - refinement_result["refined_proposals"] = refined_proposals - - return refinement_result - - async def facilitate_discussion(self, disagreements, proposals): - """Facilitate discussion to resolve disagreements""" - discussion_result = { - "summary": "", - "resolved_conflicts": [], - "new_insights": [] - } - - # Chairman facilitates discussion - if self.discussion_facilitator: - facilitation_result = await self.discussion_facilitator.facilitate_discussion( - disagreements, proposals - ) - - discussion_result["summary"] = facilitation_result["summary"] - discussion_result["resolved_conflicts"] = facilitation_result["resolved_conflicts"] - discussion_result["new_insights"] = facilitation_result["new_insights"] - - return discussion_result - - async def make_authority_decision(self, final_voting_result): - """Make authority decision when consensus cannot be reached""" - # Chairman makes final decision based on best available information - authority_decision = { - "decision_maker": "CHAIRMAN", - "decision_method": "authority_decision", - "selected_proposal": None, - "rationale": "", - "board_support_level": 0.0 - } - - # Analyze all proposals and select the best one - best_proposal = self.select_best_proposal(final_voting_result["scores"]) - authority_decision["selected_proposal"] = best_proposal["proposal_id"] - authority_decision["rationale"] = best_proposal["rationale"] - authority_decision["board_support_level"] = best_proposal["support_level"] - - return authority_decision -``` - -## Conflict Resolution Mechanisms - -### Structured Conflict Resolution - -```mermaid -graph TD - A[Conflict Identification] --> B[Conflict Analysis] - B --> C[Stakeholder Mapping] - C --> D[Root Cause Analysis] - D --> E[Resolution Strategy] - E --> F[Mediation Process] - F --> G[Compromise Facilitation] - G --> H[Agreement Building] - H --> I[Resolution Implementation] - - subgraph "Conflict Resolution Tools" - J[Mediation Techniques] - K[Compromise Strategies] - L[Consensus Building] - M[Authority Intervention] - end - - F --> J - G --> K - H --> L - I --> M -``` - -**Diagram Explanation:** -This diagram illustrates the structured conflict resolution process used by the Board of Directors. The process begins with conflict identification and proceeds through systematic analysis including stakeholder mapping and root cause analysis. A resolution strategy is developed, followed by mediation processes and compromise facilitation. The process culminates in agreement building and resolution implementation, using various tools including mediation techniques, compromise strategies, consensus building, and authority intervention when necessary. - -**Technical Implementation:** -```python -# Example: Conflict resolution system -class ConflictResolutionSystem: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.mediation_techniques = [ - "active_listening", - "interest_based_negotiation", - "brainstorming", - "consensus_building" - ] - self.resolution_strategies = [ - "compromise", - "collaboration", - "accommodation", - "authority_decision" - ] - - async def resolve_conflicts(self, conflicts, context): - """Resolve conflicts using structured approach""" - resolution_result = { - "conflicts": conflicts, - "resolution_process": [], - "final_resolution": None, - "implementation_plan": None - } - - for conflict in conflicts: - # Step 1: Analyze conflict - conflict_analysis = await self.analyze_conflict(conflict, context) - resolution_result["resolution_process"].append({ - "step": "conflict_analysis", - "conflict_id": conflict["id"], - "analysis": conflict_analysis - }) - - # Step 2: Map stakeholders - stakeholder_mapping = await self.map_stakeholders(conflict, context) - resolution_result["resolution_process"].append({ - "step": "stakeholder_mapping", - "conflict_id": conflict["id"], - "mapping": stakeholder_mapping - }) - - # Step 3: Analyze root causes - root_cause_analysis = await self.analyze_root_causes(conflict, context) - resolution_result["resolution_process"].append({ - "step": "root_cause_analysis", - "conflict_id": conflict["id"], - "analysis": root_cause_analysis - }) - - # Step 4: Develop resolution strategy - resolution_strategy = await self.develop_resolution_strategy( - conflict, conflict_analysis, stakeholder_mapping, root_cause_analysis - ) - resolution_result["resolution_process"].append({ - "step": "resolution_strategy", - "conflict_id": conflict["id"], - "strategy": resolution_strategy - }) - - # Step 5: Implement resolution - resolution_implementation = await self.implement_resolution( - conflict, resolution_strategy - ) - resolution_result["resolution_process"].append({ - "step": "resolution_implementation", - "conflict_id": conflict["id"], - "implementation": resolution_implementation - }) - - # Generate final resolution - resolution_result["final_resolution"] = await self.generate_final_resolution( - resolution_result["resolution_process"] - ) - - # Create implementation plan - resolution_result["implementation_plan"] = await self.create_implementation_plan( - resolution_result["final_resolution"] - ) - - return resolution_result - - async def analyze_conflict(self, conflict, context): - """Analyze the nature and scope of a conflict""" - analysis = { - "conflict_type": self.categorize_conflict(conflict), - "severity_level": self.assess_severity(conflict), - "scope": self.define_scope(conflict), - "impact_assessment": await self.assess_impact(conflict, context), - "urgency_level": self.assess_urgency(conflict) - } - - return analysis - - async def map_stakeholders(self, conflict, context): - """Map stakeholders involved in the conflict""" - stakeholders = { - "primary_stakeholders": [], - "secondary_stakeholders": [], - "influencers": [], - "decision_makers": [] - } - - # Identify stakeholders based on conflict type - if conflict["type"] == "resource_allocation": - stakeholders["primary_stakeholders"] = self.identify_resource_stakeholders(conflict) - elif conflict["type"] == "strategic_direction": - stakeholders["primary_stakeholders"] = self.identify_strategic_stakeholders(conflict) - elif conflict["type"] == "implementation_approach": - stakeholders["primary_stakeholders"] = self.identify_implementation_stakeholders(conflict) - - # Map stakeholder interests and positions - for stakeholder in stakeholders["primary_stakeholders"]: - stakeholder["interests"] = await self.identify_stakeholder_interests(stakeholder, conflict) - stakeholder["position"] = await self.identify_stakeholder_position(stakeholder, conflict) - stakeholder["influence_level"] = self.assess_influence_level(stakeholder) - - return stakeholders - - async def analyze_root_causes(self, conflict, context): - """Analyze root causes of the conflict""" - root_causes = { - "structural_causes": [], - "communication_causes": [], - "resource_causes": [], - "process_causes": [] - } - - # Analyze based on conflict type - if conflict["type"] == "resource_allocation": - root_causes["resource_causes"] = await self.analyze_resource_causes(conflict) - elif conflict["type"] == "strategic_direction": - root_causes["structural_causes"] = await self.analyze_structural_causes(conflict) - elif conflict["type"] == "implementation_approach": - root_causes["process_causes"] = await self.analyze_process_causes(conflict) - - # Identify communication issues - root_causes["communication_causes"] = await self.analyze_communication_causes(conflict) - - return root_causes - - async def develop_resolution_strategy(self, conflict, analysis, stakeholders, root_causes): - """Develop appropriate resolution strategy""" - strategy = { - "approach": self.select_resolution_approach(analysis, stakeholders), - "techniques": self.select_mediation_techniques(conflict, stakeholders), - "timeline": self.estimate_resolution_timeline(analysis), - "resources": self.identify_resolution_resources(analysis), - "success_criteria": self.define_success_criteria(conflict) - } - - return strategy - - async def implement_resolution(self, conflict, strategy): - """Implement the resolution strategy""" - implementation = { - "mediation_process": await self.conduct_mediation(conflict, strategy), - "compromise_facilitation": await self.facilitate_compromise(conflict, strategy), - "agreement_building": await self.build_agreement(conflict, strategy), - "implementation_oversight": await self.oversee_implementation(conflict, strategy) - } - - return implementation -``` - -## Decision Quality Assurance - -### Quality Assessment Framework - -```mermaid -graph TD - A[Decision Made] --> B[Quality Assessment] - B --> C[Completeness Check] - C --> D[Accuracy Verification] - D --> E[Feasibility Analysis] - E --> F[Risk Assessment] - F --> G[Stakeholder Impact] - G --> H[Quality Score] - H --> I{Quality Threshold?} - I -->|Yes| J[Decision Approved] - I -->|No| K[Decision Refinement] - K --> L[Additional Analysis] - L --> B - - subgraph "Quality Metrics" - M[Completeness Score] - N[Accuracy Score] - O[Feasibility Score] - P[Risk Score] - Q[Impact Score] - end - - C --> M - D --> N - E --> O - F --> P - G --> Q -``` - -**Diagram Explanation:** -This diagram shows the quality assessment framework used to ensure high-quality decisions. After a decision is made, it undergoes comprehensive quality assessment including completeness checks, accuracy verification, feasibility analysis, risk assessment, and stakeholder impact evaluation. A quality score is calculated based on these metrics, and the decision is either approved if it meets the quality threshold or sent back for refinement and additional analysis. - -**Technical Implementation:** -```python -# Example: Decision quality assurance system -class DecisionQualityAssurance: - def __init__(self, config): - self.config = config - self.quality_threshold = config.get("quality_threshold", 0.8) - self.quality_metrics = { - "completeness": {"weight": 0.2, "threshold": 0.8}, - "accuracy": {"weight": 0.25, "threshold": 0.85}, - "feasibility": {"weight": 0.2, "threshold": 0.8}, - "risk": {"weight": 0.15, "threshold": 0.7}, - "impact": {"weight": 0.2, "threshold": 0.8} - } - - async def assess_decision_quality(self, decision, context): - """Assess the quality of a decision""" - quality_assessment = { - "decision": decision, - "metrics": {}, - "overall_score": 0.0, - "threshold_met": False, - "recommendations": [] - } - - # Assess completeness - completeness_score = await self.assess_completeness(decision, context) - quality_assessment["metrics"]["completeness"] = completeness_score - - # Assess accuracy - accuracy_score = await self.assess_accuracy(decision, context) - quality_assessment["metrics"]["accuracy"] = accuracy_score - - # Assess feasibility - feasibility_score = await self.assess_feasibility(decision, context) - quality_assessment["metrics"]["feasibility"] = feasibility_score - - # Assess risk - risk_score = await self.assess_risk(decision, context) - quality_assessment["metrics"]["risk"] = risk_score - - # Assess stakeholder impact - impact_score = await self.assess_stakeholder_impact(decision, context) - quality_assessment["metrics"]["impact"] = impact_score - - # Calculate overall score - overall_score = self.calculate_overall_score(quality_assessment["metrics"]) - quality_assessment["overall_score"] = overall_score - - # Check if threshold is met - quality_assessment["threshold_met"] = overall_score >= self.quality_threshold - - # Generate recommendations - quality_assessment["recommendations"] = await self.generate_quality_recommendations( - quality_assessment["metrics"], overall_score - ) - - return quality_assessment - - async def assess_completeness(self, decision, context): - """Assess the completeness of the decision""" - completeness_factors = { - "all_aspects_covered": self.check_aspect_coverage(decision, context), - "stakeholder_consideration": self.check_stakeholder_consideration(decision, context), - "implementation_details": self.check_implementation_details(decision), - "resource_allocation": self.check_resource_allocation(decision), - "timeline_definition": self.check_timeline_definition(decision) - } - - # Calculate completeness score - completeness_score = sum(completeness_factors.values()) / len(completeness_factors) - - return { - "score": completeness_score, - "factors": completeness_factors, - "threshold_met": completeness_score >= self.quality_metrics["completeness"]["threshold"] - } - - async def assess_accuracy(self, decision, context): - """Assess the accuracy of the decision""" - accuracy_factors = { - "data_quality": self.assess_data_quality(decision, context), - "analysis_quality": self.assess_analysis_quality(decision, context), - "assumption_validity": self.assess_assumption_validity(decision, context), - "conclusion_soundness": self.assess_conclusion_soundness(decision, context) - } - - # Calculate accuracy score - accuracy_score = sum(accuracy_factors.values()) / len(accuracy_factors) - - return { - "score": accuracy_score, - "factors": accuracy_factors, - "threshold_met": accuracy_score >= self.quality_metrics["accuracy"]["threshold"] - } - - def calculate_overall_score(self, metrics): - """Calculate overall quality score""" - weighted_score = 0.0 - total_weight = 0.0 - - for metric_name, metric_data in metrics.items(): - weight = self.quality_metrics[metric_name]["weight"] - score = metric_data["score"] - - weighted_score += score * weight - total_weight += weight - - return weighted_score / total_weight if total_weight > 0 else 0.0 -``` - -## Best Practices for Decision Making - -### 1. Structured Approach -- Follow the defined decision-making framework -- Ensure all phases are completed thoroughly -- Document decisions and rationale - -### 2. Inclusive Participation -- Encourage all board members to contribute -- Value diverse perspectives and expertise -- Ensure fair representation in voting - -### 3. Quality Assurance -- Implement quality checkpoints throughout the process -- Assess decision quality before implementation -- Continuously monitor and improve decision-making processes - -### 4. Conflict Management -- Address conflicts promptly and constructively -- Use appropriate resolution strategies -- Maintain focus on organizational objectives - -### 5. Continuous Improvement -- Learn from previous decisions -- Refine decision-making processes based on outcomes -- Adapt to changing circumstances and requirements \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_example.md b/docs/swarms/structs/board_of_directors/board_of_directors_example.md deleted file mode 100644 index 34c5edc3..00000000 --- a/docs/swarms/structs/board_of_directors/board_of_directors_example.md +++ /dev/null @@ -1,466 +0,0 @@ -# Board of Directors Example - -This example demonstrates how to use the Board of Directors swarm feature for democratic decision-making and collective intelligence in multi-agent systems. - -## Overview - -The Board of Directors Swarm provides a sophisticated alternative to single-director architectures by implementing collective decision-making through voting, consensus, and role-based leadership. This example shows how to create and configure a board for strategic decision-making scenarios. - -## Basic Setup - -### 1. Import Required Modules - -```python -from swarms import Agent -from swarms.structs.board_of_directors_swarm import ( - BoardOfDirectorsSwarm, - BoardMember, - BoardMemberRole -) -from swarms.config.board_config import ( - enable_board_feature, - set_decision_threshold, - get_default_board_template -) -``` - -### 2. Enable Board Feature - -```python -# Enable the Board of Directors feature globally -enable_board_feature() - -# Set global decision threshold -set_decision_threshold(0.7) # 70% majority required -``` - -### 3. Create Board Members - -```python -# Create Chairman -chairman = Agent( - agent_name="Chairman", - agent_description="Chairman of the Board responsible for leading meetings and making final decisions", - model_name="gpt-4o-mini", - system_prompt="""You are the Chairman of the Board. Your responsibilities include: -1. Leading board meetings and discussions -2. Facilitating consensus among board members -3. Making final decisions when consensus cannot be reached -4. Ensuring all board members have an opportunity to contribute -5. Maintaining focus on the organization's goals and objectives - -You should be diplomatic, fair, and decisive in your leadership.""" -) - -# Create Vice Chairman -vice_chairman = Agent( - agent_name="Vice-Chairman", - agent_description="Vice Chairman who supports the Chairman and coordinates operations", - model_name="gpt-4o-mini", - system_prompt="""You are the Vice Chairman of the Board. Your responsibilities include: -1. Supporting the Chairman in leading board meetings -2. Coordinating operational activities and implementation -3. Ensuring effective communication between board members -4. Managing day-to-day board operations -5. Stepping in when the Chairman is unavailable - -You should be collaborative, organized, and supportive of the Chairman's leadership.""" -) - -# Create Secretary -secretary = Agent( - agent_name="Secretary", - agent_description="Secretary responsible for documentation and record keeping", - model_name="gpt-4o-mini", - system_prompt="""You are the Secretary of the Board. Your responsibilities include: -1. Documenting all board meetings and decisions -2. Maintaining accurate records and meeting minutes -3. Ensuring proper communication and notifications -4. Managing board documentation and archives -5. Supporting compliance and governance requirements - -You should be detail-oriented, organized, and thorough in your documentation.""" -) - -# Create Treasurer -treasurer = Agent( - agent_name="Treasurer", - agent_description="Treasurer responsible for financial oversight and resource management", - model_name="gpt-4o-mini", - system_prompt="""You are the Treasurer of the Board. Your responsibilities include: -1. Overseeing financial planning and budgeting -2. Monitoring resource allocation and utilization -3. Ensuring financial compliance and accountability -4. Providing financial insights for decision-making -5. Managing financial risk and controls - -You should be financially astute, analytical, and focused on value creation.""" -) - -# Create Executive Director -executive_director = Agent( - agent_name="Executive-Director", - agent_description="Executive Director responsible for strategic planning and operational oversight", - model_name="gpt-4o-mini", - system_prompt="""You are the Executive Director of the Board. Your responsibilities include: -1. Developing and implementing strategic plans -2. Overseeing operational performance and efficiency -3. Leading innovation and continuous improvement -4. Managing stakeholder relationships -5. Ensuring organizational effectiveness - -You should be strategic, results-oriented, and focused on organizational success.""" -) -``` - -### 4. Create BoardMember Objects - -```python -# Create BoardMember objects with roles, voting weights, and expertise areas -board_members = [ - BoardMember( - agent=chairman, - role=BoardMemberRole.CHAIRMAN, - voting_weight=1.5, - expertise_areas=["leadership", "strategy", "governance", "decision_making"] - ), - BoardMember( - agent=vice_chairman, - role=BoardMemberRole.VICE_CHAIRMAN, - voting_weight=1.2, - expertise_areas=["operations", "coordination", "communication", "implementation"] - ), - BoardMember( - agent=secretary, - role=BoardMemberRole.SECRETARY, - voting_weight=1.0, - expertise_areas=["documentation", "compliance", "record_keeping", "communication"] - ), - BoardMember( - agent=treasurer, - role=BoardMemberRole.TREASURER, - voting_weight=1.0, - expertise_areas=["finance", "budgeting", "risk_management", "resource_allocation"] - ), - BoardMember( - agent=executive_director, - role=BoardMemberRole.EXECUTIVE_DIRECTOR, - voting_weight=1.5, - expertise_areas=["strategy", "operations", "innovation", "performance_management"] - ) -] -``` - -### 5. Create Specialized Worker Agents - -```python -# Create specialized worker agents for different types of analysis -research_agent = Agent( - agent_name="Research-Specialist", - agent_description="Expert in market research, data analysis, and trend identification", - model_name="gpt-4o", - system_prompt="""You are a Research Specialist. Your responsibilities include: -1. Conducting comprehensive market research and analysis -2. Identifying trends, opportunities, and risks -3. Gathering and analyzing relevant data -4. Providing evidence-based insights and recommendations -5. Supporting strategic decision-making with research findings - -You should be thorough, analytical, and objective in your research.""" -) - -financial_agent = Agent( - agent_name="Financial-Analyst", - agent_description="Specialist in financial analysis, valuation, and investment assessment", - model_name="gpt-4o", - system_prompt="""You are a Financial Analyst. Your responsibilities include: -1. Conducting financial analysis and valuation -2. Assessing investment opportunities and risks -3. Analyzing financial performance and metrics -4. Providing financial insights and recommendations -5. Supporting financial decision-making - -You should be financially astute, analytical, and focused on value creation.""" -) - -technical_agent = Agent( - agent_name="Technical-Specialist", - agent_description="Expert in technical analysis, feasibility assessment, and implementation planning", - model_name="gpt-4o", - system_prompt="""You are a Technical Specialist. Your responsibilities include: -1. Conducting technical feasibility analysis -2. Assessing implementation requirements and challenges -3. Providing technical insights and recommendations -4. Supporting technical decision-making -5. Planning and coordinating technical implementations - -You should be technically proficient, practical, and solution-oriented.""" -) - -strategy_agent = Agent( - agent_name="Strategy-Specialist", - agent_description="Expert in strategic planning, competitive analysis, and business development", - model_name="gpt-4o", - system_prompt="""You are a Strategy Specialist. Your responsibilities include: -1. Developing strategic plans and initiatives -2. Conducting competitive analysis and market positioning -3. Identifying strategic opportunities and threats -4. Providing strategic insights and recommendations -5. Supporting strategic decision-making - -You should be strategic, forward-thinking, and focused on long-term success.""" -) -``` - -### 6. Initialize the Board of Directors Swarm - -```python -# Initialize the Board of Directors swarm with comprehensive configuration -board_swarm = BoardOfDirectorsSwarm( - name="Executive_Board_Swarm", - description="Executive board with specialized roles for strategic decision-making and collective intelligence", - board_members=board_members, - agents=[research_agent, financial_agent, technical_agent, strategy_agent], - max_loops=3, # Allow multiple iterations for complex analysis - verbose=True, # Enable detailed logging - decision_threshold=0.7, # 70% consensus required - enable_voting=True, # Enable voting mechanisms - enable_consensus=True, # Enable consensus building - max_workers=4, # Maximum parallel workers - output_type="dict" # Return results as dictionary -) -``` - -## Advanced Configuration - -### Custom Board Templates - -You can use pre-configured board templates for common use cases: - -```python -# Get a financial analysis board template -financial_board_template = get_default_board_template("financial_analysis") - -# Get a strategic planning board template -strategic_board_template = get_default_board_template("strategic_planning") - -# Get a technology assessment board template -tech_board_template = get_default_board_template("technology_assessment") -``` - -### Dynamic Role Assignment - -Automatically assign roles based on task requirements: - -```python -# Board members are automatically assigned roles based on expertise -board_swarm = BoardOfDirectorsSwarm( - board_members=board_members, - agents=agents, - auto_assign_roles=True, - role_mapping={ - "financial_analysis": ["Treasurer", "Financial_Member"], - "strategic_planning": ["Chairman", "Executive_Director"], - "technical_assessment": ["Technical_Member", "Executive_Director"], - "research_analysis": ["Research_Member", "Secretary"] - } -) -``` - -### Consensus Optimization - -Configure advanced consensus-building mechanisms: - -```python -# Enable advanced consensus features -board_swarm = BoardOfDirectorsSwarm( - board_members=board_members, - agents=agents, - enable_consensus=True, - consensus_timeout=300, # 5 minutes timeout - min_participation_rate=0.8, # 80% minimum participation - auto_fallback_to_chairman=True, # Chairman can make final decisions - consensus_rounds=3 # Maximum consensus building rounds -) -``` - -## Example Use Cases - -### 1. Strategic Investment Analysis - -```python -# Execute a complex strategic investment analysis -investment_task = """ -Analyze the strategic investment opportunity for a $50M Series B funding round in a -fintech startup. Consider market conditions, competitive landscape, financial projections, -technical feasibility, and strategic fit. Provide comprehensive recommendations including: -1. Investment recommendation (proceed/hold/decline) -2. Valuation analysis and suggested terms -3. Risk assessment and mitigation strategies -4. Strategic value and synergies -5. Implementation timeline and milestones -""" - -result = board_swarm.run(task=investment_task) -print("Investment Analysis Results:") -print(json.dumps(result, indent=2)) -``` - -### 2. Technology Strategy Development - -```python -# Develop a comprehensive technology strategy -tech_strategy_task = """ -Develop a comprehensive technology strategy for a mid-size manufacturing company -looking to digitize operations and implement Industry 4.0 technologies. Consider: -1. Current technology assessment and gaps -2. Technology roadmap and implementation plan -3. Investment requirements and ROI analysis -4. Risk assessment and mitigation strategies -5. Change management and training requirements -6. Competitive positioning and market advantages -""" - -result = board_swarm.run(task=tech_strategy_task) -print("Technology Strategy Results:") -print(json.dumps(result, indent=2)) -``` - -### 3. Market Entry Strategy - -```python -# Develop a market entry strategy for a new product -market_entry_task = """ -Develop a comprehensive market entry strategy for a new AI-powered productivity -software targeting the enterprise market. Consider: -1. Market analysis and opportunity assessment -2. Competitive landscape and positioning -3. Go-to-market strategy and channels -4. Pricing strategy and revenue model -5. Resource requirements and investment needs -6. Risk assessment and mitigation strategies -7. Success metrics and KPIs -""" - -result = board_swarm.run(task=market_entry_task) -print("Market Entry Strategy Results:") -print(json.dumps(result, indent=2)) -``` - -## Monitoring and Analysis - -### Board Performance Metrics - -```python -# Get board performance metrics -board_summary = board_swarm.get_board_summary() -print("Board Summary:") -print(f"Board Name: {board_summary['board_name']}") -print(f"Total Board Members: {board_summary['total_members']}") -print(f"Total Worker Agents: {board_summary['total_agents']}") -print(f"Decision Threshold: {board_summary['decision_threshold']}") -print(f"Max Loops: {board_summary['max_loops']}") - -# Display board member details -print("\nBoard Members:") -for member in board_summary['members']: - print(f"- {member['name']} (Role: {member['role']}, Weight: {member['voting_weight']})") - print(f" Expertise: {', '.join(member['expertise_areas'])}") - -# Display worker agent details -print("\nWorker Agents:") -for agent in board_summary['agents']: - print(f"- {agent['name']}: {agent['description']}") -``` - -### Decision Analysis - -```python -# Analyze decision-making patterns -if hasattr(result, 'get') and callable(result.get): - conversation_history = result.get('conversation_history', []) - - print(f"\nDecision Analysis:") - print(f"Total Messages: {len(conversation_history)}") - - # Count board member contributions - board_contributions = {} - for msg in conversation_history: - if 'Board' in msg.get('role', ''): - member_name = msg.get('agent_name', 'Unknown') - board_contributions[member_name] = board_contributions.get(member_name, 0) + 1 - - print(f"Board Member Contributions:") - for member, count in board_contributions.items(): - print(f"- {member}: {count} contributions") - - # Count agent executions - agent_executions = {} - for msg in conversation_history: - if any(agent.agent_name in msg.get('role', '') for agent in [research_agent, financial_agent, technical_agent, strategy_agent]): - agent_name = msg.get('agent_name', 'Unknown') - agent_executions[agent_name] = agent_executions.get(agent_name, 0) + 1 - - print(f"\nAgent Executions:") - for agent, count in agent_executions.items(): - print(f"- {agent}: {count} executions") -``` - -## Best Practices - -### 1. Role Definition -- Clearly define responsibilities for each board member -- Ensure expertise areas align with organizational needs -- Balance voting weights based on role importance - -### 2. Task Formulation -- Provide clear, specific task descriptions -- Include relevant context and constraints -- Specify expected outputs and deliverables - -### 3. Consensus Building -- Allow adequate time for discussion and consensus -- Encourage diverse perspectives and viewpoints -- Use structured decision-making processes - -### 4. Performance Monitoring -- Track decision quality and outcomes -- Monitor board member participation -- Analyze agent utilization and effectiveness - -### 5. Continuous Improvement -- Learn from each execution cycle -- Refine board composition and roles -- Optimize decision thresholds and processes - -## Troubleshooting - -### Common Issues - -1. **Consensus Failures**: Lower decision threshold or increase timeout -2. **Role Conflicts**: Ensure clear role definitions and responsibilities -3. **Agent Coordination**: Verify agent communication and task distribution -4. **Performance Issues**: Monitor resource usage and optimize configurations - -### Debug Commands - -```python -# Enable detailed logging -import logging -logging.basicConfig(level=logging.DEBUG) - -# Check board configuration -print(board_swarm.get_board_summary()) - -# Test individual components -for member in board_members: - print(f"Testing {member.agent.agent_name}...") - response = member.agent.run("Test message") - print(f"Response: {response[:100]}...") -``` - -## Conclusion - -This example demonstrates the comprehensive capabilities of the Board of Directors Swarm for democratic decision-making and collective intelligence. The feature provides a sophisticated alternative to single-director architectures, enabling more robust and well-considered decisions through voting, consensus, and role-based leadership. - -For more information, see the [Board of Directors Documentation](index.md) and [Configuration Guide](../config/board_config.md). \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_roles.md b/docs/swarms/structs/board_of_directors/board_of_directors_roles.md deleted file mode 100644 index 6f607c37..00000000 --- a/docs/swarms/structs/board_of_directors/board_of_directors_roles.md +++ /dev/null @@ -1,1151 +0,0 @@ -# Board of Directors Roles - -The Board of Directors system implements a hierarchical structure with clearly defined roles, responsibilities, and voting weights. Each role is designed to contribute specific expertise and authority to the decision-making process, ensuring comprehensive analysis and balanced decision-making. - -## Role Hierarchy - -```mermaid -graph TD - A[Chairman
Voting Weight: 1.5
Final Authority] --> B[Vice Chairman
Voting Weight: 1.2
Operational Support] - A --> C[Executive Director
Voting Weight: 1.5
Strategic Planning] - A --> D[Secretary
Voting Weight: 1.0
Documentation] - A --> E[Treasurer
Voting Weight: 1.0
Financial Oversight] - A --> F[Member
Voting Weight: 1.0
Expertise Contribution] - - B --> G[Operational Coordination] - C --> H[Strategic Initiatives] - D --> I[Record Keeping] - E --> J[Resource Management] - F --> K[Specialized Input] - - style A fill:#ffeb3b - style B fill:#2196f3 - style C fill:#4caf50 - style D fill:#ff9800 - style E fill:#9c27b0 - style F fill:#607d8b -``` - -**Diagram Explanation:** -This hierarchical diagram shows the organizational structure of the Board of Directors, with the Chairman at the top having final authority and the highest voting weight (1.5). The Chairman directly supervises all other board members, each with specific responsibilities and voting weights. The Vice Chairman and Executive Director have elevated voting weights (1.2 and 1.5 respectively) due to their senior positions, while the Secretary, Treasurer, and general Members have standard voting weights (1.0). Each role contributes specialized expertise to different aspects of the decision-making process. - -**Technical Implementation:** -```python -# Example: Role hierarchy implementation -class BoardRoleHierarchy: - def __init__(self): - self.roles = { - "CHAIRMAN": { - "voting_weight": 1.5, - "authority_level": "FINAL", - "supervises": ["VICE_CHAIRMAN", "EXECUTIVE_DIRECTOR", "SECRETARY", "TREASURER", "MEMBER"], - "responsibilities": ["leadership", "final_decision", "consensus_facilitation"], - "override_capability": True - }, - "VICE_CHAIRMAN": { - "voting_weight": 1.2, - "authority_level": "SENIOR", - "supervises": ["MEMBER"], - "responsibilities": ["operational_support", "coordination", "implementation"], - "backup_for": "CHAIRMAN" - }, - "EXECUTIVE_DIRECTOR": { - "voting_weight": 1.5, - "authority_level": "SENIOR", - "supervises": ["MEMBER"], - "responsibilities": ["strategic_planning", "execution_oversight", "performance_management"], - "strategic_authority": True - }, - "SECRETARY": { - "voting_weight": 1.0, - "authority_level": "STANDARD", - "supervises": [], - "responsibilities": ["documentation", "record_keeping", "communication"], - "administrative_authority": True - }, - "TREASURER": { - "voting_weight": 1.0, - "authority_level": "STANDARD", - "supervises": [], - "responsibilities": ["financial_oversight", "resource_management", "budget_control"], - "financial_authority": True - }, - "MEMBER": { - "voting_weight": 1.0, - "authority_level": "STANDARD", - "supervises": [], - "responsibilities": ["expertise_contribution", "analysis", "voting"], - "specialized_expertise": True - } - } - - def get_role_info(self, role_name): - """Get detailed information about a specific role""" - return self.roles.get(role_name, {}) - - def get_voting_weight(self, role_name): - """Get voting weight for a specific role""" - role_info = self.get_role_info(role_name) - return role_info.get("voting_weight", 1.0) - - def get_authority_level(self, role_name): - """Get authority level for a specific role""" - role_info = self.get_role_info(role_name) - return role_info.get("authority_level", "STANDARD") - - def can_override_decision(self, role_name): - """Check if a role can override board decisions""" - role_info = self.get_role_info(role_name) - return role_info.get("override_capability", False) - - def get_supervision_chain(self, role_name): - """Get the supervision chain for a specific role""" - supervision_chain = [] - current_role = role_name - - while current_role: - role_info = self.get_role_info(current_role) - if role_info: - supervision_chain.append(current_role) - # Find who supervises this role - current_role = None - for supervisor, info in self.roles.items(): - if current_role in info.get("supervises", []): - current_role = supervisor - break - else: - break - - return supervision_chain -``` - -## Chairman Role - -### Primary Responsibilities - -```mermaid -graph TD - A[Chairman] --> B[Meeting Leadership] - A --> C[Final Decision Authority] - A --> D[Consensus Facilitation] - A --> E[Strategic Direction] - A --> F[Stakeholder Communication] - - B --> G[Agenda Setting] - B --> H[Discussion Management] - B --> I[Time Management] - - C --> J[Approval Authority] - C --> K[Override Capability] - C --> L[Final Sign-off] - - D --> M[Conflict Resolution] - D --> N[Mediation] - D --> O[Compromise Facilitation] - - E --> P[Vision Setting] - E --> Q[Goal Definition] - E --> R[Priority Establishment] - - F --> S[External Relations] - F --> T[Stakeholder Updates] - F --> U[Public Communication] -``` - -**Diagram Explanation:** -This diagram illustrates the comprehensive responsibilities of the Chairman role, showing how the Chairman serves as the central authority figure with five main areas of responsibility. Meeting Leadership involves agenda setting, discussion management, and time management. Final Decision Authority includes approval authority, override capability, and final sign-off responsibilities. Consensus Facilitation covers conflict resolution, mediation, and compromise facilitation. Strategic Direction encompasses vision setting, goal definition, and priority establishment. Stakeholder Communication includes external relations, stakeholder updates, and public communication. - -**Technical Implementation:** -```python -# Example: Chairman role implementation -class Chairman: - def __init__(self, name, config): - self.name = name - self.config = config - self.authority_level = "FINAL" - self.voting_weight = 1.5 - self.override_capability = True - self.meeting_history = [] - self.decision_history = [] - - async def lead_meeting(self, task, board_members): - """Lead a board meeting for task discussion and decision-making""" - meeting_result = { - "meeting_id": self.generate_meeting_id(), - "task": task, - "participants": list(board_members.keys()), - "phases": [], - "decisions": [], - "consensus_achieved": False - } - - # Phase 1: Meeting Opening - opening_phase = await self.open_meeting(task, board_members) - meeting_result["phases"].append(opening_phase) - - # Phase 2: Agenda Review and Task Presentation - presentation_phase = await self.present_task(task, board_members) - meeting_result["phases"].append(presentation_phase) - - # Phase 3: Discussion Facilitation - discussion_phase = await self.facilitate_discussion(task, board_members) - meeting_result["phases"].append(discussion_phase) - - # Phase 4: Consensus Building - consensus_phase = await self.build_consensus(discussion_phase["proposals"], board_members) - meeting_result["phases"].append(consensus_phase) - - # Phase 5: Decision Making - decision_phase = await self.make_final_decision(consensus_phase, board_members) - meeting_result["phases"].append(decision_phase) - - meeting_result["decisions"] = decision_phase["decisions"] - meeting_result["consensus_achieved"] = consensus_phase["consensus_achieved"] - - # Record meeting in history - self.meeting_history.append(meeting_result) - - return meeting_result - - async def open_meeting(self, task, board_members): - """Open the board meeting and set the agenda""" - agenda = await self.create_agenda(task) - - opening_statement = f""" - Meeting called to order by Chairman {self.name}. - - Task: {task['description']} - Priority: {task.get('priority', 'Normal')} - Timeline: {task.get('timeline', 'Not specified')} - - Agenda: - {self.format_agenda(agenda)} - - Board members present: {', '.join(board_members.keys())} - """ - - return { - "phase": "meeting_opening", - "opening_statement": opening_statement, - "agenda": agenda, - "participants": list(board_members.keys()), - "timestamp": datetime.now().isoformat() - } - - async def create_agenda(self, task): - """Create a structured agenda for the meeting""" - agenda = { - "items": [ - { - "item": "Task Presentation", - "duration": "10 minutes", - "responsible": "Chairman", - "description": "Present task details and requirements" - }, - { - "item": "Expertise Assignment", - "duration": "5 minutes", - "responsible": "Chairman", - "description": "Assign analysis areas to board members" - }, - { - "item": "Individual Analysis", - "duration": "15 minutes", - "responsible": "All Members", - "description": "Board members analyze assigned areas" - }, - { - "item": "Group Discussion", - "duration": "20 minutes", - "responsible": "All Members", - "description": "Open discussion and debate" - }, - { - "item": "Proposal Development", - "duration": "15 minutes", - "responsible": "All Members", - "description": "Develop and refine proposals" - }, - { - "item": "Voting and Consensus", - "duration": "10 minutes", - "responsible": "All Members", - "description": "Vote on proposals and reach consensus" - }, - { - "item": "Decision Finalization", - "duration": "5 minutes", - "responsible": "Chairman", - "description": "Finalize decisions and assign execution" - } - ], - "total_duration": "80 minutes", - "break_time": "10 minutes" - } - - return agenda - - async def facilitate_discussion(self, task, board_members): - """Facilitate discussion among board members""" - discussion_result = { - "phase": "discussion_facilitation", - "discussion_points": [], - "conflicts": [], - "resolutions": [], - "proposals": [] - } - - # Guide discussion through structured phases - for member_role, member in board_members.items(): - # Get member's analysis - analysis = await member.analyze_task(task) - discussion_result["discussion_points"].append({ - "member": member_role, - "analysis": analysis, - "timestamp": datetime.now().isoformat() - }) - - # Identify conflicts - conflicts = await self.identify_conflicts(analysis, discussion_result["discussion_points"]) - discussion_result["conflicts"].extend(conflicts) - - # Facilitate conflict resolution - for conflict in conflicts: - resolution = await self.resolve_conflict(conflict, board_members) - discussion_result["resolutions"].append(resolution) - - # Develop proposals based on discussion - proposals = await self.develop_proposals(discussion_result["discussion_points"]) - discussion_result["proposals"] = proposals - - return discussion_result - - async def build_consensus(self, proposals, board_members): - """Build consensus among board members on proposals""" - consensus_result = { - "phase": "consensus_building", - "voting_rounds": [], - "consensus_achieved": False, - "final_proposal": None - } - - current_proposals = proposals - round_number = 1 - - while round_number <= self.config.get("max_consensus_rounds", 3): - # Conduct voting round - voting_result = await self.conduct_voting_round(current_proposals, board_members, round_number) - consensus_result["voting_rounds"].append(voting_result) - - # Check if consensus achieved - if voting_result["consensus_achieved"]: - consensus_result["consensus_achieved"] = True - consensus_result["final_proposal"] = voting_result["winning_proposal"] - break - - # Refine proposals for next round - current_proposals = await self.refine_proposals(current_proposals, voting_result) - round_number += 1 - - return consensus_result - - async def make_final_decision(self, consensus_result, board_members): - """Make final decision based on consensus or exercise authority""" - if consensus_result["consensus_achieved"]: - # Consensus reached, approve the decision - decision = { - "type": "consensus_decision", - "proposal": consensus_result["final_proposal"], - "approval_method": "consensus", - "board_support": "unanimous" - } - else: - # No consensus, exercise chairman authority - decision = await self.exercise_authority(consensus_result, board_members) - - decision_result = { - "phase": "final_decision", - "decision": decision, - "execution_plan": await self.create_execution_plan(decision), - "timestamp": datetime.now().isoformat() - } - - # Record decision in history - self.decision_history.append(decision_result) - - return decision_result - - async def exercise_authority(self, consensus_result, board_members): - """Exercise chairman authority when consensus cannot be reached""" - # Analyze all proposals and voting results - proposal_analysis = await self.analyze_proposals(consensus_result["voting_rounds"]) - - # Make decision based on best interests and available information - final_decision = await self.select_best_proposal(proposal_analysis) - - return { - "type": "authority_decision", - "proposal": final_decision, - "approval_method": "chairman_authority", - "rationale": final_decision["rationale"], - "board_support": final_decision["support_level"] - } -``` - -### Chairman's Decision Flow - -```mermaid -flowchart TD - A[Task Received] --> B[Assess Complexity] - B --> C[Determine Board Composition] - C --> D[Set Meeting Agenda] - D --> E[Lead Discussion] - E --> F[Facilitate Consensus] - F --> G{Consensus Reached?} - G -->|Yes| H[Approve Decision] - G -->|No| I[Exercise Authority] - I --> J[Make Final Decision] - H --> K[Oversee Execution] - J --> K - K --> L[Monitor Progress] - L --> M[Review Results] - M --> N[Final Approval] -``` - -**Diagram Explanation:** -This flowchart shows the Chairman's decision-making process from task reception to final approval. The process begins with task assessment and board composition determination, followed by agenda setting and discussion leadership. The Chairman then facilitates consensus building, and if consensus is reached, approves the decision. If consensus cannot be achieved, the Chairman exercises their authority to make a final decision. The Chairman then oversees execution, monitors progress, reviews results, and provides final approval. - -**Technical Implementation:** -```python -# Example: Chairman decision flow implementation -class ChairmanDecisionFlow: - def __init__(self, chairman): - self.chairman = chairman - self.decision_states = { - "TASK_RECEIVED": "Initial state when task is received", - "COMPLEXITY_ASSESSED": "Task complexity has been evaluated", - "BOARD_COMPOSED": "Board members have been selected", - "AGENDA_SET": "Meeting agenda has been created", - "DISCUSSION_LEAD": "Discussion has been facilitated", - "CONSENSUS_FACILITATED": "Consensus building has been attempted", - "DECISION_MADE": "Final decision has been made", - "EXECUTION_OVERSIGHT": "Execution is being monitored", - "PROGRESS_MONITORED": "Progress is being tracked", - "RESULTS_REVIEWED": "Results have been reviewed", - "FINAL_APPROVAL": "Final approval has been given" - } - - async def execute_decision_flow(self, task): - """Execute the complete chairman decision flow""" - flow_state = { - "current_state": "TASK_RECEIVED", - "task": task, - "transitions": [], - "decisions": [], - "timestamps": {} - } - - # Step 1: Assess Complexity - complexity_assessment = await self.assess_task_complexity(task) - flow_state["transitions"].append({ - "from": "TASK_RECEIVED", - "to": "COMPLEXITY_ASSESSED", - "assessment": complexity_assessment - }) - flow_state["current_state"] = "COMPLEXITY_ASSESSED" - flow_state["timestamps"]["complexity_assessed"] = datetime.now().isoformat() - - # Step 2: Determine Board Composition - board_composition = await self.determine_board_composition(complexity_assessment) - flow_state["transitions"].append({ - "from": "COMPLEXITY_ASSESSED", - "to": "BOARD_COMPOSED", - "composition": board_composition - }) - flow_state["current_state"] = "BOARD_COMPOSED" - flow_state["timestamps"]["board_composed"] = datetime.now().isoformat() - - # Step 3: Set Meeting Agenda - meeting_agenda = await self.set_meeting_agenda(task, board_composition) - flow_state["transitions"].append({ - "from": "BOARD_COMPOSED", - "to": "AGENDA_SET", - "agenda": meeting_agenda - }) - flow_state["current_state"] = "AGENDA_SET" - flow_state["timestamps"]["agenda_set"] = datetime.now().isoformat() - - # Step 4: Lead Discussion - discussion_result = await self.lead_discussion(task, board_composition, meeting_agenda) - flow_state["transitions"].append({ - "from": "AGENDA_SET", - "to": "DISCUSSION_LEAD", - "discussion": discussion_result - }) - flow_state["current_state"] = "DISCUSSION_LEAD" - flow_state["timestamps"]["discussion_led"] = datetime.now().isoformat() - - # Step 5: Facilitate Consensus - consensus_result = await self.facilitate_consensus(discussion_result) - flow_state["transitions"].append({ - "from": "DISCUSSION_LEAD", - "to": "CONSENSUS_FACILITATED", - "consensus": consensus_result - }) - flow_state["current_state"] = "CONSENSUS_FACILITATED" - flow_state["timestamps"]["consensus_facilitated"] = datetime.now().isoformat() - - # Step 6: Make Decision - if consensus_result["consensus_achieved"]: - decision = await self.approve_consensus_decision(consensus_result) - else: - decision = await self.exercise_authority(consensus_result) - - flow_state["decisions"].append(decision) - flow_state["transitions"].append({ - "from": "CONSENSUS_FACILITATED", - "to": "DECISION_MADE", - "decision": decision - }) - flow_state["current_state"] = "DECISION_MADE" - flow_state["timestamps"]["decision_made"] = datetime.now().isoformat() - - # Step 7: Oversee Execution - execution_oversight = await self.oversee_execution(decision) - flow_state["transitions"].append({ - "from": "DECISION_MADE", - "to": "EXECUTION_OVERSIGHT", - "oversight": execution_oversight - }) - flow_state["current_state"] = "EXECUTION_OVERSIGHT" - flow_state["timestamps"]["execution_oversight"] = datetime.now().isoformat() - - # Step 8: Monitor Progress - progress_monitoring = await self.monitor_progress(execution_oversight) - flow_state["transitions"].append({ - "from": "EXECUTION_OVERSIGHT", - "to": "PROGRESS_MONITORED", - "progress": progress_monitoring - }) - flow_state["current_state"] = "PROGRESS_MONITORED" - flow_state["timestamps"]["progress_monitored"] = datetime.now().isoformat() - - # Step 9: Review Results - results_review = await self.review_results(progress_monitoring) - flow_state["transitions"].append({ - "from": "PROGRESS_MONITORED", - "to": "RESULTS_REVIEWED", - "review": results_review - }) - flow_state["current_state"] = "RESULTS_REVIEWED" - flow_state["timestamps"]["results_reviewed"] = datetime.now().isoformat() - - # Step 10: Final Approval - final_approval = await self.give_final_approval(results_review) - flow_state["transitions"].append({ - "from": "RESULTS_REVIEWED", - "to": "FINAL_APPROVAL", - "approval": final_approval - }) - flow_state["current_state"] = "FINAL_APPROVAL" - flow_state["timestamps"]["final_approval"] = datetime.now().isoformat() - - return flow_state -``` - -### Key Competencies - -- **Leadership**: Ability to guide and inspire board members -- **Decision Making**: Strong analytical and judgment skills -- **Communication**: Excellent verbal and written communication -- **Conflict Resolution**: Skills in mediating disagreements -- **Strategic Thinking**: Long-term vision and planning ability -- **Stakeholder Management**: Relationship building and management - -## Vice Chairman Role - -### Supporting Responsibilities - -```mermaid -graph TD - A[Vice Chairman] --> B[Operational Support] - A --> C[Chairman Backup] - A --> D[Coordination] - A --> E[Implementation Oversight] - - B --> F[Meeting Support] - B --> G[Documentation Assistance] - B --> H[Logistics Management] - - C --> I[Acting Chairman] - C --> J[Decision Delegation] - C --> K[Authority Exercise] - - D --> L[Inter-Department Coordination] - D --> M[Resource Allocation] - D --> N[Timeline Management] - - E --> O[Execution Monitoring] - E --> P[Quality Control] - E --> Q[Performance Tracking] -``` - -**Diagram Explanation:** -This diagram shows the Vice Chairman's supporting role structure, highlighting four main areas of responsibility. Operational Support includes meeting support, documentation assistance, and logistics management. Chairman Backup involves acting as chairman when needed, decision delegation, and authority exercise. Coordination covers inter-department coordination, resource allocation, and timeline management. Implementation Oversight includes execution monitoring, quality control, and performance tracking. - -**Technical Implementation:** -```python -# Example: Vice Chairman role implementation -class ViceChairman: - def __init__(self, name, config): - self.name = name - self.config = config - self.authority_level = "SENIOR" - self.voting_weight = 1.2 - self.backup_for = "CHAIRMAN" - self.operational_areas = [] - self.coordination_history = [] - - async def provide_operational_support(self, chairman, task): - """Provide operational support to the chairman""" - support_areas = { - "meeting_support": await self.support_meeting_operations(chairman, task), - "documentation_assistance": await self.assist_with_documentation(task), - "logistics_management": await self.manage_logistics(task) - } - - return { - "support_provided": support_areas, - "timestamp": datetime.now().isoformat() - } - - async def act_as_chairman(self, chairman, reason): - """Act as chairman when the chairman is unavailable""" - acting_authority = { - "acting_chairman": self.name, - "original_chairman": chairman.name, - "reason": reason, - "authority_delegated": True, - "delegation_timestamp": datetime.now().isoformat() - } - - # Assume chairman responsibilities - acting_authority["capabilities"] = [ - "meeting_leadership", - "decision_making", - "consensus_facilitation", - "final_approval" - ] - - return acting_authority - - async def coordinate_operations(self, task, board_members): - """Coordinate operations across different areas""" - coordination_plan = { - "inter_department_coordination": await self.coordinate_departments(task), - "resource_allocation": await self.allocate_resources(task), - "timeline_management": await self.manage_timeline(task) - } - - self.coordination_history.append({ - "task": task, - "coordination_plan": coordination_plan, - "timestamp": datetime.now().isoformat() - }) - - return coordination_plan - - async def oversee_implementation(self, execution_plan): - """Oversee the implementation of board decisions""" - oversight_result = { - "execution_monitoring": await self.monitor_execution(execution_plan), - "quality_control": await self.control_quality(execution_plan), - "performance_tracking": await self.track_performance(execution_plan) - } - - return oversight_result -``` - -## Executive Director Role - -### Strategic Responsibilities - -```mermaid -graph TD - A[Executive Director] --> B[Strategic Planning] - A --> C[Execution Oversight] - A --> D[Performance Management] - A --> E[Strategic Initiatives] - - B --> F[Vision Development] - B --> G[Goal Setting] - B --> H[Strategy Formulation] - - C --> I[Implementation Monitoring] - C --> J[Progress Tracking] - C --> K[Issue Resolution] - - D --> L[KPI Definition] - D --> M[Performance Measurement] - D --> N[Improvement Planning] - - E --> O[Innovation Leadership] - E --> P[Change Management] - E --> Q[Strategic Partnerships] -``` - -**Diagram Explanation:** -This diagram illustrates the Executive Director's strategic role, showing four main areas of responsibility. Strategic Planning includes vision development, goal setting, and strategy formulation. Execution Oversight involves implementation monitoring, progress tracking, and issue resolution. Performance Management covers KPI definition, performance measurement, and improvement planning. Strategic Initiatives includes innovation leadership, change management, and strategic partnerships. - -**Technical Implementation:** -```python -# Example: Executive Director role implementation -class ExecutiveDirector: - def __init__(self, name, config): - self.name = name - self.config = config - self.authority_level = "SENIOR" - self.voting_weight = 1.5 - self.strategic_authority = True - self.performance_metrics = {} - self.strategic_initiatives = [] - - async def develop_strategic_plan(self, task, board_context): - """Develop strategic plan for task execution""" - strategic_plan = { - "vision": await self.develop_vision(task), - "goals": await self.set_goals(task), - "strategy": await self.formulate_strategy(task, board_context), - "timeline": await self.create_strategic_timeline(task), - "resources": await self.plan_strategic_resources(task) - } - - return strategic_plan - - async def oversee_execution(self, execution_plan): - """Oversee execution of strategic plans""" - oversight_result = { - "implementation_monitoring": await self.monitor_implementation(execution_plan), - "progress_tracking": await self.track_progress(execution_plan), - "issue_resolution": await self.resolve_issues(execution_plan) - } - - return oversight_result - - async def manage_performance(self, execution_results): - """Manage performance and define KPIs""" - performance_management = { - "kpi_definition": await self.define_kpis(execution_results), - "performance_measurement": await self.measure_performance(execution_results), - "improvement_planning": await self.plan_improvements(execution_results) - } - - return performance_management - - async def lead_strategic_initiatives(self, task): - """Lead strategic initiatives and innovation""" - strategic_initiatives = { - "innovation_leadership": await self.lead_innovation(task), - "change_management": await self.manage_change(task), - "strategic_partnerships": await self.develop_partnerships(task) - } - - self.strategic_initiatives.append({ - "task": task, - "initiatives": strategic_initiatives, - "timestamp": datetime.now().isoformat() - }) - - return strategic_initiatives -``` - -## Secretary Role - -### Administrative Responsibilities - -```mermaid -graph TD - A[Secretary] --> B[Documentation] - A --> C[Record Keeping] - A --> D[Communication] - A --> E[Administrative Support] - - B --> F[Meeting Minutes] - B --> G[Decision Records] - B --> H[Policy Documentation] - - C --> I[File Management] - C --> J[Record Maintenance] - C --> K[Archive Management] - - D --> L[Internal Communication] - D --> M[External Communication] - D --> N[Stakeholder Updates] - - E --> O[Meeting Coordination] - E --> P[Agenda Preparation] - E --> Q[Follow-up Actions] -``` - -**Diagram Explanation:** -This diagram shows the Secretary's administrative role structure, highlighting four main areas of responsibility. Documentation includes meeting minutes, decision records, and policy documentation. Record Keeping involves file management, record maintenance, and archive management. Communication covers internal communication, external communication, and stakeholder updates. Administrative Support includes meeting coordination, agenda preparation, and follow-up actions. - -**Technical Implementation:** -```python -# Example: Secretary role implementation -class Secretary: - def __init__(self, name, config): - self.name = name - self.config = config - self.authority_level = "STANDARD" - self.voting_weight = 1.0 - self.administrative_authority = True - self.documentation_repository = {} - self.communication_log = [] - - async def document_meeting(self, meeting_data): - """Document board meeting proceedings""" - meeting_documentation = { - "meeting_id": meeting_data["meeting_id"], - "date": meeting_data["timestamp"], - "participants": meeting_data["participants"], - "agenda": meeting_data["agenda"], - "minutes": await self.create_meeting_minutes(meeting_data), - "decisions": meeting_data["decisions"], - "action_items": await self.extract_action_items(meeting_data) - } - - # Store in documentation repository - self.documentation_repository[meeting_data["meeting_id"]] = meeting_documentation - - return meeting_documentation - - async def maintain_records(self, record_type, data): - """Maintain various types of records""" - record_entry = { - "type": record_type, - "data": data, - "timestamp": datetime.now().isoformat(), - "secretary": self.name - } - - if record_type not in self.documentation_repository: - self.documentation_repository[record_type] = [] - - self.documentation_repository[record_type].append(record_entry) - - return record_entry - - async def manage_communication(self, communication_type, content, recipients): - """Manage internal and external communication""" - communication = { - "type": communication_type, - "content": content, - "recipients": recipients, - "timestamp": datetime.now().isoformat(), - "secretary": self.name - } - - self.communication_log.append(communication) - - # Send communication based on type - if communication_type == "internal": - await self.send_internal_communication(communication) - elif communication_type == "external": - await self.send_external_communication(communication) - elif communication_type == "stakeholder_update": - await self.send_stakeholder_update(communication) - - return communication - - async def provide_administrative_support(self, board_members, task): - """Provide administrative support to board members""" - administrative_support = { - "meeting_coordination": await self.coordinate_meetings(board_members, task), - "agenda_preparation": await self.prepare_agendas(task), - "follow_up_actions": await self.manage_follow_up_actions(task) - } - - return administrative_support -``` - -## Treasurer Role - -### Financial Responsibilities - -```mermaid -graph TD - A[Treasurer] --> B[Financial Oversight] - A --> C[Resource Management] - A --> D[Budget Control] - A --> E[Financial Reporting] - - B --> F[Financial Analysis] - B --> G[Risk Assessment] - B --> H[Compliance Monitoring] - - C --> I[Resource Allocation] - C --> J[Cost Management] - C --> K[Efficiency Optimization] - - D --> L[Budget Planning] - D --> M[Expense Monitoring] - D --> N[Budget Adjustments] - - E --> O[Financial Statements] - E --> P[Performance Reports] - E --> Q[Stakeholder Reports] -``` - -**Diagram Explanation:** -This diagram illustrates the Treasurer's financial role structure, showing four main areas of responsibility. Financial Oversight includes financial analysis, risk assessment, and compliance monitoring. Resource Management involves resource allocation, cost management, and efficiency optimization. Budget Control covers budget planning, expense monitoring, and budget adjustments. Financial Reporting includes financial statements, performance reports, and stakeholder reports. - -**Technical Implementation:** -```python -# Example: Treasurer role implementation -class Treasurer: - def __init__(self, name, config): - self.name = name - self.config = config - self.authority_level = "STANDARD" - self.voting_weight = 1.0 - self.financial_authority = True - self.financial_records = {} - self.budget_tracking = {} - - async def provide_financial_oversight(self, task, budget): - """Provide financial oversight for task execution""" - financial_oversight = { - "financial_analysis": await self.analyze_financial_implications(task, budget), - "risk_assessment": await self.assess_financial_risks(task, budget), - "compliance_monitoring": await self.monitor_compliance(task, budget) - } - - return financial_oversight - - async def manage_resources(self, task, available_resources): - """Manage resource allocation and optimization""" - resource_management = { - "resource_allocation": await self.allocate_resources(task, available_resources), - "cost_management": await self.manage_costs(task), - "efficiency_optimization": await self.optimize_efficiency(task) - } - - return resource_management - - async def control_budget(self, task, budget_limits): - """Control budget and monitor expenses""" - budget_control = { - "budget_planning": await self.plan_budget(task, budget_limits), - "expense_monitoring": await self.monitor_expenses(task), - "budget_adjustments": await self.adjust_budget(task) - } - - # Track budget usage - self.budget_tracking[task["id"]] = budget_control - - return budget_control - - async def generate_financial_reports(self, task, financial_data): - """Generate financial reports and statements""" - financial_reports = { - "financial_statements": await self.create_financial_statements(financial_data), - "performance_reports": await self.create_performance_reports(financial_data), - "stakeholder_reports": await self.create_stakeholder_reports(financial_data) - } - - # Store financial records - self.financial_records[task["id"]] = financial_reports - - return financial_reports -``` - -## Member Role - -### General Responsibilities - -```mermaid -graph TD - A[Member] --> B[Expertise Contribution] - A --> C[Analysis] - A --> D[Voting] - A --> E[Specialized Input] - - B --> F[Domain Knowledge] - B --> G[Technical Expertise] - B --> H[Industry Experience] - - C --> I[Data Analysis] - C --> J[Impact Assessment] - C --> K[Feasibility Study] - - D --> L[Informed Voting] - D --> M[Proposal Evaluation] - D --> N[Consensus Building] - - E --> O[Specialized Recommendations] - E --> P[Risk Identification] - E --> Q[Opportunity Assessment] -``` - -**Diagram Explanation:** -This diagram shows the general Member role structure, highlighting four main areas of responsibility. Expertise Contribution includes domain knowledge, technical expertise, and industry experience. Analysis involves data analysis, impact assessment, and feasibility study. Voting covers informed voting, proposal evaluation, and consensus building. Specialized Input includes specialized recommendations, risk identification, and opportunity assessment. - -**Technical Implementation:** -```python -# Example: Member role implementation -class BoardMember: - def __init__(self, name, expertise_areas, config): - self.name = name - self.expertise_areas = expertise_areas - self.config = config - self.authority_level = "STANDARD" - self.voting_weight = 1.0 - self.specialized_expertise = True - self.analysis_history = [] - self.voting_history = [] - - async def contribute_expertise(self, task, expertise_areas): - """Contribute specialized expertise to task analysis""" - expertise_contribution = { - "domain_knowledge": await self.apply_domain_knowledge(task, expertise_areas), - "technical_expertise": await self.apply_technical_expertise(task, expertise_areas), - "industry_experience": await self.apply_industry_experience(task, expertise_areas) - } - - return expertise_contribution - - async def perform_analysis(self, task, data): - """Perform comprehensive analysis of task and data""" - analysis_result = { - "data_analysis": await self.analyze_data(task, data), - "impact_assessment": await self.assess_impact(task, data), - "feasibility_study": await self.study_feasibility(task, data) - } - - # Store analysis in history - self.analysis_history.append({ - "task": task, - "analysis": analysis_result, - "timestamp": datetime.now().isoformat() - }) - - return analysis_result - - async def vote_on_proposals(self, proposals, context): - """Vote on proposals based on analysis and expertise""" - voting_decision = { - "proposal_evaluations": await self.evaluate_proposals(proposals, context), - "informed_vote": await self.make_informed_vote(proposals, context), - "rationale": await self.provide_voting_rationale(proposals, context) - } - - # Store voting decision in history - self.voting_history.append({ - "proposals": proposals, - "decision": voting_decision, - "timestamp": datetime.now().isoformat() - }) - - return voting_decision - - async def provide_specialized_input(self, task, context): - """Provide specialized input based on expertise areas""" - specialized_input = { - "specialized_recommendations": await self.make_specialized_recommendations(task, context), - "risk_identification": await self.identify_risks(task, context), - "opportunity_assessment": await self.assess_opportunities(task, context) - } - - return specialized_input -``` - -## Role Interaction Patterns - -### Communication Flow - -```mermaid -graph TD - A[Chairman] --> B[Vice Chairman] - A --> C[Executive Director] - A --> D[Secretary] - A --> E[Treasurer] - A --> F[Members] - - B --> G[Operational Coordination] - C --> H[Strategic Alignment] - D --> I[Documentation Flow] - E --> J[Financial Oversight] - F --> K[Expertise Input] - - G --> L[Implementation] - H --> M[Strategic Execution] - I --> N[Record Keeping] - J --> O[Budget Control] - K --> P[Decision Support] -``` - -**Diagram Explanation:** -This diagram shows the communication flow and interaction patterns between different board roles. The Chairman serves as the central communication hub, coordinating with all other board members. Each role has specific communication channels and responsibilities: Vice Chairman handles operational coordination, Executive Director manages strategic alignment, Secretary manages documentation flow, Treasurer oversees financial matters, and Members provide expertise input. These interactions support implementation, strategic execution, record keeping, budget control, and decision support. - -**Technical Implementation:** -```python -# Example: Role interaction system -class RoleInteractionSystem: - def __init__(self, board_members): - self.board_members = board_members - self.communication_channels = {} - self.interaction_patterns = {} - - async def establish_communication_flow(self): - """Establish communication flow between board members""" - communication_flow = { - "chairman_communications": await self.setup_chairman_communications(), - "operational_coordination": await self.setup_operational_coordination(), - "strategic_alignment": await self.setup_strategic_alignment(), - "documentation_flow": await self.setup_documentation_flow(), - "financial_oversight": await self.setup_financial_oversight(), - "expertise_input": await self.setup_expertise_input() - } - - return communication_flow - - async def coordinate_role_interactions(self, task): - """Coordinate interactions between different roles""" - interactions = { - "chairman_vice_chairman": await self.coordinate_chairman_vice_chairman(task), - "chairman_executive_director": await self.coordinate_chairman_executive_director(task), - "secretary_documentation": await self.coordinate_secretary_documentation(task), - "treasurer_financial": await self.coordinate_treasurer_financial(task), - "member_expertise": await self.coordinate_member_expertise(task) - } - - return interactions -``` - -## Best Practices for Role Management - -### 1. Clear Role Definition -- Define specific responsibilities for each role -- Establish clear authority boundaries -- Document role interactions and communication protocols - -### 2. Balanced Voting Weights -- Ensure fair representation while maintaining leadership hierarchy -- Consider expertise and experience in weight assignment -- Regularly review and adjust weights based on performance - -### 3. Effective Communication -- Establish clear communication channels -- Implement regular reporting mechanisms -- Ensure transparency in decision-making processes - -### 4. Continuous Improvement -- Regularly assess role effectiveness -- Gather feedback from board members -- Implement improvements based on lessons learned - -### 5. Conflict Resolution -- Establish clear conflict resolution procedures -- Provide mediation mechanisms -- Ensure fair and impartial resolution processes \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md b/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md deleted file mode 100644 index 20223c49..00000000 --- a/docs/swarms/structs/board_of_directors/board_of_directors_swarm.md +++ /dev/null @@ -1,704 +0,0 @@ -# `BoardOfDirectorsSwarm` - -The `BoardOfDirectorsSwarm` is a sophisticated multi-agent orchestration system that implements a collective decision-making approach as an alternative to the single Director pattern. It consists of a board of directors that convenes to discuss, vote, and reach consensus on task distribution and execution strategies. - -## Overview - -The Board of Directors Swarm follows a democratic workflow pattern that mimics real-world corporate governance structures: - -1. **Task Reception**: User provides a task to the swarm -2. **Board Meeting**: Board of Directors convenes to discuss and create a plan -3. **Voting & Consensus**: Board members vote and reach consensus on task distribution -4. **Order Distribution**: Board distributes orders to specialized worker agents -5. **Execution**: Individual agents execute their assigned tasks -6. **Feedback Loop**: Board evaluates results and issues new orders if needed (up to `max_loops`) -7. **Context Preservation**: All conversation history and context is maintained throughout the process - -## Architecture - -### High-Level Workflow - -```mermaid -graph TD - A[User Task] --> B[Board of Directors] - B --> C[Board Meeting & Discussion] - C --> D[Voting & Consensus] - D --> E[Create Plan & Orders] - E --> F[Distribute to Agents] - F --> G[Agent 1] - F --> H[Agent 2] - F --> I[Agent N] - G --> J[Execute Task] - H --> J - I --> J - J --> K[Report Results] - K --> L[Board Evaluation] - L --> M{More Loops?} - M -->|Yes| C - M -->|No| N[Final Output] -``` - -**Diagram Explanation:** -This diagram illustrates the complete lifecycle of a task through the Board of Directors Swarm system. The workflow begins when a user submits a task, which triggers the board to convene for discussion and planning. The board then votes on the approach and creates detailed execution orders. These orders are distributed to multiple specialized agents who execute their tasks in parallel. Results are collected and evaluated by the board, which can trigger additional refinement loops if needed. The process continues until the board is satisfied with the results or the maximum number of loops is reached. - -**Key Technical Points:** -- **Parallel Execution**: Multiple agents can work simultaneously on different aspects of the task -- **Iterative Refinement**: The system supports multiple feedback loops for continuous improvement -- **Centralized Coordination**: The board maintains control over the entire process while delegating execution -- **Result Aggregation**: All agent outputs are collected and evaluated before proceeding - -### Detailed Decision-Making Process - -```mermaid -flowchart TD - A[Task Received] --> B[Board Convenes] - B --> C[Chairman Opens Meeting] - C --> D[Task Analysis Phase] - D --> E[Expertise Assignment] - E --> F[Individual Member Analysis] - F --> G[Discussion & Debate] - G --> H[Proposal Generation] - H --> I[Voting Process] - I --> J{Consensus Reached?} - J -->|No| K[Reconciliation Phase] - K --> G - J -->|Yes| L[Plan Finalization] - L --> M[Order Creation] - M --> N[Agent Assignment] - N --> O[Execution Phase] - O --> P[Result Collection] - P --> Q[Board Review] - Q --> R{Approval?} - R -->|No| S[Refinement Loop] - S --> G - R -->|Yes| T[Final Delivery] -``` - -**Diagram Explanation:** -This detailed flowchart shows the internal decision-making process within the board. The process begins with task reception and board convening, followed by a structured analysis phase where each board member contributes based on their expertise. The discussion and debate phase allows for thorough consideration of different approaches, leading to proposal generation and voting. If consensus isn't reached, the system enters a reconciliation phase to resolve conflicts. Once consensus is achieved, the plan is finalized and orders are created for agent assignment. The execution phase is followed by result collection and board review, with the option to enter refinement loops if the results don't meet approval criteria. - -**Technical Implementation Details:** -- **Expertise Assignment**: Board members are assigned tasks based on their specialized knowledge areas -- **Voting Mechanisms**: Configurable voting weights and decision thresholds ensure fair representation -- **Conflict Resolution**: Built-in reconciliation mechanisms handle disagreements among board members -- **Quality Gates**: Approval checkpoints ensure output quality before final delivery - -### Board Member Interaction Flow - -```mermaid -sequenceDiagram - participant User - participant Chairman - participant ViceChair - participant Secretary - participant Treasurer - participant ExecDir - participant Agents - - User->>Chairman: Submit Task - Chairman->>ViceChair: Notify Board Meeting - Chairman->>Secretary: Request Meeting Setup - Chairman->>Treasurer: Resource Assessment - Chairman->>ExecDir: Strategic Planning - - Note over Chairman,ExecDir: Board Discussion Phase - - Chairman->>ViceChair: Lead Discussion - ViceChair->>Secretary: Document Decisions - Secretary->>Treasurer: Budget Considerations - Treasurer->>ExecDir: Resource Allocation - ExecDir->>Chairman: Strategic Recommendations - - Note over Chairman,ExecDir: Voting & Consensus - - Chairman->>ViceChair: Call for Vote - ViceChair->>Secretary: Record Votes - Secretary->>Treasurer: Financial Approval - Treasurer->>ExecDir: Resource Approval - ExecDir->>Chairman: Final Decision - - Note over Chairman,Agents: Execution Phase - - Chairman->>Agents: Distribute Orders - Agents->>Chairman: Execute Tasks - Agents->>ViceChair: Progress Reports - Agents->>Secretary: Documentation - Agents->>Treasurer: Resource Usage - Agents->>ExecDir: Strategic Updates - - Note over Chairman,ExecDir: Review & Feedback - - Chairman->>User: Deliver Results -``` - -**Diagram Explanation:** -This sequence diagram shows the detailed interaction patterns between different board members and agents throughout the entire process. The interaction begins with the Chairman receiving a task from the user and immediately coordinating with other board members. Each board member has specific responsibilities: the Vice Chairman leads discussions, the Secretary documents decisions, the Treasurer handles resource assessment, and the Executive Director provides strategic planning. During the voting phase, each member contributes their expertise and votes are recorded systematically. In the execution phase, the Chairman distributes orders to agents while maintaining communication channels with all board members for progress monitoring and strategic updates. - -**Technical Communication Patterns:** -- **Hierarchical Communication**: Chairman serves as the central coordinator -- **Specialized Reporting**: Each agent reports to the appropriate board member based on their role -- **Parallel Coordination**: Multiple board members can work simultaneously on different aspects -- **Feedback Channels**: Continuous communication ensures real-time monitoring and adjustment - -### Agent Execution and Feedback Loop - -```mermaid -graph LR - subgraph "Board of Directors" - A[Chairman] - B[Vice Chairman] - C[Secretary] - D[Treasurer] - E[Executive Director] - end - - subgraph "Worker Agents" - F[Research Agent] - G[Analysis Agent] - H[Technical Agent] - I[Financial Agent] - J[Strategy Agent] - end - - subgraph "Execution Flow" - K[Task Distribution] - L[Parallel Execution] - M[Result Aggregation] - N[Quality Assessment] - O[Feedback Loop] - end - - A --> K - B --> K - C --> K - D --> K - E --> K - - K --> L - L --> F - L --> G - L --> H - L --> I - L --> J - - F --> M - G --> M - H --> M - I --> M - J --> M - - M --> N - N --> O - O --> K -``` - -**Diagram Explanation:** -This diagram illustrates the relationship between the Board of Directors and Worker Agents, showing how tasks flow from decision-making to execution and back through feedback loops. The Board of Directors collectively participates in task distribution, ensuring that all perspectives are considered. Worker agents execute tasks in parallel, each specializing in different areas (research, analysis, technical implementation, financial considerations, and strategic planning). Results are aggregated and assessed for quality before determining whether additional feedback loops are needed. - -**Technical Architecture Benefits:** -- **Separation of Concerns**: Clear distinction between decision-making (Board) and execution (Agents) -- **Scalability**: Additional agents can be added without changing the board structure -- **Fault Tolerance**: If one agent fails, others can continue working -- **Quality Control**: Centralized assessment ensures consistent output quality - -## Technical Implementation - -### Core Components - -The `BoardOfDirectorsSwarm` consists of several key components: - -1. **Board Members**: Specialized agents with specific roles and voting weights -2. **Worker Agents**: Execution agents that perform the actual tasks -3. **Voting System**: Configurable voting mechanisms for decision-making -4. **Communication Protocol**: Structured communication between board members and agents -5. **Feedback Loop Controller**: Manages iterative refinement processes - -### Configuration Options - -```python -# Example configuration for BoardOfDirectorsSwarm -board_config = { - "max_loops": 3, # Maximum number of refinement loops - "voting_threshold": 0.7, # Consensus threshold (70%) - "enable_voting_weights": True, # Use weighted voting - "consensus_method": "majority", # Voting method: "majority" or "unanimous" - "board_size": 5, # Number of board members - "enable_feedback_loops": True, # Enable iterative refinement - "output_format": "structured", # Output format: "structured", "text", "json" - "enable_logging": True, # Enable detailed logging - "parallel_execution": True, # Enable parallel agent execution - "quality_gates": True, # Enable quality checkpoints -} -``` - -### Voting Mechanisms - -The system supports multiple voting mechanisms: - -1. **Majority Voting**: Simple majority of votes required -2. **Weighted Voting**: Votes weighted by board member importance -3. **Unanimous Consensus**: All board members must agree -4. **Threshold-based**: Configurable percentage of agreement required - -```python -# Example voting configuration -voting_config = { - "method": "weighted_majority", - "threshold": 0.75, - "weights": { - "CHAIRMAN": 1.5, - "VICE_CHAIRMAN": 1.2, - "SECRETARY": 1.0, - "TREASURER": 1.0, - "EXECUTIVE_DIRECTOR": 1.5 - }, - "tie_breaker": "CHAIRMAN", # Chairman breaks ties - "allow_abstention": True, # Allow board members to abstain -} -``` - -### Communication Protocols - -The system uses structured communication protocols: - -1. **Task Distribution Protocol**: Standardized format for distributing tasks to agents -2. **Progress Reporting Protocol**: Regular status updates from agents to board -3. **Decision Communication Protocol**: Clear communication of board decisions -4. **Feedback Protocol**: Structured feedback for iterative improvement - -```python -# Example communication message format -message_format = { - "sender": "CHAIRMAN", - "recipient": "RESEARCH_AGENT", - "message_type": "TASK_DISTRIBUTION", - "content": { - "task_id": "task_123", - "task_description": "Research market trends for Q4", - "deadline": "2024-01-15T10:00:00Z", - "priority": "HIGH", - "resources": ["market_data", "analytics_tools"], - "expected_output": "structured_report" - }, - "metadata": { - "timestamp": "2024-01-10T09:00:00Z", - "session_id": "session_456", - "board_decision_id": "decision_789" - } -} -``` - -## Usage Examples - -### Basic Usage - -```python -from swarms import BoardOfDirectorsSwarm, Agent - -# Create specialized agents -research_agent = Agent( - name="Research Agent", - system_prompt="You are a research specialist focused on market analysis and data gathering.", - llm="gpt-4" -) - -analysis_agent = Agent( - name="Analysis Agent", - system_prompt="You are an analysis expert who interprets data and provides insights.", - llm="gpt-4" -) - -technical_agent = Agent( - name="Technical Agent", - system_prompt="You are a technical specialist who handles implementation and technical details.", - llm="gpt-4" -) - -# Create the Board of Directors Swarm -board_swarm = BoardOfDirectorsSwarm( - agents=[research_agent, analysis_agent, technical_agent], - max_loops=3, - voting_threshold=0.7, - enable_voting_weights=True -) - -# Execute a task -task = "Analyze the current market trends in AI and provide strategic recommendations for our company." -result = board_swarm.run(task) -print(result) -``` - -### Advanced Configuration - -```python -from swarms import BoardOfDirectorsSwarm, Agent -from swarms.structs.board_of_directors import BoardConfig - -# Create a custom board configuration -custom_config = BoardConfig( - max_loops=5, - voting_threshold=0.8, - consensus_method="unanimous", - enable_feedback_loops=True, - output_format="structured", - board_roles={ - "CHAIRMAN": {"weight": 1.5, "responsibilities": ["leadership", "final_decision"]}, - "VICE_CHAIRMAN": {"weight": 1.2, "responsibilities": ["operations", "coordination"]}, - "SECRETARY": {"weight": 1.0, "responsibilities": ["documentation", "record_keeping"]}, - "TREASURER": {"weight": 1.0, "responsibilities": ["financial_oversight", "resource_allocation"]}, - "EXECUTIVE_DIRECTOR": {"weight": 1.5, "responsibilities": ["strategic_planning", "execution"]} - } -) - -# Create specialized agents with custom prompts -agents = [ - Agent( - name="Market Research Agent", - system_prompt="""You are a market research specialist with expertise in: - - Competitive analysis - - Market sizing and segmentation - - Customer behavior analysis - - Industry trend identification - Provide detailed, data-driven insights.""", - llm="gpt-4" - ), - Agent( - name="Financial Analysis Agent", - system_prompt="""You are a financial analyst specializing in: - - Financial modeling and forecasting - - Risk assessment and mitigation - - Investment analysis - - Cost-benefit analysis - Provide comprehensive financial insights.""", - llm="gpt-4" - ), - Agent( - name="Technical Strategy Agent", - system_prompt="""You are a technical strategist focused on: - - Technology roadmap planning - - Technical feasibility assessment - - Implementation strategy - - Technology risk evaluation - Provide strategic technical guidance.""", - llm="gpt-4" - ) -] - -# Create the swarm with custom configuration -board_swarm = BoardOfDirectorsSwarm( - agents=agents, - config=custom_config -) - -# Execute a complex task -complex_task = """ -Analyze the feasibility of launching a new AI-powered product in the healthcare sector. -Consider: -1. Market opportunity and competitive landscape -2. Financial viability and investment requirements -3. Technical implementation challenges and timeline -4. Regulatory compliance requirements -5. Risk assessment and mitigation strategies - -Provide a comprehensive report with recommendations for next steps. -""" - -result = board_swarm.run(complex_task) -print(result) -``` - -### Real-World Use Cases - -#### 1. Strategic Business Planning - -```python -# Example: Strategic business planning with multiple stakeholders -business_planning_task = """ -Develop a comprehensive 5-year strategic plan for our technology company. -Include: -- Market analysis and competitive positioning -- Product development roadmap -- Financial projections and funding requirements -- Operational scaling strategy -- Risk management framework -- Success metrics and KPIs -""" - -# Configure board for strategic planning -strategic_config = BoardConfig( - max_loops=4, - voting_threshold=0.8, - consensus_method="weighted_majority", - enable_feedback_loops=True, - output_format="structured" -) - -strategic_swarm = BoardOfDirectorsSwarm( - agents=[market_agent, financial_agent, technical_agent, strategy_agent], - config=strategic_config -) - -strategic_plan = strategic_swarm.run(business_planning_task) -``` - -#### 2. Product Development Decision Making - -```python -# Example: Product development decision making -product_decision_task = """ -Evaluate the feasibility of developing a new AI-powered customer service chatbot. -Consider: -- Technical requirements and development timeline -- Market demand and competitive analysis -- Cost-benefit analysis and ROI projections -- Implementation challenges and resource requirements -- Success criteria and measurement metrics - -Provide a go/no-go recommendation with detailed rationale. -""" - -# Configure board for product decisions -product_config = BoardConfig( - max_loops=3, - voting_threshold=0.75, - consensus_method="majority", - enable_feedback_loops=True, - output_format="structured" -) - -product_swarm = BoardOfDirectorsSwarm( - agents=[technical_agent, market_agent, financial_agent], - config=product_config -) - -product_recommendation = product_swarm.run(product_decision_task) -``` - -#### 3. Crisis Management and Response - -```python -# Example: Crisis management and response planning -crisis_task = """ -Our company is facing a major data breach. Develop an immediate response plan. -Include: -- Immediate containment and mitigation steps -- Communication strategy for stakeholders -- Legal and regulatory compliance requirements -- Financial impact assessment -- Long-term recovery and prevention measures -- Timeline and resource allocation -""" - -# Configure board for crisis management -crisis_config = BoardConfig( - max_loops=2, # Faster response needed - voting_threshold=0.6, # Lower threshold for urgent decisions - consensus_method="majority", - enable_feedback_loops=False, # No time for multiple iterations - output_format="structured" -) - -crisis_swarm = BoardOfDirectorsSwarm( - agents=[security_agent, legal_agent, communications_agent, financial_agent], - config=crisis_config -) - -crisis_response = crisis_swarm.run(crisis_task) -``` - -## Performance Optimization - -### Parallel Execution Strategies - -The Board of Directors Swarm supports various parallel execution strategies: - -1. **Task-Level Parallelism**: Multiple agents work on different aspects simultaneously -2. **Board-Level Parallelism**: Board members can analyze different aspects in parallel -3. **Iteration-Level Parallelism**: Multiple refinement loops can run concurrently - -```python -# Example: Optimizing for parallel execution -parallel_config = BoardConfig( - max_loops=3, - parallel_execution=True, - max_concurrent_agents=5, - enable_async_processing=True, - timeout_per_agent=300, # 5 minutes per agent - enable_agent_pooling=True -) - -# Create agent pool for better resource utilization -agent_pool = [ - Agent(name=f"Research_Agent_{i}", system_prompt="...", llm="gpt-4") - for i in range(3) -] + [ - Agent(name=f"Analysis_Agent_{i}", system_prompt="...", llm="gpt-4") - for i in range(2) -] - -parallel_swarm = BoardOfDirectorsSwarm( - agents=agent_pool, - config=parallel_config -) -``` - -### Quality Control Mechanisms - -```python -# Example: Quality control configuration -quality_config = BoardConfig( - max_loops=3, - quality_gates=True, - quality_threshold=0.8, - enable_peer_review=True, - review_required=True, - output_validation=True, - enable_metrics_tracking=True -) - -# Define quality metrics -quality_metrics = { - "completeness": 0.9, # 90% completeness required - "accuracy": 0.85, # 85% accuracy required - "relevance": 0.9, # 90% relevance required - "clarity": 0.8 # 80% clarity required -} - -quality_swarm = BoardOfDirectorsSwarm( - agents=agents, - config=quality_config, - quality_metrics=quality_metrics -) -``` - -## Monitoring and Debugging - -### Logging and Observability - -```python -import logging -from swarms import BoardOfDirectorsSwarm - -# Configure detailed logging -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) - -# Create swarm with logging enabled -logging_swarm = BoardOfDirectorsSwarm( - agents=agents, - config=BoardConfig( - enable_logging=True, - log_level="DEBUG", - enable_metrics=True, - enable_tracing=True - ) -) - -# Execute with detailed logging -result = logging_swarm.run(task) -``` - -### Performance Metrics - -```python -# Example: Performance monitoring -from swarms.metrics import SwarmMetrics - -# Create metrics collector -metrics = SwarmMetrics() - -# Configure swarm with metrics -monitored_swarm = BoardOfDirectorsSwarm( - agents=agents, - config=BoardConfig(enable_metrics=True), - metrics_collector=metrics -) - -# Execute and collect metrics -result = monitored_swarm.run(task) - -# Analyze performance -performance_report = metrics.generate_report() -print(f"Total execution time: {performance_report['total_time']}") -print(f"Average agent response time: {performance_report['avg_agent_time']}") -print(f"Number of voting rounds: {performance_report['voting_rounds']}") -print(f"Consensus achieved: {performance_report['consensus_achieved']}") -``` - -## Best Practices - -### 1. Agent Design - -- **Specialized Expertise**: Design agents with specific, complementary expertise -- **Clear Responsibilities**: Define clear boundaries for each agent's responsibilities -- **Consistent Communication**: Use standardized communication protocols -- **Error Handling**: Implement robust error handling and recovery mechanisms - -### 2. Board Configuration - -- **Appropriate Voting Thresholds**: Set thresholds based on decision criticality -- **Balanced Voting Weights**: Ensure fair representation while maintaining leadership hierarchy -- **Flexible Consensus Methods**: Choose consensus methods based on decision type -- **Reasonable Loop Limits**: Balance quality with efficiency - -### 3. Task Design - -- **Clear Objectives**: Define clear, measurable objectives -- **Structured Requirements**: Provide structured, detailed requirements -- **Appropriate Scope**: Ensure tasks are appropriately scoped for the board size -- **Context Provision**: Provide sufficient context for informed decision-making - -### 4. Performance Optimization - -- **Parallel Execution**: Leverage parallel execution where possible -- **Resource Management**: Monitor and optimize resource usage -- **Quality Gates**: Implement appropriate quality control mechanisms -- **Continuous Monitoring**: Monitor performance and adjust configurations as needed - -## Troubleshooting - -### Common Issues and Solutions - -1. **Consensus Not Reached** - - **Issue**: Board cannot reach consensus within loop limit - - **Solution**: Lower voting threshold, increase max_loops, or adjust voting weights - -2. **Agent Timeout** - - **Issue**: Individual agents take too long to respond - - **Solution**: Increase timeout settings or optimize agent prompts - -3. **Poor Quality Output** - - **Issue**: Final output doesn't meet quality standards - - **Solution**: Enable quality gates, increase max_loops, or improve agent prompts - -4. **Resource Exhaustion** - - **Issue**: System runs out of resources during execution - - **Solution**: Implement resource limits, use agent pooling, or optimize parallel execution - -### Debugging Techniques - -```python -# Example: Debugging configuration -debug_config = BoardConfig( - max_loops=1, # Limit loops for debugging - enable_logging=True, - log_level="DEBUG", - enable_tracing=True, - debug_mode=True -) - -# Create debug swarm -debug_swarm = BoardOfDirectorsSwarm( - agents=agents, - config=debug_config -) - -# Execute with debugging -try: - result = debug_swarm.run(task) -except Exception as e: - print(f"Error: {e}") - print(f"Debug info: {debug_swarm.get_debug_info()}") -``` \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md b/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md deleted file mode 100644 index 3e913ea7..00000000 --- a/docs/swarms/structs/board_of_directors/board_of_directors_workflow.md +++ /dev/null @@ -1,908 +0,0 @@ -# Board of Directors Workflow - -The Board of Directors workflow is a sophisticated multi-stage process that ensures comprehensive task analysis, collaborative decision-making, and effective execution through specialized agents. This workflow implements a corporate governance model that balances efficiency with thoroughness, ensuring high-quality outcomes through structured collaboration. - -## Workflow Overview - -```mermaid -graph TD - A[Task Input] --> B[Initial Assessment] - B --> C[Board Assembly] - C --> D[Meeting Phase] - D --> E[Decision Phase] - E --> F[Execution Phase] - F --> G[Review Phase] - G --> H{Approval?} - H -->|No| I[Refinement] - I --> D - H -->|Yes| J[Final Delivery] - - style A fill:#e1f5fe - style J fill:#c8e6c9 - style H fill:#fff3e0 -``` - -**Diagram Explanation:** -This high-level workflow diagram shows the complete lifecycle of a task through the Board of Directors system. The process begins with task input, followed by an initial assessment to understand requirements and complexity. The board then assembles with appropriate members, conducts a structured meeting phase for analysis and discussion, makes decisions through voting and consensus, executes the plan through specialized agents, reviews results, and either approves for final delivery or returns to refinement loops for improvement. - -**Technical Implementation Details:** -- **Task Input Validation**: System validates task format, requirements, and constraints -- **Dynamic Board Assembly**: Board members are selected based on task requirements and availability -- **Structured Meeting Protocol**: Follows corporate governance best practices -- **Iterative Refinement**: Quality gates ensure output meets standards before final delivery - -## Phase 1: Initial Assessment - -### Task Analysis and Board Preparation - -```mermaid -flowchart LR - A[Task Received] --> B[Complexity Assessment] - B --> C[Resource Requirements] - C --> D[Expertise Mapping] - D --> E[Board Member Selection] - E --> F[Meeting Scheduling] - - subgraph "Assessment Criteria" - G[Task Complexity] - H[Time Constraints] - I[Resource Availability] - J[Expertise Requirements] - end - - B --> G - B --> H - C --> I - D --> J -``` - -**Diagram Explanation:** -This flowchart illustrates the systematic approach to task analysis and board preparation. When a task is received, the system performs a comprehensive assessment including complexity evaluation, resource requirement analysis, expertise mapping, and board member selection. The assessment criteria include task complexity, time constraints, resource availability, and specific expertise requirements needed for successful completion. - -**Technical Implementation:** -```python -# Example: Task assessment implementation -class TaskAssessment: - def __init__(self): - self.complexity_metrics = { - "scope": 0.0, # Task scope (0-1) - "technical_depth": 0.0, # Technical complexity (0-1) - "stakeholder_count": 0, # Number of stakeholders - "timeline_constraints": 0.0, # Time pressure (0-1) - "resource_intensity": 0.0 # Resource requirements (0-1) - } - - def assess_task(self, task_description): - """Assess task complexity and requirements""" - assessment = { - "complexity_score": self.calculate_complexity(), - "required_expertise": self.identify_expertise_needs(), - "resource_requirements": self.estimate_resources(), - "timeline_estimate": self.estimate_timeline(), - "recommended_board_size": self.calculate_board_size() - } - return assessment - - def calculate_complexity(self): - """Calculate overall task complexity score""" - weights = { - "scope": 0.25, - "technical_depth": 0.3, - "stakeholder_count": 0.15, - "timeline_constraints": 0.2, - "resource_intensity": 0.1 - } - - complexity_score = sum( - self.complexity_metrics[key] * weights[key] - for key in weights - ) - return min(complexity_score, 1.0) - - def identify_expertise_needs(self): - """Identify required expertise areas""" - expertise_areas = [] - - if self.complexity_metrics["technical_depth"] > 0.5: - expertise_areas.append("technical") - - if self.complexity_metrics["stakeholder_count"] > 3: - expertise_areas.append("stakeholder_management") - - if self.complexity_metrics["resource_intensity"] > 0.5: - expertise_areas.append("resource_management") - - return expertise_areas -``` - -### Board Member Activation - -```mermaid -sequenceDiagram - participant System - participant Chairman - participant Members - participant Agents - - System->>Chairman: Notify New Task - Chairman->>System: Assess Task Requirements - System->>Members: Activate Relevant Members - Members->>Chairman: Confirm Availability - Chairman->>Agents: Prepare Agent Pool - Agents->>Chairman: Confirm Readiness - Chairman->>System: Board Ready for Meeting -``` - -**Diagram Explanation:** -This sequence diagram shows the systematic process of board member activation and preparation. The system notifies the Chairman of a new task, who then assesses requirements and determines which board members are needed. The system activates relevant members, who confirm their availability. The Chairman prepares the agent pool and confirms readiness before declaring the board ready for the meeting. - -**Technical Implementation:** -```python -# Example: Board member activation system -class BoardActivation: - def __init__(self, board_members, agents): - self.board_members = board_members - self.agents = agents - self.activation_status = {} - - async def activate_board(self, task_assessment): - """Activate board members based on task requirements""" - # Determine required members - required_members = self.select_required_members(task_assessment) - - # Activate members - activation_results = [] - for member in required_members: - status = await self.activate_member(member, task_assessment) - activation_results.append(status) - - # Prepare agent pool - agent_pool = self.prepare_agent_pool(task_assessment) - - # Confirm readiness - readiness_status = await self.confirm_readiness(activation_results, agent_pool) - - return { - "board_ready": readiness_status["ready"], - "active_members": readiness_status["active_members"], - "agent_pool": readiness_status["agent_pool"], - "estimated_start_time": readiness_status["start_time"] - } - - def select_required_members(self, assessment): - """Select board members based on task requirements""" - required_members = ["CHAIRMAN"] # Chairman always required - - if assessment["complexity_score"] > 0.7: - required_members.extend(["VICE_CHAIRMAN", "EXECUTIVE_DIRECTOR"]) - - if "technical" in assessment["required_expertise"]: - required_members.append("TECHNICAL_DIRECTOR") - - if "financial" in assessment["required_expertise"]: - required_members.append("TREASURER") - - if assessment["stakeholder_count"] > 5: - required_members.append("SECRETARY") - - return list(set(required_members)) # Remove duplicates - - async def activate_member(self, member_role, assessment): - """Activate individual board member""" - member = self.board_members.get(member_role) - if not member: - return {"status": "error", "message": f"Member {member_role} not found"} - - # Check availability - availability = await member.check_availability() - if not availability["available"]: - return {"status": "unavailable", "member": member_role, "reason": availability["reason"]} - - # Prepare member for task - preparation_status = await member.prepare_for_task(assessment) - - return { - "status": "activated", - "member": member_role, - "preparation_time": preparation_status["preparation_time"], - "expertise_areas": preparation_status["expertise_areas"] - } -``` - -## Phase 2: Board Meeting - -### Meeting Structure - -```mermaid -graph TD - A[Meeting Opens] --> B[Agenda Review] - B --> C[Task Presentation] - C --> D[Expertise Assignment] - D --> E[Individual Analysis] - E --> F[Group Discussion] - F --> G[Proposal Development] - G --> H[Voting Process] - H --> I[Consensus Building] - I --> J[Plan Finalization] - - subgraph "Meeting Components" - K[Time Management] - L[Documentation] - M[Conflict Resolution] - N[Decision Recording] - end - - A --> K - F --> L - I --> M - J --> N -``` - -**Diagram Explanation:** -This diagram shows the structured meeting process that follows corporate governance best practices. The meeting begins with agenda review and task presentation, followed by expertise assignment where board members are given specific areas to analyze. Individual analysis leads to group discussion, proposal development, voting, consensus building, and plan finalization. Throughout the process, time management, documentation, conflict resolution, and decision recording ensure effective governance. - -**Technical Implementation:** -```python -# Example: Meeting management system -class BoardMeeting: - def __init__(self, board_members, task, config): - self.board_members = board_members - self.task = task - self.config = config - self.meeting_phases = [] - self.decisions = [] - self.documentation = {} - - async def conduct_meeting(self): - """Conduct the board meeting following structured phases""" - meeting_result = { - "phases": [], - "decisions": [], - "documentation": {}, - "consensus_achieved": False, - "final_plan": None - } - - # Phase 1: Meeting Opening and Agenda Review - opening_phase = await self.conduct_opening_phase() - meeting_result["phases"].append(opening_phase) - - # Phase 2: Task Presentation and Expertise Assignment - presentation_phase = await self.conduct_presentation_phase() - meeting_result["phases"].append(presentation_phase) - - # Phase 3: Individual Analysis - analysis_phase = await self.conduct_analysis_phase() - meeting_result["phases"].append(analysis_phase) - - # Phase 4: Group Discussion - discussion_phase = await self.conduct_discussion_phase() - meeting_result["phases"].append(discussion_phase) - - # Phase 5: Proposal Development - proposal_phase = await self.conduct_proposal_phase() - meeting_result["phases"].append(proposal_phase) - - # Phase 6: Voting and Consensus - voting_phase = await self.conduct_voting_phase() - meeting_result["phases"].append(voting_phase) - - # Phase 7: Plan Finalization - finalization_phase = await self.conduct_finalization_phase() - meeting_result["phases"].append(finalization_phase) - - meeting_result["decisions"] = self.decisions - meeting_result["documentation"] = self.documentation - meeting_result["consensus_achieved"] = voting_phase["consensus_achieved"] - meeting_result["final_plan"] = finalization_phase["final_plan"] - - return meeting_result - - async def conduct_opening_phase(self): - """Conduct meeting opening and agenda review""" - chairman = self.board_members["CHAIRMAN"] - - # Open meeting - opening_statement = await chairman.open_meeting(self.task) - - # Review agenda - agenda_review = await chairman.review_agenda(self.task) - - # Set meeting parameters - meeting_params = { - "time_limit": self.config.get("meeting_time_limit", 3600), # 1 hour default - "voting_threshold": self.config.get("voting_threshold", 0.7), - "consensus_method": self.config.get("consensus_method", "majority") - } - - return { - "phase": "opening", - "opening_statement": opening_statement, - "agenda_review": agenda_review, - "meeting_params": meeting_params, - "timestamp": datetime.now().isoformat() - } - - async def conduct_presentation_phase(self): - """Conduct task presentation and expertise assignment""" - chairman = self.board_members["CHAIRMAN"] - - # Present task details - task_presentation = await chairman.present_task(self.task) - - # Assign expertise areas - expertise_assignments = await chairman.assign_expertise_areas( - self.board_members, self.task - ) - - return { - "phase": "presentation", - "task_presentation": task_presentation, - "expertise_assignments": expertise_assignments, - "timestamp": datetime.now().isoformat() - } -``` - -### Discussion and Debate Process - -```mermaid -flowchart TD - A[Discussion Opens] --> B[Expertise-Based Input] - B --> C[Cross-Examination] - C --> D[Alternative Proposals] - D --> E[Impact Analysis] - E --> F[Risk Assessment] - F --> G[Stakeholder Consideration] - G --> H[Resource Evaluation] - H --> I[Timeline Assessment] - I --> J[Consensus Check] - J -->|No Consensus| K[Conflict Resolution] - K --> L[Mediation Process] - L --> J - J -->|Consensus| M[Proposal Finalization] - - subgraph "Discussion Elements" - N[Evidence-Based Arguments] - O[Stakeholder Perspectives] - P[Risk-Benefit Analysis] - Q[Implementation Feasibility] - end - - B --> N - G --> O - E --> P - H --> Q -``` - -**Diagram Explanation:** -This flowchart details the discussion and debate process that ensures thorough consideration of all aspects before decision-making. The process begins with expertise-based input from each board member, followed by cross-examination to validate claims. Alternative proposals are considered, and comprehensive impact analysis, risk assessment, stakeholder consideration, resource evaluation, and timeline assessment are conducted. If consensus isn't reached, conflict resolution and mediation processes are employed until agreement is achieved. - -**Technical Implementation:** -```python -# Example: Discussion and debate management -class DiscussionManager: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.discussion_points = [] - self.conflicts = [] - self.consensus_status = False - - async def conduct_discussion(self, task, expertise_assignments): - """Conduct structured discussion and debate""" - discussion_result = { - "phases": [], - "consensus_achieved": False, - "final_proposal": None, - "conflicts_resolved": [] - } - - # Phase 1: Expertise-based input - expertise_input = await self.gather_expertise_input(task, expertise_assignments) - discussion_result["phases"].append(expertise_input) - - # Phase 2: Cross-examination - cross_examination = await self.conduct_cross_examination(expertise_input) - discussion_result["phases"].append(cross_examination) - - # Phase 3: Alternative proposals - alternatives = await self.generate_alternatives(task, expertise_input) - discussion_result["phases"].append(alternatives) - - # Phase 4: Comprehensive analysis - analysis = await self.conduct_comprehensive_analysis(task, alternatives) - discussion_result["phases"].append(analysis) - - # Phase 5: Consensus building - consensus = await self.build_consensus(analysis) - discussion_result["phases"].append(consensus) - - discussion_result["consensus_achieved"] = consensus["achieved"] - discussion_result["final_proposal"] = consensus["final_proposal"] - discussion_result["conflicts_resolved"] = consensus["conflicts_resolved"] - - return discussion_result - - async def gather_expertise_input(self, task, expertise_assignments): - """Gather input from each board member based on their expertise""" - expertise_inputs = {} - - for member_role, expertise_areas in expertise_assignments.items(): - member = self.board_members[member_role] - - # Generate expertise-based analysis - analysis = await member.analyze_task_areas(task, expertise_areas) - - expertise_inputs[member_role] = { - "expertise_areas": expertise_areas, - "analysis": analysis, - "recommendations": analysis.get("recommendations", []), - "concerns": analysis.get("concerns", []), - "proposals": analysis.get("proposals", []) - } - - return { - "phase": "expertise_input", - "inputs": expertise_inputs, - "timestamp": datetime.now().isoformat() - } - - async def conduct_cross_examination(self, expertise_input): - """Conduct cross-examination of expertise inputs""" - cross_examination_results = {} - - for member_role, input_data in expertise_input["inputs"].items(): - member = self.board_members[member_role] - - # Other members examine this member's input - examinations = [] - for examiner_role, examiner in self.board_members.items(): - if examiner_role != member_role: - examination = await examiner.examine_input( - input_data, member_role - ) - examinations.append({ - "examiner": examiner_role, - "examination": examination - }) - - cross_examination_results[member_role] = { - "original_input": input_data, - "examinations": examinations, - "validation_status": self.assess_validation_status(examinations) - } - - return { - "phase": "cross_examination", - "results": cross_examination_results, - "timestamp": datetime.now().isoformat() - } - - async def generate_alternatives(self, task, expertise_input): - """Generate alternative proposals based on expertise input""" - alternatives = [] - - # Generate alternatives from each expertise area - for member_role, input_data in expertise_input["inputs"].items(): - member = self.board_members[member_role] - - member_alternatives = await member.generate_alternatives( - task, input_data - ) - - alternatives.extend(member_alternatives) - - # Combine and synthesize alternatives - synthesized_alternatives = await self.synthesize_alternatives(alternatives) - - return { - "phase": "alternatives", - "individual_alternatives": alternatives, - "synthesized_alternatives": synthesized_alternatives, - "timestamp": datetime.now().isoformat() - } -``` - -## Phase 3: Decision Making - -### Voting and Consensus Process - -```mermaid -graph TD - A[Proposal Review] --> B[Voting Preparation] - B --> C[Individual Voting] - C --> D[Vote Aggregation] - D --> E[Threshold Check] - E -->|Below Threshold| F[Consensus Building] - F --> G[Proposal Refinement] - G --> C - E -->|Above Threshold| H[Decision Finalization] - H --> I[Plan Creation] - I --> J[Execution Orders] - - subgraph "Voting Components" - K[Weighted Voting] - L[Secret Ballot] - M[Transparent Process] - N[Conflict Resolution] - end - - C --> K - C --> L - D --> M - F --> N -``` - -**Diagram Explanation:** -This diagram shows the structured voting and consensus process that ensures fair and transparent decision-making. The process begins with proposal review and voting preparation, followed by individual voting where each board member casts their vote. Votes are aggregated and checked against the consensus threshold. If the threshold isn't met, consensus building and proposal refinement processes are initiated. Once the threshold is achieved, the decision is finalized, a plan is created, and execution orders are generated. - -**Technical Implementation:** -```python -# Example: Voting and consensus system -class VotingSystem: - def __init__(self, board_members, config): - self.board_members = board_members - self.config = config - self.voting_history = [] - self.consensus_threshold = config.get("voting_threshold", 0.7) - self.voting_weights = config.get("voting_weights", {}) - - async def conduct_voting(self, proposals): - """Conduct voting on proposals""" - voting_result = { - "rounds": [], - "final_decision": None, - "consensus_achieved": False, - "voting_summary": {} - } - - current_proposals = proposals - round_number = 1 - - while round_number <= self.config.get("max_voting_rounds", 3): - # Conduct voting round - round_result = await self.conduct_voting_round( - current_proposals, round_number - ) - voting_result["rounds"].append(round_result) - - # Check if consensus achieved - if round_result["consensus_achieved"]: - voting_result["final_decision"] = round_result["winning_proposal"] - voting_result["consensus_achieved"] = True - break - - # Refine proposals for next round - current_proposals = await self.refine_proposals( - current_proposals, round_result - ) - - round_number += 1 - - # Generate voting summary - voting_result["voting_summary"] = self.generate_voting_summary( - voting_result["rounds"] - ) - - return voting_result - - async def conduct_voting_round(self, proposals, round_number): - """Conduct a single voting round""" - round_result = { - "round": round_number, - "proposals": proposals, - "votes": {}, - "aggregated_results": {}, - "consensus_achieved": False, - "winning_proposal": None - } - - # Collect votes from each board member - for member_role, member in self.board_members.items(): - vote = await member.vote_on_proposals(proposals, round_number) - round_result["votes"][member_role] = vote - - # Aggregate votes - aggregated_results = self.aggregate_votes( - round_result["votes"], proposals - ) - round_result["aggregated_results"] = aggregated_results - - # Check consensus - consensus_check = self.check_consensus(aggregated_results) - round_result["consensus_achieved"] = consensus_check["achieved"] - round_result["winning_proposal"] = consensus_check["winning_proposal"] - - return round_result - - def aggregate_votes(self, votes, proposals): - """Aggregate votes using weighted voting system""" - aggregated_results = {} - - for proposal_id in [p["id"] for p in proposals]: - total_weighted_score = 0 - total_weight = 0 - vote_counts = {} - - for member_role, vote in votes.items(): - member_weight = self.voting_weights.get(member_role, 1.0) - - if proposal_id in vote["scores"]: - score = vote["scores"][proposal_id] - weighted_score = score * member_weight - total_weighted_score += weighted_score - total_weight += member_weight - - # Track vote distribution - vote_counts[member_role] = { - "score": score, - "weight": member_weight, - "weighted_score": weighted_score - } - - # Calculate final score - final_score = total_weighted_score / total_weight if total_weight > 0 else 0 - - aggregated_results[proposal_id] = { - "final_score": final_score, - "total_weight": total_weight, - "vote_counts": vote_counts, - "consensus_percentage": final_score - } - - return aggregated_results - - def check_consensus(self, aggregated_results): - """Check if consensus threshold is met""" - best_proposal = None - best_score = 0 - - for proposal_id, result in aggregated_results.items(): - if result["final_score"] > best_score: - best_score = result["final_score"] - best_proposal = proposal_id - - consensus_achieved = best_score >= self.consensus_threshold - - return { - "achieved": consensus_achieved, - "winning_proposal": best_proposal if consensus_achieved else None, - "best_score": best_score, - "threshold": self.consensus_threshold - } -``` - -## Phase 4: Execution and Monitoring - -### Agent Execution Management - -```mermaid -graph TD - A[Execution Orders] --> B[Agent Assignment] - B --> C[Task Distribution] - C --> D[Parallel Execution] - D --> E[Progress Monitoring] - E --> F[Result Collection] - F --> G[Quality Assessment] - G --> H{Quality Met?} - H -->|No| I[Refinement Request] - I --> J[Agent Re-execution] - J --> E - H -->|Yes| K[Result Aggregation] - K --> L[Board Review] - - subgraph "Execution Components" - M[Resource Allocation] - N[Deadline Management] - O[Error Handling] - P[Performance Tracking] - end - - B --> M - C --> N - D --> O - E --> P -``` - -**Diagram Explanation:** -This diagram illustrates the execution and monitoring phase where the board's decisions are implemented through specialized agents. Execution orders are created and distributed to appropriate agents, who execute tasks in parallel while being monitored for progress. Results are collected, assessed for quality, and either approved or sent back for refinement. Once quality standards are met, results are aggregated and presented to the board for final review. - -**Technical Implementation:** -```python -# Example: Execution management system -class ExecutionManager: - def __init__(self, agents, config): - self.agents = agents - self.config = config - self.execution_status = {} - self.performance_metrics = {} - - async def execute_plan(self, execution_plan): - """Execute the board's approved plan""" - execution_result = { - "phases": [], - "final_results": None, - "quality_metrics": {}, - "performance_summary": {} - } - - # Phase 1: Agent assignment and task distribution - assignment_phase = await self.assign_agents(execution_plan) - execution_result["phases"].append(assignment_phase) - - # Phase 2: Parallel execution with monitoring - execution_phase = await self.execute_tasks(assignment_phase["assignments"]) - execution_result["phases"].append(execution_phase) - - # Phase 3: Result collection and quality assessment - assessment_phase = await self.assess_results(execution_phase["results"]) - execution_result["phases"].append(assessment_phase) - - # Phase 4: Result aggregation and board review - review_phase = await self.prepare_board_review(assessment_phase) - execution_result["phases"].append(review_phase) - - execution_result["final_results"] = review_phase["final_results"] - execution_result["quality_metrics"] = assessment_phase["quality_metrics"] - execution_result["performance_summary"] = execution_phase["performance_summary"] - - return execution_result - - async def assign_agents(self, execution_plan): - """Assign agents to tasks based on execution plan""" - assignments = {} - - for task_id, task_details in execution_plan["tasks"].items(): - # Select appropriate agent based on task requirements - selected_agent = await self.select_agent_for_task(task_details) - - # Prepare task assignment - assignment = { - "task_id": task_id, - "agent": selected_agent, - "task_details": task_details, - "deadline": task_details.get("deadline"), - "priority": task_details.get("priority", "normal"), - "resources": task_details.get("resources", []), - "expected_output": task_details.get("expected_output") - } - - assignments[task_id] = assignment - - return { - "phase": "agent_assignment", - "assignments": assignments, - "timestamp": datetime.now().isoformat() - } - - async def execute_tasks(self, assignments): - """Execute tasks in parallel with monitoring""" - execution_tasks = [] - execution_results = {} - performance_metrics = {} - - # Create execution tasks - for task_id, assignment in assignments.items(): - task = self.create_execution_task(assignment) - execution_tasks.append(task) - - # Execute tasks in parallel - results = await asyncio.gather(*execution_tasks, return_exceptions=True) - - # Process results - for i, result in enumerate(results): - task_id = list(assignments.keys())[i] - - if isinstance(result, Exception): - execution_results[task_id] = { - "status": "error", - "error": str(result), - "retry_count": 0 - } - else: - execution_results[task_id] = { - "status": "completed", - "result": result, - "execution_time": result.get("execution_time"), - "quality_score": result.get("quality_score") - } - - # Collect performance metrics - performance_metrics[task_id] = self.collect_performance_metrics( - task_id, result - ) - - return { - "phase": "task_execution", - "results": execution_results, - "performance_summary": self.summarize_performance(performance_metrics), - "timestamp": datetime.now().isoformat() - } - - async def assess_results(self, execution_results): - """Assess quality of execution results""" - quality_metrics = {} - overall_quality_score = 0 - total_tasks = len(execution_results) - - for task_id, result in execution_results.items(): - if result["status"] == "completed": - # Assess individual task quality - task_quality = await self.assess_task_quality(result) - quality_metrics[task_id] = task_quality - overall_quality_score += task_quality["overall_score"] - else: - quality_metrics[task_id] = { - "overall_score": 0, - "completeness": 0, - "accuracy": 0, - "relevance": 0, - "issues": ["Task failed to execute"] - } - - # Calculate overall quality - overall_quality_score = overall_quality_score / total_tasks if total_tasks > 0 else 0 - - return { - "phase": "quality_assessment", - "quality_metrics": quality_metrics, - "overall_quality_score": overall_quality_score, - "quality_threshold_met": overall_quality_score >= self.config.get("quality_threshold", 0.8), - "timestamp": datetime.now().isoformat() - } -``` - -## Best Practices and Optimization - -### Performance Optimization Strategies - -1. **Parallel Execution**: Maximize parallel agent execution for faster results -2. **Resource Pooling**: Implement agent pooling for better resource utilization -3. **Caching**: Cache common analysis results to avoid redundant computation -4. **Load Balancing**: Distribute tasks evenly across available agents - -### Quality Assurance - -1. **Quality Gates**: Implement quality checkpoints throughout the process -2. **Peer Review**: Enable peer review mechanisms for critical decisions -3. **Validation**: Validate outputs against predefined criteria -4. **Continuous Improvement**: Learn from previous executions to improve future performance - -### Monitoring and Analytics - -```python -# Example: Performance monitoring system -class PerformanceMonitor: - def __init__(self): - self.metrics = { - "execution_times": [], - "quality_scores": [], - "consensus_rounds": [], - "error_rates": [] - } - - def track_execution_time(self, phase, duration): - """Track execution time for different phases""" - self.metrics["execution_times"].append({ - "phase": phase, - "duration": duration, - "timestamp": datetime.now().isoformat() - }) - - def track_quality_score(self, score): - """Track quality scores""" - self.metrics["quality_scores"].append({ - "score": score, - "timestamp": datetime.now().isoformat() - }) - - def generate_performance_report(self): - """Generate comprehensive performance report""" - return { - "average_execution_time": self.calculate_average_execution_time(), - "quality_trends": self.analyze_quality_trends(), - "consensus_efficiency": self.analyze_consensus_efficiency(), - "error_analysis": self.analyze_errors(), - "recommendations": self.generate_recommendations() - } -``` \ No newline at end of file diff --git a/docs/swarms/structs/board_of_directors/index.md b/docs/swarms/structs/board_of_directors/index.md deleted file mode 100644 index 136fdea8..00000000 --- a/docs/swarms/structs/board_of_directors/index.md +++ /dev/null @@ -1,291 +0,0 @@ -# Board of Directors - Multi-Agent Architecture - -The Board of Directors is a sophisticated multi-agent architecture that implements collective decision-making through democratic processes, voting mechanisms, and role-based leadership. This architecture provides an alternative to single-director patterns by enabling collaborative intelligence through structured governance. - -## 🏛️ Overview - -The Board of Directors architecture follows a democratic workflow pattern: - -1. **Task Reception**: User provides a task to the swarm -2. **Board Meeting**: Board of Directors convenes to discuss and create a plan -3. **Voting & Consensus**: Board members vote and reach consensus on task distribution -4. **Order Distribution**: Board distributes orders to specialized worker agents -5. **Execution**: Individual agents execute their assigned tasks -6. **Feedback Loop**: Board evaluates results and issues new orders if needed (up to `max_loops`) -7. **Context Preservation**: All conversation history and context is maintained throughout the process - -## 🏗️ Architecture Components - -### Core Components - -| Component | Description | Purpose | -|-----------|-------------|---------| -| **[BoardOfDirectorsSwarm](board_of_directors_swarm.md)** | Main orchestration class | Manages the entire board workflow and agent coordination | -| **[Board Member Roles](board_of_directors_roles.md)** | Role definitions and hierarchy | Defines responsibilities and voting weights for each board member | -| **[Decision Making Process](board_of_directors_decision_making.md)** | Voting and consensus mechanisms | Implements democratic decision-making with weighted voting | -| **[Workflow Management](board_of_directors_workflow.md)** | Process orchestration | Manages the complete lifecycle from task reception to final delivery | - -### Architecture Diagram - -```mermaid -graph TD - A[User Task] --> B[Board of Directors] - B --> C[Board Meeting & Discussion] - C --> D[Voting & Consensus] - D --> E[Create Plan & Orders] - E --> F[Distribute to Agents] - F --> G[Agent 1] - F --> H[Agent 2] - F --> I[Agent N] - G --> J[Execute Task] - H --> J - I --> J - J --> K[Report Results] - K --> L[Board Evaluation] - L --> M{More Loops?} - M -->|Yes| C - M -->|No| N[Final Output] -``` - -## 🎯 Key Features - -### Democratic Decision Making -- **Collective Intelligence**: Multiple perspectives through board member collaboration -- **Weighted Voting**: Different voting weights based on roles and expertise -- **Consensus Building**: Support for both majority voting and consensus approaches -- **Conflict Resolution**: Structured processes for handling disagreements - -### Role-Based Leadership -- **Hierarchical Structure**: Clear roles with defined responsibilities -- **Specialized Expertise**: Each board member brings unique domain knowledge -- **Authority Distribution**: Balanced power distribution across roles -- **Accountability**: Clear lines of responsibility and decision ownership - -### Operational Excellence -- **Iterative Refinement**: Multiple feedback loops for improved results -- **Context Preservation**: Full conversation history maintained -- **Flexible Output Formats**: Support for various output types (dict, str, list) -- **Comprehensive Logging**: Detailed logging for debugging and monitoring - -## 👥 Board Member Roles - -The Board of Directors supports various roles with different responsibilities and voting weights: - -| Role | Description | Voting Weight | Responsibilities | -|------|-------------|---------------|------------------| -| `CHAIRMAN` | Primary leader responsible for board meetings and final decisions | 1.5 | Leading meetings, facilitating consensus, making final decisions | -| `VICE_CHAIRMAN` | Secondary leader who supports the chairman | 1.2 | Supporting chairman, coordinating operations | -| `SECRETARY` | Responsible for documentation and meeting minutes | 1.0 | Documenting meetings, maintaining records | -| `TREASURER` | Manages financial aspects and resource allocation | 1.0 | Financial oversight, resource management | -| `EXECUTIVE_DIRECTOR` | Executive-level board member with operational authority | 1.5 | Strategic planning, operational oversight | -| `MEMBER` | General board member with specific expertise | 1.0 | Contributing expertise, participating in decisions | - -## 🚀 Quick Start - -### Basic Setup - -```python -from swarms import Agent -from swarms.structs.board_of_directors_swarm import ( - BoardOfDirectorsSwarm, - BoardMember, - BoardMemberRole -) -from swarms.config.board_config import enable_board_feature - -# Enable the Board of Directors feature -enable_board_feature() - -# Create board members with specific roles -chairman = Agent( - agent_name="Chairman", - agent_description="Chairman of the Board responsible for leading meetings", - model_name="gpt-4o-mini", - system_prompt="You are the Chairman of the Board..." -) - -vice_chairman = Agent( - agent_name="Vice-Chairman", - agent_description="Vice Chairman who supports the Chairman", - model_name="gpt-4o-mini", - system_prompt="You are the Vice Chairman..." -) - -# Create BoardMember objects with roles and expertise -board_members = [ - BoardMember(chairman, BoardMemberRole.CHAIRMAN, 1.5, ["leadership", "strategy"]), - BoardMember(vice_chairman, BoardMemberRole.VICE_CHAIRMAN, 1.2, ["operations", "coordination"]), -] - -# Create worker agents -research_agent = Agent( - agent_name="Research-Specialist", - agent_description="Expert in market research and analysis", - model_name="gpt-4o", -) - -financial_agent = Agent( - agent_name="Financial-Analyst", - agent_description="Specialist in financial analysis and valuation", - model_name="gpt-4o", -) - -# Initialize the Board of Directors swarm -board_swarm = BoardOfDirectorsSwarm( - name="Executive_Board_Swarm", - description="Executive board with specialized roles for strategic decision-making", - board_members=board_members, - agents=[research_agent, financial_agent], - max_loops=2, - verbose=True, - decision_threshold=0.6, - enable_voting=True, - enable_consensus=True, -) - -# Execute a complex task with democratic decision-making -result = board_swarm.run(task="Analyze the market potential for Tesla (TSLA) stock") -print(result) -``` - -## 📋 Use Cases - -### Corporate Governance -- **Strategic Planning**: Long-term business strategy development -- **Risk Management**: Comprehensive risk assessment and mitigation -- **Resource Allocation**: Optimal distribution of company resources -- **Performance Oversight**: Monitoring and evaluating organizational performance - -### Financial Analysis -- **Portfolio Management**: Investment portfolio optimization and rebalancing -- **Market Analysis**: Comprehensive market research and trend analysis -- **Risk Assessment**: Financial risk evaluation and management -- **Compliance Monitoring**: Regulatory compliance and audit preparation - -### Research & Development -- **Technology Assessment**: Evaluation of emerging technologies -- **Product Development**: Strategic product planning and development -- **Innovation Management**: Managing innovation pipelines and initiatives -- **Quality Assurance**: Ensuring high standards across development processes - -### Project Management -- **Complex Project Planning**: Multi-faceted project strategy development -- **Resource Optimization**: Efficient allocation of project resources -- **Stakeholder Management**: Coordinating diverse stakeholder interests -- **Risk Mitigation**: Identifying and addressing project risks - -## ⚙️ Configuration - -### Decision Thresholds -- **Default Threshold**: 60% consensus required for decisions -- **Configurable**: Adjustable based on organizational needs -- **Role-Based**: Different thresholds for different types of decisions -- **Fallback Mechanisms**: Chairman can make final decisions when consensus fails - -### Voting Mechanisms -- **Weighted Voting**: Different voting weights based on roles -- **Consensus Building**: Support for both majority and consensus approaches -- **Conflict Resolution**: Structured processes for handling disagreements -- **Transparency**: Clear visibility into decision-making processes - -### Operational Settings -- **Meeting Duration**: Configurable board meeting time limits -- **Participation Requirements**: Minimum participation rates for valid decisions -- **Feedback Loops**: Multiple iterations for complex problem-solving -- **Documentation**: Comprehensive record-keeping of all decisions - -## 🔧 Advanced Features - -### Custom Board Templates -Pre-configured board templates for common use cases: - -```python -from swarms.config.board_config import get_default_board_template - -# Get a financial analysis board template -financial_board = get_default_board_template("financial_analysis") - -# Get a strategic planning board template -strategic_board = get_default_board_template("strategic_planning") -``` - -### Dynamic Role Assignment -Automatically assign roles based on task requirements: - -```python -# Board members are automatically assigned roles based on expertise -board_swarm = BoardOfDirectorsSwarm( - board_members=board_members, - auto_assign_roles=True, - role_mapping={ - "financial_analysis": ["Treasurer", "Financial_Member"], - "strategic_planning": ["Chairman", "Executive_Director"] - } -) -``` - -### Consensus Optimization -Advanced consensus-building mechanisms: - -```python -# Enable advanced consensus features -board_swarm = BoardOfDirectorsSwarm( - enable_consensus=True, - consensus_timeout=300, # 5 minutes - min_participation_rate=0.5, # 50% minimum participation - auto_fallback_to_chairman=True -) -``` - -## 📊 Performance Monitoring - -### Decision Metrics -- **Consensus Rate**: Percentage of decisions reached by consensus -- **Voting Participation**: Board member participation rates -- **Decision Quality**: Assessment of decision outcomes -- **Execution Efficiency**: Time from decision to implementation - -### Operational Metrics -- **Meeting Duration**: Average board meeting length -- **Agent Utilization**: How effectively worker agents are utilized -- **Feedback Loop Efficiency**: Speed of iterative improvements -- **Resource Optimization**: Efficient use of computational resources - -## 🛠️ Troubleshooting - -### Common Issues - -1. **Consensus Failures**: Lower decision threshold or increase timeout -2. **Role Conflicts**: Ensure clear role definitions and responsibilities -3. **Agent Coordination**: Verify agent communication and task distribution -4. **Performance Issues**: Monitor resource usage and optimize configurations - -### Best Practices - -1. **Role Clarity**: Define clear responsibilities for each board member -2. **Expertise Alignment**: Match board member expertise to task requirements -3. **Consensus Building**: Allow adequate time for discussion and consensus -4. **Documentation**: Maintain comprehensive records of all decisions -5. **Continuous Improvement**: Learn from each execution cycle - -## 📚 Documentation Sections - -- **[BoardOfDirectorsSwarm](board_of_directors_swarm.md)**: Complete API reference and implementation details -- **[Board Member Roles](board_of_directors_roles.md)**: Detailed role definitions and responsibilities -- **[Decision Making Process](board_of_directors_decision_making.md)**: Voting mechanisms and consensus building -- **[Workflow Management](board_of_directors_workflow.md)**: Complete process orchestration guide - -## 🎯 Success Criteria - -A successful Board of Directors implementation should demonstrate: - -- ✅ **Democratic Decision Making**: All board members contribute to decisions -- ✅ **Consensus Achievement**: Decisions reached through collaborative processes -- ✅ **Role Effectiveness**: Each board member fulfills their responsibilities -- ✅ **Agent Coordination**: Worker agents execute tasks efficiently -- ✅ **Quality Output**: High-quality results through collective intelligence -- ✅ **Process Transparency**: Clear visibility into decision-making processes - ---- - -The Board of Directors architecture represents a sophisticated approach to multi-agent collaboration, enabling organizations to leverage collective intelligence through structured governance and democratic decision-making processes. \ No newline at end of file From 091fc59f6943b39bd220cc75651598f76f831bf5 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:50:25 +0300 Subject: [PATCH 08/73] Add files via upload --- docs/swarms/structs/BoardOfDirectors.md | 903 ++++++++++++++++++++++++ 1 file changed, 903 insertions(+) create mode 100644 docs/swarms/structs/BoardOfDirectors.md diff --git a/docs/swarms/structs/BoardOfDirectors.md b/docs/swarms/structs/BoardOfDirectors.md new file mode 100644 index 00000000..1b1664a9 --- /dev/null +++ b/docs/swarms/structs/BoardOfDirectors.md @@ -0,0 +1,903 @@ +# Board of Directors - Multi-Agent Architecture + +The Board of Directors is a sophisticated multi-agent architecture that implements collective decision-making through democratic processes, voting mechanisms, and role-based leadership. This architecture provides an alternative to single-director patterns by enabling collaborative intelligence through structured governance. + +## 🏛️ Overview + +The Board of Directors architecture follows a democratic workflow pattern: + +1. **Task Reception**: User provides a task to the swarm +2. **Board Meeting**: Board of Directors convenes to discuss and create a plan +3. **Voting & Consensus**: Board members vote and reach consensus on task distribution +4. **Order Distribution**: Board distributes orders to specialized worker agents +5. **Execution**: Individual agents execute their assigned tasks +6. **Feedback Loop**: Board evaluates results and issues new orders if needed (up to `max_loops`) +7. **Context Preservation**: All conversation history and context is maintained throughout the process + +## 🏗️ Architecture Components + +### Core Components + +| Component | Description | Purpose | +|-----------|-------------|---------| +| **BoardOfDirectorsSwarm** | Main orchestration class | Manages the entire board workflow and agent coordination | +| **Board Member Roles** | Role definitions and hierarchy | Defines responsibilities and voting weights for each board member | +| **Decision Making Process** | Voting and consensus mechanisms | Implements democratic decision-making with weighted voting | +| **Workflow Management** | Process orchestration | Manages the complete lifecycle from task reception to final delivery | + +### Board Member Interaction Flow + +```mermaid +sequenceDiagram + participant User + participant Chairman + participant ViceChair + participant Secretary + participant Treasurer + participant ExecDir + participant Agents + + User->>Chairman: Submit Task + Chairman->>ViceChair: Notify Board Meeting + Chairman->>Secretary: Request Meeting Setup + Chairman->>Treasurer: Resource Assessment + Chairman->>ExecDir: Strategic Planning + + Note over Chairman,ExecDir: Board Discussion Phase + + Chairman->>ViceChair: Lead Discussion + ViceChair->>Secretary: Document Decisions + Secretary->>Treasurer: Budget Considerations + Treasurer->>ExecDir: Resource Allocation + ExecDir->>Chairman: Strategic Recommendations + + Note over Chairman,ExecDir: Voting & Consensus + + Chairman->>ViceChair: Call for Vote + ViceChair->>Secretary: Record Votes + Secretary->>Treasurer: Financial Approval + Treasurer->>ExecDir: Resource Approval + ExecDir->>Chairman: Final Decision + + Note over Chairman,Agents: Execution Phase + + Chairman->>Agents: Distribute Orders + Agents->>Chairman: Execute Tasks + Agents->>ViceChair: Progress Reports + Agents->>Secretary: Documentation + Agents->>Treasurer: Resource Usage + Agents->>ExecDir: Strategic Updates + + Note over Chairman,ExecDir: Review & Feedback + + Chairman->>User: Deliver Results +``` + +## 👥 Board Member Roles + +The Board of Directors supports various roles with different responsibilities and voting weights: + +| Role | Description | Voting Weight | Responsibilities | +|------|-------------|---------------|------------------| +| `CHAIRMAN` | Primary leader responsible for board meetings and final decisions | 1.5 | Leading meetings, facilitating consensus, making final decisions | +| `VICE_CHAIRMAN` | Secondary leader who supports the chairman | 1.2 | Supporting chairman, coordinating operations | +| `SECRETARY` | Responsible for documentation and meeting minutes | 1.0 | Documenting meetings, maintaining records | +| `TREASURER` | Manages financial aspects and resource allocation | 1.0 | Financial oversight, resource management | +| `EXECUTIVE_DIRECTOR` | Executive-level board member with operational authority | 1.5 | Strategic planning, operational oversight | +| `MEMBER` | General board member with specific expertise | 1.0 | Contributing expertise, participating in decisions | + +### Role Hierarchy and Authority + +```python +# Example: Role hierarchy implementation +class BoardRoleHierarchy: + def __init__(self): + self.roles = { + "CHAIRMAN": { + "voting_weight": 1.5, + "authority_level": "FINAL", + "supervises": ["VICE_CHAIRMAN", "EXECUTIVE_DIRECTOR", "SECRETARY", "TREASURER", "MEMBER"], + "responsibilities": ["leadership", "final_decision", "consensus_facilitation"], + "override_capability": True + }, + "VICE_CHAIRMAN": { + "voting_weight": 1.2, + "authority_level": "SENIOR", + "supervises": ["MEMBER"], + "responsibilities": ["operational_support", "coordination", "implementation"], + "backup_for": "CHAIRMAN" + }, + "EXECUTIVE_DIRECTOR": { + "voting_weight": 1.5, + "authority_level": "SENIOR", + "supervises": ["MEMBER"], + "responsibilities": ["strategic_planning", "execution_oversight", "performance_management"], + "strategic_authority": True + }, + "SECRETARY": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["documentation", "record_keeping", "communication"], + "administrative_authority": True + }, + "TREASURER": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["financial_oversight", "resource_management", "budget_control"], + "financial_authority": True + }, + "MEMBER": { + "voting_weight": 1.0, + "authority_level": "STANDARD", + "supervises": [], + "responsibilities": ["expertise_contribution", "analysis", "voting"], + "specialized_expertise": True + } + } +``` + +## 🚀 Quick Start + +### Basic Setup + +```python +from swarms import Agent +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, + BoardMember, + BoardMemberRole +) +from swarms.config.board_config import enable_board_feature + +# Enable the Board of Directors feature +enable_board_feature() + +# Create board members with specific roles +chairman = Agent( + agent_name="Chairman", + agent_description="Chairman of the Board responsible for leading meetings", + model_name="gpt-4o-mini", + system_prompt="You are the Chairman of the Board..." +) + +vice_chairman = Agent( + agent_name="Vice-Chairman", + agent_description="Vice Chairman who supports the Chairman", + model_name="gpt-4o-mini", + system_prompt="You are the Vice Chairman..." +) + +# Create BoardMember objects with roles and expertise +board_members = [ + BoardMember(chairman, BoardMemberRole.CHAIRMAN, 1.5, ["leadership", "strategy"]), + BoardMember(vice_chairman, BoardMemberRole.VICE_CHAIRMAN, 1.2, ["operations", "coordination"]), +] + +# Create worker agents +research_agent = Agent( + agent_name="Research-Specialist", + agent_description="Expert in market research and analysis", + model_name="gpt-4o", +) + +financial_agent = Agent( + agent_name="Financial-Analyst", + agent_description="Specialist in financial analysis and valuation", + model_name="gpt-4o", +) + +# Initialize the Board of Directors swarm +board_swarm = BoardOfDirectorsSwarm( + name="Executive_Board_Swarm", + description="Executive board with specialized roles for strategic decision-making", + board_members=board_members, + agents=[research_agent, financial_agent], + max_loops=2, + verbose=True, + decision_threshold=0.6, + enable_voting=True, + enable_consensus=True, +) + +# Execute a complex task with democratic decision-making +result = board_swarm.run(task="Analyze the market potential for Tesla (TSLA) stock") +print(result) +``` + +## 📋 Comprehensive Examples + +### 1. Strategic Investment Analysis + +```python +# Create specialized agents for investment analysis +market_research_agent = Agent( + agent_name="Market-Research-Specialist", + agent_description="Expert in market research, competitive analysis, and industry trends", + model_name="gpt-4o", + system_prompt="""You are a Market Research Specialist. Your responsibilities include: +1. Conducting comprehensive market research and analysis +2. Identifying market trends, opportunities, and risks +3. Analyzing competitive landscape and positioning +4. Providing market size and growth projections +5. Supporting strategic decision-making with research findings + +You should be thorough, analytical, and objective in your research.""" +) + +financial_analyst_agent = Agent( + agent_name="Financial-Analyst", + agent_description="Specialist in financial analysis, valuation, and investment assessment", + model_name="gpt-4o", + system_prompt="""You are a Financial Analyst. Your responsibilities include: +1. Conducting financial analysis and valuation +2. Assessing investment opportunities and risks +3. Analyzing financial performance and metrics +4. Providing financial insights and recommendations +5. Supporting financial decision-making + +You should be financially astute, analytical, and focused on value creation.""" +) + +technical_assessor_agent = Agent( + agent_name="Technical-Assessor", + agent_description="Expert in technical feasibility and implementation assessment", + model_name="gpt-4o", + system_prompt="""You are a Technical Assessor. Your responsibilities include: +1. Evaluating technical feasibility and requirements +2. Assessing implementation challenges and risks +3. Analyzing technology stack and architecture +4. Providing technical insights and recommendations +5. Supporting technical decision-making + +You should be technically proficient, practical, and solution-oriented.""" +) + +# Create comprehensive board members +board_members = [ + BoardMember( + chairman, + BoardMemberRole.CHAIRMAN, + 1.5, + ["leadership", "strategy", "governance", "decision_making"] + ), + BoardMember( + vice_chairman, + BoardMemberRole.VICE_CHAIRMAN, + 1.2, + ["operations", "coordination", "communication", "implementation"] + ), + BoardMember( + secretary, + BoardMemberRole.SECRETARY, + 1.0, + ["documentation", "compliance", "record_keeping", "communication"] + ), + BoardMember( + treasurer, + BoardMemberRole.TREASURER, + 1.0, + ["finance", "budgeting", "risk_management", "resource_allocation"] + ), + BoardMember( + executive_director, + BoardMemberRole.EXECUTIVE_DIRECTOR, + 1.5, + ["strategy", "operations", "innovation", "performance_management"] + ) +] + +# Initialize the investment analysis board +investment_board = BoardOfDirectorsSwarm( + name="Investment_Analysis_Board", + description="Specialized board for investment analysis and decision-making", + board_members=board_members, + agents=[market_research_agent, financial_analyst_agent, technical_assessor_agent], + max_loops=3, + verbose=True, + decision_threshold=0.75, # Higher threshold for investment decisions + enable_voting=True, + enable_consensus=True, + max_workers=3, + output_type="dict" +) + +# Execute investment analysis +investment_task = """ +Analyze the strategic investment opportunity for a $50M Series B funding round in a +fintech startup. Consider market conditions, competitive landscape, financial projections, +technical feasibility, and strategic fit. Provide comprehensive recommendations including: +1. Investment recommendation (proceed/hold/decline) +2. Valuation analysis and suggested terms +3. Risk assessment and mitigation strategies +4. Strategic value and synergies +5. Implementation timeline and milestones +""" + +result = investment_board.run(task=investment_task) +print("Investment Analysis Results:") +print(json.dumps(result, indent=2)) +``` + +### 2. Technology Strategy Development + +```python +# Create technology-focused agents +tech_strategy_agent = Agent( + agent_name="Tech-Strategy-Specialist", + agent_description="Expert in technology strategy and digital transformation", + model_name="gpt-4o", + system_prompt="""You are a Technology Strategy Specialist. Your responsibilities include: +1. Developing technology roadmaps and strategies +2. Assessing digital transformation opportunities +3. Evaluating emerging technologies and trends +4. Planning technology investments and priorities +5. Supporting technology decision-making + +You should be strategic, forward-thinking, and technology-savvy.""" +) + +implementation_planner_agent = Agent( + agent_name="Implementation-Planner", + agent_description="Expert in implementation planning and project management", + model_name="gpt-4o", + system_prompt="""You are an Implementation Planner. Your responsibilities include: +1. Creating detailed implementation plans +2. Assessing resource requirements and timelines +3. Identifying implementation risks and challenges +4. Planning change management strategies +5. Supporting implementation decision-making + +You should be practical, organized, and execution-focused.""" +) + +# Technology strategy board configuration +tech_board = BoardOfDirectorsSwarm( + name="Technology_Strategy_Board", + description="Specialized board for technology strategy and digital transformation", + board_members=board_members, + agents=[tech_strategy_agent, implementation_planner_agent, technical_assessor_agent], + max_loops=4, # More loops for complex technology planning + verbose=True, + decision_threshold=0.7, + enable_voting=True, + enable_consensus=True, + max_workers=3, + output_type="dict" +) + +# Execute technology strategy development +tech_strategy_task = """ +Develop a comprehensive technology strategy for a mid-size manufacturing company +looking to digitize operations and implement Industry 4.0 technologies. Consider: +1. Current technology assessment and gaps +2. Technology roadmap and implementation plan +3. Investment requirements and ROI analysis +4. Risk assessment and mitigation strategies +5. Change management and training requirements +6. Competitive positioning and market advantages +""" + +result = tech_board.run(task=tech_strategy_task) +print("Technology Strategy Results:") +print(json.dumps(result, indent=2)) +``` + +### 3. Crisis Management and Response + +```python +# Create crisis management agents +crisis_coordinator_agent = Agent( + agent_name="Crisis-Coordinator", + agent_description="Expert in crisis management and emergency response", + model_name="gpt-4o", + system_prompt="""You are a Crisis Coordinator. Your responsibilities include: +1. Coordinating crisis response efforts +2. Assessing crisis severity and impact +3. Developing immediate response plans +4. Managing stakeholder communications +5. Supporting crisis decision-making + +You should be calm, decisive, and action-oriented.""" +) + +communications_specialist_agent = Agent( + agent_name="Communications-Specialist", + agent_description="Expert in crisis communications and stakeholder management", + model_name="gpt-4o", + system_prompt="""You are a Communications Specialist. Your responsibilities include: +1. Developing crisis communication strategies +2. Managing stakeholder communications +3. Coordinating public relations efforts +4. Ensuring message consistency and accuracy +5. Supporting communication decision-making + +You should be clear, empathetic, and strategic in communications.""" +) + +# Crisis management board configuration +crisis_board = BoardOfDirectorsSwarm( + name="Crisis_Management_Board", + description="Specialized board for crisis management and emergency response", + board_members=board_members, + agents=[crisis_coordinator_agent, communications_specialist_agent, financial_analyst_agent], + max_loops=2, # Faster response needed + verbose=True, + decision_threshold=0.6, # Lower threshold for urgent decisions + enable_voting=True, + enable_consensus=True, + max_workers=3, + output_type="dict" +) + +# Execute crisis management +crisis_task = """ +Our company is facing a major data breach. Develop an immediate response plan. +Include: +1. Immediate containment and mitigation steps +2. Communication strategy for stakeholders +3. Legal and regulatory compliance requirements +4. Financial impact assessment +5. Long-term recovery and prevention measures +6. Timeline and resource allocation +""" + +result = crisis_board.run(task=crisis_task) +print("Crisis Management Results:") +print(json.dumps(result, indent=2)) +``` + +## ⚙️ Configuration and Parameters + +### BoardOfDirectorsSwarm Parameters + +```python +# Complete parameter reference +board_swarm = BoardOfDirectorsSwarm( + # Basic Configuration + name="Board_Name", # Name of the board + description="Board description", # Description of the board's purpose + + # Board Members and Agents + board_members=board_members, # List of BoardMember objects + agents=worker_agents, # List of worker Agent objects + + # Execution Control + max_loops=3, # Maximum number of refinement loops + max_workers=4, # Maximum parallel workers + + # Decision Making + decision_threshold=0.7, # Consensus threshold (0.0-1.0) + enable_voting=True, # Enable voting mechanisms + enable_consensus=True, # Enable consensus building + + # Advanced Features + auto_assign_roles=True, # Auto-assign roles based on expertise + role_mapping={ # Custom role mapping + "financial_analysis": ["Treasurer", "Financial_Member"], + "strategic_planning": ["Chairman", "Executive_Director"] + }, + + # Consensus Configuration + consensus_timeout=300, # Consensus timeout in seconds + min_participation_rate=0.8, # Minimum participation rate + auto_fallback_to_chairman=True, # Chairman can make final decisions + consensus_rounds=3, # Maximum consensus building rounds + + # Output Configuration + output_type="dict", # Output format: "dict", "str", "list" + verbose=True, # Enable detailed logging + + # Quality Control + quality_threshold=0.8, # Quality threshold for outputs + enable_quality_gates=True, # Enable quality checkpoints + enable_peer_review=True, # Enable peer review mechanisms + + # Performance Optimization + parallel_execution=True, # Enable parallel execution + enable_agent_pooling=True, # Enable agent pooling + timeout_per_agent=300, # Timeout per agent in seconds + + # Monitoring and Logging + enable_logging=True, # Enable detailed logging + log_level="INFO", # Logging level + enable_metrics=True, # Enable performance metrics + enable_tracing=True # Enable request tracing +) +``` + +### Voting Configuration + +```python +# Voting system configuration +voting_config = { + "method": "weighted_majority", # Voting method + "threshold": 0.75, # Consensus threshold + "weights": { # Role-based voting weights + "CHAIRMAN": 1.5, + "VICE_CHAIRMAN": 1.2, + "SECRETARY": 1.0, + "TREASURER": 1.0, + "EXECUTIVE_DIRECTOR": 1.5 + }, + "tie_breaker": "CHAIRMAN", # Tie breaker role + "allow_abstention": True, # Allow board members to abstain + "secret_ballot": False, # Use secret ballot voting + "transparent_process": True # Transparent voting process +} +``` + +### Quality Control Configuration + +```python +# Quality control configuration +quality_config = { + "quality_gates": True, # Enable quality checkpoints + "quality_threshold": 0.8, # Quality threshold + "enable_peer_review": True, # Enable peer review + "review_required": True, # Require peer review + "output_validation": True, # Validate outputs + "enable_metrics_tracking": True, # Track quality metrics + + # Quality metrics + "quality_metrics": { + "completeness": {"weight": 0.2, "threshold": 0.8}, + "accuracy": {"weight": 0.25, "threshold": 0.85}, + "feasibility": {"weight": 0.2, "threshold": 0.8}, + "risk": {"weight": 0.15, "threshold": 0.7}, + "impact": {"weight": 0.2, "threshold": 0.8} + } +} +``` + +## 📊 Performance Monitoring and Analytics + +### Board Performance Metrics + +```python +# Get comprehensive board performance metrics +board_summary = board_swarm.get_board_summary() +print("Board Summary:") +print(f"Board Name: {board_summary['board_name']}") +print(f"Total Board Members: {board_summary['total_members']}") +print(f"Total Worker Agents: {board_summary['total_agents']}") +print(f"Decision Threshold: {board_summary['decision_threshold']}") +print(f"Max Loops: {board_summary['max_loops']}") + +# Display board member details +print("\nBoard Members:") +for member in board_summary['members']: + print(f"- {member['name']} (Role: {member['role']}, Weight: {member['voting_weight']})") + print(f" Expertise: {', '.join(member['expertise_areas'])}") + +# Display worker agent details +print("\nWorker Agents:") +for agent in board_summary['agents']: + print(f"- {agent['name']}: {agent['description']}") +``` + +### Decision Analysis + +```python +# Analyze decision-making patterns +if hasattr(result, 'get') and callable(result.get): + conversation_history = result.get('conversation_history', []) + + print(f"\nDecision Analysis:") + print(f"Total Messages: {len(conversation_history)}") + + # Count board member contributions + board_contributions = {} + for msg in conversation_history: + if 'Board' in msg.get('role', ''): + member_name = msg.get('agent_name', 'Unknown') + board_contributions[member_name] = board_contributions.get(member_name, 0) + 1 + + print(f"Board Member Contributions:") + for member, count in board_contributions.items(): + print(f"- {member}: {count} contributions") + + # Count agent executions + agent_executions = {} + for msg in conversation_history: + if any(agent.agent_name in msg.get('role', '') for agent in worker_agents): + agent_name = msg.get('agent_name', 'Unknown') + agent_executions[agent_name] = agent_executions.get(agent_name, 0) + 1 + + print(f"\nAgent Executions:") + for agent, count in agent_executions.items(): + print(f"- {agent}: {count} executions") +``` + +### Performance Monitoring System + +```python +# Performance monitoring system +class PerformanceMonitor: + def __init__(self): + self.metrics = { + "execution_times": [], + "quality_scores": [], + "consensus_rounds": [], + "error_rates": [] + } + + def track_execution_time(self, phase, duration): + """Track execution time for different phases""" + self.metrics["execution_times"].append({ + "phase": phase, + "duration": duration, + "timestamp": datetime.now().isoformat() + }) + + def track_quality_score(self, score): + """Track quality scores""" + self.metrics["quality_scores"].append({ + "score": score, + "timestamp": datetime.now().isoformat() + }) + + def generate_performance_report(self): + """Generate comprehensive performance report""" + return { + "average_execution_time": self.calculate_average_execution_time(), + "quality_trends": self.analyze_quality_trends(), + "consensus_efficiency": self.analyze_consensus_efficiency(), + "error_analysis": self.analyze_errors(), + "recommendations": self.generate_recommendations() + } + +# Usage example +monitor = PerformanceMonitor() +# ... track metrics during execution ... +report = monitor.generate_performance_report() +print("Performance Report:") +print(json.dumps(report, indent=2)) +``` + +## 🔧 Advanced Features and Customization + +### Custom Board Templates + +```python +from swarms.config.board_config import get_default_board_template + +# Get pre-configured board templates +financial_board = get_default_board_template("financial_analysis") +strategic_board = get_default_board_template("strategic_planning") +tech_board = get_default_board_template("technology_assessment") +crisis_board = get_default_board_template("crisis_management") + +# Custom board template +custom_template = { + "name": "Custom_Board", + "description": "Custom board for specific use case", + "board_members": [ + {"role": "CHAIRMAN", "expertise": ["leadership", "strategy"]}, + {"role": "VICE_CHAIRMAN", "expertise": ["operations", "coordination"]}, + {"role": "SECRETARY", "expertise": ["documentation", "communication"]}, + {"role": "TREASURER", "expertise": ["finance", "budgeting"]}, + {"role": "EXECUTIVE_DIRECTOR", "expertise": ["strategy", "operations"]} + ], + "agents": [ + {"name": "Research_Agent", "expertise": ["research", "analysis"]}, + {"name": "Technical_Agent", "expertise": ["technical", "implementation"]} + ], + "config": { + "max_loops": 3, + "decision_threshold": 0.7, + "enable_voting": True, + "enable_consensus": True + } +} +``` + +### Dynamic Role Assignment + +```python +# Automatically assign roles based on task requirements +board_swarm = BoardOfDirectorsSwarm( + board_members=board_members, + agents=agents, + auto_assign_roles=True, + role_mapping={ + "financial_analysis": ["Treasurer", "Financial_Member"], + "strategic_planning": ["Chairman", "Executive_Director"], + "technical_assessment": ["Technical_Member", "Executive_Director"], + "research_analysis": ["Research_Member", "Secretary"], + "crisis_management": ["Chairman", "Vice_Chairman", "Communications_Member"] + } +) +``` + +### Consensus Optimization + +```python +# Advanced consensus-building mechanisms +board_swarm = BoardOfDirectorsSwarm( + board_members=board_members, + agents=agents, + enable_consensus=True, + consensus_timeout=300, # 5 minutes timeout + min_participation_rate=0.8, # 80% minimum participation + auto_fallback_to_chairman=True, # Chairman can make final decisions + consensus_rounds=3, # Maximum consensus building rounds + consensus_method="weighted_majority", # Consensus method + enable_mediation=True, # Enable mediation for conflicts + mediation_timeout=120 # Mediation timeout in seconds +) +``` + +## 🛠️ Troubleshooting and Debugging + +### Common Issues and Solutions + +1. **Consensus Failures** + - **Issue**: Board cannot reach consensus within loop limit + - **Solution**: Lower voting threshold, increase max_loops, or adjust voting weights + ```python + board_swarm = BoardOfDirectorsSwarm( + decision_threshold=0.6, # Lower threshold + max_loops=5, # More loops + consensus_timeout=600 # Longer timeout + ) + ``` + +2. **Agent Timeout** + - **Issue**: Individual agents take too long to respond + - **Solution**: Increase timeout settings or optimize agent prompts + ```python + board_swarm = BoardOfDirectorsSwarm( + timeout_per_agent=600, # 10 minutes per agent + enable_agent_pooling=True # Use agent pooling + ) + ``` + +3. **Poor Quality Output** + - **Issue**: Final output doesn't meet quality standards + - **Solution**: Enable quality gates, increase max_loops, or improve agent prompts + ```python + board_swarm = BoardOfDirectorsSwarm( + enable_quality_gates=True, + quality_threshold=0.8, + enable_peer_review=True, + max_loops=4 + ) + ``` + +4. **Resource Exhaustion** + - **Issue**: System runs out of resources during execution + - **Solution**: Implement resource limits, use agent pooling, or optimize parallel execution + ```python + board_swarm = BoardOfDirectorsSwarm( + max_workers=2, # Limit parallel workers + enable_agent_pooling=True, + parallel_execution=False # Disable parallel execution + ) + ``` + +### Debugging Techniques + +```python +# Debugging configuration +debug_config = BoardConfig( + max_loops=1, # Limit loops for debugging + enable_logging=True, + log_level="DEBUG", + enable_tracing=True, + debug_mode=True +) + +# Create debug swarm +debug_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=debug_config +) + +# Execute with debugging +try: + result = debug_swarm.run(task) +except Exception as e: + print(f"Error: {e}") + print(f"Debug info: {debug_swarm.get_debug_info()}") + +# Enable detailed logging +import logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Create swarm with logging enabled +logging_swarm = BoardOfDirectorsSwarm( + agents=agents, + config=BoardConfig( + enable_logging=True, + log_level="DEBUG", + enable_metrics=True, + enable_tracing=True + ) +) +``` + +## 📋 Use Cases + +### Corporate Governance +- **Strategic Planning**: Long-term business strategy development +- **Risk Management**: Comprehensive risk assessment and mitigation +- **Resource Allocation**: Optimal distribution of company resources +- **Performance Oversight**: Monitoring and evaluating organizational performance + +### Financial Analysis +- **Portfolio Management**: Investment portfolio optimization and rebalancing +- **Market Analysis**: Comprehensive market research and trend analysis +- **Risk Assessment**: Financial risk evaluation and management +- **Compliance Monitoring**: Regulatory compliance and audit preparation + +### Research & Development +- **Technology Assessment**: Evaluation of emerging technologies +- **Product Development**: Strategic product planning and development +- **Innovation Management**: Managing innovation pipelines and initiatives +- **Quality Assurance**: Ensuring high standards across development processes + +### Project Management +- **Complex Project Planning**: Multi-faceted project strategy development +- **Resource Optimization**: Efficient allocation of project resources +- **Stakeholder Management**: Coordinating diverse stakeholder interests +- **Risk Mitigation**: Identifying and addressing project risks + +### Crisis Management +- **Emergency Response**: Rapid response to critical situations +- **Stakeholder Communication**: Managing communications during crises +- **Recovery Planning**: Developing recovery and prevention strategies +- **Legal Compliance**: Ensuring compliance during crisis situations + +## 🎯 Success Criteria + +A successful Board of Directors implementation should demonstrate: + +- ✅ **Democratic Decision Making**: All board members contribute to decisions +- ✅ **Consensus Achievement**: Decisions reached through collaborative processes +- ✅ **Role Effectiveness**: Each board member fulfills their responsibilities +- ✅ **Agent Coordination**: Worker agents execute tasks efficiently +- ✅ **Quality Output**: High-quality results through collective intelligence +- ✅ **Process Transparency**: Clear visibility into decision-making processes +- ✅ **Performance Optimization**: Efficient resource utilization and execution +- ✅ **Continuous Improvement**: Learning from each execution cycle + +## 📚 Best Practices + +### 1. Role Definition +- Clearly define responsibilities for each board member +- Ensure expertise areas align with organizational needs +- Balance voting weights based on role importance +- Document role interactions and communication protocols + +### 2. Task Formulation +- Provide clear, specific task descriptions +- Include relevant context and constraints +- Specify expected outputs and deliverables +- Define quality criteria and success metrics + +### 3. Consensus Building +- Allow adequate time for discussion and consensus +- Encourage diverse perspectives and viewpoints +- Use structured decision-making processes +- Implement conflict resolution mechanisms + +### 4. Performance Monitoring +- Track decision quality and outcomes +- Monitor board member participation +- Analyze agent utilization and effectiveness +- Implement continuous improvement processes + +### 5. Resource Management +- Optimize agent allocation and utilization +- Implement parallel execution where appropriate +- Monitor resource usage and performance +- Scale resources based on task complexity + +--- + +The Board of Directors architecture represents a sophisticated approach to multi-agent collaboration, enabling organizations to leverage collective intelligence through structured governance and democratic decision-making processes. This comprehensive implementation provides the tools and frameworks needed to build effective, scalable, and intelligent decision-making systems. \ No newline at end of file From 7f4f5481df70fd717660c2b951b5d6e62b77a74d Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:52:30 +0300 Subject: [PATCH 10/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e48e92a7..0b5c13a4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -277,7 +277,7 @@ nav: - Overview: "swarms/structs/overview.md" - Custom Multi Agent Architectures: "swarms/structs/custom_swarm.md" - + - Board of Directors: "swarms/structs/BoardOfDirectors.md" - MajorityVoting: "swarms/structs/majorityvoting.md" - RoundRobin: "swarms/structs/round_robin_swarm.md" - Mixture of Agents: "swarms/structs/moa.md" From 485c42c7aecc1cd9e8f65de86a3c31a6993c2403 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:04:30 +0300 Subject: [PATCH 11/73] Add files via upload --- swarms/structs/board_of_directors_swarm.py | 1144 ++++++++++++++++++++ 1 file changed, 1144 insertions(+) create mode 100644 swarms/structs/board_of_directors_swarm.py diff --git a/swarms/structs/board_of_directors_swarm.py b/swarms/structs/board_of_directors_swarm.py new file mode 100644 index 00000000..a6d24794 --- /dev/null +++ b/swarms/structs/board_of_directors_swarm.py @@ -0,0 +1,1144 @@ +""" +Board of Directors Swarm Implementation + +This module implements a Board of Directors feature as an alternative to the Director feature +in the Swarms Framework. The Board of Directors operates as a collective decision-making body +that can be enabled manually through configuration. + +The implementation follows the Swarms philosophy of: +- Readable code with comprehensive type annotations and documentation +- Performance optimization through concurrency and parallelism +- Simplified abstractions for multi-agent collaboration + +Flow: +1. User provides a task +2. Board of Directors convenes to discuss and create a plan +3. Board distributes orders to agents through voting and consensus +4. Agents execute tasks and report back to the board +5. Board evaluates results and issues new orders if needed (up to max_loops) +6. All context and conversation history is preserved throughout the process +""" + +import asyncio +import json +import os +import re +import traceback +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Callable, Dict, List, Optional, Union, Tuple + +from loguru import logger +from pydantic import BaseModel, Field + +from swarms.structs.agent import Agent +from swarms.structs.base_swarm import BaseSwarm +from swarms.structs.conversation import Conversation +from swarms.structs.ma_utils import list_all_agents +from swarms.utils.history_output_formatter import history_output_formatter +from swarms.utils.loguru_logger import initialize_logger +from swarms.utils.output_types import OutputType + +# Initialize logger for Board of Directors swarm +board_logger = initialize_logger(log_folder="board_of_directors_swarm") + + +class BoardMemberRole(str, Enum): + """Enumeration of possible board member roles. + + This enum defines the various roles that board members can have within + the Board of Directors swarm. Each role has specific responsibilities + and voting weights associated with it. + + Attributes: + CHAIRMAN: Primary leader responsible for board meetings and final decisions + VICE_CHAIRMAN: Secondary leader who supports the chairman + SECRETARY: Responsible for documentation and meeting minutes + TREASURER: Manages financial aspects and resource allocation + MEMBER: General board member with specific expertise + EXECUTIVE_DIRECTOR: Executive-level board member with operational authority + """ + + CHAIRMAN = "chairman" + VICE_CHAIRMAN = "vice_chairman" + SECRETARY = "secretary" + TREASURER = "treasurer" + MEMBER = "member" + EXECUTIVE_DIRECTOR = "executive_director" + + +class BoardDecisionType(str, Enum): + """Enumeration of board decision types. + + This enum defines the different types of decisions that can be made + by the Board of Directors, including voting mechanisms and consensus + approaches. + + Attributes: + UNANIMOUS: All board members agree on the decision + MAJORITY: More than 50% of votes are in favor + CONSENSUS: General agreement without formal voting + CHAIRMAN_DECISION: Final decision made by the chairman + """ + + UNANIMOUS = "unanimous" + MAJORITY = "majority" + CONSENSUS = "consensus" + CHAIRMAN_DECISION = "chairman_decision" + + +@dataclass +class BoardMember: + """ + Represents a member of the Board of Directors. + + This dataclass encapsulates all information about a board member, + including their agent representation, role, voting weight, and + areas of expertise. + + Attributes: + agent: The agent representing this board member + role: The role of this board member within the board + voting_weight: The weight of this member's vote (default: 1.0) + expertise_areas: Areas of expertise for this board member + """ + + agent: Agent + role: BoardMemberRole + voting_weight: float = 1.0 + expertise_areas: List[str] = field(default_factory=list) + + def __post_init__(self) -> None: + """Initialize default values after object creation. + + This method ensures that the expertise_areas list is properly + initialized as an empty list if not provided. + """ + if self.expertise_areas is None: + self.expertise_areas = [] + + +class BoardOrder(BaseModel): + """ + Represents an order issued by the Board of Directors. + + This model defines the structure of orders that the board issues + to worker agents, including task assignments, priorities, and + deadlines. + + Attributes: + agent_name: The name of the agent to which the task is assigned + task: The specific task to be executed by the assigned agent + priority: Priority level of the task (1-5, where 1 is highest) + deadline: Optional deadline for task completion + assigned_by: The board member who assigned this task + """ + + agent_name: str = Field( + ..., + description="Specifies the name of the agent to which the task is assigned.", + ) + task: str = Field( + ..., + description="Defines the specific task to be executed by the assigned agent.", + ) + priority: int = Field( + default=3, + ge=1, + le=5, + description="Priority level of the task (1-5, where 1 is highest priority).", + ) + deadline: Optional[str] = Field( + default=None, + description="Optional deadline for task completion.", + ) + assigned_by: str = Field( + default="Board of Directors", + description="The board member who assigned this task.", + ) + + +class BoardDecision(BaseModel): + """ + Represents a decision made by the Board of Directors. + + This model tracks the details of decisions made by the board, + including voting results, decision types, and reasoning. + + Attributes: + decision_type: The type of decision (unanimous, majority, etc.) + decision: The actual decision made + votes_for: Number of votes in favor + votes_against: Number of votes against + abstentions: Number of abstentions + reasoning: The reasoning behind the decision + """ + + decision_type: BoardDecisionType = Field( + ..., + description="The type of decision made by the board.", + ) + decision: str = Field( + ..., + description="The actual decision made by the board.", + ) + votes_for: int = Field( + default=0, + ge=0, + description="Number of votes in favor of the decision.", + ) + votes_against: int = Field( + default=0, + ge=0, + description="Number of votes against the decision.", + ) + abstentions: int = Field( + default=0, + ge=0, + description="Number of abstentions.", + ) + reasoning: str = Field( + default="", + description="The reasoning behind the decision.", + ) + + +class BoardSpec(BaseModel): + """ + Specification for Board of Directors operations. + + This model represents the complete output of a board meeting, + including the plan, orders, decisions, and meeting summary. + + Attributes: + plan: The overall plan created by the board + orders: List of orders issued by the board + decisions: List of decisions made by the board + meeting_summary: Summary of the board meeting + """ + + plan: str = Field( + ..., + description="Outlines the sequence of actions to be taken by the swarm as decided by the board.", + ) + orders: List[BoardOrder] = Field( + ..., + description="A collection of task assignments to specific agents within the swarm.", + ) + decisions: List[BoardDecision] = Field( + default_factory=list, + description="List of decisions made by the board during the meeting.", + ) + meeting_summary: str = Field( + default="", + description="Summary of the board meeting and key outcomes.", + ) + + +class BoardOfDirectorsSwarm(BaseSwarm): + """ + A hierarchical swarm of agents with a Board of Directors that orchestrates tasks. + + The Board of Directors operates as a collective decision-making body that can be + enabled manually through configuration. It provides an alternative to the single + Director approach with more democratic and collaborative decision-making. + + The workflow follows a hierarchical pattern: + 1. Task is received and sent to the Board of Directors + 2. Board convenes to discuss and create a plan through voting and consensus + 3. Board distributes orders to agents based on collective decisions + 4. Agents execute tasks and report back to the board + 5. Board evaluates results and issues new orders if needed (up to max_loops) + 6. All context and conversation history is preserved throughout the process + + Attributes: + name: The name of the swarm + description: A description of the swarm + board_members: List of board members with their roles and expertise + agents: A list of agents within the swarm + max_loops: The maximum number of feedback loops between the board and agents + output_type: The format in which to return the output (dict, str, or list) + board_model_name: The model name for board member agents + verbose: Enable detailed logging with loguru + add_collaboration_prompt: Add collaboration prompts to agents + board_feedback_on: Enable board feedback on agent outputs + decision_threshold: Threshold for majority decisions (0.0-1.0) + enable_voting: Enable voting mechanisms for board decisions + enable_consensus: Enable consensus-building mechanisms + max_workers: Maximum number of workers for parallel execution + """ + + def __init__( + self, + name: str = "BoardOfDirectorsSwarm", + description: str = "Distributed task swarm with collective decision-making", + board_members: Optional[List[BoardMember]] = None, + agents: Optional[List[Union[Agent, Callable, Any]]] = None, + max_loops: int = 1, + output_type: OutputType = "dict-all-except-first", + board_model_name: str = "gpt-4o-mini", + verbose: bool = False, + add_collaboration_prompt: bool = True, + board_feedback_on: bool = True, + decision_threshold: float = 0.6, + enable_voting: bool = True, + enable_consensus: bool = True, + max_workers: Optional[int] = None, + *args: Any, + **kwargs: Any, + ) -> None: + """ + Initialize the Board of Directors Swarm with the given parameters. + + Args: + name: The name of the swarm + description: A description of the swarm + board_members: List of board members with their roles and expertise + agents: A list of agents within the swarm + max_loops: The maximum number of feedback loops between the board and agents + output_type: The format in which to return the output (dict, str, or list) + board_model_name: The model name for board member agents + verbose: Enable detailed logging with loguru + add_collaboration_prompt: Add collaboration prompts to agents + board_feedback_on: Enable board feedback on agent outputs + decision_threshold: Threshold for majority decisions (0.0-1.0) + enable_voting: Enable voting mechanisms for board decisions + enable_consensus: Enable consensus-building mechanisms + max_workers: Maximum number of workers for parallel execution + *args: Additional positional arguments passed to BaseSwarm + **kwargs: Additional keyword arguments passed to BaseSwarm + + Raises: + ValueError: If critical requirements are not met during initialization + """ + super().__init__( + name=name, + description=description, + agents=agents, + ) + + self.name = name + self.board_members = board_members or [] + self.agents = agents or [] + self.max_loops = max_loops + self.output_type = output_type + self.board_model_name = board_model_name + self.verbose = verbose + self.add_collaboration_prompt = add_collaboration_prompt + self.board_feedback_on = board_feedback_on + self.decision_threshold = decision_threshold + self.enable_voting = enable_voting + self.enable_consensus = enable_consensus + self.max_workers = max_workers or min(32, (os.cpu_count() or 1) + 4) + + # Initialize the swarm + self._init_board_swarm() + + def _init_board_swarm(self) -> None: + """ + Initialize the Board of Directors swarm. + + This method sets up the board members, initializes the conversation, + performs reliability checks, and prepares the board for operation. + + Raises: + ValueError: If reliability checks fail + """ + if self.verbose: + board_logger.info(f"🚀 Initializing Board of Directors Swarm: {self.name}") + board_logger.info(f"📊 Configuration - Max loops: {self.max_loops}") + + self.conversation = Conversation(time_enabled=False) + + # Perform reliability checks + self._perform_reliability_checks() + + # Setup board members if not provided + if not self.board_members: + self._setup_default_board() + + # Add context to board members + self._add_context_to_board() + + if self.verbose: + board_logger.success(f"✅ Board of Directors Swarm initialized successfully: {self.name}") + + def _setup_default_board(self) -> None: + """ + Set up a default Board of Directors if none is provided. + + Creates a basic board structure with Chairman, Vice Chairman, and Secretary roles. + This method is called automatically if no board members are provided during initialization. + """ + if self.verbose: + board_logger.info("🎯 Setting up default Board of Directors") + + # Create default board members + chairman = Agent( + agent_name="Chairman", + agent_description="Chairman of the Board responsible for leading meetings and making final decisions", + model_name=self.board_model_name, + max_loops=1, + system_prompt=self._get_chairman_prompt(), + ) + + vice_chairman = Agent( + agent_name="Vice-Chairman", + agent_description="Vice Chairman who supports the Chairman and leads in their absence", + model_name=self.board_model_name, + max_loops=1, + system_prompt=self._get_vice_chairman_prompt(), + ) + + secretary = Agent( + agent_name="Secretary", + agent_description="Board Secretary responsible for documentation and meeting minutes", + model_name=self.board_model_name, + max_loops=1, + system_prompt=self._get_secretary_prompt(), + ) + + self.board_members = [ + BoardMember(chairman, BoardMemberRole.CHAIRMAN, 1.5, ["leadership", "strategy"]), + BoardMember(vice_chairman, BoardMemberRole.VICE_CHAIRMAN, 1.2, ["operations", "coordination"]), + BoardMember(secretary, BoardMemberRole.SECRETARY, 1.0, ["documentation", "communication"]), + ] + + if self.verbose: + board_logger.success("✅ Default Board of Directors setup completed") + + def _get_chairman_prompt(self) -> str: + """ + Get the system prompt for the Chairman role. + + Returns: + str: The system prompt defining the Chairman's responsibilities and behavior + """ + return """You are the Chairman of the Board of Directors. Your responsibilities include: +1. Leading board meetings and discussions +2. Facilitating consensus among board members +3. Making final decisions when consensus cannot be reached +4. Ensuring all board members have an opportunity to contribute +5. Maintaining focus on the organization's goals and objectives +6. Providing strategic direction and oversight + +You should be diplomatic, fair, and decisive in your leadership.""" + + def _get_vice_chairman_prompt(self) -> str: + """ + Get the system prompt for the Vice Chairman role. + + Returns: + str: The system prompt defining the Vice Chairman's responsibilities and behavior + """ + return """You are the Vice Chairman of the Board of Directors. Your responsibilities include: +1. Supporting the Chairman in leading board meetings +2. Taking leadership when the Chairman is unavailable +3. Coordinating with other board members +4. Ensuring operational efficiency +5. Providing strategic input and analysis +6. Maintaining board cohesion and effectiveness + +You should be collaborative, analytical, and supportive in your role.""" + + def _get_secretary_prompt(self) -> str: + """ + Get the system prompt for the Secretary role. + + Returns: + str: The system prompt defining the Secretary's responsibilities and behavior + """ + return """You are the Secretary of the Board of Directors. Your responsibilities include: +1. Documenting all board meetings and decisions +2. Maintaining accurate records of board proceedings +3. Ensuring proper communication between board members +4. Tracking action items and follow-ups +5. Providing administrative support to the board +6. Ensuring compliance with governance requirements + +You should be thorough, organized, and detail-oriented in your documentation.""" + + def _add_context_to_board(self) -> None: + """ + Add agent context to all board members' conversations. + + This ensures that board members are aware of all available agents + and their capabilities when making decisions. + + Raises: + Exception: If context addition fails + """ + try: + if self.verbose: + board_logger.info("📝 Adding agent context to board members") + + # Add context to each board member + for board_member in self.board_members: + list_all_agents( + agents=self.agents, + conversation=self.conversation, + add_to_conversation=True, + add_collaboration_prompt=self.add_collaboration_prompt, + ) + + if self.verbose: + board_logger.success("✅ Agent context added to board members successfully") + + except Exception as e: + error_msg = f"❌ Failed to add context to board members: {str(e)}" + board_logger.error(f"{error_msg}\n🔍 Traceback: {traceback.format_exc()}") + raise + + def _perform_reliability_checks(self) -> None: + """ + Perform reliability checks for the Board of Directors swarm. + + This method validates critical requirements and configuration + parameters to ensure the swarm can operate correctly. + + Raises: + ValueError: If critical requirements are not met + """ + try: + if self.verbose: + board_logger.info(f"🔍 Running reliability checks for swarm: {self.name}") + + if not self.agents or len(self.agents) == 0: + raise ValueError( + "No agents found in the swarm. At least one agent must be provided to create a Board of Directors swarm." + ) + + if self.max_loops <= 0: + raise ValueError( + "Max loops must be greater than 0. Please set a valid number of loops." + ) + + if self.decision_threshold < 0.0 or self.decision_threshold > 1.0: + raise ValueError( + "Decision threshold must be between 0.0 and 1.0." + ) + + if self.verbose: + board_logger.success(f"✅ Reliability checks passed for swarm: {self.name}") + board_logger.info(f"📊 Swarm stats - Agents: {len(self.agents)}, Max loops: {self.max_loops}") + + except Exception as e: + error_msg = f"❌ Failed reliability checks: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def run_board_meeting( + self, + task: str, + img: Optional[str] = None, + ) -> BoardSpec: + """ + Run a board meeting to discuss and decide on the given task. + + This method orchestrates a complete board meeting, including discussion, + decision-making, and task distribution to worker agents. + + Args: + task: The task to be discussed and planned by the board + img: Optional image to be used with the task + + Returns: + BoardSpec: The board's plan and orders + + Raises: + Exception: If board meeting execution fails + """ + try: + if self.verbose: + board_logger.info(f"🏛️ Running board meeting with task: {task[:100]}...") + + # Create board meeting prompt + meeting_prompt = self._create_board_meeting_prompt(task) + + # Run board discussion + board_discussion = self._conduct_board_discussion(meeting_prompt, img) + + # Parse board decisions + board_spec = self._parse_board_decisions(board_discussion) + + # Add to conversation history + self.conversation.add(role="Board of Directors", content=board_discussion) + + if self.verbose: + board_logger.success("✅ Board meeting completed") + board_logger.debug(f"📋 Board output type: {type(board_spec)}") + + return board_spec + + except Exception as e: + error_msg = f"❌ Failed to run board meeting: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def _create_board_meeting_prompt(self, task: str) -> str: + """ + Create a prompt for the board meeting. + + This method generates a comprehensive prompt that guides the board + through the meeting process, including task discussion, decision-making, + and task distribution. + + Args: + task: The task to be discussed + + Returns: + str: The board meeting prompt + """ + return f"""BOARD OF DIRECTORS MEETING + +TASK: {task} + +CONVERSATION HISTORY: {self.conversation.get_str()} + +AVAILABLE AGENTS: {[agent.agent_name for agent in self.agents]} + +BOARD MEMBERS: +{self._format_board_members_info()} + +INSTRUCTIONS: +1. Discuss the task thoroughly as a board +2. Consider all perspectives and expertise areas +3. Reach consensus or majority decision on the approach +4. Create a detailed plan for task execution +5. Assign specific tasks to appropriate agents +6. Document all decisions and reasoning + +Please provide your response in the following format: +{{ + "plan": "Detailed plan for task execution", + "orders": [ + {{ + "agent_name": "Agent Name", + "task": "Specific task description", + "priority": 1-5, + "deadline": "Optional deadline", + "assigned_by": "Board Member Name" + }} + ], + "decisions": [ + {{ + "decision_type": "unanimous/majority/consensus/chairman_decision", + "decision": "Description of the decision", + "votes_for": 0, + "votes_against": 0, + "abstentions": 0, + "reasoning": "Reasoning behind the decision" + }} + ], + "meeting_summary": "Summary of the board meeting and key outcomes" +}}""" + + def _format_board_members_info(self) -> str: + """ + Format board members information for the prompt. + + This method creates a formatted string containing information about + all board members, their roles, and expertise areas. + + Returns: + str: Formatted board members information + """ + info = [] + for member in self.board_members: + info.append(f"- {member.agent.agent_name} ({member.role.value}): {member.agent.agent_description}") + if member.expertise_areas: + info.append(f" Expertise: {', '.join(member.expertise_areas)}") + return "\n".join(info) + + def _conduct_board_discussion(self, prompt: str, img: Optional[str] = None) -> str: + """ + Conduct the board discussion using the chairman as the primary speaker. + + This method uses the chairman agent to lead the board discussion + and generate the meeting output. + + Args: + prompt: The board meeting prompt + img: Optional image input + + Returns: + str: The board discussion output + + Raises: + ValueError: If no chairman is found in board members + """ + # Use the chairman to lead the discussion + chairman = next((member.agent for member in self.board_members + if member.role == BoardMemberRole.CHAIRMAN), + self.board_members[0].agent if self.board_members else None) + + if not chairman: + raise ValueError("No chairman found in board members") + + return chairman.run(task=prompt, img=img) + + def _parse_board_decisions(self, board_output: str) -> BoardSpec: + """ + Parse the board output into a BoardSpec object. + + This method attempts to parse the board discussion output as JSON + and convert it into a structured BoardSpec object. If parsing fails, + it returns a basic BoardSpec with the raw output. + + Args: + board_output: The output from the board discussion + + Returns: + BoardSpec: Parsed board specification + """ + try: + # Try to parse as JSON first + if isinstance(board_output, str): + # Try to extract JSON from the response + json_match = re.search(r'\{.*\}', board_output, re.DOTALL) + if json_match: + board_output = json_match.group() + + parsed = json.loads(board_output) + else: + parsed = board_output + + # Extract components + plan = parsed.get("plan", "") + orders_data = parsed.get("orders", []) + decisions_data = parsed.get("decisions", []) + meeting_summary = parsed.get("meeting_summary", "") + + # Create BoardOrder objects + orders = [] + for order_data in orders_data: + order = BoardOrder( + agent_name=order_data.get("agent_name", ""), + task=order_data.get("task", ""), + priority=order_data.get("priority", 3), + deadline=order_data.get("deadline"), + assigned_by=order_data.get("assigned_by", "Board of Directors") + ) + orders.append(order) + + # Create BoardDecision objects + decisions = [] + for decision_data in decisions_data: + decision = BoardDecision( + decision_type=BoardDecisionType(decision_data.get("decision_type", "consensus")), + decision=decision_data.get("decision", ""), + votes_for=decision_data.get("votes_for", 0), + votes_against=decision_data.get("votes_against", 0), + abstentions=decision_data.get("abstentions", 0), + reasoning=decision_data.get("reasoning", "") + ) + decisions.append(decision) + + return BoardSpec( + plan=plan, + orders=orders, + decisions=decisions, + meeting_summary=meeting_summary + ) + + except Exception as e: + board_logger.error(f"Failed to parse board decisions: {str(e)}") + # Return a basic BoardSpec if parsing fails + return BoardSpec( + plan=board_output, + orders=[], + decisions=[], + meeting_summary="Parsing failed, using raw output" + ) + + def step(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + """ + Execute a single step of the Board of Directors swarm. + + This method runs one complete cycle of board meeting and task execution. + It includes board discussion, task distribution, and optional feedback. + + Args: + task: The task to be executed + img: Optional image input + *args: Additional positional arguments + **kwargs: Additional keyword arguments + + Returns: + Any: The result of the step execution + + Raises: + Exception: If step execution fails + """ + try: + if self.verbose: + board_logger.info(f"👣 Executing single step for task: {task[:100]}...") + + # Run board meeting + board_spec = self.run_board_meeting(task=task, img=img) + + if self.verbose: + board_logger.info(f"📋 Board created plan and {len(board_spec.orders)} orders") + + # Execute the orders + outputs = self._execute_orders(board_spec.orders) + + if self.verbose: + board_logger.info(f"⚡ Executed {len(outputs)} orders") + + # Provide board feedback if enabled + if self.board_feedback_on: + feedback = self._generate_board_feedback(outputs) + else: + feedback = outputs + + if self.verbose: + board_logger.success("✅ Step completed successfully") + + return feedback + + except Exception as e: + error_msg = f"❌ Failed to execute step: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def run(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + """ + Run the Board of Directors swarm for the specified number of loops. + + This method executes the complete swarm workflow, including multiple + iterations if max_loops is greater than 1. Each iteration includes + board meeting, task execution, and feedback generation. + + Args: + task: The task to be executed + img: Optional image input + *args: Additional positional arguments + **kwargs: Additional keyword arguments + + Returns: + Any: The final result of the swarm execution + + Raises: + Exception: If swarm execution fails + """ + try: + if self.verbose: + board_logger.info(f"🏛️ Starting Board of Directors swarm execution: {self.name}") + board_logger.info(f"📋 Task: {task[:100]}...") + + current_loop = 0 + while current_loop < self.max_loops: + if self.verbose: + board_logger.info(f"🔄 Executing loop {current_loop + 1}/{self.max_loops}") + + # Execute step + result = self.step(task=task, img=img, *args, **kwargs) + + # Add to conversation + self.conversation.add(role="System", content=f"Loop {current_loop + 1} completed") + + current_loop += 1 + + if self.verbose: + board_logger.success(f"🎉 Board of Directors swarm run completed: {self.name}") + board_logger.info(f"📊 Total loops executed: {current_loop}") + + return history_output_formatter( + conversation=self.conversation, type=self.output_type + ) + + except Exception as e: + error_msg = f"❌ Failed to run Board of Directors swarm: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + async def arun(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + """ + Run the Board of Directors swarm asynchronously. + + This method provides an asynchronous interface for running the swarm, + allowing for non-blocking execution in async contexts. + + Args: + task: The task to be executed + img: Optional image input + *args: Additional positional arguments + **kwargs: Additional keyword arguments + + Returns: + Any: The final result of the swarm execution + """ + loop = asyncio.get_event_loop() + result = await loop.run_in_executor( + None, self.run, task, img, *args, **kwargs + ) + return result + + def _generate_board_feedback(self, outputs: List[Any]) -> str: + """ + Provide feedback from the Board of Directors based on agent outputs. + + This method uses the chairman to review and provide feedback on + the outputs generated by worker agents. + + Args: + outputs: List of outputs from agents + + Returns: + str: Board feedback on the outputs + + Raises: + ValueError: If no chairman is found for feedback + Exception: If feedback generation fails + """ + try: + if self.verbose: + board_logger.info("📝 Generating board feedback") + + task = f"History: {self.conversation.get_str()} \n\n" + + # Use the chairman for feedback + chairman = next((member.agent for member in self.board_members + if member.role == BoardMemberRole.CHAIRMAN), + self.board_members[0].agent if self.board_members else None) + + if not chairman: + raise ValueError("No chairman found for feedback") + + feedback_prompt = ( + "You are the Chairman of the Board. Review the outputs generated by all the worker agents " + "in the previous step. Provide specific, actionable feedback for each agent, highlighting " + "strengths, weaknesses, and concrete suggestions for improvement. " + "If any outputs are unclear, incomplete, or could be enhanced, explain exactly how. " + f"Your feedback should help the agents refine their work in the next iteration. " + f"Worker Agent Responses: {task}" + ) + + output = chairman.run(task=feedback_prompt) + self.conversation.add(role=chairman.agent_name, content=output) + + if self.verbose: + board_logger.success("✅ Board feedback generated successfully") + + return output + + except Exception as e: + error_msg = f"❌ Failed to generate board feedback: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def _call_single_agent( + self, + agent_name: str, + task: str, + *args: Any, + **kwargs: Any + ) -> Any: + """ + Call a single agent with the given task. + + This method finds and executes a specific agent with the provided task. + It includes error handling and logging for agent execution. + + Args: + agent_name: The name of the agent to call + task: The task to assign to the agent + *args: Additional positional arguments + **kwargs: Additional keyword arguments + + Returns: + Any: The output from the agent + + Raises: + ValueError: If the specified agent is not found + Exception: If agent execution fails + """ + try: + if self.verbose: + board_logger.info(f"📞 Calling agent: {agent_name}") + + # Find agent by name + agent = None + for a in self.agents: + if hasattr(a, "agent_name") and a.agent_name == agent_name: + agent = a + break + + if agent is None: + available_agents = [ + a.agent_name for a in self.agents if hasattr(a, "agent_name") + ] + raise ValueError( + f"Agent '{agent_name}' not found in swarm. Available agents: {available_agents}" + ) + + output = agent.run( + task=f"History: {self.conversation.get_str()} \n\n Task: {task}", + *args, + **kwargs, + ) + self.conversation.add(role=agent_name, content=output) + + if self.verbose: + board_logger.success(f"✅ Agent {agent_name} completed task successfully") + + return output + + except Exception as e: + error_msg = f"❌ Failed to call agent {agent_name}: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def _execute_orders(self, orders: List[BoardOrder]) -> List[Dict[str, Any]]: + """ + Execute the orders issued by the Board of Directors. + + This method uses ThreadPoolExecutor to execute multiple orders in parallel, + improving performance for complex task distributions. + + Args: + orders: List of board orders to execute + + Returns: + List[Dict[str, Any]]: List of outputs from executed orders + + Raises: + Exception: If order execution fails + """ + try: + if self.verbose: + board_logger.info(f"⚡ Executing {len(orders)} board orders") + + # Use ThreadPoolExecutor for parallel execution + outputs = [] + with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + # Submit all orders for execution + future_to_order = { + executor.submit(self._execute_single_order, order): order + for order in orders + } + + # Collect results as they complete + for future in as_completed(future_to_order): + order = future_to_order[future] + try: + output = future.result() + outputs.append({ + "agent_name": order.agent_name, + "task": order.task, + "output": output, + "priority": order.priority, + "assigned_by": order.assigned_by, + }) + except Exception as e: + board_logger.error(f"Failed to execute order for {order.agent_name}: {str(e)}") + outputs.append({ + "agent_name": order.agent_name, + "task": order.task, + "output": f"Error: {str(e)}", + "priority": order.priority, + "assigned_by": order.assigned_by, + }) + + if self.verbose: + board_logger.success(f"✅ Executed {len(outputs)} orders successfully") + + return outputs + + except Exception as e: + error_msg = f"❌ Failed to execute orders: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" + board_logger.error(error_msg) + raise + + def _execute_single_order(self, order: BoardOrder) -> Any: + """ + Execute a single board order. + + This method is a wrapper around _call_single_agent for executing + individual board orders. + + Args: + order: The board order to execute + + Returns: + Any: The output from the executed order + """ + return self._call_single_agent( + agent_name=order.agent_name, + task=order.task, + ) + + def add_board_member(self, board_member: BoardMember) -> None: + """ + Add a new member to the Board of Directors. + + This method allows dynamic addition of board members after swarm initialization. + + Args: + board_member: The board member to add + """ + self.board_members.append(board_member) + if self.verbose: + board_logger.info(f"✅ Added board member: {board_member.agent.agent_name}") + + def remove_board_member(self, agent_name: str) -> None: + """ + Remove a board member by agent name. + + This method allows dynamic removal of board members after swarm initialization. + + Args: + agent_name: The name of the agent to remove from the board + """ + self.board_members = [ + member for member in self.board_members + if member.agent.agent_name != agent_name + ] + if self.verbose: + board_logger.info(f"✅ Removed board member: {agent_name}") + + def get_board_member(self, agent_name: str) -> Optional[BoardMember]: + """ + Get a board member by agent name. + + This method retrieves a specific board member by their agent name. + + Args: + agent_name: The name of the agent + + Returns: + Optional[BoardMember]: The board member if found, None otherwise + """ + for member in self.board_members: + if member.agent.agent_name == agent_name: + return member + return None + + def get_board_summary(self) -> Dict[str, Any]: + """ + Get a summary of the Board of Directors. + + This method provides a comprehensive summary of the board structure, + including member information, configuration, and statistics. + + Returns: + Dict[str, Any]: Summary of the board structure and members + """ + return { + "board_name": self.name, + "total_members": len(self.board_members), + "members": [ + { + "name": member.agent.agent_name, + "role": member.role.value, + "voting_weight": member.voting_weight, + "expertise_areas": member.expertise_areas, + } + for member in self.board_members + ], + "total_agents": len(self.agents), + "max_loops": self.max_loops, + "decision_threshold": self.decision_threshold, + } \ No newline at end of file From cb0ca8f2a708274d909e362578ebe940d3f9d5af Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:06:32 +0300 Subject: [PATCH 12/73] Add files via upload --- swarms/config/board_config.py | 596 ++++++++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 swarms/config/board_config.py diff --git a/swarms/config/board_config.py b/swarms/config/board_config.py new file mode 100644 index 00000000..8b29c0ae --- /dev/null +++ b/swarms/config/board_config.py @@ -0,0 +1,596 @@ +""" +Board of Directors Configuration Module + +This module provides configuration management for the Board of Directors feature +in the Swarms Framework. It allows users to enable and configure the Board of +Directors feature manually through environment variables or configuration files. + +The implementation follows the Swarms philosophy of: +- Readable code with comprehensive type annotations and documentation +- Performance optimization through caching and efficient loading +- Simplified abstractions for configuration management +""" + +import os +from typing import Dict, Any, Optional, List, Union +from dataclasses import dataclass, field +from enum import Enum +from pathlib import Path +from functools import lru_cache + +from pydantic import BaseModel, Field +from loguru import logger + + +class BoardFeatureStatus(str, Enum): + """Enumeration of Board of Directors feature status. + + This enum defines the possible states of the Board of Directors feature + within the Swarms Framework. + + Attributes: + ENABLED: Feature is explicitly enabled + DISABLED: Feature is explicitly disabled + AUTO: Feature state is determined automatically + """ + + ENABLED = "enabled" + DISABLED = "disabled" + AUTO = "auto" + + +class BoardConfigModel(BaseModel): + """ + Configuration model for Board of Directors feature. + + This model defines all configurable parameters for the Board of Directors + feature, including feature status, board composition, and operational settings. + + Attributes: + board_feature_enabled: Whether the Board of Directors feature is enabled globally + default_board_size: Default number of board members when creating a new board + decision_threshold: Threshold for majority decisions (0.0-1.0) + enable_voting: Enable voting mechanisms for board decisions + enable_consensus: Enable consensus-building mechanisms + default_board_model: Default model for board member agents + verbose_logging: Enable verbose logging for board operations + max_board_meeting_duration: Maximum duration for board meetings in seconds + auto_fallback_to_director: Automatically fall back to Director mode if Board fails + custom_board_templates: Custom board templates for different use cases + """ + + # Feature control + board_feature_enabled: bool = Field( + default=False, + description="Whether the Board of Directors feature is enabled globally." + ) + + # Board composition + default_board_size: int = Field( + default=3, + ge=1, + le=10, + description="Default number of board members when creating a new board." + ) + + # Operational settings + decision_threshold: float = Field( + default=0.6, + ge=0.0, + le=1.0, + description="Threshold for majority decisions (0.0-1.0)." + ) + + enable_voting: bool = Field( + default=True, + description="Enable voting mechanisms for board decisions." + ) + + enable_consensus: bool = Field( + default=True, + description="Enable consensus-building mechanisms." + ) + + # Model settings + default_board_model: str = Field( + default="gpt-4o-mini", + description="Default model for board member agents." + ) + + # Logging and monitoring + verbose_logging: bool = Field( + default=False, + description="Enable verbose logging for board operations." + ) + + # Performance settings + max_board_meeting_duration: int = Field( + default=300, + ge=60, + le=3600, + description="Maximum duration for board meetings in seconds." + ) + + # Integration settings + auto_fallback_to_director: bool = Field( + default=True, + description="Automatically fall back to Director mode if Board fails." + ) + + # Custom board templates + custom_board_templates: Dict[str, Dict[str, Any]] = Field( + default_factory=dict, + description="Custom board templates for different use cases." + ) + + +@dataclass +class BoardConfig: + """ + Board of Directors configuration manager. + + This class manages the configuration for the Board of Directors feature, + including loading from environment variables, configuration files, and + providing default values. + + Attributes: + config_file_path: Optional path to configuration file + config_data: Optional configuration data dictionary + config: The current configuration model instance + """ + + config_file_path: Optional[str] = None + config_data: Optional[Dict[str, Any]] = None + config: BoardConfigModel = field(init=False) + + def __post_init__(self) -> None: + """Initialize the configuration after object creation.""" + self._load_config() + + def _load_config(self) -> None: + """ + Load configuration from various sources. + + Priority order: + 1. Environment variables + 2. Configuration file + 3. Default values + + Raises: + Exception: If configuration loading fails + """ + try: + # Start with default configuration + self.config = BoardConfigModel() + + # Load from configuration file if specified + if self.config_file_path and os.path.exists(self.config_file_path): + self._load_from_file() + + # Override with environment variables + self._load_from_environment() + + # Override with explicit config data + if self.config_data: + self._load_from_dict(self.config_data) + + except Exception as e: + logger.error(f"Failed to load Board of Directors configuration: {str(e)}") + raise + + def _load_from_file(self) -> None: + """ + Load configuration from file. + + Raises: + Exception: If file loading fails + """ + try: + import yaml + with open(self.config_file_path, 'r') as f: + file_config = yaml.safe_load(f) + self._load_from_dict(file_config) + logger.info(f"Loaded Board of Directors config from: {self.config_file_path}") + except Exception as e: + logger.warning(f"Failed to load config file {self.config_file_path}: {e}") + raise + + def _load_from_environment(self) -> None: + """ + Load configuration from environment variables. + + This method maps environment variables to configuration parameters + and handles type conversion appropriately. + """ + env_mappings = { + 'SWARMS_BOARD_FEATURE_ENABLED': 'board_feature_enabled', + 'SWARMS_BOARD_DEFAULT_SIZE': 'default_board_size', + 'SWARMS_BOARD_DECISION_THRESHOLD': 'decision_threshold', + 'SWARMS_BOARD_ENABLE_VOTING': 'enable_voting', + 'SWARMS_BOARD_ENABLE_CONSENSUS': 'enable_consensus', + 'SWARMS_BOARD_DEFAULT_MODEL': 'default_board_model', + 'SWARMS_BOARD_VERBOSE_LOGGING': 'verbose_logging', + 'SWARMS_BOARD_MAX_MEETING_DURATION': 'max_board_meeting_duration', + 'SWARMS_BOARD_AUTO_FALLBACK': 'auto_fallback_to_director', + } + + for env_var, config_key in env_mappings.items(): + value = os.getenv(env_var) + if value is not None: + try: + # Convert string values to appropriate types + if config_key in ['board_feature_enabled', 'enable_voting', 'enable_consensus', 'verbose_logging', 'auto_fallback_to_director']: + converted_value = value.lower() in ['true', '1', 'yes', 'on'] + elif config_key in ['default_board_size', 'max_board_meeting_duration']: + converted_value = int(value) + elif config_key in ['decision_threshold']: + converted_value = float(value) + else: + converted_value = value + + setattr(self.config, config_key, converted_value) + logger.debug(f"Loaded {config_key} from environment: {converted_value}") + except (ValueError, TypeError) as e: + logger.warning(f"Failed to parse environment variable {env_var}: {e}") + + def _load_from_dict(self, config_dict: Dict[str, Any]) -> None: + """ + Load configuration from dictionary. + + Args: + config_dict: Dictionary containing configuration values + + Raises: + ValueError: If configuration values are invalid + """ + for key, value in config_dict.items(): + if hasattr(self.config, key): + try: + setattr(self.config, key, value) + except (ValueError, TypeError) as e: + logger.warning(f"Failed to set config {key}: {e}") + raise ValueError(f"Invalid configuration value for {key}: {e}") + + def is_enabled(self) -> bool: + """ + Check if the Board of Directors feature is enabled. + + Returns: + bool: True if the feature is enabled, False otherwise + """ + return self.config.board_feature_enabled + + def get_config(self) -> BoardConfigModel: + """ + Get the current configuration. + + Returns: + BoardConfigModel: The current configuration + """ + return self.config + + def update_config(self, updates: Dict[str, Any]) -> None: + """ + Update the configuration with new values. + + Args: + updates: Dictionary of configuration updates + + Raises: + ValueError: If any update values are invalid + """ + try: + self._load_from_dict(updates) + except ValueError as e: + logger.error(f"Failed to update configuration: {e}") + raise + + def save_config(self, file_path: Optional[str] = None) -> None: + """ + Save the current configuration to a file. + + Args: + file_path: Optional file path to save to (uses config_file_path if not provided) + + Raises: + Exception: If saving fails + """ + save_path = file_path or self.config_file_path + if not save_path: + logger.warning("No file path specified for saving configuration") + return + + try: + import yaml + # Convert config to dictionary + config_dict = self.config.model_dump() + + # Ensure directory exists + os.makedirs(os.path.dirname(save_path), exist_ok=True) + + with open(save_path, 'w') as f: + yaml.dump(config_dict, f, default_flow_style=False, indent=2) + + logger.info(f"Saved Board of Directors config to: {save_path}") + except Exception as e: + logger.error(f"Failed to save config to {save_path}: {e}") + raise + + @lru_cache(maxsize=128) + def get_default_board_template(self, template_name: str = "standard") -> Dict[str, Any]: + """ + Get a default board template. + + This method provides predefined board templates for common use cases. + Templates are cached for improved performance. + + Args: + template_name: Name of the template to retrieve + + Returns: + Dict[str, Any]: Board template configuration + """ + templates = { + "standard": { + "roles": [ + {"name": "Chairman", "weight": 1.5, "expertise": ["leadership", "strategy"]}, + {"name": "Vice-Chairman", "weight": 1.2, "expertise": ["operations", "coordination"]}, + {"name": "Secretary", "weight": 1.0, "expertise": ["documentation", "communication"]}, + ] + }, + "executive": { + "roles": [ + {"name": "CEO", "weight": 2.0, "expertise": ["executive_leadership", "strategy"]}, + {"name": "CFO", "weight": 1.5, "expertise": ["finance", "risk_management"]}, + {"name": "CTO", "weight": 1.5, "expertise": ["technology", "innovation"]}, + {"name": "COO", "weight": 1.3, "expertise": ["operations", "efficiency"]}, + ] + }, + "advisory": { + "roles": [ + {"name": "Lead_Advisor", "weight": 1.3, "expertise": ["strategy", "consulting"]}, + {"name": "Technical_Advisor", "weight": 1.2, "expertise": ["technology", "architecture"]}, + {"name": "Business_Advisor", "weight": 1.2, "expertise": ["business", "market_analysis"]}, + {"name": "Legal_Advisor", "weight": 1.1, "expertise": ["legal", "compliance"]}, + ] + }, + "minimal": { + "roles": [ + {"name": "Chairman", "weight": 1.0, "expertise": ["leadership"]}, + {"name": "Member", "weight": 1.0, "expertise": ["general"]}, + ] + } + } + + # Check custom templates first + if template_name in self.config.custom_board_templates: + return self.config.custom_board_templates[template_name] + + # Return standard template if requested template not found + return templates.get(template_name, templates["standard"]) + + def validate_config(self) -> List[str]: + """ + Validate the current configuration. + + This method performs comprehensive validation of the configuration + to ensure all values are within acceptable ranges and constraints. + + Returns: + List[str]: List of validation errors (empty if valid) + """ + errors = [] + + try: + # Validate the configuration model + self.config.model_validate(self.config.model_dump()) + except Exception as e: + errors.append(f"Configuration validation failed: {e}") + + # Additional custom validations + if self.config.decision_threshold < 0.5: + errors.append("Decision threshold should be at least 0.5 for meaningful majority decisions") + + if self.config.default_board_size < 2: + errors.append("Board size should be at least 2 for meaningful discussions") + + if self.config.max_board_meeting_duration < 60: + errors.append("Board meeting duration should be at least 60 seconds") + + return errors + + +# Global configuration instance +_board_config: Optional[BoardConfig] = None + + +@lru_cache(maxsize=1) +def get_board_config(config_file_path: Optional[str] = None) -> BoardConfig: + """ + Get the global Board of Directors configuration instance. + + This function provides a singleton pattern for accessing the Board of Directors + configuration. The configuration is cached for improved performance. + + Args: + config_file_path: Optional path to configuration file + + Returns: + BoardConfig: The global configuration instance + """ + global _board_config + + if _board_config is None: + _board_config = BoardConfig(config_file_path=config_file_path) + + return _board_config + + +def enable_board_feature(config_file_path: Optional[str] = None) -> None: + """ + Enable the Board of Directors feature globally. + + This function enables the Board of Directors feature and saves the configuration + to the specified file path. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"board_feature_enabled": True}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Board of Directors feature enabled") + + +def disable_board_feature(config_file_path: Optional[str] = None) -> None: + """ + Disable the Board of Directors feature globally. + + This function disables the Board of Directors feature and saves the configuration + to the specified file path. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"board_feature_enabled": False}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Board of Directors feature disabled") + + +def is_board_feature_enabled(config_file_path: Optional[str] = None) -> bool: + """ + Check if the Board of Directors feature is enabled. + + Args: + config_file_path: Optional path to configuration file + + Returns: + bool: True if the feature is enabled, False otherwise + """ + config = get_board_config(config_file_path) + return config.is_enabled() + + +def create_default_config_file(file_path: str = "swarms_board_config.yaml") -> None: + """ + Create a default configuration file. + + This function creates a default Board of Directors configuration file + with recommended settings. + + Args: + file_path: Path where to create the configuration file + """ + default_config = { + "board_feature_enabled": False, + "default_board_size": 3, + "decision_threshold": 0.6, + "enable_voting": True, + "enable_consensus": True, + "default_board_model": "gpt-4o-mini", + "verbose_logging": False, + "max_board_meeting_duration": 300, + "auto_fallback_to_director": True, + "custom_board_templates": {} + } + + config = BoardConfig(config_file_path=file_path, config_data=default_config) + config.save_config(file_path) + + logger.info(f"Created default Board of Directors config file: {file_path}") + + +def set_board_size(size: int, config_file_path: Optional[str] = None) -> None: + """ + Set the default board size. + + Args: + size: The default board size (1-10) + config_file_path: Optional path to save the configuration + """ + if not 1 <= size <= 10: + raise ValueError("Board size must be between 1 and 10") + + config = get_board_config(config_file_path) + config.update_config({"default_board_size": size}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Default board size set to: {size}") + + +def set_decision_threshold(threshold: float, config_file_path: Optional[str] = None) -> None: + """ + Set the decision threshold for majority decisions. + + Args: + threshold: The decision threshold (0.0-1.0) + config_file_path: Optional path to save the configuration + """ + if not 0.0 <= threshold <= 1.0: + raise ValueError("Decision threshold must be between 0.0 and 1.0") + + config = get_board_config(config_file_path) + config.update_config({"decision_threshold": threshold}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Decision threshold set to: {threshold}") + + +def set_board_model(model: str, config_file_path: Optional[str] = None) -> None: + """ + Set the default board model. + + Args: + model: The default model name for board members + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"default_board_model": model}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Default board model set to: {model}") + + +def enable_verbose_logging(config_file_path: Optional[str] = None) -> None: + """ + Enable verbose logging for board operations. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"verbose_logging": True}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Verbose logging enabled for Board of Directors") + + +def disable_verbose_logging(config_file_path: Optional[str] = None) -> None: + """ + Disable verbose logging for board operations. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"verbose_logging": False}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Verbose logging disabled for Board of Directors") \ No newline at end of file From b207a879614945bf71942420ce9eaef6ea7ba142 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:07:39 +0300 Subject: [PATCH 13/73] Add files via upload --- .../structs/test_board_of_directors_swarm.py | 1020 +++++++++++++++++ 1 file changed, 1020 insertions(+) create mode 100644 tests/structs/test_board_of_directors_swarm.py diff --git a/tests/structs/test_board_of_directors_swarm.py b/tests/structs/test_board_of_directors_swarm.py new file mode 100644 index 00000000..b87e563c --- /dev/null +++ b/tests/structs/test_board_of_directors_swarm.py @@ -0,0 +1,1020 @@ +""" +Comprehensive test suite for Board of Directors Swarm. + +This module contains extensive tests for the Board of Directors swarm implementation, +covering all aspects including initialization, board operations, task execution, +error handling, and performance characteristics. + +The test suite follows the Swarms testing philosophy: +- Comprehensive coverage of all functionality +- Proper mocking and isolation +- Performance and integration testing +- Error handling validation +""" + +import os +import pytest +import asyncio +from unittest.mock import Mock, patch, MagicMock, AsyncMock +from typing import List, Dict, Any, Optional + +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, + BoardMember, + BoardMemberRole, + BoardDecisionType, + BoardOrder, + BoardDecision, + BoardSpec, +) +from swarms.structs.agent import Agent +from swarms.structs.conversation import Conversation + + +# Test fixtures +@pytest.fixture +def mock_agent(): + """Create a mock agent for testing.""" + agent = Mock(spec=Agent) + agent.agent_name = "TestAgent" + agent.agent_description = "A test agent for unit testing" + agent.run = Mock(return_value="Test agent response") + agent.arun = AsyncMock(return_value="Async test agent response") + return agent + + +@pytest.fixture +def mock_board_member(mock_agent): + """Create a mock board member for testing.""" + return BoardMember( + agent=mock_agent, + role=BoardMemberRole.CHAIRMAN, + voting_weight=1.5, + expertise_areas=["leadership", "strategy"] + ) + + +@pytest.fixture +def sample_agents(): + """Create sample agents for testing.""" + agents = [] + for i in range(3): + agent = Mock(spec=Agent) + agent.agent_name = f"Agent{i+1}" + agent.agent_description = f"Test agent {i+1}" + agent.run = Mock(return_value=f"Response from Agent{i+1}") + agents.append(agent) + return agents + + +@pytest.fixture +def sample_board_members(sample_agents): + """Create sample board members for testing.""" + roles = [BoardMemberRole.CHAIRMAN, BoardMemberRole.VICE_CHAIRMAN, BoardMemberRole.SECRETARY] + board_members = [] + + for i, (agent, role) in enumerate(zip(sample_agents, roles)): + board_member = BoardMember( + agent=agent, + role=role, + voting_weight=1.0 + (i * 0.2), + expertise_areas=[f"expertise_{i+1}"] + ) + board_members.append(board_member) + + return board_members + + +@pytest.fixture +def basic_board_swarm(sample_agents): + """Create a basic Board of Directors swarm for testing.""" + return BoardOfDirectorsSwarm( + name="TestBoard", + agents=sample_agents, + verbose=False, + max_loops=1 + ) + + +@pytest.fixture +def configured_board_swarm(sample_agents, sample_board_members): + """Create a configured Board of Directors swarm for testing.""" + return BoardOfDirectorsSwarm( + name="ConfiguredBoard", + description="A configured board for testing", + board_members=sample_board_members, + agents=sample_agents, + max_loops=2, + verbose=True, + decision_threshold=0.7, + enable_voting=True, + enable_consensus=True, + max_workers=4 + ) + + +# Unit tests for enums and data models +class TestBoardMemberRole: + """Test BoardMemberRole enum.""" + + def test_enum_values(self): + """Test that all enum values are correctly defined.""" + assert BoardMemberRole.CHAIRMAN == "chairman" + assert BoardMemberRole.VICE_CHAIRMAN == "vice_chairman" + assert BoardMemberRole.SECRETARY == "secretary" + assert BoardMemberRole.TREASURER == "treasurer" + assert BoardMemberRole.MEMBER == "member" + assert BoardMemberRole.EXECUTIVE_DIRECTOR == "executive_director" + + +class TestBoardDecisionType: + """Test BoardDecisionType enum.""" + + def test_enum_values(self): + """Test that all enum values are correctly defined.""" + assert BoardDecisionType.UNANIMOUS == "unanimous" + assert BoardDecisionType.MAJORITY == "majority" + assert BoardDecisionType.CONSENSUS == "consensus" + assert BoardDecisionType.CHAIRMAN_DECISION == "chairman_decision" + + +class TestBoardMember: + """Test BoardMember dataclass.""" + + def test_board_member_creation(self, mock_agent): + """Test creating a board member.""" + board_member = BoardMember( + agent=mock_agent, + role=BoardMemberRole.CHAIRMAN, + voting_weight=1.5, + expertise_areas=["leadership", "strategy"] + ) + + assert board_member.agent == mock_agent + assert board_member.role == BoardMemberRole.CHAIRMAN + assert board_member.voting_weight == 1.5 + assert board_member.expertise_areas == ["leadership", "strategy"] + + def test_board_member_defaults(self, mock_agent): + """Test board member with default values.""" + board_member = BoardMember( + agent=mock_agent, + role=BoardMemberRole.MEMBER + ) + + assert board_member.voting_weight == 1.0 + assert board_member.expertise_areas == [] + + def test_board_member_post_init(self, mock_agent): + """Test board member post-init with None expertise areas.""" + board_member = BoardMember( + agent=mock_agent, + role=BoardMemberRole.MEMBER, + expertise_areas=None + ) + + assert board_member.expertise_areas == [] + + +class TestBoardOrder: + """Test BoardOrder model.""" + + def test_board_order_creation(self): + """Test creating a board order.""" + order = BoardOrder( + agent_name="TestAgent", + task="Test task", + priority=1, + deadline="2024-01-01", + assigned_by="Chairman" + ) + + assert order.agent_name == "TestAgent" + assert order.task == "Test task" + assert order.priority == 1 + assert order.deadline == "2024-01-01" + assert order.assigned_by == "Chairman" + + def test_board_order_defaults(self): + """Test board order with default values.""" + order = BoardOrder( + agent_name="TestAgent", + task="Test task" + ) + + assert order.priority == 3 + assert order.deadline is None + assert order.assigned_by == "Board of Directors" + + def test_board_order_validation(self): + """Test board order validation.""" + # Test priority validation + with pytest.raises(ValueError): + BoardOrder( + agent_name="TestAgent", + task="Test task", + priority=0 # Invalid priority + ) + + with pytest.raises(ValueError): + BoardOrder( + agent_name="TestAgent", + task="Test task", + priority=6 # Invalid priority + ) + + +class TestBoardDecision: + """Test BoardDecision model.""" + + def test_board_decision_creation(self): + """Test creating a board decision.""" + decision = BoardDecision( + decision_type=BoardDecisionType.MAJORITY, + decision="Approve the proposal", + votes_for=3, + votes_against=1, + abstentions=0, + reasoning="The proposal aligns with our strategic goals" + ) + + assert decision.decision_type == BoardDecisionType.MAJORITY + assert decision.decision == "Approve the proposal" + assert decision.votes_for == 3 + assert decision.votes_against == 1 + assert decision.abstentions == 0 + assert decision.reasoning == "The proposal aligns with our strategic goals" + + def test_board_decision_defaults(self): + """Test board decision with default values.""" + decision = BoardDecision( + decision_type=BoardDecisionType.CONSENSUS, + decision="Test decision" + ) + + assert decision.votes_for == 0 + assert decision.votes_against == 0 + assert decision.abstentions == 0 + assert decision.reasoning == "" + + +class TestBoardSpec: + """Test BoardSpec model.""" + + def test_board_spec_creation(self): + """Test creating a board spec.""" + orders = [ + BoardOrder(agent_name="Agent1", task="Task 1"), + BoardOrder(agent_name="Agent2", task="Task 2") + ] + decisions = [ + BoardDecision( + decision_type=BoardDecisionType.MAJORITY, + decision="Decision 1" + ) + ] + + spec = BoardSpec( + plan="Test plan", + orders=orders, + decisions=decisions, + meeting_summary="Test meeting summary" + ) + + assert spec.plan == "Test plan" + assert len(spec.orders) == 2 + assert len(spec.decisions) == 1 + assert spec.meeting_summary == "Test meeting summary" + + def test_board_spec_defaults(self): + """Test board spec with default values.""" + spec = BoardSpec( + plan="Test plan", + orders=[] + ) + + assert spec.decisions == [] + assert spec.meeting_summary == "" + + +# Unit tests for BoardOfDirectorsSwarm +class TestBoardOfDirectorsSwarmInitialization: + """Test BoardOfDirectorsSwarm initialization.""" + + def test_basic_initialization(self, sample_agents): + """Test basic swarm initialization.""" + swarm = BoardOfDirectorsSwarm( + name="TestSwarm", + agents=sample_agents + ) + + assert swarm.name == "TestSwarm" + assert len(swarm.agents) == 3 + assert swarm.max_loops == 1 + assert swarm.verbose is False + assert swarm.decision_threshold == 0.6 + + def test_configured_initialization(self, sample_agents, sample_board_members): + """Test configured swarm initialization.""" + swarm = BoardOfDirectorsSwarm( + name="ConfiguredSwarm", + description="Test description", + board_members=sample_board_members, + agents=sample_agents, + max_loops=3, + verbose=True, + decision_threshold=0.8, + enable_voting=False, + enable_consensus=False, + max_workers=8 + ) + + assert swarm.name == "ConfiguredSwarm" + assert swarm.description == "Test description" + assert len(swarm.board_members) == 3 + assert len(swarm.agents) == 3 + assert swarm.max_loops == 3 + assert swarm.verbose is True + assert swarm.decision_threshold == 0.8 + assert swarm.enable_voting is False + assert swarm.enable_consensus is False + assert swarm.max_workers == 8 + + def test_default_board_setup(self, sample_agents): + """Test default board setup when no board members provided.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + + assert len(swarm.board_members) == 3 + assert swarm.board_members[0].role == BoardMemberRole.CHAIRMAN + assert swarm.board_members[1].role == BoardMemberRole.VICE_CHAIRMAN + assert swarm.board_members[2].role == BoardMemberRole.SECRETARY + + def test_initialization_without_agents(self): + """Test initialization without agents should raise error.""" + with pytest.raises(ValueError, match="No agents found in the swarm"): + BoardOfDirectorsSwarm(agents=[]) + + def test_initialization_with_invalid_max_loops(self, sample_agents): + """Test initialization with invalid max_loops.""" + with pytest.raises(ValueError, match="Max loops must be greater than 0"): + BoardOfDirectorsSwarm(agents=sample_agents, max_loops=0) + + def test_initialization_with_invalid_decision_threshold(self, sample_agents): + """Test initialization with invalid decision threshold.""" + with pytest.raises(ValueError, match="Decision threshold must be between 0.0 and 1.0"): + BoardOfDirectorsSwarm(agents=sample_agents, decision_threshold=1.5) + + +class TestBoardOfDirectorsSwarmMethods: + """Test BoardOfDirectorsSwarm methods.""" + + def test_setup_default_board(self, sample_agents): + """Test default board setup.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + + assert len(swarm.board_members) == 3 + assert all(hasattr(member.agent, 'agent_name') for member in swarm.board_members) + assert all(hasattr(member.agent, 'run') for member in swarm.board_members) + + def test_get_chairman_prompt(self, sample_agents): + """Test chairman prompt generation.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + prompt = swarm._get_chairman_prompt() + + assert "Chairman" in prompt + assert "board meetings" in prompt + assert "consensus" in prompt + + def test_get_vice_chairman_prompt(self, sample_agents): + """Test vice chairman prompt generation.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + prompt = swarm._get_vice_chairman_prompt() + + assert "Vice Chairman" in prompt + assert "supporting" in prompt + assert "operational" in prompt + + def test_get_secretary_prompt(self, sample_agents): + """Test secretary prompt generation.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + prompt = swarm._get_secretary_prompt() + + assert "Secretary" in prompt + assert "documenting" in prompt + assert "records" in prompt + + def test_format_board_members_info(self, configured_board_swarm): + """Test board members info formatting.""" + info = configured_board_swarm._format_board_members_info() + + assert "Chairman" in info + assert "Vice-Chairman" in info + assert "Secretary" in info + assert "expertise" in info + + def test_add_board_member(self, basic_board_swarm, mock_board_member): + """Test adding a board member.""" + initial_count = len(basic_board_swarm.board_members) + basic_board_swarm.add_board_member(mock_board_member) + + assert len(basic_board_swarm.board_members) == initial_count + 1 + assert mock_board_member in basic_board_swarm.board_members + + def test_remove_board_member(self, configured_board_swarm): + """Test removing a board member.""" + member_to_remove = configured_board_swarm.board_members[0] + member_name = member_to_remove.agent.agent_name + + initial_count = len(configured_board_swarm.board_members) + configured_board_swarm.remove_board_member(member_name) + + assert len(configured_board_swarm.board_members) == initial_count - 1 + assert member_to_remove not in configured_board_swarm.board_members + + def test_get_board_member(self, configured_board_swarm): + """Test getting a board member by name.""" + member = configured_board_swarm.board_members[0] + member_name = member.agent.agent_name + + found_member = configured_board_swarm.get_board_member(member_name) + assert found_member == member + + # Test with non-existent member + not_found = configured_board_swarm.get_board_member("NonExistent") + assert not_found is None + + def test_get_board_summary(self, configured_board_swarm): + """Test getting board summary.""" + summary = configured_board_swarm.get_board_summary() + + assert "board_name" in summary + assert "total_members" in summary + assert "total_agents" in summary + assert "max_loops" in summary + assert "decision_threshold" in summary + assert "members" in summary + + assert summary["board_name"] == "ConfiguredBoard" + assert summary["total_members"] == 3 + assert summary["total_agents"] == 3 + + +class TestBoardMeetingOperations: + """Test board meeting operations.""" + + def test_create_board_meeting_prompt(self, configured_board_swarm): + """Test board meeting prompt creation.""" + task = "Test task for board meeting" + prompt = configured_board_swarm._create_board_meeting_prompt(task) + + assert task in prompt + assert "BOARD OF DIRECTORS MEETING" in prompt + assert "INSTRUCTIONS" in prompt + assert "plan" in prompt + assert "orders" in prompt + + def test_conduct_board_discussion(self, configured_board_swarm): + """Test board discussion conduction.""" + prompt = "Test board meeting prompt" + + with patch.object(configured_board_swarm.board_members[0].agent, 'run') as mock_run: + mock_run.return_value = "Board discussion result" + result = configured_board_swarm._conduct_board_discussion(prompt) + + assert result == "Board discussion result" + mock_run.assert_called_once_with(task=prompt, img=None) + + def test_conduct_board_discussion_no_chairman(self, sample_agents): + """Test board discussion when no chairman is found.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + # Remove all board members + swarm.board_members = [] + + with pytest.raises(ValueError, match="No chairman found in board members"): + swarm._conduct_board_discussion("Test prompt") + + def test_parse_board_decisions_valid_json(self, configured_board_swarm): + """Test parsing valid JSON board decisions.""" + valid_json = """ + { + "plan": "Test plan", + "orders": [ + { + "agent_name": "Agent1", + "task": "Task 1", + "priority": 1, + "assigned_by": "Chairman" + } + ], + "decisions": [ + { + "decision_type": "majority", + "decision": "Test decision", + "votes_for": 2, + "votes_against": 1, + "abstentions": 0, + "reasoning": "Test reasoning" + } + ], + "meeting_summary": "Test summary" + } + """ + + result = configured_board_swarm._parse_board_decisions(valid_json) + + assert isinstance(result, BoardSpec) + assert result.plan == "Test plan" + assert len(result.orders) == 1 + assert len(result.decisions) == 1 + assert result.meeting_summary == "Test summary" + + def test_parse_board_decisions_invalid_json(self, configured_board_swarm): + """Test parsing invalid JSON board decisions.""" + invalid_json = "Invalid JSON content" + + result = configured_board_swarm._parse_board_decisions(invalid_json) + + assert isinstance(result, BoardSpec) + assert result.plan == invalid_json + assert len(result.orders) == 0 + assert len(result.decisions) == 0 + assert result.meeting_summary == "Parsing failed, using raw output" + + def test_run_board_meeting(self, configured_board_swarm): + """Test running a complete board meeting.""" + task = "Test board meeting task" + + with patch.object(configured_board_swarm, '_conduct_board_discussion') as mock_discuss: + with patch.object(configured_board_swarm, '_parse_board_decisions') as mock_parse: + mock_discuss.return_value = "Board discussion" + mock_parse.return_value = BoardSpec( + plan="Test plan", + orders=[], + decisions=[], + meeting_summary="Test summary" + ) + + result = configured_board_swarm.run_board_meeting(task) + + assert isinstance(result, BoardSpec) + mock_discuss.assert_called_once() + mock_parse.assert_called_once_with("Board discussion") + + +class TestTaskExecution: + """Test task execution methods.""" + + def test_call_single_agent(self, configured_board_swarm): + """Test calling a single agent.""" + agent_name = "Agent1" + task = "Test task" + + with patch.object(configured_board_swarm.agents[0], 'run') as mock_run: + mock_run.return_value = "Agent response" + result = configured_board_swarm._call_single_agent(agent_name, task) + + assert result == "Agent response" + mock_run.assert_called_once() + + def test_call_single_agent_not_found(self, configured_board_swarm): + """Test calling a non-existent agent.""" + with pytest.raises(ValueError, match="Agent 'NonExistent' not found"): + configured_board_swarm._call_single_agent("NonExistent", "Test task") + + def test_execute_single_order(self, configured_board_swarm): + """Test executing a single order.""" + order = BoardOrder( + agent_name="Agent1", + task="Test order task", + priority=1, + assigned_by="Chairman" + ) + + with patch.object(configured_board_swarm, '_call_single_agent') as mock_call: + mock_call.return_value = "Order execution result" + result = configured_board_swarm._execute_single_order(order) + + assert result == "Order execution result" + mock_call.assert_called_once_with( + agent_name="Agent1", + task="Test order task" + ) + + def test_execute_orders(self, configured_board_swarm): + """Test executing multiple orders.""" + orders = [ + BoardOrder(agent_name="Agent1", task="Task 1", priority=1), + BoardOrder(agent_name="Agent2", task="Task 2", priority=2), + ] + + with patch.object(configured_board_swarm, '_execute_single_order') as mock_execute: + mock_execute.side_effect = ["Result 1", "Result 2"] + results = configured_board_swarm._execute_orders(orders) + + assert len(results) == 2 + assert results[0]["agent_name"] == "Agent1" + assert results[0]["output"] == "Result 1" + assert results[1]["agent_name"] == "Agent2" + assert results[1]["output"] == "Result 2" + + def test_generate_board_feedback(self, configured_board_swarm): + """Test generating board feedback.""" + outputs = [ + {"agent_name": "Agent1", "output": "Output 1"}, + {"agent_name": "Agent2", "output": "Output 2"} + ] + + with patch.object(configured_board_swarm.board_members[0].agent, 'run') as mock_run: + mock_run.return_value = "Board feedback" + result = configured_board_swarm._generate_board_feedback(outputs) + + assert result == "Board feedback" + mock_run.assert_called_once() + + def test_generate_board_feedback_no_chairman(self, sample_agents): + """Test generating feedback when no chairman is found.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + swarm.board_members = [] # Remove all board members + + with pytest.raises(ValueError, match="No chairman found for feedback"): + swarm._generate_board_feedback([]) + + +class TestStepAndRunMethods: + """Test step and run methods.""" + + def test_step_method(self, configured_board_swarm): + """Test the step method.""" + task = "Test step task" + + with patch.object(configured_board_swarm, 'run_board_meeting') as mock_meeting: + with patch.object(configured_board_swarm, '_execute_orders') as mock_execute: + with patch.object(configured_board_swarm, '_generate_board_feedback') as mock_feedback: + mock_meeting.return_value = BoardSpec( + plan="Test plan", + orders=[BoardOrder(agent_name="Agent1", task="Task 1")], + decisions=[], + meeting_summary="Test summary" + ) + mock_execute.return_value = [{"agent_name": "Agent1", "output": "Result"}] + mock_feedback.return_value = "Board feedback" + + result = configured_board_swarm.step(task) + + assert result == "Board feedback" + mock_meeting.assert_called_once_with(task=task, img=None) + mock_execute.assert_called_once() + mock_feedback.assert_called_once() + + def test_step_method_no_feedback(self, configured_board_swarm): + """Test the step method with feedback disabled.""" + configured_board_swarm.board_feedback_on = False + task = "Test step task" + + with patch.object(configured_board_swarm, 'run_board_meeting') as mock_meeting: + with patch.object(configured_board_swarm, '_execute_orders') as mock_execute: + mock_meeting.return_value = BoardSpec( + plan="Test plan", + orders=[BoardOrder(agent_name="Agent1", task="Task 1")], + decisions=[], + meeting_summary="Test summary" + ) + mock_execute.return_value = [{"agent_name": "Agent1", "output": "Result"}] + + result = configured_board_swarm.step(task) + + assert result == [{"agent_name": "Agent1", "output": "Result"}] + + def test_run_method(self, configured_board_swarm): + """Test the run method.""" + task = "Test run task" + + with patch.object(configured_board_swarm, 'step') as mock_step: + with patch.object(configured_board_swarm, 'conversation') as mock_conversation: + mock_step.return_value = "Step result" + mock_conversation.add = Mock() + + result = configured_board_swarm.run(task) + + assert mock_step.call_count == 2 # max_loops = 2 + assert mock_conversation.add.call_count == 2 + + def test_arun_method(self, configured_board_swarm): + """Test the async run method.""" + task = "Test async run task" + + with patch.object(configured_board_swarm, 'run') as mock_run: + mock_run.return_value = "Async result" + + async def test_async(): + result = await configured_board_swarm.arun(task) + return result + + result = asyncio.run(test_async()) + assert result == "Async result" + mock_run.assert_called_once_with(task=task, img=None) + + +# Integration tests +class TestBoardOfDirectorsSwarmIntegration: + """Integration tests for BoardOfDirectorsSwarm.""" + + def test_full_workflow_integration(self, sample_agents): + """Test full workflow integration.""" + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, + verbose=False, + max_loops=1 + ) + + task = "Create a simple report" + + # Mock the board discussion to return structured output + mock_board_output = """ + { + "plan": "Create a comprehensive report", + "orders": [ + { + "agent_name": "Agent1", + "task": "Research the topic", + "priority": 1, + "assigned_by": "Chairman" + }, + { + "agent_name": "Agent2", + "task": "Write the report", + "priority": 2, + "assigned_by": "Chairman" + } + ], + "decisions": [ + { + "decision_type": "consensus", + "decision": "Proceed with report creation", + "votes_for": 3, + "votes_against": 0, + "abstentions": 0, + "reasoning": "Report is needed for decision making" + } + ], + "meeting_summary": "Board agreed to create a comprehensive report" + } + """ + + with patch.object(swarm.board_members[0].agent, 'run') as mock_run: + mock_run.return_value = mock_board_output + result = swarm.run(task) + + assert result is not None + assert isinstance(result, dict) + + def test_board_member_management_integration(self, sample_agents): + """Test board member management integration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents) + + # Test adding a new board member + new_member = BoardMember( + agent=sample_agents[0], + role=BoardMemberRole.MEMBER, + voting_weight=1.0, + expertise_areas=["testing"] + ) + + initial_count = len(swarm.board_members) + swarm.add_board_member(new_member) + assert len(swarm.board_members) == initial_count + 1 + + # Test removing a board member + member_name = swarm.board_members[0].agent.agent_name + swarm.remove_board_member(member_name) + assert len(swarm.board_members) == initial_count + + # Test getting board member + member = swarm.get_board_member(swarm.board_members[0].agent.agent_name) + assert member is not None + + +# Parameterized tests +@pytest.mark.parametrize("max_loops", [1, 2, 3]) +def test_max_loops_parameterization(sample_agents, max_loops): + """Test swarm with different max_loops values.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, max_loops=max_loops) + assert swarm.max_loops == max_loops + + +@pytest.mark.parametrize("decision_threshold", [0.5, 0.6, 0.7, 0.8, 0.9]) +def test_decision_threshold_parameterization(sample_agents, decision_threshold): + """Test swarm with different decision threshold values.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, decision_threshold=decision_threshold) + assert swarm.decision_threshold == decision_threshold + + +@pytest.mark.parametrize("board_model", ["gpt-4o-mini", "gpt-4", "claude-3-sonnet"]) +def test_board_model_parameterization(sample_agents, board_model): + """Test swarm with different board models.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_model_name=board_model) + assert swarm.board_model_name == board_model + + +# Error handling tests +class TestBoardOfDirectorsSwarmErrorHandling: + """Test error handling in BoardOfDirectorsSwarm.""" + + def test_initialization_error_handling(self): + """Test error handling during initialization.""" + with pytest.raises(ValueError): + BoardOfDirectorsSwarm(agents=[]) + + def test_board_meeting_error_handling(self, configured_board_swarm): + """Test error handling during board meeting.""" + with patch.object(configured_board_swarm, '_conduct_board_discussion') as mock_discuss: + mock_discuss.side_effect = Exception("Board meeting failed") + + with pytest.raises(Exception, match="Board meeting failed"): + configured_board_swarm.run_board_meeting("Test task") + + def test_task_execution_error_handling(self, configured_board_swarm): + """Test error handling during task execution.""" + with patch.object(configured_board_swarm, '_call_single_agent') as mock_call: + mock_call.side_effect = Exception("Task execution failed") + + with pytest.raises(Exception, match="Task execution failed"): + configured_board_swarm._call_single_agent("Agent1", "Test task") + + def test_order_execution_error_handling(self, configured_board_swarm): + """Test error handling during order execution.""" + orders = [BoardOrder(agent_name="Agent1", task="Task 1")] + + with patch.object(configured_board_swarm, '_execute_single_order') as mock_execute: + mock_execute.side_effect = Exception("Order execution failed") + + # Should not raise exception, but log error + results = configured_board_swarm._execute_orders(orders) + assert len(results) == 1 + assert "Error" in results[0]["output"] + + +# Performance tests +class TestBoardOfDirectorsSwarmPerformance: + """Test performance characteristics of BoardOfDirectorsSwarm.""" + + def test_parallel_execution_performance(self, sample_agents): + """Test parallel execution performance.""" + import time + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, + max_workers=3, + verbose=False + ) + + # Create multiple orders + orders = [ + BoardOrder(agent_name=f"Agent{i+1}", task=f"Task {i+1}") + for i in range(3) + ] + + start_time = time.time() + + with patch.object(swarm, '_execute_single_order') as mock_execute: + mock_execute.side_effect = lambda order: f"Result for {order.task}" + results = swarm._execute_orders(orders) + + end_time = time.time() + execution_time = end_time - start_time + + assert len(results) == 3 + assert execution_time < 1.0 # Should complete quickly with parallel execution + + def test_memory_usage(self, sample_agents): + """Test memory usage characteristics.""" + import psutil + import os + + process = psutil.Process(os.getpid()) + initial_memory = process.memory_info().rss + + # Create multiple swarms + swarms = [] + for i in range(5): + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, + name=f"Swarm{i}", + verbose=False + ) + swarms.append(swarm) + + final_memory = process.memory_info().rss + memory_increase = final_memory - initial_memory + + # Memory increase should be reasonable (less than 100MB) + assert memory_increase < 100 * 1024 * 1024 + + +# Configuration tests +class TestBoardOfDirectorsSwarmConfiguration: + """Test configuration options for BoardOfDirectorsSwarm.""" + + def test_verbose_configuration(self, sample_agents): + """Test verbose configuration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, verbose=True) + assert swarm.verbose is True + + swarm = BoardOfDirectorsSwarm(agents=sample_agents, verbose=False) + assert swarm.verbose is False + + def test_collaboration_prompt_configuration(self, sample_agents): + """Test collaboration prompt configuration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, add_collaboration_prompt=True) + assert swarm.add_collaboration_prompt is True + + swarm = BoardOfDirectorsSwarm(agents=sample_agents, add_collaboration_prompt=False) + assert swarm.add_collaboration_prompt is False + + def test_board_feedback_configuration(self, sample_agents): + """Test board feedback configuration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_feedback_on=True) + assert swarm.board_feedback_on is True + + swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_feedback_on=False) + assert swarm.board_feedback_on is False + + def test_voting_configuration(self, sample_agents): + """Test voting configuration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_voting=True) + assert swarm.enable_voting is True + + swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_voting=False) + assert swarm.enable_voting is False + + def test_consensus_configuration(self, sample_agents): + """Test consensus configuration.""" + swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_consensus=True) + assert swarm.enable_consensus is True + + swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_consensus=False) + assert swarm.enable_consensus is False + + +# Real integration tests (skipped if no API key) +@pytest.mark.skipif( + not os.getenv("OPENAI_API_KEY"), + reason="OpenAI API key not available" +) +class TestBoardOfDirectorsSwarmRealIntegration: + """Real integration tests for BoardOfDirectorsSwarm.""" + + def test_real_board_meeting(self): + """Test real board meeting with actual API calls.""" + # Create real agents + agents = [ + Agent( + agent_name="Researcher", + agent_description="Research analyst", + model_name="gpt-4o-mini", + max_loops=1 + ), + Agent( + agent_name="Writer", + agent_description="Content writer", + model_name="gpt-4o-mini", + max_loops=1 + ) + ] + + swarm = BoardOfDirectorsSwarm( + agents=agents, + verbose=False, + max_loops=1 + ) + + task = "Create a brief market analysis report" + + result = swarm.run(task) + + assert result is not None + assert isinstance(result, dict) + assert "conversation_history" in result + + def test_real_board_member_management(self): + """Test real board member management.""" + agents = [ + Agent( + agent_name="TestAgent", + agent_description="Test agent", + model_name="gpt-4o-mini", + max_loops=1 + ) + ] + + swarm = BoardOfDirectorsSwarm(agents=agents, verbose=False) + + # Test board summary + summary = swarm.get_board_summary() + assert summary["total_members"] == 3 # Default board + assert summary["total_agents"] == 1 + + +# Test runner +if __name__ == "__main__": + pytest.main([__file__, "-v", "--tb=short"]) \ No newline at end of file From cf510a7198d5c63874ccffedca581bc7db81a57d Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:10:27 +0300 Subject: [PATCH 14/73] Add files via upload --- .../board_of_directors_example.py | 437 ++++++++++++++++++ .../trading_showcase/test_results.log | 7 + 2 files changed, 444 insertions(+) create mode 100644 examples/multi_agent/board_of_directors/board_of_directors_example.py create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/test_results.log diff --git a/examples/multi_agent/board_of_directors/board_of_directors_example.py b/examples/multi_agent/board_of_directors/board_of_directors_example.py new file mode 100644 index 00000000..425d460b --- /dev/null +++ b/examples/multi_agent/board_of_directors/board_of_directors_example.py @@ -0,0 +1,437 @@ +""" +Board of Directors Example + +This example demonstrates how to use the Board of Directors swarm feature +in the Swarms Framework. It shows how to create a board, configure it, +and use it to orchestrate tasks across multiple agents. + +The example includes: +1. Basic Board of Directors setup and usage +2. Custom board member configuration +3. Task execution and feedback +4. Configuration management +""" + +import os +import sys +from typing import List, Optional + +# Add the parent directory to the path to import swarms +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) + +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, + BoardMember, + BoardMemberRole, +) +from swarms.structs.agent import Agent +from swarms.config.board_config import ( + enable_board_feature, + disable_board_feature, + is_board_feature_enabled, + create_default_config_file, + set_board_size, + set_decision_threshold, + set_board_model, + enable_verbose_logging, + disable_verbose_logging, +) + + +def enable_board_directors_feature() -> None: + """ + Enable the Board of Directors feature. + + This function demonstrates how to enable the Board of Directors feature + globally and create a default configuration file. + """ + print("🔧 Enabling Board of Directors feature...") + + try: + # Create a default configuration file + create_default_config_file("swarms_board_config.yaml") + + # Enable the feature + enable_board_feature("swarms_board_config.yaml") + + # Configure some default settings + set_board_size(3) + set_decision_threshold(0.6) + set_board_model("gpt-4o-mini") + enable_verbose_logging("swarms_board_config.yaml") + + print("✅ Board of Directors feature enabled successfully!") + print("📁 Configuration file created: swarms_board_config.yaml") + + except Exception as e: + print(f"❌ Failed to enable Board of Directors feature: {e}") + raise + + +def create_custom_board_members() -> List[BoardMember]: + """ + Create custom board members with specific roles and expertise. + + This function demonstrates how to create a custom board with + specialized roles and expertise areas. + + Returns: + List[BoardMember]: List of custom board members + """ + print("👥 Creating custom board members...") + + # Create specialized board members + chairman = Agent( + agent_name="Executive_Chairman", + agent_description="Executive Chairman with strategic vision and leadership expertise", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are the Executive Chairman of the Board. Your role is to: +1. Provide strategic leadership and vision +2. Facilitate high-level decision-making +3. Ensure board effectiveness and governance +4. Represent the organization's interests +5. Guide long-term strategic planning + +You should be visionary, strategic, and focused on organizational success.""", + ) + + cto = Agent( + agent_name="CTO", + agent_description="Chief Technology Officer with deep technical expertise", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are the Chief Technology Officer. Your role is to: +1. Provide technical leadership and strategy +2. Evaluate technology solutions and architectures +3. Ensure technical feasibility of proposed solutions +4. Guide technology-related decisions +5. Maintain technical standards and best practices + +You should be technically proficient, innovative, and focused on technical excellence.""", + ) + + cfo = Agent( + agent_name="CFO", + agent_description="Chief Financial Officer with financial and risk management expertise", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are the Chief Financial Officer. Your role is to: +1. Provide financial analysis and insights +2. Evaluate financial implications of decisions +3. Ensure financial sustainability and risk management +4. Guide resource allocation and budgeting +5. Maintain financial controls and compliance + +You should be financially astute, risk-aware, and focused on financial health.""", + ) + + # Create BoardMember objects with roles and expertise + board_members = [ + BoardMember( + agent=chairman, + role=BoardMemberRole.CHAIRMAN, + voting_weight=2.0, + expertise_areas=["strategic_planning", "leadership", "governance", "business_strategy"] + ), + BoardMember( + agent=cto, + role=BoardMemberRole.EXECUTIVE_DIRECTOR, + voting_weight=1.5, + expertise_areas=["technology", "architecture", "innovation", "technical_strategy"] + ), + BoardMember( + agent=cfo, + role=BoardMemberRole.EXECUTIVE_DIRECTOR, + voting_weight=1.5, + expertise_areas=["finance", "risk_management", "budgeting", "financial_analysis"] + ), + ] + + print(f"✅ Created {len(board_members)} custom board members") + for member in board_members: + print(f" - {member.agent.agent_name} ({member.role.value})") + + return board_members + + +def create_worker_agents() -> List[Agent]: + """ + Create worker agents for the swarm. + + This function creates specialized worker agents that will be + managed by the Board of Directors. + + Returns: + List[Agent]: List of worker agents + """ + print("🛠️ Creating worker agents...") + + # Create specialized worker agents + researcher = Agent( + agent_name="Research_Analyst", + agent_description="Research analyst specializing in market research and data analysis", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are a Research Analyst. Your responsibilities include: +1. Conducting thorough research on assigned topics +2. Analyzing data and market trends +3. Preparing comprehensive research reports +4. Providing data-driven insights and recommendations +5. Maintaining high standards of research quality + +You should be analytical, thorough, and evidence-based in your work.""", + ) + + developer = Agent( + agent_name="Software_Developer", + agent_description="Software developer with expertise in system design and implementation", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are a Software Developer. Your responsibilities include: +1. Designing and implementing software solutions +2. Writing clean, maintainable code +3. Conducting code reviews and testing +4. Collaborating with team members +5. Following best practices and coding standards + +You should be technically skilled, detail-oriented, and focused on quality.""", + ) + + marketer = Agent( + agent_name="Marketing_Specialist", + agent_description="Marketing specialist with expertise in digital marketing and brand strategy", + model_name="gpt-4o-mini", + max_loops=1, + system_prompt="""You are a Marketing Specialist. Your responsibilities include: +1. Developing marketing strategies and campaigns +2. Creating compelling content and messaging +3. Analyzing market trends and customer behavior +4. Managing brand presence and reputation +5. Measuring and optimizing marketing performance + +You should be creative, strategic, and customer-focused in your approach.""", + ) + + agents = [researcher, developer, marketer] + + print(f"✅ Created {len(agents)} worker agents") + for agent in agents: + print(f" - {agent.agent_name}: {agent.agent_description}") + + return agents + + +def run_board_of_directors_example() -> None: + """ + Run a comprehensive Board of Directors example. + + This function demonstrates the complete workflow of using + the Board of Directors swarm to orchestrate tasks. + """ + print("\n" + "="*60) + print("🏛️ BOARD OF DIRECTORS SWARM EXAMPLE") + print("="*60) + + try: + # Check if Board of Directors feature is enabled + if not is_board_feature_enabled(): + print("⚠️ Board of Directors feature is not enabled. Enabling now...") + enable_board_directors_feature() + + # Create custom board members + board_members = create_custom_board_members() + + # Create worker agents + worker_agents = create_worker_agents() + + # Create the Board of Directors swarm + print("\n🏛️ Creating Board of Directors swarm...") + board_swarm = BoardOfDirectorsSwarm( + name="Executive_Board_Swarm", + description="Executive board with specialized roles for strategic decision-making", + board_members=board_members, + agents=worker_agents, + max_loops=2, + verbose=True, + decision_threshold=0.6, + enable_voting=True, + enable_consensus=True, + ) + + print("✅ Board of Directors swarm created successfully!") + + # Display board summary + summary = board_swarm.get_board_summary() + print(f"\n📊 Board Summary:") + print(f" Board Name: {summary['board_name']}") + print(f" Total Members: {summary['total_members']}") + print(f" Total Agents: {summary['total_agents']}") + print(f" Max Loops: {summary['max_loops']}") + print(f" Decision Threshold: {summary['decision_threshold']}") + + print(f"\n👥 Board Members:") + for member in summary['members']: + print(f" - {member['name']} ({member['role']}) - Weight: {member['voting_weight']}") + print(f" Expertise: {', '.join(member['expertise_areas'])}") + + # Define a complex task for the board to handle + task = """ + Develop a comprehensive strategy for launching a new AI-powered product in the market. + + The task involves: + 1. Market research and competitive analysis + 2. Technical architecture and development planning + 3. Marketing strategy and go-to-market plan + 4. Financial projections and risk assessment + + Please coordinate the efforts of all team members to create a cohesive strategy. + """ + + print(f"\n📋 Executing task: {task.strip()[:100]}...") + + # Execute the task using the Board of Directors swarm + result = board_swarm.run(task=task) + + print("\n✅ Task completed successfully!") + print(f"📄 Result type: {type(result)}") + + # Display conversation history + if hasattr(result, 'get') and callable(result.get): + conversation_history = result.get('conversation_history', []) + print(f"\n💬 Conversation History ({len(conversation_history)} messages):") + for i, message in enumerate(conversation_history[-5:], 1): # Show last 5 messages + role = message.get('role', 'Unknown') + content = message.get('content', '')[:100] + "..." if len(message.get('content', '')) > 100 else message.get('content', '') + print(f" {i}. {role}: {content}") + else: + print(f"\n📝 Result: {str(result)[:200]}...") + + print("\n🎉 Board of Directors example completed successfully!") + + except Exception as e: + print(f"❌ Error in Board of Directors example: {e}") + import traceback + traceback.print_exc() + + +def run_simple_board_example() -> None: + """ + Run a simple Board of Directors example with default settings. + + This function demonstrates a basic usage of the Board of Directors + swarm with minimal configuration. + """ + print("\n" + "="*60) + print("🏛️ SIMPLE BOARD OF DIRECTORS EXAMPLE") + print("="*60) + + try: + # Create simple worker agents + print("🛠️ Creating simple worker agents...") + + analyst = Agent( + agent_name="Data_Analyst", + agent_description="Data analyst for processing and analyzing information", + model_name="gpt-4o-mini", + max_loops=1, + ) + + writer = Agent( + agent_name="Content_Writer", + agent_description="Content writer for creating reports and documentation", + model_name="gpt-4o-mini", + max_loops=1, + ) + + agents = [analyst, writer] + + # Create Board of Directors swarm with default settings + print("🏛️ Creating Board of Directors swarm with default settings...") + board_swarm = BoardOfDirectorsSwarm( + name="Simple_Board_Swarm", + agents=agents, + verbose=True, + ) + + print("✅ Simple Board of Directors swarm created!") + + # Simple task + task = "Analyze the current market trends and create a summary report with recommendations." + + print(f"\n📋 Executing simple task: {task}") + + # Execute the task + result = board_swarm.run(task=task) + + print("\n✅ Simple task completed successfully!") + print(f"📄 Result type: {type(result)}") + + if hasattr(result, 'get') and callable(result.get): + conversation_history = result.get('conversation_history', []) + print(f"\n💬 Conversation History ({len(conversation_history)} messages):") + for i, message in enumerate(conversation_history[-3:], 1): # Show last 3 messages + role = message.get('role', 'Unknown') + content = message.get('content', '')[:80] + "..." if len(message.get('content', '')) > 80 else message.get('content', '') + print(f" {i}. {role}: {content}") + else: + print(f"\n📝 Result: {str(result)[:150]}...") + + print("\n🎉 Simple Board of Directors example completed!") + + except Exception as e: + print(f"❌ Error in simple Board of Directors example: {e}") + import traceback + traceback.print_exc() + + +def check_environment() -> bool: + """ + Check if the environment is properly set up for the example. + + Returns: + bool: True if environment is ready, False otherwise + """ + # Check for OpenAI API key + if not os.getenv("OPENAI_API_KEY"): + print("⚠️ Warning: OPENAI_API_KEY environment variable not set.") + print(" The example may not work without a valid API key.") + print(" Please set your OpenAI API key: export OPENAI_API_KEY='your-key-here'") + return False + + return True + + +def main() -> None: + """ + Main function to run the Board of Directors examples. + """ + print("🚀 Board of Directors Swarm Examples") + print("="*50) + + # Check environment + if not check_environment(): + print("\n⚠️ Environment check failed. Please set up your environment properly.") + return + + try: + # Run simple example first + run_simple_board_example() + + # Run comprehensive example + run_board_of_directors_example() + + print("\n" + "="*60) + print("🎉 All Board of Directors examples completed successfully!") + print("="*60) + + except KeyboardInterrupt: + print("\n⚠️ Examples interrupted by user.") + except Exception as e: + print(f"\n❌ Unexpected error: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/multi_agent/board_of_directors/trading_showcase/test_results.log b/examples/multi_agent/board_of_directors/trading_showcase/test_results.log new file mode 100644 index 00000000..69749700 --- /dev/null +++ b/examples/multi_agent/board_of_directors/trading_showcase/test_results.log @@ -0,0 +1,7 @@ +2025-07-25 16:33:37 | INFO | __main__:run_all_tests:380 - 🚀 Starting Comprehensive Test Suite +2025-07-25 16:33:37 | INFO | __main__:test_ollama_installation:67 - 🔍 Testing Ollama Installation +2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama --version +2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama list +2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama list +2025-07-25 16:33:37 | INFO | __main__:test_python_dependencies:86 - 🐍 Testing Python Dependencies +2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: C:\Users\arona\scoop\apps\python\current\python.exe -m pip list From eec69739675c6e6901f57602313bda1da5efb9ad Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:11:15 +0300 Subject: [PATCH 15/73] Add files via upload --- .../trading_showcase/agent_workspace/error.txt | 0 .../__pycache__/litellm_config.cpython-313.pyc | Bin 0 -> 8218 bytes .../src/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 223 bytes .../src/__pycache__/board_setup.cpython-313.pyc | Bin 0 -> 15640 bytes .../src/__pycache__/main.cpython-313.pyc | Bin 0 -> 17456 bytes .../src/__pycache__/market_data.cpython-313.pyc | Bin 0 -> 21895 bytes .../__pycache__/trading_agents.cpython-313.pyc | Bin 0 -> 20355 bytes .../trading_analysis.cpython-313.pyc | Bin 0 -> 25034 bytes .../tests/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 301 bytes ...est_market_data.cpython-313-pytest-8.3.5.pyc | Bin 0 -> 42872 bytes ...t_ollama_models.cpython-313-pytest-8.3.5.pyc | Bin 0 -> 29543 bytes ...rading_analysis.cpython-313-pytest-8.3.5.pyc | Bin 0 -> 64869 bytes 12 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/__init__.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_agents.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_market_data.cpython-313-pytest-8.3.5.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc create mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_trading_analysis.cpython-313-pytest-8.3.5.pyc diff --git a/examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt b/examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf77d0ec70679fb6ca15baa950e97ec37c015727 GIT binary patch literal 8218 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 literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..725e1b6948ff3c114f456620d2eda5a1f8864c63 GIT binary patch literal 15640 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 literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b43bb05fd0e8192518af3d388a7f3f124562cf0c GIT binary patch literal 17456 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 literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c2209c2fdd87924b5f032217d0d29b06f7c81e2 GIT binary patch literal 21895 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 LjS(d-k~sb!%Zo6y literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab50fe302e046b50b5b1a02218aae802ca096b6c GIT binary patch literal 25034 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 literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..589fde012ef6b421397518b9b87da5fc551498cb GIT binary patch literal 301 zcmXw#F;2rk5JkNqg2++|Zm?~d8~_kSa1Dh+H_~YFj_nopuC=o!Kn2`^LvSUxTmaz< zuqMfG=KpEt{TY|bG>JNY|ERM#zo&4NIGAw}&1aG)KB>fN_IjCaiG%D^@u(`2rSme> zBsWfZEnO)y6jjd!}yIrA}*6Hm^3}=Yi6^LKSI9(XtZZ6=S$ulVLtb#jB znKw@anJfC1Y+%TD%D236Io^2qAuFGcot`823iu3c-U;@aYRw<=0GN%!x&ww|;pd6IC_E;}Sz;*_1FM3hBx&X@DyY(3>VSs)jj zbjxlHu23$7eA}s_lOEY~(kpvOzWr43$r8Cl63&ag+t?uVbFW{Iv)Fm3Dda9zuAd(Q z*{DOX*!l7fxL!Phj-a!{O5-e#j8r^Tak5gbJXs}IovfCtC80vtEClnbgrKV_3tk&*cLWtZ#ha9oi_lhKKhm^2QsiZnhPlSXNU zC{~Ck#$!Wr1nWqVL{#cJb3z(4HWF~XhlAiGrGCnZu zx2TShGuJT6C~@x)MgA`!nBla-k2 z!CWOaFrJjdvGGA=z3_< zC{aE&^&w&j9lWiCURI|wTWSn7tbCcgW-Sszo^I`lY=G z>n5c9WF-bhB8nmnCgq$d&YjKy4ZFxN91!C5z5nkZcthwXnHcnHc*HX>&u&c7_$oEn zJ#47hFR}!m6}YQ|)(#uZ4+$f@pC%z_d)5LuI8Jg5+5L`GbuczQF(!@1#)p$pa=Ed{ zz%W6ufQ-$nj)7!iFh2Ca9T|&n9=}t~ zODd`j5NTMoeKr|SsLp6yPDDmysu&Ir#z$h|uv*9(A{nY1kvXhf9=(tpQEpjOn-Uut zRB&b`b@K4O^C1QB>U>0o8{zyjiCDJ`h<3g^IWRFAON=Y$gO?)msB$P7g{<=mNt}

(|J;>bMYo%EUbW$D{T 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 literal 0 HcmV?d00001 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc b/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62f6bfdf1156772005a0228bdd97292968c27a77 GIT binary patch literal 29543 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 Date: Mon, 28 Jul 2025 12:10:22 +0300 Subject: [PATCH 16/73] Delete examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__ directory --- .../__pycache__/litellm_config.cpython-313.pyc | Bin 8218 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc diff --git a/examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/config/__pycache__/litellm_config.cpython-313.pyc deleted file mode 100644 index bf77d0ec70679fb6ca15baa950e97ec37c015727..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8218 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 Date: Mon, 28 Jul 2025 12:10:30 +0300 Subject: [PATCH 17/73] Delete examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__ directory --- .../src/__pycache__/__init__.cpython-313.pyc | Bin 223 -> 0 bytes .../src/__pycache__/board_setup.cpython-313.pyc | Bin 15640 -> 0 bytes .../src/__pycache__/main.cpython-313.pyc | Bin 17456 -> 0 bytes .../src/__pycache__/market_data.cpython-313.pyc | Bin 21895 -> 0 bytes .../__pycache__/trading_agents.cpython-313.pyc | Bin 20355 -> 0 bytes .../trading_analysis.cpython-313.pyc | Bin 25034 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/__init__.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_agents.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/__init__.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 78bfa93e46b4f13813c27816e3a3eb45361276aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmXwzJ&FQB5QV2#7Z$-wn3;HhMGVwf12HRTdZ%k=8m7CYs>h#1@DOHRz$=-0f@LBz zYh-;%eXpu`yr$ppWunXDR1Rd`Tl1Fsho&oV&WdcP7T1hE#5L{dcAR~~nvoa`#b96= za87QEFpqidF(ii7&XDJ6w6X$z8Nj{qn+h3xT_vYYA)oLQ4Otz3wRMDfiV~X0SQiS- oW!&Y45twq&+69-i9&HY{ud$D|u5d!1Qm3uUE&O$qQq&Q80)@~+MgRZ+ diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/board_setup.cpython-313.pyc deleted file mode 100644 index 725e1b6948ff3c114f456620d2eda5a1f8864c63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15640 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 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/main.cpython-313.pyc deleted file mode 100644 index b43bb05fd0e8192518af3d388a7f3f124562cf0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17456 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 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/market_data.cpython-313.pyc deleted file mode 100644 index 8c2209c2fdd87924b5f032217d0d29b06f7c81e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21895 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 LjS(d-k~sb!%Zo6y diff --git a/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/src/__pycache__/trading_analysis.cpython-313.pyc deleted file mode 100644 index ab50fe302e046b50b5b1a02218aae802ca096b6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25034 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 From e3a09e2e59acaff1cf57fa1499ffddbae46fc764 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:10:39 +0300 Subject: [PATCH 18/73] Delete examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__ directory --- .../tests/__pycache__/__init__.cpython-313.pyc | Bin 301 -> 0 bytes ...est_market_data.cpython-313-pytest-8.3.5.pyc | Bin 42872 -> 0 bytes ...t_ollama_models.cpython-313-pytest-8.3.5.pyc | Bin 29543 -> 0 bytes ...rading_analysis.cpython-313-pytest-8.3.5.pyc | Bin 64869 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_market_data.cpython-313-pytest-8.3.5.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_trading_analysis.cpython-313-pytest-8.3.5.pyc diff --git a/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc b/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 589fde012ef6b421397518b9b87da5fc551498cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmXw#F;2rk5JkNqg2++|Zm?~d8~_kSa1Dh+H_~YFj_nopuC=o!Kn2`^LvSUxTmaz< zuqMfG=KpEt{TY|bG>JNY|ERM#zo&4NIGAw}&1aG)KB>fN_IjCaiG%D^@u(`2rSme> zBsWfZEnO)y6jjd!}yIrA}*6Hm^3}=Yi6^LKSI9(XtZZ6=S$ulVLtb#jB znKw@anJfC1Y+%TD%D236Io^2qAuFGcot`823iu3c-U;@aYRw<=0GN%!x&ww|;pd6IC_E;}Sz;*_1FM3hBx&X@DyY(3>VSs)jj zbjxlHu23$7eA}s_lOEY~(kpvOzWr43$r8Cl63&ag+t?uVbFW{Iv)Fm3Dda9zuAd(Q z*{DOX*!l7fxL!Phj-a!{O5-e#j8r^Tak5gbJXs}IovfCtC80vtEClnbgrKV_3tk&*cLWtZ#ha9oi_lhKKhm^2QsiZnhPlSXNU zC{~Ck#$!Wr1nWqVL{#cJb3z(4HWF~XhlAiGrGCnZu zx2TShGuJT6C~@x)MgA`!nBla-k2 z!CWOaFrJjdvGGA=z3_< zC{aE&^&w&j9lWiCURI|wTWSn7tbCcgW-Sszo^I`lY=G z>n5c9WF-bhB8nmnCgq$d&YjKy4ZFxN91!C5z5nkZcthwXnHcnHc*HX>&u&c7_$oEn zJ#47hFR}!m6}YQ|)(#uZ4+$f@pC%z_d)5LuI8Jg5+5L`GbuczQF(!@1#)p$pa=Ed{ zz%W6ufQ-$nj)7!iFh2Ca9T|&n9=}t~ zODd`j5NTMoeKr|SsLp6yPDDmysu&Ir#z$h|uv*9(A{nY1kvXhf9=(tpQEpjOn-Uut zRB&b`b@K4O^C1QB>U>0o8{zyjiCDJ`h<3g^IWRFAON=Y$gO?)msB$P7g{<=mNt}

(|J;>bMYo%EUbW$D{T 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 diff --git a/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc b/examples/multi_agent/board_of_directors/trading_showcase/tests/__pycache__/test_ollama_models.cpython-313-pytest-8.3.5.pyc deleted file mode 100644 index 62f6bfdf1156772005a0228bdd97292968c27a77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29543 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 Date: Mon, 28 Jul 2025 16:12:09 +0000 Subject: [PATCH 19/73] Update transformers requirement Updates the requirements on [transformers](https://github.com/huggingface/transformers) to permit the latest version. - [Release notes](https://github.com/huggingface/transformers/releases) - [Commits](https://github.com/huggingface/transformers/compare/v4.39.0...v4.54.0) --- updated-dependencies: - dependency-name: transformers dependency-version: 4.54.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4f7ae7f3..b25173d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ torch>=2.1.1,<3.0 -transformers>=4.39.0,<4.51.0 +transformers>=4.39.0,<4.55.0 asyncio>=3.4.3,<4.0 toml pypdf==5.1.0 From 6d70c32cb51a60dbe745f77ebbae972e685762f1 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:49:28 +0300 Subject: [PATCH 20/73] Update board_of_directors_swarm.py --- swarms/structs/board_of_directors_swarm.py | 594 ++++++++++++++++++++- 1 file changed, 593 insertions(+), 1 deletion(-) diff --git a/swarms/structs/board_of_directors_swarm.py b/swarms/structs/board_of_directors_swarm.py index a6d24794..f80fb4a4 100644 --- a/swarms/structs/board_of_directors_swarm.py +++ b/swarms/structs/board_of_directors_swarm.py @@ -27,6 +27,8 @@ import traceback from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass, field from enum import Enum +from functools import lru_cache +from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Union, Tuple from loguru import logger @@ -44,6 +46,588 @@ from swarms.utils.output_types import OutputType board_logger = initialize_logger(log_folder="board_of_directors_swarm") +# ============================================================================ +# BOARD OF DIRECTORS CONFIGURATION +# ============================================================================ + +class BoardFeatureStatus(str, Enum): + """Enumeration of Board of Directors feature status. + + This enum defines the possible states of the Board of Directors feature + within the Swarms Framework. + + Attributes: + ENABLED: Feature is explicitly enabled + DISABLED: Feature is explicitly disabled + AUTO: Feature state is determined automatically + """ + + ENABLED = "enabled" + DISABLED = "disabled" + AUTO = "auto" + + +class BoardConfigModel(BaseModel): + """ + Configuration model for Board of Directors feature. + + This model defines all configurable parameters for the Board of Directors + feature, including feature status, board composition, and operational settings. + + Attributes: + board_feature_enabled: Whether the Board of Directors feature is enabled globally + default_board_size: Default number of board members when creating a new board + decision_threshold: Threshold for majority decisions (0.0-1.0) + enable_voting: Enable voting mechanisms for board decisions + enable_consensus: Enable consensus-building mechanisms + default_board_model: Default model for board member agents + verbose_logging: Enable verbose logging for board operations + max_board_meeting_duration: Maximum duration for board meetings in seconds + auto_fallback_to_director: Automatically fall back to Director mode if Board fails + custom_board_templates: Custom board templates for different use cases + """ + + # Feature control + board_feature_enabled: bool = Field( + default=False, + description="Whether the Board of Directors feature is enabled globally." + ) + + # Board composition + default_board_size: int = Field( + default=3, + ge=1, + le=10, + description="Default number of board members when creating a new board." + ) + + # Operational settings + decision_threshold: float = Field( + default=0.6, + ge=0.0, + le=1.0, + description="Threshold for majority decisions (0.0-1.0)." + ) + + enable_voting: bool = Field( + default=True, + description="Enable voting mechanisms for board decisions." + ) + + enable_consensus: bool = Field( + default=True, + description="Enable consensus-building mechanisms." + ) + + # Model settings + default_board_model: str = Field( + default="gpt-4o-mini", + description="Default model for board member agents." + ) + + # Logging and monitoring + verbose_logging: bool = Field( + default=False, + description="Enable verbose logging for board operations." + ) + + # Performance settings + max_board_meeting_duration: int = Field( + default=300, + ge=60, + le=3600, + description="Maximum duration for board meetings in seconds." + ) + + # Integration settings + auto_fallback_to_director: bool = Field( + default=True, + description="Automatically fall back to Director mode if Board fails." + ) + + # Custom board templates + custom_board_templates: Dict[str, Dict[str, Any]] = Field( + default_factory=dict, + description="Custom board templates for different use cases." + ) + + +@dataclass +class BoardConfig: + """ + Board of Directors configuration manager. + + This class manages the configuration for the Board of Directors feature, + including loading from environment variables, configuration files, and + providing default values. + + Attributes: + config_file_path: Optional path to configuration file + config_data: Optional configuration data dictionary + config: The current configuration model instance + """ + + config_file_path: Optional[str] = None + config_data: Optional[Dict[str, Any]] = None + config: BoardConfigModel = field(init=False) + + def __post_init__(self) -> None: + """Initialize the configuration after object creation.""" + self._load_config() + + def _load_config(self) -> None: + """ + Load configuration from various sources. + + Priority order: + 1. Environment variables + 2. Configuration file + 3. Default values + + Raises: + Exception: If configuration loading fails + """ + try: + # Start with default configuration + self.config = BoardConfigModel() + + # Load from configuration file if specified + if self.config_file_path and os.path.exists(self.config_file_path): + self._load_from_file() + + # Override with environment variables + self._load_from_environment() + + # Override with explicit config data + if self.config_data: + self._load_from_dict(self.config_data) + + except Exception as e: + logger.error(f"Failed to load Board of Directors configuration: {str(e)}") + raise + + def _load_from_file(self) -> None: + """ + Load configuration from file. + + Raises: + Exception: If file loading fails + """ + try: + import yaml + with open(self.config_file_path, 'r') as f: + file_config = yaml.safe_load(f) + self._load_from_dict(file_config) + logger.info(f"Loaded Board of Directors config from: {self.config_file_path}") + except Exception as e: + logger.warning(f"Failed to load config file {self.config_file_path}: {e}") + raise + + def _load_from_environment(self) -> None: + """ + Load configuration from environment variables. + + This method maps environment variables to configuration parameters + and handles type conversion appropriately. + """ + env_mappings = { + 'SWARMS_BOARD_FEATURE_ENABLED': 'board_feature_enabled', + 'SWARMS_BOARD_DEFAULT_SIZE': 'default_board_size', + 'SWARMS_BOARD_DECISION_THRESHOLD': 'decision_threshold', + 'SWARMS_BOARD_ENABLE_VOTING': 'enable_voting', + 'SWARMS_BOARD_ENABLE_CONSENSUS': 'enable_consensus', + 'SWARMS_BOARD_DEFAULT_MODEL': 'default_board_model', + 'SWARMS_BOARD_VERBOSE_LOGGING': 'verbose_logging', + 'SWARMS_BOARD_MAX_MEETING_DURATION': 'max_board_meeting_duration', + 'SWARMS_BOARD_AUTO_FALLBACK': 'auto_fallback_to_director', + } + + for env_var, config_key in env_mappings.items(): + value = os.getenv(env_var) + if value is not None: + try: + # Convert string values to appropriate types + if config_key in ['board_feature_enabled', 'enable_voting', 'enable_consensus', 'verbose_logging', 'auto_fallback_to_director']: + converted_value = value.lower() in ['true', '1', 'yes', 'on'] + elif config_key in ['default_board_size', 'max_board_meeting_duration']: + converted_value = int(value) + elif config_key in ['decision_threshold']: + converted_value = float(value) + else: + converted_value = value + + setattr(self.config, config_key, converted_value) + logger.debug(f"Loaded {config_key} from environment: {converted_value}") + except (ValueError, TypeError) as e: + logger.warning(f"Failed to parse environment variable {env_var}: {e}") + + def _load_from_dict(self, config_dict: Dict[str, Any]) -> None: + """ + Load configuration from dictionary. + + Args: + config_dict: Dictionary containing configuration values + + Raises: + ValueError: If configuration values are invalid + """ + for key, value in config_dict.items(): + if hasattr(self.config, key): + try: + setattr(self.config, key, value) + except (ValueError, TypeError) as e: + logger.warning(f"Failed to set config {key}: {e}") + raise ValueError(f"Invalid configuration value for {key}: {e}") + + def is_enabled(self) -> bool: + """ + Check if the Board of Directors feature is enabled. + + Returns: + bool: True if the feature is enabled, False otherwise + """ + return self.config.board_feature_enabled + + def get_config(self) -> BoardConfigModel: + """ + Get the current configuration. + + Returns: + BoardConfigModel: The current configuration + """ + return self.config + + def update_config(self, updates: Dict[str, Any]) -> None: + """ + Update the configuration with new values. + + Args: + updates: Dictionary of configuration updates + + Raises: + ValueError: If any update values are invalid + """ + try: + self._load_from_dict(updates) + except ValueError as e: + logger.error(f"Failed to update configuration: {e}") + raise + + def save_config(self, file_path: Optional[str] = None) -> None: + """ + Save the current configuration to a file. + + Args: + file_path: Optional file path to save to (uses config_file_path if not provided) + + Raises: + Exception: If saving fails + """ + save_path = file_path or self.config_file_path + if not save_path: + logger.warning("No file path specified for saving configuration") + return + + try: + import yaml + # Convert config to dictionary + config_dict = self.config.model_dump() + + # Ensure directory exists + os.makedirs(os.path.dirname(save_path), exist_ok=True) + + with open(save_path, 'w') as f: + yaml.dump(config_dict, f, default_flow_style=False, indent=2) + + logger.info(f"Saved Board of Directors config to: {save_path}") + except Exception as e: + logger.error(f"Failed to save config to {save_path}: {e}") + raise + + @lru_cache(maxsize=128) + def get_default_board_template(self, template_name: str = "standard") -> Dict[str, Any]: + """ + Get a default board template. + + This method provides predefined board templates for common use cases. + Templates are cached for improved performance. + + Args: + template_name: Name of the template to retrieve + + Returns: + Dict[str, Any]: Board template configuration + """ + templates = { + "standard": { + "roles": [ + {"name": "Chairman", "weight": 1.5, "expertise": ["leadership", "strategy"]}, + {"name": "Vice-Chairman", "weight": 1.2, "expertise": ["operations", "coordination"]}, + {"name": "Secretary", "weight": 1.0, "expertise": ["documentation", "communication"]}, + ] + }, + "executive": { + "roles": [ + {"name": "CEO", "weight": 2.0, "expertise": ["executive_leadership", "strategy"]}, + {"name": "CFO", "weight": 1.5, "expertise": ["finance", "risk_management"]}, + {"name": "CTO", "weight": 1.5, "expertise": ["technology", "innovation"]}, + {"name": "COO", "weight": 1.3, "expertise": ["operations", "efficiency"]}, + ] + }, + "advisory": { + "roles": [ + {"name": "Lead_Advisor", "weight": 1.3, "expertise": ["strategy", "consulting"]}, + {"name": "Technical_Advisor", "weight": 1.2, "expertise": ["technology", "architecture"]}, + {"name": "Business_Advisor", "weight": 1.2, "expertise": ["business", "market_analysis"]}, + {"name": "Legal_Advisor", "weight": 1.1, "expertise": ["legal", "compliance"]}, + ] + }, + "minimal": { + "roles": [ + {"name": "Chairman", "weight": 1.0, "expertise": ["leadership"]}, + {"name": "Member", "weight": 1.0, "expertise": ["general"]}, + ] + } + } + + # Check custom templates first + if template_name in self.config.custom_board_templates: + return self.config.custom_board_templates[template_name] + + # Return standard template if requested template not found + return templates.get(template_name, templates["standard"]) + + def validate_config(self) -> List[str]: + """ + Validate the current configuration. + + This method performs comprehensive validation of the configuration + to ensure all values are within acceptable ranges and constraints. + + Returns: + List[str]: List of validation errors (empty if valid) + """ + errors = [] + + try: + # Validate the configuration model + self.config.model_validate(self.config.model_dump()) + except Exception as e: + errors.append(f"Configuration validation failed: {e}") + + # Additional custom validations + if self.config.decision_threshold < 0.5: + errors.append("Decision threshold should be at least 0.5 for meaningful majority decisions") + + if self.config.default_board_size < 2: + errors.append("Board size should be at least 2 for meaningful discussions") + + if self.config.max_board_meeting_duration < 60: + errors.append("Board meeting duration should be at least 60 seconds") + + return errors + + +# Global configuration instance +_board_config: Optional[BoardConfig] = None + + +@lru_cache(maxsize=1) +def get_board_config(config_file_path: Optional[str] = None) -> BoardConfig: + """ + Get the global Board of Directors configuration instance. + + This function provides a singleton pattern for accessing the Board of Directors + configuration. The configuration is cached for improved performance. + + Args: + config_file_path: Optional path to configuration file + + Returns: + BoardConfig: The global configuration instance + """ + global _board_config + + if _board_config is None: + _board_config = BoardConfig(config_file_path=config_file_path) + + return _board_config + + +def enable_board_feature(config_file_path: Optional[str] = None) -> None: + """ + Enable the Board of Directors feature globally. + + This function enables the Board of Directors feature and saves the configuration + to the specified file path. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"board_feature_enabled": True}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Board of Directors feature enabled") + + +def disable_board_feature(config_file_path: Optional[str] = None) -> None: + """ + Disable the Board of Directors feature globally. + + This function disables the Board of Directors feature and saves the configuration + to the specified file path. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"board_feature_enabled": False}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Board of Directors feature disabled") + + +def is_board_feature_enabled(config_file_path: Optional[str] = None) -> bool: + """ + Check if the Board of Directors feature is enabled. + + Args: + config_file_path: Optional path to configuration file + + Returns: + bool: True if the feature is enabled, False otherwise + """ + config = get_board_config(config_file_path) + return config.is_enabled() + + +def create_default_config_file(file_path: str = "swarms_board_config.yaml") -> None: + """ + Create a default configuration file. + + This function creates a default Board of Directors configuration file + with recommended settings. + + Args: + file_path: Path where to create the configuration file + """ + default_config = { + "board_feature_enabled": False, + "default_board_size": 3, + "decision_threshold": 0.6, + "enable_voting": True, + "enable_consensus": True, + "default_board_model": "gpt-4o-mini", + "verbose_logging": False, + "max_board_meeting_duration": 300, + "auto_fallback_to_director": True, + "custom_board_templates": {} + } + + config = BoardConfig(config_file_path=file_path, config_data=default_config) + config.save_config(file_path) + + logger.info(f"Created default Board of Directors config file: {file_path}") + + +def set_board_size(size: int, config_file_path: Optional[str] = None) -> None: + """ + Set the default board size. + + Args: + size: The default board size (1-10) + config_file_path: Optional path to save the configuration + """ + if not 1 <= size <= 10: + raise ValueError("Board size must be between 1 and 10") + + config = get_board_config(config_file_path) + config.update_config({"default_board_size": size}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Default board size set to: {size}") + + +def set_decision_threshold(threshold: float, config_file_path: Optional[str] = None) -> None: + """ + Set the decision threshold for majority decisions. + + Args: + threshold: The decision threshold (0.0-1.0) + config_file_path: Optional path to save the configuration + """ + if not 0.0 <= threshold <= 1.0: + raise ValueError("Decision threshold must be between 0.0 and 1.0") + + config = get_board_config(config_file_path) + config.update_config({"decision_threshold": threshold}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Decision threshold set to: {threshold}") + + +def set_board_model(model: str, config_file_path: Optional[str] = None) -> None: + """ + Set the default board model. + + Args: + model: The default model name for board members + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"default_board_model": model}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info(f"Default board model set to: {model}") + + +def enable_verbose_logging(config_file_path: Optional[str] = None) -> None: + """ + Enable verbose logging for board operations. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"verbose_logging": True}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Verbose logging enabled for Board of Directors") + + +def disable_verbose_logging(config_file_path: Optional[str] = None) -> None: + """ + Disable verbose logging for board operations. + + Args: + config_file_path: Optional path to save the configuration + """ + config = get_board_config(config_file_path) + config.update_config({"verbose_logging": False}) + + if config_file_path: + config.save_config(config_file_path) + + logger.info("Verbose logging disabled for Board of Directors") + + +# ============================================================================ +# BOARD OF DIRECTORS IMPLEMENTATION +# ============================================================================ + class BoardMemberRole(str, Enum): """Enumeration of possible board member roles. @@ -504,6 +1088,14 @@ You should be thorough, organized, and detail-oriented in your documentation.""" if self.verbose: board_logger.info(f"🔍 Running reliability checks for swarm: {self.name}") + # Check if Board of Directors feature is enabled + board_config = get_board_config() + if not board_config.is_enabled(): + raise ValueError( + "Board of Directors feature is not enabled. Please enable it using " + "enable_board_feature() or set SWARMS_BOARD_FEATURE_ENABLED=true environment variable." + ) + if not self.agents or len(self.agents) == 0: raise ValueError( "No agents found in the swarm. At least one agent must be provided to create a Board of Directors swarm." @@ -1141,4 +1733,4 @@ Please provide your response in the following format: "total_agents": len(self.agents), "max_loops": self.max_loops, "decision_threshold": self.decision_threshold, - } \ No newline at end of file + } From 0e1f70e68e17211b3968fbd3a47c12f871d6b3cb Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:49:43 +0300 Subject: [PATCH 21/73] Delete swarms/config/board_config.py --- swarms/config/board_config.py | 596 ---------------------------------- 1 file changed, 596 deletions(-) delete mode 100644 swarms/config/board_config.py diff --git a/swarms/config/board_config.py b/swarms/config/board_config.py deleted file mode 100644 index 8b29c0ae..00000000 --- a/swarms/config/board_config.py +++ /dev/null @@ -1,596 +0,0 @@ -""" -Board of Directors Configuration Module - -This module provides configuration management for the Board of Directors feature -in the Swarms Framework. It allows users to enable and configure the Board of -Directors feature manually through environment variables or configuration files. - -The implementation follows the Swarms philosophy of: -- Readable code with comprehensive type annotations and documentation -- Performance optimization through caching and efficient loading -- Simplified abstractions for configuration management -""" - -import os -from typing import Dict, Any, Optional, List, Union -from dataclasses import dataclass, field -from enum import Enum -from pathlib import Path -from functools import lru_cache - -from pydantic import BaseModel, Field -from loguru import logger - - -class BoardFeatureStatus(str, Enum): - """Enumeration of Board of Directors feature status. - - This enum defines the possible states of the Board of Directors feature - within the Swarms Framework. - - Attributes: - ENABLED: Feature is explicitly enabled - DISABLED: Feature is explicitly disabled - AUTO: Feature state is determined automatically - """ - - ENABLED = "enabled" - DISABLED = "disabled" - AUTO = "auto" - - -class BoardConfigModel(BaseModel): - """ - Configuration model for Board of Directors feature. - - This model defines all configurable parameters for the Board of Directors - feature, including feature status, board composition, and operational settings. - - Attributes: - board_feature_enabled: Whether the Board of Directors feature is enabled globally - default_board_size: Default number of board members when creating a new board - decision_threshold: Threshold for majority decisions (0.0-1.0) - enable_voting: Enable voting mechanisms for board decisions - enable_consensus: Enable consensus-building mechanisms - default_board_model: Default model for board member agents - verbose_logging: Enable verbose logging for board operations - max_board_meeting_duration: Maximum duration for board meetings in seconds - auto_fallback_to_director: Automatically fall back to Director mode if Board fails - custom_board_templates: Custom board templates for different use cases - """ - - # Feature control - board_feature_enabled: bool = Field( - default=False, - description="Whether the Board of Directors feature is enabled globally." - ) - - # Board composition - default_board_size: int = Field( - default=3, - ge=1, - le=10, - description="Default number of board members when creating a new board." - ) - - # Operational settings - decision_threshold: float = Field( - default=0.6, - ge=0.0, - le=1.0, - description="Threshold for majority decisions (0.0-1.0)." - ) - - enable_voting: bool = Field( - default=True, - description="Enable voting mechanisms for board decisions." - ) - - enable_consensus: bool = Field( - default=True, - description="Enable consensus-building mechanisms." - ) - - # Model settings - default_board_model: str = Field( - default="gpt-4o-mini", - description="Default model for board member agents." - ) - - # Logging and monitoring - verbose_logging: bool = Field( - default=False, - description="Enable verbose logging for board operations." - ) - - # Performance settings - max_board_meeting_duration: int = Field( - default=300, - ge=60, - le=3600, - description="Maximum duration for board meetings in seconds." - ) - - # Integration settings - auto_fallback_to_director: bool = Field( - default=True, - description="Automatically fall back to Director mode if Board fails." - ) - - # Custom board templates - custom_board_templates: Dict[str, Dict[str, Any]] = Field( - default_factory=dict, - description="Custom board templates for different use cases." - ) - - -@dataclass -class BoardConfig: - """ - Board of Directors configuration manager. - - This class manages the configuration for the Board of Directors feature, - including loading from environment variables, configuration files, and - providing default values. - - Attributes: - config_file_path: Optional path to configuration file - config_data: Optional configuration data dictionary - config: The current configuration model instance - """ - - config_file_path: Optional[str] = None - config_data: Optional[Dict[str, Any]] = None - config: BoardConfigModel = field(init=False) - - def __post_init__(self) -> None: - """Initialize the configuration after object creation.""" - self._load_config() - - def _load_config(self) -> None: - """ - Load configuration from various sources. - - Priority order: - 1. Environment variables - 2. Configuration file - 3. Default values - - Raises: - Exception: If configuration loading fails - """ - try: - # Start with default configuration - self.config = BoardConfigModel() - - # Load from configuration file if specified - if self.config_file_path and os.path.exists(self.config_file_path): - self._load_from_file() - - # Override with environment variables - self._load_from_environment() - - # Override with explicit config data - if self.config_data: - self._load_from_dict(self.config_data) - - except Exception as e: - logger.error(f"Failed to load Board of Directors configuration: {str(e)}") - raise - - def _load_from_file(self) -> None: - """ - Load configuration from file. - - Raises: - Exception: If file loading fails - """ - try: - import yaml - with open(self.config_file_path, 'r') as f: - file_config = yaml.safe_load(f) - self._load_from_dict(file_config) - logger.info(f"Loaded Board of Directors config from: {self.config_file_path}") - except Exception as e: - logger.warning(f"Failed to load config file {self.config_file_path}: {e}") - raise - - def _load_from_environment(self) -> None: - """ - Load configuration from environment variables. - - This method maps environment variables to configuration parameters - and handles type conversion appropriately. - """ - env_mappings = { - 'SWARMS_BOARD_FEATURE_ENABLED': 'board_feature_enabled', - 'SWARMS_BOARD_DEFAULT_SIZE': 'default_board_size', - 'SWARMS_BOARD_DECISION_THRESHOLD': 'decision_threshold', - 'SWARMS_BOARD_ENABLE_VOTING': 'enable_voting', - 'SWARMS_BOARD_ENABLE_CONSENSUS': 'enable_consensus', - 'SWARMS_BOARD_DEFAULT_MODEL': 'default_board_model', - 'SWARMS_BOARD_VERBOSE_LOGGING': 'verbose_logging', - 'SWARMS_BOARD_MAX_MEETING_DURATION': 'max_board_meeting_duration', - 'SWARMS_BOARD_AUTO_FALLBACK': 'auto_fallback_to_director', - } - - for env_var, config_key in env_mappings.items(): - value = os.getenv(env_var) - if value is not None: - try: - # Convert string values to appropriate types - if config_key in ['board_feature_enabled', 'enable_voting', 'enable_consensus', 'verbose_logging', 'auto_fallback_to_director']: - converted_value = value.lower() in ['true', '1', 'yes', 'on'] - elif config_key in ['default_board_size', 'max_board_meeting_duration']: - converted_value = int(value) - elif config_key in ['decision_threshold']: - converted_value = float(value) - else: - converted_value = value - - setattr(self.config, config_key, converted_value) - logger.debug(f"Loaded {config_key} from environment: {converted_value}") - except (ValueError, TypeError) as e: - logger.warning(f"Failed to parse environment variable {env_var}: {e}") - - def _load_from_dict(self, config_dict: Dict[str, Any]) -> None: - """ - Load configuration from dictionary. - - Args: - config_dict: Dictionary containing configuration values - - Raises: - ValueError: If configuration values are invalid - """ - for key, value in config_dict.items(): - if hasattr(self.config, key): - try: - setattr(self.config, key, value) - except (ValueError, TypeError) as e: - logger.warning(f"Failed to set config {key}: {e}") - raise ValueError(f"Invalid configuration value for {key}: {e}") - - def is_enabled(self) -> bool: - """ - Check if the Board of Directors feature is enabled. - - Returns: - bool: True if the feature is enabled, False otherwise - """ - return self.config.board_feature_enabled - - def get_config(self) -> BoardConfigModel: - """ - Get the current configuration. - - Returns: - BoardConfigModel: The current configuration - """ - return self.config - - def update_config(self, updates: Dict[str, Any]) -> None: - """ - Update the configuration with new values. - - Args: - updates: Dictionary of configuration updates - - Raises: - ValueError: If any update values are invalid - """ - try: - self._load_from_dict(updates) - except ValueError as e: - logger.error(f"Failed to update configuration: {e}") - raise - - def save_config(self, file_path: Optional[str] = None) -> None: - """ - Save the current configuration to a file. - - Args: - file_path: Optional file path to save to (uses config_file_path if not provided) - - Raises: - Exception: If saving fails - """ - save_path = file_path or self.config_file_path - if not save_path: - logger.warning("No file path specified for saving configuration") - return - - try: - import yaml - # Convert config to dictionary - config_dict = self.config.model_dump() - - # Ensure directory exists - os.makedirs(os.path.dirname(save_path), exist_ok=True) - - with open(save_path, 'w') as f: - yaml.dump(config_dict, f, default_flow_style=False, indent=2) - - logger.info(f"Saved Board of Directors config to: {save_path}") - except Exception as e: - logger.error(f"Failed to save config to {save_path}: {e}") - raise - - @lru_cache(maxsize=128) - def get_default_board_template(self, template_name: str = "standard") -> Dict[str, Any]: - """ - Get a default board template. - - This method provides predefined board templates for common use cases. - Templates are cached for improved performance. - - Args: - template_name: Name of the template to retrieve - - Returns: - Dict[str, Any]: Board template configuration - """ - templates = { - "standard": { - "roles": [ - {"name": "Chairman", "weight": 1.5, "expertise": ["leadership", "strategy"]}, - {"name": "Vice-Chairman", "weight": 1.2, "expertise": ["operations", "coordination"]}, - {"name": "Secretary", "weight": 1.0, "expertise": ["documentation", "communication"]}, - ] - }, - "executive": { - "roles": [ - {"name": "CEO", "weight": 2.0, "expertise": ["executive_leadership", "strategy"]}, - {"name": "CFO", "weight": 1.5, "expertise": ["finance", "risk_management"]}, - {"name": "CTO", "weight": 1.5, "expertise": ["technology", "innovation"]}, - {"name": "COO", "weight": 1.3, "expertise": ["operations", "efficiency"]}, - ] - }, - "advisory": { - "roles": [ - {"name": "Lead_Advisor", "weight": 1.3, "expertise": ["strategy", "consulting"]}, - {"name": "Technical_Advisor", "weight": 1.2, "expertise": ["technology", "architecture"]}, - {"name": "Business_Advisor", "weight": 1.2, "expertise": ["business", "market_analysis"]}, - {"name": "Legal_Advisor", "weight": 1.1, "expertise": ["legal", "compliance"]}, - ] - }, - "minimal": { - "roles": [ - {"name": "Chairman", "weight": 1.0, "expertise": ["leadership"]}, - {"name": "Member", "weight": 1.0, "expertise": ["general"]}, - ] - } - } - - # Check custom templates first - if template_name in self.config.custom_board_templates: - return self.config.custom_board_templates[template_name] - - # Return standard template if requested template not found - return templates.get(template_name, templates["standard"]) - - def validate_config(self) -> List[str]: - """ - Validate the current configuration. - - This method performs comprehensive validation of the configuration - to ensure all values are within acceptable ranges and constraints. - - Returns: - List[str]: List of validation errors (empty if valid) - """ - errors = [] - - try: - # Validate the configuration model - self.config.model_validate(self.config.model_dump()) - except Exception as e: - errors.append(f"Configuration validation failed: {e}") - - # Additional custom validations - if self.config.decision_threshold < 0.5: - errors.append("Decision threshold should be at least 0.5 for meaningful majority decisions") - - if self.config.default_board_size < 2: - errors.append("Board size should be at least 2 for meaningful discussions") - - if self.config.max_board_meeting_duration < 60: - errors.append("Board meeting duration should be at least 60 seconds") - - return errors - - -# Global configuration instance -_board_config: Optional[BoardConfig] = None - - -@lru_cache(maxsize=1) -def get_board_config(config_file_path: Optional[str] = None) -> BoardConfig: - """ - Get the global Board of Directors configuration instance. - - This function provides a singleton pattern for accessing the Board of Directors - configuration. The configuration is cached for improved performance. - - Args: - config_file_path: Optional path to configuration file - - Returns: - BoardConfig: The global configuration instance - """ - global _board_config - - if _board_config is None: - _board_config = BoardConfig(config_file_path=config_file_path) - - return _board_config - - -def enable_board_feature(config_file_path: Optional[str] = None) -> None: - """ - Enable the Board of Directors feature globally. - - This function enables the Board of Directors feature and saves the configuration - to the specified file path. - - Args: - config_file_path: Optional path to save the configuration - """ - config = get_board_config(config_file_path) - config.update_config({"board_feature_enabled": True}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info("Board of Directors feature enabled") - - -def disable_board_feature(config_file_path: Optional[str] = None) -> None: - """ - Disable the Board of Directors feature globally. - - This function disables the Board of Directors feature and saves the configuration - to the specified file path. - - Args: - config_file_path: Optional path to save the configuration - """ - config = get_board_config(config_file_path) - config.update_config({"board_feature_enabled": False}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info("Board of Directors feature disabled") - - -def is_board_feature_enabled(config_file_path: Optional[str] = None) -> bool: - """ - Check if the Board of Directors feature is enabled. - - Args: - config_file_path: Optional path to configuration file - - Returns: - bool: True if the feature is enabled, False otherwise - """ - config = get_board_config(config_file_path) - return config.is_enabled() - - -def create_default_config_file(file_path: str = "swarms_board_config.yaml") -> None: - """ - Create a default configuration file. - - This function creates a default Board of Directors configuration file - with recommended settings. - - Args: - file_path: Path where to create the configuration file - """ - default_config = { - "board_feature_enabled": False, - "default_board_size": 3, - "decision_threshold": 0.6, - "enable_voting": True, - "enable_consensus": True, - "default_board_model": "gpt-4o-mini", - "verbose_logging": False, - "max_board_meeting_duration": 300, - "auto_fallback_to_director": True, - "custom_board_templates": {} - } - - config = BoardConfig(config_file_path=file_path, config_data=default_config) - config.save_config(file_path) - - logger.info(f"Created default Board of Directors config file: {file_path}") - - -def set_board_size(size: int, config_file_path: Optional[str] = None) -> None: - """ - Set the default board size. - - Args: - size: The default board size (1-10) - config_file_path: Optional path to save the configuration - """ - if not 1 <= size <= 10: - raise ValueError("Board size must be between 1 and 10") - - config = get_board_config(config_file_path) - config.update_config({"default_board_size": size}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info(f"Default board size set to: {size}") - - -def set_decision_threshold(threshold: float, config_file_path: Optional[str] = None) -> None: - """ - Set the decision threshold for majority decisions. - - Args: - threshold: The decision threshold (0.0-1.0) - config_file_path: Optional path to save the configuration - """ - if not 0.0 <= threshold <= 1.0: - raise ValueError("Decision threshold must be between 0.0 and 1.0") - - config = get_board_config(config_file_path) - config.update_config({"decision_threshold": threshold}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info(f"Decision threshold set to: {threshold}") - - -def set_board_model(model: str, config_file_path: Optional[str] = None) -> None: - """ - Set the default board model. - - Args: - model: The default model name for board members - config_file_path: Optional path to save the configuration - """ - config = get_board_config(config_file_path) - config.update_config({"default_board_model": model}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info(f"Default board model set to: {model}") - - -def enable_verbose_logging(config_file_path: Optional[str] = None) -> None: - """ - Enable verbose logging for board operations. - - Args: - config_file_path: Optional path to save the configuration - """ - config = get_board_config(config_file_path) - config.update_config({"verbose_logging": True}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info("Verbose logging enabled for Board of Directors") - - -def disable_verbose_logging(config_file_path: Optional[str] = None) -> None: - """ - Disable verbose logging for board operations. - - Args: - config_file_path: Optional path to save the configuration - """ - config = get_board_config(config_file_path) - config.update_config({"verbose_logging": False}) - - if config_file_path: - config.save_config(config_file_path) - - logger.info("Verbose logging disabled for Board of Directors") \ No newline at end of file From 1ae94fd04240722b7abc0a4090ce4e976dfce194 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:50:15 +0300 Subject: [PATCH 22/73] Delete examples/multi_agent/board_of_directors/trading_showcase/agent_workspace directory --- .../board_of_directors/trading_showcase/agent_workspace/error.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt diff --git a/examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt b/examples/multi_agent/board_of_directors/trading_showcase/agent_workspace/error.txt deleted file mode 100644 index e69de29b..00000000 From 4e6eced828e9ed131f1e8647d9ad14a2fc5e922a Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 29 Jul 2025 19:45:02 +0300 Subject: [PATCH 23/73] Delete examples/multi_agent/board_of_directors/trading_showcase/test_results.log --- .../board_of_directors/trading_showcase/test_results.log | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 examples/multi_agent/board_of_directors/trading_showcase/test_results.log diff --git a/examples/multi_agent/board_of_directors/trading_showcase/test_results.log b/examples/multi_agent/board_of_directors/trading_showcase/test_results.log deleted file mode 100644 index 69749700..00000000 --- a/examples/multi_agent/board_of_directors/trading_showcase/test_results.log +++ /dev/null @@ -1,7 +0,0 @@ -2025-07-25 16:33:37 | INFO | __main__:run_all_tests:380 - 🚀 Starting Comprehensive Test Suite -2025-07-25 16:33:37 | INFO | __main__:test_ollama_installation:67 - 🔍 Testing Ollama Installation -2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama --version -2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama list -2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: ollama list -2025-07-25 16:33:37 | INFO | __main__:test_python_dependencies:86 - 🐍 Testing Python Dependencies -2025-07-25 16:33:37 | INFO | __main__:run_command:35 - Running command: C:\Users\arona\scoop\apps\python\current\python.exe -m pip list From 932a3e7a472d79b1cb704489e06a73dd534e31cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A5=A5=E5=AE=87?= <625024108@qq.com> Date: Wed, 30 Jul 2025 15:13:14 +0800 Subject: [PATCH 24/73] fix bugs truncate_memory_with_tokenizer --- swarms/structs/conversation.py | 119 +++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 14 deletions(-) diff --git a/swarms/structs/conversation.py b/swarms/structs/conversation.py index 7c8d3109..3e47cae1 100644 --- a/swarms/structs/conversation.py +++ b/swarms/structs/conversation.py @@ -184,6 +184,7 @@ class Conversation: system_prompt: Optional[str] = None, time_enabled: bool = False, autosave: bool = False, # Changed default to False + save_enabled: bool = False, # New parameter to control if saving is enabled save_filepath: str = None, load_filepath: str = None, # New parameter to specify which file to load from context_length: int = 8192, @@ -222,6 +223,7 @@ class Conversation: self.system_prompt = system_prompt self.time_enabled = time_enabled self.autosave = autosave + self.save_enabled = save_enabled self.conversations_dir = conversations_dir self.tokenizer_model_name = tokenizer_model_name self.message_id_on = message_id_on @@ -1019,6 +1021,13 @@ class Conversation: ) return + # Don't save if saving is disabled (你的PR代码) + if not self.save_enabled: + logger.warning( + "An attempt to save the conversation failed: save_enabled is False." + "Please set save_enabled=True when creating a Conversation object to enable saving." + ) + return # Get the full data including metadata and conversation history data = self.get_init_params() @@ -1267,39 +1276,121 @@ class Conversation: def truncate_memory_with_tokenizer(self): """ - Truncates the conversation history based on the total number of tokens using a tokenizer. - + Truncate conversation history based on the total token count using tokenizer. + + This version is more generic, not dependent on a specific LLM model, and can work with any model that provides a counter. + Uses count_tokens function to calculate and truncate by message, ensuring the result is still valid content. + Returns: None """ + from swarms.utils.litellm_tokenizer import count_tokens + total_tokens = 0 truncated_history = [] - + for message in self.conversation_history: role = message.get("role") content = message.get("content") - tokens = count_tokens(content, self.tokenizer_model_name) - count = tokens # Assign the token count - total_tokens += count - - if total_tokens <= self.context_length: + + # Convert content to string if it's not already a string + if not isinstance(content, str): + content = str(content) + + # Calculate token count for this message + token_count = count_tokens(content, self.tokenizer_model_name) + + # Check if adding this message would exceed the limit + if total_tokens + token_count <= self.context_length: + # If not exceeding limit, add the full message truncated_history.append(message) + total_tokens += token_count else: - remaining_tokens = self.context_length - ( - total_tokens - count + # Calculate remaining tokens we can include + remaining_tokens = self.context_length - total_tokens + + # If no token space left, break the loop + if remaining_tokens <= 0: + break + + # If we have space left, we need to truncate this message + # Use binary search to find content length that fits remaining token space + truncated_content = self._binary_search_truncate( + content, + remaining_tokens, + self.tokenizer_model_name ) - truncated_content = content[ - :remaining_tokens - ] # Truncate the content based on the remaining tokens + + # Create the truncated message truncated_message = { "role": role, "content": truncated_content, } + + # Add any other fields from the original message + for key, value in message.items(): + if key not in ["role", "content"]: + truncated_message[key] = value + truncated_history.append(truncated_message) break - + + # Update conversation history self.conversation_history = truncated_history + def _binary_search_truncate(self, text, target_tokens, model_name): + """ + Use binary search to find the maximum text substring that fits the target token count. + + Parameters: + text (str): Original text to truncate + target_tokens (int): Target token count + model_name (str): Model name for token counting + + Returns: + str: Truncated text with token count not exceeding target_tokens + """ + from swarms.utils.litellm_tokenizer import count_tokens + + # If text is empty or target tokens is 0, return empty string + if not text or target_tokens <= 0: + return "" + + # If original text token count is already less than or equal to target, return as is + original_tokens = count_tokens(text, model_name) + if original_tokens <= target_tokens: + return text + + # Binary search + left, right = 0, len(text) + best_length = 0 + best_text = "" + + while left <= right: + mid = (left + right) // 2 + truncated = text[:mid] + tokens = count_tokens(truncated, model_name) + + if tokens <= target_tokens: + # If current truncated text token count is less than or equal to target, try longer text + best_length = mid + best_text = truncated + left = mid + 1 + else: + # Otherwise try shorter text + right = mid - 1 + + # Try to truncate at sentence boundaries if possible + sentence_delimiters = ['.', '!', '?', '\n'] + for delimiter in sentence_delimiters: + last_pos = best_text.rfind(delimiter) + if last_pos > len(best_text) * 0.75: # Only truncate at sentence boundary if we don't lose too much content + truncated_at_sentence = best_text[:last_pos+1] + if count_tokens(truncated_at_sentence, model_name) <= target_tokens: + return truncated_at_sentence + + return best_text + def clear(self): """Clear the conversation history.""" if self.backend_instance: From b04e60ca170aeabf71ba54a11ccd90dc29e7c9de Mon Sep 17 00:00:00 2001 From: Filip Michalsky Date: Wed, 30 Jul 2025 21:28:34 -0400 Subject: [PATCH 25/73] add stagehand example --- .../stagehand/1_stagehand_wrapper_agent.py | 257 +++++++++++++ examples/stagehand/2_stagehand_tools_agent.py | 332 ++++++++++++++++ examples/stagehand/3_stagehand_mcp_agent.py | 252 ++++++++++++ .../4_stagehand_multi_agent_workflow.py | 359 ++++++++++++++++++ examples/stagehand/README.md | 249 ++++++++++++ examples/stagehand/requirements.txt | 13 + tests/stagehand/test_stagehand_integration.py | 356 +++++++++++++++++ 7 files changed, 1818 insertions(+) create mode 100644 examples/stagehand/1_stagehand_wrapper_agent.py create mode 100644 examples/stagehand/2_stagehand_tools_agent.py create mode 100644 examples/stagehand/3_stagehand_mcp_agent.py create mode 100644 examples/stagehand/4_stagehand_multi_agent_workflow.py create mode 100644 examples/stagehand/README.md create mode 100644 examples/stagehand/requirements.txt create mode 100644 tests/stagehand/test_stagehand_integration.py diff --git a/examples/stagehand/1_stagehand_wrapper_agent.py b/examples/stagehand/1_stagehand_wrapper_agent.py new file mode 100644 index 00000000..158549ff --- /dev/null +++ b/examples/stagehand/1_stagehand_wrapper_agent.py @@ -0,0 +1,257 @@ +""" +Stagehand Browser Automation Agent for Swarms +============================================= + +This example demonstrates how to create a Swarms-compatible agent +that wraps Stagehand's browser automation capabilities. + +The StagehandAgent class inherits from the Swarms Agent base class +and implements browser automation through natural language commands. +""" + +import asyncio +import json +import os +from typing import Any, Dict, List, Optional, Union + +from dotenv import load_dotenv +from loguru import logger +from pydantic import BaseModel, Field + +from swarms import Agent as SwarmsAgent +from stagehand import Stagehand, StagehandConfig + +load_dotenv() + + +class WebData(BaseModel): + """Schema for extracted web data.""" + + url: str = Field(..., description="The URL of the page") + title: str = Field(..., description="Page title") + content: str = Field(..., description="Extracted content") + metadata: Dict[str, Any] = Field( + default_factory=dict, description="Additional metadata" + ) + + +class StagehandAgent(SwarmsAgent): + """ + A Swarms agent that integrates Stagehand for browser automation. + + This agent can navigate websites, extract data, perform actions, + and observe page elements using natural language instructions. + """ + + def __init__( + self, + agent_name: str = "StagehandBrowserAgent", + browserbase_api_key: Optional[str] = None, + browserbase_project_id: Optional[str] = None, + model_name: str = "gpt-4o-mini", + model_api_key: Optional[str] = None, + env: str = "LOCAL", # LOCAL or BROWSERBASE + *args, + **kwargs, + ): + """ + Initialize the StagehandAgent. + + Args: + agent_name: Name of the agent + browserbase_api_key: API key for Browserbase (if using cloud) + browserbase_project_id: Project ID for Browserbase + model_name: LLM model to use + model_api_key: API key for the model + env: Environment - LOCAL or BROWSERBASE + """ + # Don't pass stagehand-specific args to parent + super().__init__(agent_name=agent_name, *args, **kwargs) + + self.stagehand_config = StagehandConfig( + env=env, + api_key=browserbase_api_key + or os.getenv("BROWSERBASE_API_KEY"), + project_id=browserbase_project_id + or os.getenv("BROWSERBASE_PROJECT_ID"), + model_name=model_name, + model_api_key=model_api_key or os.getenv("OPENAI_API_KEY"), + ) + self.stagehand = None + self._initialized = False + + async def _init_stagehand(self): + """Initialize Stagehand instance.""" + if not self._initialized: + self.stagehand = Stagehand(self.stagehand_config) + await self.stagehand.init() + self._initialized = True + logger.info(f"Stagehand initialized for {self.agent_name}") + + async def _close_stagehand(self): + """Close Stagehand instance.""" + if self.stagehand and self._initialized: + await self.stagehand.close() + self._initialized = False + logger.info(f"Stagehand closed for {self.agent_name}") + + def run(self, task: str, *args, **kwargs) -> str: + """ + Execute a browser automation task. + + The task string should contain instructions like: + - "Navigate to example.com and extract the main content" + - "Go to google.com and search for 'AI agents'" + - "Extract all company names from https://ycombinator.com" + + Args: + task: Natural language description of the browser task + + Returns: + String result of the task execution + """ + return asyncio.run(self._async_run(task, *args, **kwargs)) + + async def _async_run( + self, task: str, *args, **kwargs + ) -> str: + """Async implementation of run method.""" + try: + await self._init_stagehand() + + # Parse the task to determine actions + result = await self._execute_browser_task(task) + + return json.dumps(result, indent=2) + + except Exception as e: + logger.error(f"Error in browser task: {str(e)}") + return f"Error executing browser task: {str(e)}" + finally: + # Keep browser open for potential follow-up tasks + pass + + async def _execute_browser_task( + self, task: str + ) -> Dict[str, Any]: + """ + Execute a browser task based on natural language instructions. + + This method interprets the task and calls appropriate Stagehand methods. + """ + page = self.stagehand.page + result = {"task": task, "status": "completed", "data": {}} + + # Determine if task involves navigation + if any( + keyword in task.lower() + for keyword in ["navigate", "go to", "visit", "open"] + ): + # Extract URL from task + import re + + url_pattern = r"https?://[^\s]+" + urls = re.findall(url_pattern, task) + if not urls and any( + domain in task for domain in [".com", ".org", ".net"] + ): + # Try to extract domain names + domain_pattern = r"(\w+\.\w+)" + domains = re.findall(domain_pattern, task) + if domains: + urls = [f"https://{domain}" for domain in domains] + + if urls: + url = urls[0] + await page.goto(url) + result["data"]["navigated_to"] = url + logger.info(f"Navigated to {url}") + + # Determine action type + if "extract" in task.lower(): + # Perform extraction + extraction_prompt = task.replace("extract", "").strip() + extracted = await page.extract(extraction_prompt) + result["data"]["extracted"] = extracted + result["action"] = "extract" + + elif "click" in task.lower() or "press" in task.lower(): + # Perform action + action_result = await page.act(task) + result["data"]["action_performed"] = str(action_result) + result["action"] = "act" + + elif "search" in task.lower(): + # Perform search action + search_query = task.split("search for")[-1].strip().strip("'\"") + # First, find the search box + search_box = await page.observe("find the search input field") + if search_box: + # Click on search box and type + await page.act(f"click on {search_box[0]}") + await page.act(f"type '{search_query}'") + await page.act("press Enter") + result["data"]["search_query"] = search_query + result["action"] = "search" + + elif "observe" in task.lower() or "find" in task.lower(): + # Perform observation + observation = await page.observe(task) + result["data"]["observation"] = [ + {"description": obs.description, "selector": obs.selector} + for obs in observation + ] + result["action"] = "observe" + + else: + # General action + action_result = await page.act(task) + result["data"]["action_result"] = str(action_result) + result["action"] = "general" + + return result + + def cleanup(self): + """Clean up browser resources.""" + if self._initialized: + asyncio.run(self._close_stagehand()) + + def __del__(self): + """Ensure browser is closed on deletion.""" + self.cleanup() + + +# Example usage +if __name__ == "__main__": + # Create a Stagehand browser agent + browser_agent = StagehandAgent( + agent_name="WebScraperAgent", + model_name="gpt-4o-mini", + env="LOCAL", # Use LOCAL for Playwright, BROWSERBASE for cloud + ) + + # Example 1: Navigate and extract data + print("Example 1: Basic navigation and extraction") + result1 = browser_agent.run( + "Navigate to https://news.ycombinator.com and extract the titles of the top 5 stories" + ) + print(result1) + print("\n" + "=" * 50 + "\n") + + # Example 2: Perform a search + print("Example 2: Search on a website") + result2 = browser_agent.run( + "Go to google.com and search for 'Swarms AI framework'" + ) + print(result2) + print("\n" + "=" * 50 + "\n") + + # Example 3: Extract structured data + print("Example 3: Extract specific information") + result3 = browser_agent.run( + "Navigate to https://example.com and extract the main heading and first paragraph" + ) + print(result3) + + # Clean up + browser_agent.cleanup() \ No newline at end of file diff --git a/examples/stagehand/2_stagehand_tools_agent.py b/examples/stagehand/2_stagehand_tools_agent.py new file mode 100644 index 00000000..15cae06b --- /dev/null +++ b/examples/stagehand/2_stagehand_tools_agent.py @@ -0,0 +1,332 @@ +""" +Stagehand Tools for Swarms Agent +================================= + +This example demonstrates how to create Stagehand browser automation tools +that can be used by a standard Swarms Agent. Each Stagehand method (act, +extract, observe) becomes a separate tool that the agent can use. + +This approach gives the agent more fine-grained control over browser +automation tasks. +""" + +import asyncio +import json +import os +from typing import Any, Dict, List, Optional, Union + +from dotenv import load_dotenv +from loguru import logger +from pydantic import BaseModel, Field + +from swarms import Agent +from swarms.tools.base_tool import BaseTool +from stagehand import Stagehand, StagehandConfig + +load_dotenv() + + +class BrowserState: + """Singleton to manage browser state across tools.""" + + _instance = None + _stagehand = None + _initialized = False + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + async def init_browser( + self, + env: str = "LOCAL", + api_key: Optional[str] = None, + project_id: Optional[str] = None, + model_name: str = "gpt-4o-mini", + model_api_key: Optional[str] = None, + ): + """Initialize the browser if not already initialized.""" + if not self._initialized: + config = StagehandConfig( + env=env, + api_key=api_key or os.getenv("BROWSERBASE_API_KEY"), + project_id=project_id or os.getenv("BROWSERBASE_PROJECT_ID"), + model_name=model_name, + model_api_key=model_api_key or os.getenv("OPENAI_API_KEY"), + ) + self._stagehand = Stagehand(config) + await self._stagehand.init() + self._initialized = True + logger.info("Stagehand browser initialized") + + async def get_page(self): + """Get the current page instance.""" + if not self._initialized: + raise RuntimeError("Browser not initialized. Call init_browser first.") + return self._stagehand.page + + async def close(self): + """Close the browser.""" + if self._initialized and self._stagehand: + await self._stagehand.close() + self._initialized = False + logger.info("Stagehand browser closed") + + +# Browser state instance +browser_state = BrowserState() + + +class NavigateTool(BaseTool): + """Tool for navigating to URLs in the browser.""" + + def __init__(self): + super().__init__( + name="navigate_browser", + description="Navigate to a URL in the browser. Input should be a valid URL starting with http:// or https://", + verbose=True, + ) + + def run(self, url: str) -> str: + """Navigate to the specified URL.""" + return asyncio.run(self._async_run(url)) + + async def _async_run(self, url: str) -> str: + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + # Ensure URL has protocol + if not url.startswith(("http://", "https://")): + url = f"https://{url}" + + await page.goto(url) + return f"Successfully navigated to {url}" + except Exception as e: + logger.error(f"Navigation error: {str(e)}") + return f"Failed to navigate to {url}: {str(e)}" + + +class ActTool(BaseTool): + """Tool for performing actions on web pages.""" + + def __init__(self): + super().__init__( + name="browser_act", + description=( + "Perform an action on the current web page using natural language. " + "Examples: 'click the submit button', 'type hello@example.com in the email field', " + "'scroll down', 'press Enter'" + ), + verbose=True, + ) + + def run(self, action: str) -> str: + """Perform the specified action.""" + return asyncio.run(self._async_run(action)) + + async def _async_run(self, action: str) -> str: + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + result = await page.act(action) + return f"Action performed: {action}. Result: {result}" + except Exception as e: + logger.error(f"Action error: {str(e)}") + return f"Failed to perform action '{action}': {str(e)}" + + +class ExtractTool(BaseTool): + """Tool for extracting data from web pages.""" + + def __init__(self): + super().__init__( + name="browser_extract", + description=( + "Extract information from the current web page using natural language. " + "Examples: 'extract all email addresses', 'get the main article text', " + "'find all product prices', 'extract the page title and meta description'" + ), + verbose=True, + ) + + def run(self, query: str) -> str: + """Extract information based on the query.""" + return asyncio.run(self._async_run(query)) + + async def _async_run(self, query: str) -> str: + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + extracted = await page.extract(query) + + # Convert to JSON string for agent consumption + if isinstance(extracted, (dict, list)): + return json.dumps(extracted, indent=2) + else: + return str(extracted) + except Exception as e: + logger.error(f"Extraction error: {str(e)}") + return f"Failed to extract '{query}': {str(e)}" + + +class ObserveTool(BaseTool): + """Tool for observing elements on web pages.""" + + def __init__(self): + super().__init__( + name="browser_observe", + description=( + "Observe and find elements on the current web page using natural language. " + "Returns information about elements including their selectors. " + "Examples: 'find the search box', 'locate the submit button', " + "'find all navigation links'" + ), + verbose=True, + ) + + def run(self, query: str) -> str: + """Observe elements based on the query.""" + return asyncio.run(self._async_run(query)) + + async def _async_run(self, query: str) -> str: + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + observations = await page.observe(query) + + # Format observations for readability + result = [] + for obs in observations: + result.append({ + "description": obs.description, + "selector": obs.selector, + "method": obs.method + }) + + return json.dumps(result, indent=2) + except Exception as e: + logger.error(f"Observation error: {str(e)}") + return f"Failed to observe '{query}': {str(e)}" + + +class ScreenshotTool(BaseTool): + """Tool for taking screenshots of the current page.""" + + def __init__(self): + super().__init__( + name="browser_screenshot", + description="Take a screenshot of the current web page. Optionally provide a filename.", + verbose=True, + ) + + def run(self, filename: str = "screenshot.png") -> str: + """Take a screenshot.""" + return asyncio.run(self._async_run(filename)) + + async def _async_run(self, filename: str) -> str: + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + # Ensure .png extension + if not filename.endswith(".png"): + filename += ".png" + + # Get the underlying Playwright page + playwright_page = page.page + await playwright_page.screenshot(path=filename) + + return f"Screenshot saved to {filename}" + except Exception as e: + logger.error(f"Screenshot error: {str(e)}") + return f"Failed to take screenshot: {str(e)}" + + +class CloseBrowserTool(BaseTool): + """Tool for closing the browser.""" + + def __init__(self): + super().__init__( + name="close_browser", + description="Close the browser when done with automation tasks", + verbose=True, + ) + + def run(self, *args) -> str: + """Close the browser.""" + return asyncio.run(self._async_run()) + + async def _async_run(self) -> str: + try: + await browser_state.close() + return "Browser closed successfully" + except Exception as e: + logger.error(f"Close browser error: {str(e)}") + return f"Failed to close browser: {str(e)}" + + +# Example usage +if __name__ == "__main__": + # Create browser automation tools + navigate_tool = NavigateTool() + act_tool = ActTool() + extract_tool = ExtractTool() + observe_tool = ObserveTool() + screenshot_tool = ScreenshotTool() + close_browser_tool = CloseBrowserTool() + + # Create a Swarms agent with browser tools + browser_agent = Agent( + agent_name="BrowserAutomationAgent", + model_name="gpt-4o-mini", + max_loops=1, + tools=[ + navigate_tool, + act_tool, + extract_tool, + observe_tool, + screenshot_tool, + close_browser_tool, + ], + system_prompt="""You are a web browser automation specialist. You can: + 1. Navigate to websites using the navigate_browser tool + 2. Perform actions like clicking and typing using the browser_act tool + 3. Extract information from pages using the browser_extract tool + 4. Find and observe elements using the browser_observe tool + 5. Take screenshots using the browser_screenshot tool + 6. Close the browser when done using the close_browser tool + + Always start by navigating to a URL before trying to interact with a page. + Be specific in your actions and extractions. When done with tasks, close the browser.""", + ) + + # Example 1: Research task + print("Example 1: Automated web research") + result1 = browser_agent.run( + "Go to hackernews (news.ycombinator.com) and extract the titles of the top 5 stories. Then take a screenshot." + ) + print(result1) + print("\n" + "=" * 50 + "\n") + + # Example 2: Search task + print("Example 2: Perform a web search") + result2 = browser_agent.run( + "Navigate to google.com, search for 'Python web scraping best practices', and extract the first 3 search result titles" + ) + print(result2) + print("\n" + "=" * 50 + "\n") + + # Example 3: Form interaction + print("Example 3: Interact with a form") + result3 = browser_agent.run( + "Go to example.com and observe what elements are on the page. Then extract all the text content." + ) + print(result3) + + # Clean up + browser_agent.run("Close the browser") \ No newline at end of file diff --git a/examples/stagehand/3_stagehand_mcp_agent.py b/examples/stagehand/3_stagehand_mcp_agent.py new file mode 100644 index 00000000..4483a497 --- /dev/null +++ b/examples/stagehand/3_stagehand_mcp_agent.py @@ -0,0 +1,252 @@ +""" +Stagehand MCP Server Integration with Swarms +============================================ + +This example demonstrates how to use the Stagehand MCP (Model Context Protocol) +server with Swarms agents. The MCP server provides browser automation capabilities +as standardized tools that can be discovered and used by agents. + +Prerequisites: +1. Install and run the Stagehand MCP server: + cd stagehand-mcp-server + npm install + npm run build + npm start + +2. The server will start on http://localhost:3000/sse + +Features: +- Automatic tool discovery from MCP server +- Multi-session browser management +- Built-in screenshot resources +- Prompt templates for common tasks +""" + +import asyncio +import os +from typing import List, Optional + +from dotenv import load_dotenv +from loguru import logger + +from swarms import Agent + +load_dotenv() + + +class StagehandMCPAgent: + """ + A Swarms agent that connects to the Stagehand MCP server + for browser automation capabilities. + """ + + def __init__( + self, + agent_name: str = "StagehandMCPAgent", + mcp_server_url: str = "http://localhost:3000/sse", + model_name: str = "gpt-4o-mini", + max_loops: int = 1, + ): + """ + Initialize the Stagehand MCP Agent. + + Args: + agent_name: Name of the agent + mcp_server_url: URL of the Stagehand MCP server + model_name: LLM model to use + max_loops: Maximum number of reasoning loops + """ + self.agent = Agent( + agent_name=agent_name, + model_name=model_name, + max_loops=max_loops, + # Connect to the Stagehand MCP server + mcp_url=mcp_server_url, + system_prompt="""You are a web browser automation specialist with access to Stagehand MCP tools. + +Available tools from the MCP server: +- navigate: Navigate to a URL +- act: Perform actions on web pages (click, type, etc.) +- extract: Extract data from web pages +- observe: Find and observe elements on pages +- screenshot: Take screenshots +- createSession: Create new browser sessions for parallel tasks +- listSessions: List active browser sessions +- closeSession: Close browser sessions + +For multi-page workflows, you can create multiple sessions. +Always be specific in your actions and extractions. +Remember to close sessions when done with them.""", + verbose=True, + ) + + def run(self, task: str) -> str: + """Run a browser automation task.""" + return self.agent.run(task) + + +class MultiSessionBrowserSwarm: + """ + A multi-agent swarm that uses multiple browser sessions + for parallel web automation tasks. + """ + + def __init__( + self, + mcp_server_url: str = "http://localhost:3000/sse", + num_agents: int = 3, + ): + """ + Initialize a swarm of browser automation agents. + + Args: + mcp_server_url: URL of the Stagehand MCP server + num_agents: Number of agents to create + """ + self.agents = [] + + # Create specialized agents for different tasks + agent_roles = [ + ("DataExtractor", "You specialize in extracting structured data from websites."), + ("FormFiller", "You specialize in filling out forms and interacting with web applications."), + ("WebMonitor", "You specialize in monitoring websites for changes and capturing screenshots."), + ] + + for i in range(min(num_agents, len(agent_roles))): + name, specialization = agent_roles[i] + agent = Agent( + agent_name=f"{name}_{i}", + model_name="gpt-4o-mini", + max_loops=1, + mcp_url=mcp_server_url, + system_prompt=f"""You are a web browser automation specialist. {specialization} + +You have access to Stagehand MCP tools including: +- createSession: Create a new browser session +- navigate_session: Navigate to URLs in a specific session +- act_session: Perform actions in a specific session +- extract_session: Extract data from a specific session +- observe_session: Observe elements in a specific session +- closeSession: Close a session when done + +Always create your own session for tasks to work independently from other agents.""", + verbose=True, + ) + self.agents.append(agent) + + def distribute_tasks(self, tasks: List[str]) -> List[str]: + """Distribute tasks among agents.""" + results = [] + + # Distribute tasks round-robin among agents + for i, task in enumerate(tasks): + agent_idx = i % len(self.agents) + agent = self.agents[agent_idx] + + logger.info(f"Assigning task to {agent.agent_name}: {task}") + result = agent.run(task) + results.append(result) + + return results + + +# Example usage +if __name__ == "__main__": + print("=" * 70) + print("Stagehand MCP Server Integration Examples") + print("=" * 70) + print("\nMake sure the Stagehand MCP server is running on http://localhost:3000/sse") + print("Run: cd stagehand-mcp-server && npm start\n") + + # Example 1: Single agent with MCP tools + print("\nExample 1: Single Agent with MCP Tools") + print("-" * 40) + + mcp_agent = StagehandMCPAgent( + agent_name="WebResearchAgent", + mcp_server_url="http://localhost:3000/sse", + ) + + # Research task using MCP tools + result1 = mcp_agent.run( + """Navigate to news.ycombinator.com and extract the following: + 1. The titles of the top 5 stories + 2. Their points/scores + 3. Number of comments for each + Then take a screenshot of the page.""" + ) + print(f"Result: {result1}") + + print("\n" + "=" * 70 + "\n") + + # Example 2: Multi-session parallel browsing + print("Example 2: Multi-Session Parallel Browsing") + print("-" * 40) + + parallel_agent = StagehandMCPAgent( + agent_name="ParallelBrowserAgent", + mcp_server_url="http://localhost:3000/sse", + ) + + result2 = parallel_agent.run( + """Create 3 browser sessions and perform these tasks in parallel: + 1. Session 1: Go to github.com/trending and extract the top 3 trending repositories + 2. Session 2: Go to reddit.com/r/programming and extract the top 3 posts + 3. Session 3: Go to stackoverflow.com and extract the featured questions + + After extracting data from all sessions, close them.""" + ) + print(f"Result: {result2}") + + print("\n" + "=" * 70 + "\n") + + # Example 3: Multi-agent browser swarm + print("Example 3: Multi-Agent Browser Swarm") + print("-" * 40) + + # Create a swarm of specialized browser agents + browser_swarm = MultiSessionBrowserSwarm( + mcp_server_url="http://localhost:3000/sse", + num_agents=3, + ) + + # Define tasks for the swarm + swarm_tasks = [ + "Create a session, navigate to python.org, and extract information about the latest Python version and its key features", + "Create a session, go to npmjs.com, search for 'stagehand', and extract information about the package including version and description", + "Create a session, visit playwright.dev, and extract the main features and benefits listed on the homepage", + ] + + print("Distributing tasks to browser swarm...") + swarm_results = browser_swarm.distribute_tasks(swarm_tasks) + + for i, result in enumerate(swarm_results): + print(f"\nTask {i+1} Result: {result}") + + print("\n" + "=" * 70 + "\n") + + # Example 4: Complex workflow with session management + print("Example 4: Complex Multi-Page Workflow") + print("-" * 40) + + workflow_agent = StagehandMCPAgent( + agent_name="WorkflowAgent", + mcp_server_url="http://localhost:3000/sse", + max_loops=2, # Allow more complex reasoning + ) + + result4 = workflow_agent.run( + """Perform a comprehensive analysis of AI frameworks: + 1. Create a new session + 2. Navigate to github.com/huggingface/transformers and extract the star count and latest release info + 3. In the same session, navigate to github.com/openai/gpt-3 and extract similar information + 4. Navigate to github.com/anthropics/anthropic-sdk-python and extract repository statistics + 5. Take screenshots of each repository page + 6. Compile a comparison report of all three repositories + 7. Close the session when done""" + ) + print(f"Result: {result4}") + + print("\n" + "=" * 70) + print("All examples completed!") + print("=" * 70) \ No newline at end of file diff --git a/examples/stagehand/4_stagehand_multi_agent_workflow.py b/examples/stagehand/4_stagehand_multi_agent_workflow.py new file mode 100644 index 00000000..31bb1d21 --- /dev/null +++ b/examples/stagehand/4_stagehand_multi_agent_workflow.py @@ -0,0 +1,359 @@ +""" +Stagehand Multi-Agent Browser Automation Workflows +================================================= + +This example demonstrates advanced multi-agent workflows using Stagehand +for complex browser automation scenarios. It shows how multiple agents +can work together to accomplish sophisticated web tasks. + +Use cases: +1. E-commerce price monitoring across multiple sites +2. Competitive analysis and market research +3. Automated testing and validation workflows +4. Data aggregation from multiple sources +""" + +import asyncio +import json +import os +from datetime import datetime +from typing import Any, Dict, List + +from dotenv import load_dotenv +from loguru import logger +from pydantic import BaseModel, Field + +from swarms import Agent, SequentialWorkflow, ConcurrentWorkflow +from swarms.structs.agent_rearrange import AgentRearrange +from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + +load_dotenv() + + +# Pydantic models for structured data +class ProductInfo(BaseModel): + """Product information schema.""" + name: str = Field(..., description="Product name") + price: float = Field(..., description="Product price") + availability: str = Field(..., description="Availability status") + url: str = Field(..., description="Product URL") + screenshot_path: Optional[str] = Field(None, description="Screenshot file path") + + +class MarketAnalysis(BaseModel): + """Market analysis report schema.""" + timestamp: datetime = Field(default_factory=datetime.now) + products: List[ProductInfo] = Field(..., description="List of products analyzed") + price_range: Dict[str, float] = Field(..., description="Min and max prices") + recommendations: List[str] = Field(..., description="Analysis recommendations") + + +# Specialized browser agents +class ProductScraperAgent(StagehandAgent): + """Specialized agent for scraping product information.""" + + def __init__(self, site_name: str, *args, **kwargs): + super().__init__( + agent_name=f"ProductScraper_{site_name}", + *args, + **kwargs + ) + self.site_name = site_name + + +class PriceMonitorAgent(StagehandAgent): + """Specialized agent for monitoring price changes.""" + + def __init__(self, *args, **kwargs): + super().__init__( + agent_name="PriceMonitorAgent", + *args, + **kwargs + ) + + +# Example 1: E-commerce Price Comparison Workflow +def create_price_comparison_workflow(): + """ + Create a workflow that compares prices across multiple e-commerce sites. + """ + + # Create specialized agents for different sites + amazon_agent = StagehandAgent( + agent_name="AmazonScraperAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + ebay_agent = StagehandAgent( + agent_name="EbayScraperAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + analysis_agent = Agent( + agent_name="PriceAnalysisAgent", + model_name="gpt-4o-mini", + system_prompt="""You are a price analysis expert. Analyze product prices from multiple sources + and provide insights on the best deals, price trends, and recommendations. + Focus on value for money and highlight any significant price differences.""", + ) + + # Create concurrent workflow for parallel scraping + scraping_workflow = ConcurrentWorkflow( + agents=[amazon_agent, ebay_agent], + max_loops=1, + verbose=True, + ) + + # Create sequential workflow: scrape -> analyze + full_workflow = SequentialWorkflow( + agents=[scraping_workflow, analysis_agent], + max_loops=1, + verbose=True, + ) + + return full_workflow + + +# Example 2: Competitive Analysis Workflow +def create_competitive_analysis_workflow(): + """ + Create a workflow for competitive analysis across multiple company websites. + """ + + # Agent for extracting company information + company_researcher = StagehandAgent( + agent_name="CompanyResearchAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Agent for analyzing social media presence + social_media_agent = StagehandAgent( + agent_name="SocialMediaAnalysisAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Agent for compiling competitive analysis report + report_compiler = Agent( + agent_name="CompetitiveAnalysisReporter", + model_name="gpt-4o-mini", + system_prompt="""You are a competitive analysis expert. Compile comprehensive reports + based on company information and social media presence data. Identify strengths, + weaknesses, and market positioning for each company.""", + ) + + # Create agent rearrange for flexible routing + workflow_pattern = "company_researcher -> social_media_agent -> report_compiler" + + competitive_workflow = AgentRearrange( + agents=[company_researcher, social_media_agent, report_compiler], + flow=workflow_pattern, + verbose=True, + ) + + return competitive_workflow + + +# Example 3: Automated Testing Workflow +def create_automated_testing_workflow(): + """ + Create a workflow for automated web application testing. + """ + + # Agent for UI testing + ui_tester = StagehandAgent( + agent_name="UITestingAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Agent for form validation testing + form_tester = StagehandAgent( + agent_name="FormValidationAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Agent for accessibility testing + accessibility_tester = StagehandAgent( + agent_name="AccessibilityTestingAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Agent for compiling test results + test_reporter = Agent( + agent_name="TestReportCompiler", + model_name="gpt-4o-mini", + system_prompt="""You are a QA test report specialist. Compile test results from + UI, form validation, and accessibility testing into a comprehensive report. + Highlight any failures, warnings, and provide recommendations for fixes.""", + ) + + # Concurrent testing followed by report generation + testing_workflow = ConcurrentWorkflow( + agents=[ui_tester, form_tester, accessibility_tester], + max_loops=1, + verbose=True, + ) + + full_test_workflow = SequentialWorkflow( + agents=[testing_workflow, test_reporter], + max_loops=1, + verbose=True, + ) + + return full_test_workflow + + +# Example 4: News Aggregation and Sentiment Analysis +def create_news_aggregation_workflow(): + """ + Create a workflow for news aggregation and sentiment analysis. + """ + + # Multiple news scraper agents + news_scrapers = [] + news_sites = [ + ("TechCrunch", "https://techcrunch.com"), + ("HackerNews", "https://news.ycombinator.com"), + ("Reddit", "https://reddit.com/r/technology"), + ] + + for site_name, url in news_sites: + scraper = StagehandAgent( + agent_name=f"{site_name}Scraper", + model_name="gpt-4o-mini", + env="LOCAL", + ) + news_scrapers.append(scraper) + + # Sentiment analysis agent + sentiment_analyzer = Agent( + agent_name="SentimentAnalyzer", + model_name="gpt-4o-mini", + system_prompt="""You are a sentiment analysis expert. Analyze news articles and posts + to determine overall sentiment (positive, negative, neutral) and identify key themes + and trends in the technology sector.""", + ) + + # Trend identification agent + trend_identifier = Agent( + agent_name="TrendIdentifier", + model_name="gpt-4o-mini", + system_prompt="""You are a trend analysis expert. Based on aggregated news and sentiment + data, identify emerging trends, hot topics, and potential market movements in the + technology sector.""", + ) + + # Create workflow: parallel scraping -> sentiment analysis -> trend identification + scraping_workflow = ConcurrentWorkflow( + agents=news_scrapers, + max_loops=1, + verbose=True, + ) + + analysis_workflow = SequentialWorkflow( + agents=[scraping_workflow, sentiment_analyzer, trend_identifier], + max_loops=1, + verbose=True, + ) + + return analysis_workflow + + +# Main execution examples +if __name__ == "__main__": + print("=" * 70) + print("Stagehand Multi-Agent Workflow Examples") + print("=" * 70) + + # Example 1: Price Comparison + print("\nExample 1: E-commerce Price Comparison") + print("-" * 40) + + price_workflow = create_price_comparison_workflow() + + # Search for a specific product across multiple sites + price_result = price_workflow.run( + """Search for 'iPhone 15 Pro Max 256GB' on: + 1. Amazon - extract price, availability, and seller information + 2. eBay - extract price range, number of listings, and average price + Take screenshots of search results from both sites. + Compare the prices and provide recommendations on where to buy.""" + ) + print(f"Price Comparison Result:\n{price_result}") + + print("\n" + "=" * 70 + "\n") + + # Example 2: Competitive Analysis + print("Example 2: Competitive Analysis") + print("-" * 40) + + competitive_workflow = create_competitive_analysis_workflow() + + competitive_result = competitive_workflow.run( + """Analyze these three AI companies: + 1. OpenAI - visit openai.com and extract mission, products, and recent announcements + 2. Anthropic - visit anthropic.com and extract their AI safety approach and products + 3. DeepMind - visit deepmind.com and extract research focus and achievements + + Then check their Twitter/X presence and recent posts. + Compile a competitive analysis report comparing their market positioning.""" + ) + print(f"Competitive Analysis Result:\n{competitive_result}") + + print("\n" + "=" * 70 + "\n") + + # Example 3: Automated Testing + print("Example 3: Automated Web Testing") + print("-" * 40) + + testing_workflow = create_automated_testing_workflow() + + test_result = testing_workflow.run( + """Test the website example.com: + 1. UI Testing: Check if all main navigation links work, images load, and layout is responsive + 2. Form Testing: If there are any forms, test with valid and invalid inputs + 3. Accessibility: Check for alt texts, ARIA labels, and keyboard navigation + + Take screenshots of any issues found and compile a comprehensive test report.""" + ) + print(f"Test Results:\n{test_result}") + + print("\n" + "=" * 70 + "\n") + + # Example 4: News Aggregation + print("Example 4: Tech News Aggregation and Analysis") + print("-" * 40) + + news_workflow = create_news_aggregation_workflow() + + news_result = news_workflow.run( + """For each news source: + 1. TechCrunch: Extract the top 5 headlines about AI or machine learning + 2. HackerNews: Extract the top 5 posts related to AI/ML with most points + 3. Reddit r/technology: Extract top 5 posts about AI from the past week + + Analyze sentiment and identify emerging trends in AI technology.""" + ) + print(f"News Analysis Result:\n{news_result}") + + # Cleanup all browser instances + print("\n" + "=" * 70) + print("Cleaning up browser instances...") + + # Clean up agents + for agent in price_workflow.agents: + if isinstance(agent, StagehandAgent): + agent.cleanup() + elif hasattr(agent, 'agents'): # For nested workflows + for sub_agent in agent.agents: + if isinstance(sub_agent, StagehandAgent): + sub_agent.cleanup() + + print("All workflows completed!") + print("=" * 70) \ No newline at end of file diff --git a/examples/stagehand/README.md b/examples/stagehand/README.md new file mode 100644 index 00000000..2d1ee341 --- /dev/null +++ b/examples/stagehand/README.md @@ -0,0 +1,249 @@ +# Stagehand Browser Automation Integration for Swarms + +This directory contains examples demonstrating how to integrate [Stagehand](https://github.com/browserbase/stagehand), an AI-powered browser automation framework, with the Swarms multi-agent framework. + +## Overview + +Stagehand provides natural language browser automation capabilities that can be seamlessly integrated into Swarms agents. This integration enables: + +- 🌐 **Natural Language Web Automation**: Use simple commands like "click the submit button" or "extract product prices" +- 🤖 **Multi-Agent Browser Workflows**: Multiple agents can automate different websites simultaneously +- 🔧 **Flexible Integration Options**: Use as a wrapped agent, individual tools, or via MCP server +- 📊 **Complex Automation Scenarios**: E-commerce monitoring, competitive analysis, automated testing, and more + +## Examples + +### 1. Stagehand Wrapper Agent (`1_stagehand_wrapper_agent.py`) + +The simplest integration - wraps Stagehand as a Swarms-compatible agent. + +```python +from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + +# Create a browser automation agent +browser_agent = StagehandAgent( + agent_name="WebScraperAgent", + model_name="gpt-4o-mini", + env="LOCAL", # or "BROWSERBASE" for cloud execution +) + +# Use natural language to control the browser +result = browser_agent.run( + "Navigate to news.ycombinator.com and extract the top 5 story titles" +) +``` + +**Features:** +- Inherits from Swarms `Agent` base class +- Automatic browser lifecycle management +- Natural language task interpretation +- Support for both local (Playwright) and cloud (Browserbase) execution + +### 2. Stagehand as Tools (`2_stagehand_tools_agent.py`) + +Provides fine-grained control by exposing Stagehand methods as individual tools. + +```python +from swarms import Agent +from examples.stagehand.stagehand_tools_agent import ( + NavigateTool, ActTool, ExtractTool, ObserveTool, ScreenshotTool +) + +# Create agent with browser tools +browser_agent = Agent( + agent_name="BrowserAutomationAgent", + model_name="gpt-4o-mini", + tools=[ + NavigateTool(), + ActTool(), + ExtractTool(), + ObserveTool(), + ScreenshotTool(), + ], +) + +# Agent can now use tools strategically +result = browser_agent.run( + "Go to google.com, search for 'Python tutorials', and extract the first 3 results" +) +``` + +**Available Tools:** +- `NavigateTool`: Navigate to URLs +- `ActTool`: Perform actions (click, type, scroll) +- `ExtractTool`: Extract data from pages +- `ObserveTool`: Find elements on pages +- `ScreenshotTool`: Capture screenshots +- `CloseBrowserTool`: Clean up browser resources + +### 3. Stagehand MCP Server (`3_stagehand_mcp_agent.py`) + +Integrates with Stagehand's Model Context Protocol (MCP) server for standardized tool access. + +```python +from examples.stagehand.stagehand_mcp_agent import StagehandMCPAgent + +# Connect to Stagehand MCP server +mcp_agent = StagehandMCPAgent( + agent_name="WebResearchAgent", + mcp_server_url="http://localhost:3000/sse", +) + +# Use MCP tools including multi-session management +result = mcp_agent.run(""" + Create 3 browser sessions and: + 1. Session 1: Check Python.org for latest version + 2. Session 2: Check PyPI for trending packages + 3. Session 3: Check GitHub Python trending repos + Compile a Python ecosystem status report. +""") +``` + +**MCP Features:** +- Automatic tool discovery +- Multi-session browser management +- Built-in screenshot resources +- Prompt templates for common tasks + +### 4. Multi-Agent Workflows (`4_stagehand_multi_agent_workflow.py`) + +Demonstrates complex multi-agent browser automation scenarios. + +```python +from examples.stagehand.stagehand_multi_agent_workflow import ( + create_price_comparison_workflow, + create_competitive_analysis_workflow, + create_automated_testing_workflow, + create_news_aggregation_workflow +) + +# Price comparison across multiple e-commerce sites +price_workflow = create_price_comparison_workflow() +result = price_workflow.run( + "Compare prices for iPhone 15 Pro on Amazon and eBay" +) + +# Competitive analysis of multiple companies +competitive_workflow = create_competitive_analysis_workflow() +result = competitive_workflow.run( + "Analyze OpenAI, Anthropic, and DeepMind websites and social media" +) +``` + +**Workflow Examples:** +- **E-commerce Monitoring**: Track prices across multiple sites +- **Competitive Analysis**: Research competitors' websites and social media +- **Automated Testing**: UI, form validation, and accessibility testing +- **News Aggregation**: Collect and analyze news from multiple sources + +## Setup + +### Prerequisites + +1. **Install Swarms and Stagehand:** +```bash +pip install swarms stagehand +``` + +2. **Set up environment variables:** +```bash +# For local browser automation (using Playwright) +export OPENAI_API_KEY="your-openai-key" + +# For cloud browser automation (using Browserbase) +export BROWSERBASE_API_KEY="your-browserbase-key" +export BROWSERBASE_PROJECT_ID="your-project-id" +``` + +3. **For MCP Server examples:** +```bash +# Install and run the Stagehand MCP server +cd stagehand-mcp-server +npm install +npm run build +npm start +``` + +## Use Cases + +### E-commerce Automation +- Price monitoring and comparison +- Inventory tracking +- Automated purchasing workflows +- Review aggregation + +### Research and Analysis +- Competitive intelligence gathering +- Market research automation +- Social media monitoring +- News and trend analysis + +### Quality Assurance +- Automated UI testing +- Cross-browser compatibility testing +- Form validation testing +- Accessibility compliance checking + +### Data Collection +- Web scraping at scale +- Real-time data monitoring +- Structured data extraction +- Screenshot documentation + +## Best Practices + +1. **Resource Management**: Always clean up browser instances when done +```python +browser_agent.cleanup() # For wrapper agents +``` + +2. **Error Handling**: Stagehand includes self-healing capabilities, but wrap critical operations in try-except blocks + +3. **Parallel Execution**: Use `ConcurrentWorkflow` for simultaneous browser automation across multiple sites + +4. **Session Management**: For complex multi-page workflows, use the MCP server's session management capabilities + +5. **Rate Limiting**: Be respectful of websites - add delays between requests when necessary + +## Testing + +Run the test suite to verify the integration: + +```bash +pytest tests/stagehand/test_stagehand_integration.py -v +``` + +## Troubleshooting + +### Common Issues + +1. **Browser not starting**: Ensure Playwright is properly installed +```bash +playwright install +``` + +2. **MCP connection failed**: Verify the MCP server is running on the correct port + +3. **Timeout errors**: Increase timeout in StagehandConfig or agent initialization + +### Debug Mode + +Enable verbose logging: +```python +agent = StagehandAgent( + agent_name="DebugAgent", + verbose=True, # Enable detailed logging +) +``` + +## Contributing + +We welcome contributions! Please: +1. Follow the existing code style +2. Add tests for new features +3. Update documentation +4. Submit PRs with clear descriptions + +## License + +These examples are provided under the same license as the Swarms framework. Stagehand is licensed separately - see [Stagehand's repository](https://github.com/browserbase/stagehand) for details. \ No newline at end of file diff --git a/examples/stagehand/requirements.txt b/examples/stagehand/requirements.txt new file mode 100644 index 00000000..32f493b0 --- /dev/null +++ b/examples/stagehand/requirements.txt @@ -0,0 +1,13 @@ +# Requirements for Stagehand integration examples +swarms>=8.0.0 +stagehand>=0.1.0 +python-dotenv>=1.0.0 +pydantic>=2.0.0 +loguru>=0.7.0 + +# For MCP server examples (optional) +httpx>=0.24.0 + +# For testing +pytest>=7.0.0 +pytest-asyncio>=0.21.0 \ No newline at end of file diff --git a/tests/stagehand/test_stagehand_integration.py b/tests/stagehand/test_stagehand_integration.py new file mode 100644 index 00000000..c1e913ae --- /dev/null +++ b/tests/stagehand/test_stagehand_integration.py @@ -0,0 +1,356 @@ +""" +Tests for Stagehand Integration with Swarms +========================================== + +This module contains tests for the Stagehand browser automation +integration with the Swarms framework. +""" + +import asyncio +import json +import pytest +from unittest.mock import AsyncMock, MagicMock, patch + +from swarms import Agent +from swarms.tools.base_tool import BaseTool + + +# Mock Stagehand classes +class MockObserveResult: + def __init__(self, description, selector, method="click"): + self.description = description + self.selector = selector + self.method = method + + +class MockStagehandPage: + async def goto(self, url): + return None + + async def act(self, action): + return f"Performed action: {action}" + + async def extract(self, query): + return {"extracted": query, "data": ["item1", "item2"]} + + async def observe(self, query): + return [ + MockObserveResult("Search box", "#search-input"), + MockObserveResult("Submit button", "#submit-btn"), + ] + + +class MockStagehand: + def __init__(self, config): + self.config = config + self.page = MockStagehandPage() + + async def init(self): + pass + + async def close(self): + pass + + +# Test StagehandAgent wrapper +class TestStagehandAgent: + """Test the StagehandAgent wrapper class.""" + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_agent_initialization(self): + """Test that StagehandAgent initializes correctly.""" + from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + + agent = StagehandAgent( + agent_name="TestAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + assert agent.agent_name == "TestAgent" + assert agent.stagehand_config.env == "LOCAL" + assert agent.stagehand_config.model_name == "gpt-4o-mini" + assert not agent._initialized + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_navigation_task(self): + """Test navigation and extraction task.""" + from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + + agent = StagehandAgent( + agent_name="TestAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + result = agent.run("Navigate to example.com and extract the main content") + + # Parse result + result_data = json.loads(result) + assert result_data["status"] == "completed" + assert "navigated_to" in result_data["data"] + assert result_data["data"]["navigated_to"] == "https://example.com" + assert "extracted" in result_data["data"] + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_search_task(self): + """Test search functionality.""" + from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + + agent = StagehandAgent( + agent_name="TestAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + result = agent.run("Go to google.com and search for 'test query'") + + result_data = json.loads(result) + assert result_data["status"] == "completed" + assert result_data["data"]["search_query"] == "test query" + assert result_data["action"] == "search" + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_cleanup(self): + """Test that cleanup properly closes browser.""" + from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + + agent = StagehandAgent( + agent_name="TestAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Initialize the agent + agent.run("Navigate to example.com") + assert agent._initialized + + # Cleanup + agent.cleanup() + + # After cleanup, should be able to run again + result = agent.run("Navigate to example.com") + assert result is not None + + +# Test Stagehand Tools +class TestStagehandTools: + """Test individual Stagehand tools.""" + + @patch('examples.stagehand.stagehand_tools_agent.browser_state') + async def test_navigate_tool(self, mock_browser_state): + """Test NavigateTool functionality.""" + from examples.stagehand.stagehand_tools_agent import NavigateTool + + # Setup mock + mock_page = AsyncMock() + mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.init_browser = AsyncMock() + + tool = NavigateTool() + result = await tool._async_run("https://example.com") + + assert "Successfully navigated to https://example.com" in result + mock_page.goto.assert_called_once_with("https://example.com") + + @patch('examples.stagehand.stagehand_tools_agent.browser_state') + async def test_act_tool(self, mock_browser_state): + """Test ActTool functionality.""" + from examples.stagehand.stagehand_tools_agent import ActTool + + # Setup mock + mock_page = AsyncMock() + mock_page.act = AsyncMock(return_value="Action completed") + mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.init_browser = AsyncMock() + + tool = ActTool() + result = await tool._async_run("click the button") + + assert "Action performed" in result + assert "click the button" in result + mock_page.act.assert_called_once_with("click the button") + + @patch('examples.stagehand.stagehand_tools_agent.browser_state') + async def test_extract_tool(self, mock_browser_state): + """Test ExtractTool functionality.""" + from examples.stagehand.stagehand_tools_agent import ExtractTool + + # Setup mock + mock_page = AsyncMock() + mock_page.extract = AsyncMock(return_value={"title": "Test Page", "content": "Test content"}) + mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.init_browser = AsyncMock() + + tool = ExtractTool() + result = await tool._async_run("extract the page title") + + # Result should be JSON string + parsed_result = json.loads(result) + assert parsed_result["title"] == "Test Page" + assert parsed_result["content"] == "Test content" + + @patch('examples.stagehand.stagehand_tools_agent.browser_state') + async def test_observe_tool(self, mock_browser_state): + """Test ObserveTool functionality.""" + from examples.stagehand.stagehand_tools_agent import ObserveTool + + # Setup mock + mock_page = AsyncMock() + mock_observations = [ + MockObserveResult("Search input", "#search"), + MockObserveResult("Submit button", "#submit"), + ] + mock_page.observe = AsyncMock(return_value=mock_observations) + mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.init_browser = AsyncMock() + + tool = ObserveTool() + result = await tool._async_run("find the search box") + + # Result should be JSON string + parsed_result = json.loads(result) + assert len(parsed_result) == 2 + assert parsed_result[0]["description"] == "Search input" + assert parsed_result[0]["selector"] == "#search" + + +# Test MCP integration +class TestStagehandMCP: + """Test Stagehand MCP server integration.""" + + def test_mcp_agent_initialization(self): + """Test that MCP agent initializes with correct parameters.""" + from examples.stagehand.stagehand_mcp_agent import StagehandMCPAgent + + mcp_agent = StagehandMCPAgent( + agent_name="TestMCPAgent", + mcp_server_url="http://localhost:3000/sse", + model_name="gpt-4o-mini", + ) + + assert mcp_agent.agent.agent_name == "TestMCPAgent" + assert mcp_agent.agent.mcp_url == "http://localhost:3000/sse" + assert mcp_agent.agent.model_name == "gpt-4o-mini" + + def test_multi_session_swarm_creation(self): + """Test multi-session browser swarm creation.""" + from examples.stagehand.stagehand_mcp_agent import MultiSessionBrowserSwarm + + swarm = MultiSessionBrowserSwarm( + mcp_server_url="http://localhost:3000/sse", + num_agents=3, + ) + + assert len(swarm.agents) == 3 + assert swarm.agents[0].agent_name == "DataExtractor_0" + assert swarm.agents[1].agent_name == "FormFiller_1" + assert swarm.agents[2].agent_name == "WebMonitor_2" + + @patch('swarms.Agent.run') + def test_task_distribution(self, mock_run): + """Test task distribution among swarm agents.""" + from examples.stagehand.stagehand_mcp_agent import MultiSessionBrowserSwarm + + mock_run.return_value = "Task completed" + + swarm = MultiSessionBrowserSwarm(num_agents=2) + tasks = ["Task 1", "Task 2", "Task 3"] + + results = swarm.distribute_tasks(tasks) + + assert len(results) == 3 + assert all(result == "Task completed" for result in results) + assert mock_run.call_count == 3 + + +# Test multi-agent workflows +class TestMultiAgentWorkflows: + """Test multi-agent workflow configurations.""" + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_price_comparison_workflow_creation(self): + """Test creation of price comparison workflow.""" + from examples.stagehand.stagehand_multi_agent_workflow import create_price_comparison_workflow + + workflow = create_price_comparison_workflow() + + # Should be a SequentialWorkflow with 2 agents + assert len(workflow.agents) == 2 + # First agent should be a ConcurrentWorkflow + assert hasattr(workflow.agents[0], 'agents') + # Second agent should be the analysis agent + assert workflow.agents[1].agent_name == "PriceAnalysisAgent" + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_competitive_analysis_workflow_creation(self): + """Test creation of competitive analysis workflow.""" + from examples.stagehand.stagehand_multi_agent_workflow import create_competitive_analysis_workflow + + workflow = create_competitive_analysis_workflow() + + # Should have 3 agents in the rearrange pattern + assert len(workflow.agents) == 3 + assert workflow.flow == "company_researcher -> social_media_agent -> report_compiler" + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_automated_testing_workflow_creation(self): + """Test creation of automated testing workflow.""" + from examples.stagehand.stagehand_multi_agent_workflow import create_automated_testing_workflow + + workflow = create_automated_testing_workflow() + + # Should be a SequentialWorkflow + assert len(workflow.agents) == 2 + # First should be concurrent testing + assert hasattr(workflow.agents[0], 'agents') + assert len(workflow.agents[0].agents) == 3 # UI, Form, Accessibility testers + + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + def test_news_aggregation_workflow_creation(self): + """Test creation of news aggregation workflow.""" + from examples.stagehand.stagehand_multi_agent_workflow import create_news_aggregation_workflow + + workflow = create_news_aggregation_workflow() + + # Should be a SequentialWorkflow with 3 stages + assert len(workflow.agents) == 3 + # First stage should be concurrent scrapers + assert hasattr(workflow.agents[0], 'agents') + assert len(workflow.agents[0].agents) == 3 # 3 news sources + + +# Integration tests +class TestIntegration: + """End-to-end integration tests.""" + + @pytest.mark.asyncio + @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + async def test_full_browser_automation_flow(self): + """Test a complete browser automation flow.""" + from examples.stagehand.stagehand_wrapper_agent import StagehandAgent + + agent = StagehandAgent( + agent_name="IntegrationTestAgent", + model_name="gpt-4o-mini", + env="LOCAL", + ) + + # Test navigation + nav_result = agent.run("Navigate to example.com") + assert "navigated_to" in nav_result + + # Test extraction + extract_result = agent.run("Extract all text from the page") + assert "extracted" in extract_result + + # Test observation + observe_result = agent.run("Find all buttons on the page") + assert "observation" in observe_result + + # Cleanup + agent.cleanup() + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) \ No newline at end of file From 2d7dfca4a4c1980d7f5cee9a0d101b78214d4486 Mon Sep 17 00:00:00 2001 From: Filip Michalsky Date: Wed, 30 Jul 2025 21:51:50 -0400 Subject: [PATCH 26/73] clean up --- .../stagehand/1_stagehand_wrapper_agent.py | 28 +- examples/stagehand/2_stagehand_tools_agent.py | 51 +-- examples/stagehand/3_stagehand_mcp_agent.py | 75 +++-- .../4_stagehand_multi_agent_workflow.py | 144 ++++---- tests/stagehand/test_stagehand_integration.py | 310 +++++++++++------- tests/stagehand/test_stagehand_simple.py | 304 +++++++++++++++++ 6 files changed, 666 insertions(+), 246 deletions(-) create mode 100644 tests/stagehand/test_stagehand_simple.py diff --git a/examples/stagehand/1_stagehand_wrapper_agent.py b/examples/stagehand/1_stagehand_wrapper_agent.py index 158549ff..c4a04906 100644 --- a/examples/stagehand/1_stagehand_wrapper_agent.py +++ b/examples/stagehand/1_stagehand_wrapper_agent.py @@ -12,7 +12,7 @@ and implements browser automation through natural language commands. import asyncio import json import os -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, Optional from dotenv import load_dotenv from loguru import logger @@ -75,7 +75,8 @@ class StagehandAgent(SwarmsAgent): project_id=browserbase_project_id or os.getenv("BROWSERBASE_PROJECT_ID"), model_name=model_name, - model_api_key=model_api_key or os.getenv("OPENAI_API_KEY"), + model_api_key=model_api_key + or os.getenv("OPENAI_API_KEY"), ) self.stagehand = None self._initialized = False @@ -86,7 +87,9 @@ class StagehandAgent(SwarmsAgent): self.stagehand = Stagehand(self.stagehand_config) await self.stagehand.init() self._initialized = True - logger.info(f"Stagehand initialized for {self.agent_name}") + logger.info( + f"Stagehand initialized for {self.agent_name}" + ) async def _close_stagehand(self): """Close Stagehand instance.""" @@ -112,9 +115,7 @@ class StagehandAgent(SwarmsAgent): """ return asyncio.run(self._async_run(task, *args, **kwargs)) - async def _async_run( - self, task: str, *args, **kwargs - ) -> str: + async def _async_run(self, task: str, *args, **kwargs) -> str: """Async implementation of run method.""" try: await self._init_stagehand() @@ -183,9 +184,13 @@ class StagehandAgent(SwarmsAgent): elif "search" in task.lower(): # Perform search action - search_query = task.split("search for")[-1].strip().strip("'\"") + search_query = ( + task.split("search for")[-1].strip().strip("'\"") + ) # First, find the search box - search_box = await page.observe("find the search input field") + search_box = await page.observe( + "find the search input field" + ) if search_box: # Click on search box and type await page.act(f"click on {search_box[0]}") @@ -198,7 +203,10 @@ class StagehandAgent(SwarmsAgent): # Perform observation observation = await page.observe(task) result["data"]["observation"] = [ - {"description": obs.description, "selector": obs.selector} + { + "description": obs.description, + "selector": obs.selector, + } for obs in observation ] result["action"] = "observe" @@ -254,4 +262,4 @@ if __name__ == "__main__": print(result3) # Clean up - browser_agent.cleanup() \ No newline at end of file + browser_agent.cleanup() diff --git a/examples/stagehand/2_stagehand_tools_agent.py b/examples/stagehand/2_stagehand_tools_agent.py index 15cae06b..f4931ceb 100644 --- a/examples/stagehand/2_stagehand_tools_agent.py +++ b/examples/stagehand/2_stagehand_tools_agent.py @@ -3,7 +3,7 @@ Stagehand Tools for Swarms Agent ================================= This example demonstrates how to create Stagehand browser automation tools -that can be used by a standard Swarms Agent. Each Stagehand method (act, +that can be used by a standard Swarms Agent. Each Stagehand method (act, extract, observe) becomes a separate tool that the agent can use. This approach gives the agent more fine-grained control over browser @@ -13,11 +13,10 @@ automation tasks. import asyncio import json import os -from typing import Any, Dict, List, Optional, Union +from typing import Optional from dotenv import load_dotenv from loguru import logger -from pydantic import BaseModel, Field from swarms import Agent from swarms.tools.base_tool import BaseTool @@ -51,9 +50,11 @@ class BrowserState: config = StagehandConfig( env=env, api_key=api_key or os.getenv("BROWSERBASE_API_KEY"), - project_id=project_id or os.getenv("BROWSERBASE_PROJECT_ID"), + project_id=project_id + or os.getenv("BROWSERBASE_PROJECT_ID"), model_name=model_name, - model_api_key=model_api_key or os.getenv("OPENAI_API_KEY"), + model_api_key=model_api_key + or os.getenv("OPENAI_API_KEY"), ) self._stagehand = Stagehand(config) await self._stagehand.init() @@ -63,7 +64,9 @@ class BrowserState: async def get_page(self): """Get the current page instance.""" if not self._initialized: - raise RuntimeError("Browser not initialized. Call init_browser first.") + raise RuntimeError( + "Browser not initialized. Call init_browser first." + ) return self._stagehand.page async def close(self): @@ -96,11 +99,11 @@ class NavigateTool(BaseTool): try: await browser_state.init_browser() page = await browser_state.get_page() - + # Ensure URL has protocol if not url.startswith(("http://", "https://")): url = f"https://{url}" - + await page.goto(url) return f"Successfully navigated to {url}" except Exception as e: @@ -130,7 +133,7 @@ class ActTool(BaseTool): try: await browser_state.init_browser() page = await browser_state.get_page() - + result = await page.act(action) return f"Action performed: {action}. Result: {result}" except Exception as e: @@ -160,9 +163,9 @@ class ExtractTool(BaseTool): try: await browser_state.init_browser() page = await browser_state.get_page() - + extracted = await page.extract(query) - + # Convert to JSON string for agent consumption if isinstance(extracted, (dict, list)): return json.dumps(extracted, indent=2) @@ -196,18 +199,20 @@ class ObserveTool(BaseTool): try: await browser_state.init_browser() page = await browser_state.get_page() - + observations = await page.observe(query) - + # Format observations for readability result = [] for obs in observations: - result.append({ - "description": obs.description, - "selector": obs.selector, - "method": obs.method - }) - + result.append( + { + "description": obs.description, + "selector": obs.selector, + "method": obs.method, + } + ) + return json.dumps(result, indent=2) except Exception as e: logger.error(f"Observation error: {str(e)}") @@ -232,15 +237,15 @@ class ScreenshotTool(BaseTool): try: await browser_state.init_browser() page = await browser_state.get_page() - + # Ensure .png extension if not filename.endswith(".png"): filename += ".png" - + # Get the underlying Playwright page playwright_page = page.page await playwright_page.screenshot(path=filename) - + return f"Screenshot saved to {filename}" except Exception as e: logger.error(f"Screenshot error: {str(e)}") @@ -329,4 +334,4 @@ if __name__ == "__main__": print(result3) # Clean up - browser_agent.run("Close the browser") \ No newline at end of file + browser_agent.run("Close the browser") diff --git a/examples/stagehand/3_stagehand_mcp_agent.py b/examples/stagehand/3_stagehand_mcp_agent.py index 4483a497..64688490 100644 --- a/examples/stagehand/3_stagehand_mcp_agent.py +++ b/examples/stagehand/3_stagehand_mcp_agent.py @@ -22,9 +22,7 @@ Features: - Prompt templates for common tasks """ -import asyncio -import os -from typing import List, Optional +from typing import List from dotenv import load_dotenv from loguru import logger @@ -104,14 +102,23 @@ class MultiSessionBrowserSwarm: num_agents: Number of agents to create """ self.agents = [] - + # Create specialized agents for different tasks agent_roles = [ - ("DataExtractor", "You specialize in extracting structured data from websites."), - ("FormFiller", "You specialize in filling out forms and interacting with web applications."), - ("WebMonitor", "You specialize in monitoring websites for changes and capturing screenshots."), + ( + "DataExtractor", + "You specialize in extracting structured data from websites.", + ), + ( + "FormFiller", + "You specialize in filling out forms and interacting with web applications.", + ), + ( + "WebMonitor", + "You specialize in monitoring websites for changes and capturing screenshots.", + ), ] - + for i in range(min(num_agents, len(agent_roles))): name, specialization = agent_roles[i] agent = Agent( @@ -137,16 +144,18 @@ Always create your own session for tasks to work independently from other agents def distribute_tasks(self, tasks: List[str]) -> List[str]: """Distribute tasks among agents.""" results = [] - + # Distribute tasks round-robin among agents for i, task in enumerate(tasks): agent_idx = i % len(self.agents) agent = self.agents[agent_idx] - - logger.info(f"Assigning task to {agent.agent_name}: {task}") + + logger.info( + f"Assigning task to {agent.agent_name}: {task}" + ) result = agent.run(task) results.append(result) - + return results @@ -155,18 +164,20 @@ if __name__ == "__main__": print("=" * 70) print("Stagehand MCP Server Integration Examples") print("=" * 70) - print("\nMake sure the Stagehand MCP server is running on http://localhost:3000/sse") + print( + "\nMake sure the Stagehand MCP server is running on http://localhost:3000/sse" + ) print("Run: cd stagehand-mcp-server && npm start\n") - + # Example 1: Single agent with MCP tools print("\nExample 1: Single Agent with MCP Tools") print("-" * 40) - + mcp_agent = StagehandMCPAgent( agent_name="WebResearchAgent", mcp_server_url="http://localhost:3000/sse", ) - + # Research task using MCP tools result1 = mcp_agent.run( """Navigate to news.ycombinator.com and extract the following: @@ -176,18 +187,18 @@ if __name__ == "__main__": Then take a screenshot of the page.""" ) print(f"Result: {result1}") - + print("\n" + "=" * 70 + "\n") - + # Example 2: Multi-session parallel browsing print("Example 2: Multi-Session Parallel Browsing") print("-" * 40) - + parallel_agent = StagehandMCPAgent( agent_name="ParallelBrowserAgent", mcp_server_url="http://localhost:3000/sse", ) - + result2 = parallel_agent.run( """Create 3 browser sessions and perform these tasks in parallel: 1. Session 1: Go to github.com/trending and extract the top 3 trending repositories @@ -197,44 +208,44 @@ if __name__ == "__main__": After extracting data from all sessions, close them.""" ) print(f"Result: {result2}") - + print("\n" + "=" * 70 + "\n") - + # Example 3: Multi-agent browser swarm print("Example 3: Multi-Agent Browser Swarm") print("-" * 40) - + # Create a swarm of specialized browser agents browser_swarm = MultiSessionBrowserSwarm( mcp_server_url="http://localhost:3000/sse", num_agents=3, ) - + # Define tasks for the swarm swarm_tasks = [ "Create a session, navigate to python.org, and extract information about the latest Python version and its key features", "Create a session, go to npmjs.com, search for 'stagehand', and extract information about the package including version and description", "Create a session, visit playwright.dev, and extract the main features and benefits listed on the homepage", ] - + print("Distributing tasks to browser swarm...") swarm_results = browser_swarm.distribute_tasks(swarm_tasks) - + for i, result in enumerate(swarm_results): print(f"\nTask {i+1} Result: {result}") - + print("\n" + "=" * 70 + "\n") - + # Example 4: Complex workflow with session management print("Example 4: Complex Multi-Page Workflow") print("-" * 40) - + workflow_agent = StagehandMCPAgent( agent_name="WorkflowAgent", mcp_server_url="http://localhost:3000/sse", max_loops=2, # Allow more complex reasoning ) - + result4 = workflow_agent.run( """Perform a comprehensive analysis of AI frameworks: 1. Create a new session @@ -246,7 +257,7 @@ if __name__ == "__main__": 7. Close the session when done""" ) print(f"Result: {result4}") - + print("\n" + "=" * 70) print("All examples completed!") - print("=" * 70) \ No newline at end of file + print("=" * 70) diff --git a/examples/stagehand/4_stagehand_multi_agent_workflow.py b/examples/stagehand/4_stagehand_multi_agent_workflow.py index 31bb1d21..4f8f8433 100644 --- a/examples/stagehand/4_stagehand_multi_agent_workflow.py +++ b/examples/stagehand/4_stagehand_multi_agent_workflow.py @@ -13,14 +13,10 @@ Use cases: 4. Data aggregation from multiple sources """ -import asyncio -import json -import os from datetime import datetime -from typing import Any, Dict, List +from typing import Dict, List, Optional from dotenv import load_dotenv -from loguru import logger from pydantic import BaseModel, Field from swarms import Agent, SequentialWorkflow, ConcurrentWorkflow @@ -33,42 +29,48 @@ load_dotenv() # Pydantic models for structured data class ProductInfo(BaseModel): """Product information schema.""" + name: str = Field(..., description="Product name") price: float = Field(..., description="Product price") availability: str = Field(..., description="Availability status") url: str = Field(..., description="Product URL") - screenshot_path: Optional[str] = Field(None, description="Screenshot file path") + screenshot_path: Optional[str] = Field( + None, description="Screenshot file path" + ) class MarketAnalysis(BaseModel): """Market analysis report schema.""" + timestamp: datetime = Field(default_factory=datetime.now) - products: List[ProductInfo] = Field(..., description="List of products analyzed") - price_range: Dict[str, float] = Field(..., description="Min and max prices") - recommendations: List[str] = Field(..., description="Analysis recommendations") + products: List[ProductInfo] = Field( + ..., description="List of products analyzed" + ) + price_range: Dict[str, float] = Field( + ..., description="Min and max prices" + ) + recommendations: List[str] = Field( + ..., description="Analysis recommendations" + ) # Specialized browser agents class ProductScraperAgent(StagehandAgent): """Specialized agent for scraping product information.""" - + def __init__(self, site_name: str, *args, **kwargs): super().__init__( - agent_name=f"ProductScraper_{site_name}", - *args, - **kwargs + agent_name=f"ProductScraper_{site_name}", *args, **kwargs ) self.site_name = site_name class PriceMonitorAgent(StagehandAgent): """Specialized agent for monitoring price changes.""" - + def __init__(self, *args, **kwargs): super().__init__( - agent_name="PriceMonitorAgent", - *args, - **kwargs + agent_name="PriceMonitorAgent", *args, **kwargs ) @@ -77,20 +79,20 @@ def create_price_comparison_workflow(): """ Create a workflow that compares prices across multiple e-commerce sites. """ - + # Create specialized agents for different sites amazon_agent = StagehandAgent( agent_name="AmazonScraperAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + ebay_agent = StagehandAgent( agent_name="EbayScraperAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + analysis_agent = Agent( agent_name="PriceAnalysisAgent", model_name="gpt-4o-mini", @@ -98,21 +100,21 @@ def create_price_comparison_workflow(): and provide insights on the best deals, price trends, and recommendations. Focus on value for money and highlight any significant price differences.""", ) - + # Create concurrent workflow for parallel scraping scraping_workflow = ConcurrentWorkflow( agents=[amazon_agent, ebay_agent], max_loops=1, verbose=True, ) - + # Create sequential workflow: scrape -> analyze full_workflow = SequentialWorkflow( agents=[scraping_workflow, analysis_agent], max_loops=1, verbose=True, ) - + return full_workflow @@ -121,21 +123,21 @@ def create_competitive_analysis_workflow(): """ Create a workflow for competitive analysis across multiple company websites. """ - + # Agent for extracting company information company_researcher = StagehandAgent( agent_name="CompanyResearchAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Agent for analyzing social media presence social_media_agent = StagehandAgent( agent_name="SocialMediaAnalysisAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Agent for compiling competitive analysis report report_compiler = Agent( agent_name="CompetitiveAnalysisReporter", @@ -144,16 +146,22 @@ def create_competitive_analysis_workflow(): based on company information and social media presence data. Identify strengths, weaknesses, and market positioning for each company.""", ) - + # Create agent rearrange for flexible routing - workflow_pattern = "company_researcher -> social_media_agent -> report_compiler" - + workflow_pattern = ( + "company_researcher -> social_media_agent -> report_compiler" + ) + competitive_workflow = AgentRearrange( - agents=[company_researcher, social_media_agent, report_compiler], + agents=[ + company_researcher, + social_media_agent, + report_compiler, + ], flow=workflow_pattern, verbose=True, ) - + return competitive_workflow @@ -162,28 +170,28 @@ def create_automated_testing_workflow(): """ Create a workflow for automated web application testing. """ - + # Agent for UI testing ui_tester = StagehandAgent( agent_name="UITestingAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Agent for form validation testing form_tester = StagehandAgent( agent_name="FormValidationAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Agent for accessibility testing accessibility_tester = StagehandAgent( agent_name="AccessibilityTestingAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Agent for compiling test results test_reporter = Agent( agent_name="TestReportCompiler", @@ -192,20 +200,20 @@ def create_automated_testing_workflow(): UI, form validation, and accessibility testing into a comprehensive report. Highlight any failures, warnings, and provide recommendations for fixes.""", ) - + # Concurrent testing followed by report generation testing_workflow = ConcurrentWorkflow( agents=[ui_tester, form_tester, accessibility_tester], max_loops=1, verbose=True, ) - + full_test_workflow = SequentialWorkflow( agents=[testing_workflow, test_reporter], max_loops=1, verbose=True, ) - + return full_test_workflow @@ -214,7 +222,7 @@ def create_news_aggregation_workflow(): """ Create a workflow for news aggregation and sentiment analysis. """ - + # Multiple news scraper agents news_scrapers = [] news_sites = [ @@ -222,7 +230,7 @@ def create_news_aggregation_workflow(): ("HackerNews", "https://news.ycombinator.com"), ("Reddit", "https://reddit.com/r/technology"), ] - + for site_name, url in news_sites: scraper = StagehandAgent( agent_name=f"{site_name}Scraper", @@ -230,7 +238,7 @@ def create_news_aggregation_workflow(): env="LOCAL", ) news_scrapers.append(scraper) - + # Sentiment analysis agent sentiment_analyzer = Agent( agent_name="SentimentAnalyzer", @@ -239,7 +247,7 @@ def create_news_aggregation_workflow(): to determine overall sentiment (positive, negative, neutral) and identify key themes and trends in the technology sector.""", ) - + # Trend identification agent trend_identifier = Agent( agent_name="TrendIdentifier", @@ -248,20 +256,24 @@ def create_news_aggregation_workflow(): data, identify emerging trends, hot topics, and potential market movements in the technology sector.""", ) - + # Create workflow: parallel scraping -> sentiment analysis -> trend identification scraping_workflow = ConcurrentWorkflow( agents=news_scrapers, max_loops=1, verbose=True, ) - + analysis_workflow = SequentialWorkflow( - agents=[scraping_workflow, sentiment_analyzer, trend_identifier], + agents=[ + scraping_workflow, + sentiment_analyzer, + trend_identifier, + ], max_loops=1, verbose=True, ) - + return analysis_workflow @@ -270,13 +282,13 @@ if __name__ == "__main__": print("=" * 70) print("Stagehand Multi-Agent Workflow Examples") print("=" * 70) - + # Example 1: Price Comparison print("\nExample 1: E-commerce Price Comparison") print("-" * 40) - + price_workflow = create_price_comparison_workflow() - + # Search for a specific product across multiple sites price_result = price_workflow.run( """Search for 'iPhone 15 Pro Max 256GB' on: @@ -286,15 +298,15 @@ if __name__ == "__main__": Compare the prices and provide recommendations on where to buy.""" ) print(f"Price Comparison Result:\n{price_result}") - + print("\n" + "=" * 70 + "\n") - + # Example 2: Competitive Analysis print("Example 2: Competitive Analysis") print("-" * 40) - + competitive_workflow = create_competitive_analysis_workflow() - + competitive_result = competitive_workflow.run( """Analyze these three AI companies: 1. OpenAI - visit openai.com and extract mission, products, and recent announcements @@ -305,15 +317,15 @@ if __name__ == "__main__": Compile a competitive analysis report comparing their market positioning.""" ) print(f"Competitive Analysis Result:\n{competitive_result}") - + print("\n" + "=" * 70 + "\n") - + # Example 3: Automated Testing print("Example 3: Automated Web Testing") print("-" * 40) - + testing_workflow = create_automated_testing_workflow() - + test_result = testing_workflow.run( """Test the website example.com: 1. UI Testing: Check if all main navigation links work, images load, and layout is responsive @@ -323,15 +335,15 @@ if __name__ == "__main__": Take screenshots of any issues found and compile a comprehensive test report.""" ) print(f"Test Results:\n{test_result}") - + print("\n" + "=" * 70 + "\n") - + # Example 4: News Aggregation print("Example 4: Tech News Aggregation and Analysis") print("-" * 40) - + news_workflow = create_news_aggregation_workflow() - + news_result = news_workflow.run( """For each news source: 1. TechCrunch: Extract the top 5 headlines about AI or machine learning @@ -341,19 +353,19 @@ if __name__ == "__main__": Analyze sentiment and identify emerging trends in AI technology.""" ) print(f"News Analysis Result:\n{news_result}") - + # Cleanup all browser instances print("\n" + "=" * 70) print("Cleaning up browser instances...") - + # Clean up agents for agent in price_workflow.agents: if isinstance(agent, StagehandAgent): agent.cleanup() - elif hasattr(agent, 'agents'): # For nested workflows + elif hasattr(agent, "agents"): # For nested workflows for sub_agent in agent.agents: if isinstance(sub_agent, StagehandAgent): sub_agent.cleanup() - + print("All workflows completed!") - print("=" * 70) \ No newline at end of file + print("=" * 70) diff --git a/tests/stagehand/test_stagehand_integration.py b/tests/stagehand/test_stagehand_integration.py index c1e913ae..d2048d11 100644 --- a/tests/stagehand/test_stagehand_integration.py +++ b/tests/stagehand/test_stagehand_integration.py @@ -6,13 +6,9 @@ This module contains tests for the Stagehand browser automation integration with the Swarms framework. """ -import asyncio import json import pytest -from unittest.mock import AsyncMock, MagicMock, patch - -from swarms import Agent -from swarms.tools.base_tool import BaseTool +from unittest.mock import AsyncMock, patch # Mock Stagehand classes @@ -26,13 +22,13 @@ class MockObserveResult: class MockStagehandPage: async def goto(self, url): return None - + async def act(self, action): return f"Performed action: {action}" - + async def extract(self, query): return {"extracted": query, "data": ["item1", "item2"]} - + async def observe(self, query): return [ MockObserveResult("Search box", "#search-input"), @@ -44,10 +40,10 @@ class MockStagehand: def __init__(self, config): self.config = config self.page = MockStagehandPage() - + async def init(self): pass - + async def close(self): pass @@ -55,79 +51,106 @@ class MockStagehand: # Test StagehandAgent wrapper class TestStagehandAgent: """Test the StagehandAgent wrapper class.""" - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_agent_initialization(self): """Test that StagehandAgent initializes correctly.""" - from examples.stagehand.stagehand_wrapper_agent import StagehandAgent - + from examples.stagehand.stagehand_wrapper_agent import ( + StagehandAgent, + ) + agent = StagehandAgent( agent_name="TestAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + assert agent.agent_name == "TestAgent" assert agent.stagehand_config.env == "LOCAL" assert agent.stagehand_config.model_name == "gpt-4o-mini" assert not agent._initialized - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_navigation_task(self): """Test navigation and extraction task.""" - from examples.stagehand.stagehand_wrapper_agent import StagehandAgent - + from examples.stagehand.stagehand_wrapper_agent import ( + StagehandAgent, + ) + agent = StagehandAgent( agent_name="TestAgent", model_name="gpt-4o-mini", env="LOCAL", ) - - result = agent.run("Navigate to example.com and extract the main content") - + + result = agent.run( + "Navigate to example.com and extract the main content" + ) + # Parse result result_data = json.loads(result) assert result_data["status"] == "completed" assert "navigated_to" in result_data["data"] - assert result_data["data"]["navigated_to"] == "https://example.com" + assert ( + result_data["data"]["navigated_to"] + == "https://example.com" + ) assert "extracted" in result_data["data"] - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_search_task(self): """Test search functionality.""" - from examples.stagehand.stagehand_wrapper_agent import StagehandAgent - + from examples.stagehand.stagehand_wrapper_agent import ( + StagehandAgent, + ) + agent = StagehandAgent( agent_name="TestAgent", model_name="gpt-4o-mini", env="LOCAL", ) - - result = agent.run("Go to google.com and search for 'test query'") - + + result = agent.run( + "Go to google.com and search for 'test query'" + ) + result_data = json.loads(result) assert result_data["status"] == "completed" assert result_data["data"]["search_query"] == "test query" assert result_data["action"] == "search" - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_cleanup(self): """Test that cleanup properly closes browser.""" - from examples.stagehand.stagehand_wrapper_agent import StagehandAgent - + from examples.stagehand.stagehand_wrapper_agent import ( + StagehandAgent, + ) + agent = StagehandAgent( agent_name="TestAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Initialize the agent agent.run("Navigate to example.com") assert agent._initialized - + # Cleanup agent.cleanup() - + # After cleanup, should be able to run again result = agent.run("Navigate to example.com") assert result is not None @@ -136,65 +159,84 @@ class TestStagehandAgent: # Test Stagehand Tools class TestStagehandTools: """Test individual Stagehand tools.""" - - @patch('examples.stagehand.stagehand_tools_agent.browser_state') + + @patch("examples.stagehand.stagehand_tools_agent.browser_state") async def test_navigate_tool(self, mock_browser_state): """Test NavigateTool functionality.""" - from examples.stagehand.stagehand_tools_agent import NavigateTool - + from examples.stagehand.stagehand_tools_agent import ( + NavigateTool, + ) + # Setup mock mock_page = AsyncMock() - mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.get_page = AsyncMock( + return_value=mock_page + ) mock_browser_state.init_browser = AsyncMock() - + tool = NavigateTool() result = await tool._async_run("https://example.com") - - assert "Successfully navigated to https://example.com" in result + + assert ( + "Successfully navigated to https://example.com" in result + ) mock_page.goto.assert_called_once_with("https://example.com") - - @patch('examples.stagehand.stagehand_tools_agent.browser_state') + + @patch("examples.stagehand.stagehand_tools_agent.browser_state") async def test_act_tool(self, mock_browser_state): """Test ActTool functionality.""" from examples.stagehand.stagehand_tools_agent import ActTool - + # Setup mock mock_page = AsyncMock() mock_page.act = AsyncMock(return_value="Action completed") - mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.get_page = AsyncMock( + return_value=mock_page + ) mock_browser_state.init_browser = AsyncMock() - + tool = ActTool() result = await tool._async_run("click the button") - + assert "Action performed" in result assert "click the button" in result mock_page.act.assert_called_once_with("click the button") - - @patch('examples.stagehand.stagehand_tools_agent.browser_state') + + @patch("examples.stagehand.stagehand_tools_agent.browser_state") async def test_extract_tool(self, mock_browser_state): """Test ExtractTool functionality.""" - from examples.stagehand.stagehand_tools_agent import ExtractTool - + from examples.stagehand.stagehand_tools_agent import ( + ExtractTool, + ) + # Setup mock mock_page = AsyncMock() - mock_page.extract = AsyncMock(return_value={"title": "Test Page", "content": "Test content"}) - mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_page.extract = AsyncMock( + return_value={ + "title": "Test Page", + "content": "Test content", + } + ) + mock_browser_state.get_page = AsyncMock( + return_value=mock_page + ) mock_browser_state.init_browser = AsyncMock() - + tool = ExtractTool() result = await tool._async_run("extract the page title") - + # Result should be JSON string parsed_result = json.loads(result) assert parsed_result["title"] == "Test Page" assert parsed_result["content"] == "Test content" - - @patch('examples.stagehand.stagehand_tools_agent.browser_state') + + @patch("examples.stagehand.stagehand_tools_agent.browser_state") async def test_observe_tool(self, mock_browser_state): """Test ObserveTool functionality.""" - from examples.stagehand.stagehand_tools_agent import ObserveTool - + from examples.stagehand.stagehand_tools_agent import ( + ObserveTool, + ) + # Setup mock mock_page = AsyncMock() mock_observations = [ @@ -202,12 +244,14 @@ class TestStagehandTools: MockObserveResult("Submit button", "#submit"), ] mock_page.observe = AsyncMock(return_value=mock_observations) - mock_browser_state.get_page = AsyncMock(return_value=mock_page) + mock_browser_state.get_page = AsyncMock( + return_value=mock_page + ) mock_browser_state.init_browser = AsyncMock() - + tool = ObserveTool() result = await tool._async_run("find the search box") - + # Result should be JSON string parsed_result = json.loads(result) assert len(parsed_result) == 2 @@ -218,47 +262,53 @@ class TestStagehandTools: # Test MCP integration class TestStagehandMCP: """Test Stagehand MCP server integration.""" - + def test_mcp_agent_initialization(self): """Test that MCP agent initializes with correct parameters.""" - from examples.stagehand.stagehand_mcp_agent import StagehandMCPAgent - + from examples.stagehand.stagehand_mcp_agent import ( + StagehandMCPAgent, + ) + mcp_agent = StagehandMCPAgent( agent_name="TestMCPAgent", mcp_server_url="http://localhost:3000/sse", model_name="gpt-4o-mini", ) - + assert mcp_agent.agent.agent_name == "TestMCPAgent" assert mcp_agent.agent.mcp_url == "http://localhost:3000/sse" assert mcp_agent.agent.model_name == "gpt-4o-mini" - + def test_multi_session_swarm_creation(self): """Test multi-session browser swarm creation.""" - from examples.stagehand.stagehand_mcp_agent import MultiSessionBrowserSwarm - + from examples.stagehand.stagehand_mcp_agent import ( + MultiSessionBrowserSwarm, + ) + swarm = MultiSessionBrowserSwarm( mcp_server_url="http://localhost:3000/sse", num_agents=3, ) - + assert len(swarm.agents) == 3 assert swarm.agents[0].agent_name == "DataExtractor_0" assert swarm.agents[1].agent_name == "FormFiller_1" assert swarm.agents[2].agent_name == "WebMonitor_2" - - @patch('swarms.Agent.run') + + @patch("swarms.Agent.run") def test_task_distribution(self, mock_run): """Test task distribution among swarm agents.""" - from examples.stagehand.stagehand_mcp_agent import MultiSessionBrowserSwarm - + from examples.stagehand.stagehand_mcp_agent import ( + MultiSessionBrowserSwarm, + ) + mock_run.return_value = "Task completed" - + swarm = MultiSessionBrowserSwarm(num_agents=2) tasks = ["Task 1", "Task 2", "Task 3"] - + results = swarm.distribute_tasks(tasks) - + assert len(results) == 3 assert all(result == "Task completed" for result in results) assert mock_run.call_count == 3 @@ -267,90 +317,120 @@ class TestStagehandMCP: # Test multi-agent workflows class TestMultiAgentWorkflows: """Test multi-agent workflow configurations.""" - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_price_comparison_workflow_creation(self): """Test creation of price comparison workflow.""" - from examples.stagehand.stagehand_multi_agent_workflow import create_price_comparison_workflow - + from examples.stagehand.stagehand_multi_agent_workflow import ( + create_price_comparison_workflow, + ) + workflow = create_price_comparison_workflow() - + # Should be a SequentialWorkflow with 2 agents assert len(workflow.agents) == 2 # First agent should be a ConcurrentWorkflow - assert hasattr(workflow.agents[0], 'agents') + assert hasattr(workflow.agents[0], "agents") # Second agent should be the analysis agent assert workflow.agents[1].agent_name == "PriceAnalysisAgent" - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_competitive_analysis_workflow_creation(self): """Test creation of competitive analysis workflow.""" - from examples.stagehand.stagehand_multi_agent_workflow import create_competitive_analysis_workflow - + from examples.stagehand.stagehand_multi_agent_workflow import ( + create_competitive_analysis_workflow, + ) + workflow = create_competitive_analysis_workflow() - + # Should have 3 agents in the rearrange pattern assert len(workflow.agents) == 3 - assert workflow.flow == "company_researcher -> social_media_agent -> report_compiler" - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + assert ( + workflow.flow + == "company_researcher -> social_media_agent -> report_compiler" + ) + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_automated_testing_workflow_creation(self): """Test creation of automated testing workflow.""" - from examples.stagehand.stagehand_multi_agent_workflow import create_automated_testing_workflow - + from examples.stagehand.stagehand_multi_agent_workflow import ( + create_automated_testing_workflow, + ) + workflow = create_automated_testing_workflow() - + # Should be a SequentialWorkflow assert len(workflow.agents) == 2 # First should be concurrent testing - assert hasattr(workflow.agents[0], 'agents') - assert len(workflow.agents[0].agents) == 3 # UI, Form, Accessibility testers - - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + assert hasattr(workflow.agents[0], "agents") + assert ( + len(workflow.agents[0].agents) == 3 + ) # UI, Form, Accessibility testers + + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) def test_news_aggregation_workflow_creation(self): """Test creation of news aggregation workflow.""" - from examples.stagehand.stagehand_multi_agent_workflow import create_news_aggregation_workflow - + from examples.stagehand.stagehand_multi_agent_workflow import ( + create_news_aggregation_workflow, + ) + workflow = create_news_aggregation_workflow() - + # Should be a SequentialWorkflow with 3 stages assert len(workflow.agents) == 3 # First stage should be concurrent scrapers - assert hasattr(workflow.agents[0], 'agents') + assert hasattr(workflow.agents[0], "agents") assert len(workflow.agents[0].agents) == 3 # 3 news sources # Integration tests class TestIntegration: """End-to-end integration tests.""" - + @pytest.mark.asyncio - @patch('examples.stagehand.stagehand_wrapper_agent.Stagehand', MockStagehand) + @patch( + "examples.stagehand.stagehand_wrapper_agent.Stagehand", + MockStagehand, + ) async def test_full_browser_automation_flow(self): """Test a complete browser automation flow.""" - from examples.stagehand.stagehand_wrapper_agent import StagehandAgent - + from examples.stagehand.stagehand_wrapper_agent import ( + StagehandAgent, + ) + agent = StagehandAgent( agent_name="IntegrationTestAgent", model_name="gpt-4o-mini", env="LOCAL", ) - + # Test navigation nav_result = agent.run("Navigate to example.com") assert "navigated_to" in nav_result - + # Test extraction extract_result = agent.run("Extract all text from the page") assert "extracted" in extract_result - + # Test observation observe_result = agent.run("Find all buttons on the page") assert "observation" in observe_result - + # Cleanup agent.cleanup() if __name__ == "__main__": - pytest.main([__file__, "-v"]) \ No newline at end of file + pytest.main([__file__, "-v"]) diff --git a/tests/stagehand/test_stagehand_simple.py b/tests/stagehand/test_stagehand_simple.py new file mode 100644 index 00000000..9a220f1c --- /dev/null +++ b/tests/stagehand/test_stagehand_simple.py @@ -0,0 +1,304 @@ +""" +Simple tests for Stagehand Integration with Swarms +================================================= + +These tests verify the basic structure and functionality of the +Stagehand integration without requiring external dependencies. +""" + +import json +import pytest +from unittest.mock import MagicMock + + +class TestStagehandIntegrationStructure: + """Test that integration files have correct structure.""" + + def test_examples_directory_exists(self): + """Test that examples directory structure is correct.""" + import os + + base_path = "examples/stagehand" + assert os.path.exists(base_path) + + expected_files = [ + "1_stagehand_wrapper_agent.py", + "2_stagehand_tools_agent.py", + "3_stagehand_mcp_agent.py", + "4_stagehand_multi_agent_workflow.py", + "README.md", + "requirements.txt", + ] + + for file in expected_files: + file_path = os.path.join(base_path, file) + assert os.path.exists(file_path), f"Missing file: {file}" + + def test_wrapper_agent_imports(self): + """Test that wrapper agent has correct imports.""" + with open( + "examples/stagehand/1_stagehand_wrapper_agent.py", "r" + ) as f: + content = f.read() + + # Check for required imports + assert "from swarms import Agent" in content + assert "import asyncio" in content + assert "import json" in content + assert "class StagehandAgent" in content + + def test_tools_agent_imports(self): + """Test that tools agent has correct imports.""" + with open( + "examples/stagehand/2_stagehand_tools_agent.py", "r" + ) as f: + content = f.read() + + # Check for required imports + assert ( + "from swarms.tools.base_tool import BaseTool" in content + ) + assert "class NavigateTool" in content + assert "class ActTool" in content + assert "class ExtractTool" in content + + def test_mcp_agent_imports(self): + """Test that MCP agent has correct imports.""" + with open( + "examples/stagehand/3_stagehand_mcp_agent.py", "r" + ) as f: + content = f.read() + + # Check for required imports + assert "from swarms import Agent" in content + assert "class StagehandMCPAgent" in content + assert "mcp_url" in content + + def test_workflow_agent_imports(self): + """Test that workflow agent has correct imports.""" + with open( + "examples/stagehand/4_stagehand_multi_agent_workflow.py", + "r", + ) as f: + content = f.read() + + # Check for required imports + assert ( + "from swarms import Agent, SequentialWorkflow, ConcurrentWorkflow" + in content + ) + assert ( + "from swarms.structs.agent_rearrange import AgentRearrange" + in content + ) + + +class TestStagehandMockIntegration: + """Test Stagehand integration with mocked dependencies.""" + + def test_mock_stagehand_initialization(self): + """Test that Stagehand can be mocked and initialized.""" + + # Setup mock without importing actual stagehand + mock_stagehand = MagicMock() + mock_instance = MagicMock() + mock_instance.init = MagicMock() + mock_stagehand.return_value = mock_instance + + # Mock config creation + config = MagicMock() + stagehand_instance = mock_stagehand(config) + + # Verify mock works + assert stagehand_instance is not None + assert hasattr(stagehand_instance, "init") + + def test_json_serialization(self): + """Test JSON serialization for agent responses.""" + + # Test data that would come from browser automation + test_data = { + "task": "Navigate to example.com", + "status": "completed", + "data": { + "navigated_to": "https://example.com", + "extracted": ["item1", "item2"], + "action": "navigate", + }, + } + + # Test serialization + json_result = json.dumps(test_data, indent=2) + assert isinstance(json_result, str) + + # Test deserialization + parsed_data = json.loads(json_result) + assert parsed_data["task"] == "Navigate to example.com" + assert parsed_data["status"] == "completed" + assert len(parsed_data["data"]["extracted"]) == 2 + + def test_url_extraction_logic(self): + """Test URL extraction logic from task strings.""" + import re + + # Test cases + test_cases = [ + ( + "Navigate to https://example.com", + ["https://example.com"], + ), + ("Go to google.com and search", ["google.com"]), + ( + "Visit https://github.com/repo", + ["https://github.com/repo"], + ), + ("Open example.org", ["example.org"]), + ] + + url_pattern = r"https?://[^\s]+" + domain_pattern = r"(\w+\.\w+)" + + for task, expected in test_cases: + # Extract full URLs + urls = re.findall(url_pattern, task) + + # If no full URLs, extract domains + if not urls: + domains = re.findall(domain_pattern, task) + if domains: + urls = domains + + assert ( + len(urls) > 0 + ), f"Failed to extract URL from: {task}" + assert ( + urls[0] in expected + ), f"Expected {expected}, got {urls}" + + +class TestSwarmsPatternsCompliance: + """Test compliance with Swarms framework patterns.""" + + def test_agent_inheritance_pattern(self): + """Test that wrapper agent follows Swarms Agent inheritance pattern.""" + + # Read the wrapper agent file + with open( + "examples/stagehand/1_stagehand_wrapper_agent.py", "r" + ) as f: + content = f.read() + + # Check inheritance pattern + assert "class StagehandAgent(SwarmsAgent):" in content + assert "def run(self, task: str" in content + assert "return" in content + + def test_tools_pattern(self): + """Test that tools follow Swarms BaseTool pattern.""" + + # Read the tools agent file + with open( + "examples/stagehand/2_stagehand_tools_agent.py", "r" + ) as f: + content = f.read() + + # Check tool pattern + assert "class NavigateTool(BaseTool):" in content + assert "def run(self," in content + assert "name=" in content + assert "description=" in content + + def test_mcp_integration_pattern(self): + """Test MCP integration follows Swarms pattern.""" + + # Read the MCP agent file + with open( + "examples/stagehand/3_stagehand_mcp_agent.py", "r" + ) as f: + content = f.read() + + # Check MCP pattern + assert "mcp_url=" in content + assert "Agent(" in content + + def test_workflow_patterns(self): + """Test workflow patterns are properly used.""" + + # Read the workflow file + with open( + "examples/stagehand/4_stagehand_multi_agent_workflow.py", + "r", + ) as f: + content = f.read() + + # Check workflow patterns + assert "SequentialWorkflow" in content + assert "ConcurrentWorkflow" in content + assert "AgentRearrange" in content + + +class TestDocumentationAndExamples: + """Test documentation and example completeness.""" + + def test_readme_completeness(self): + """Test that README contains essential information.""" + + with open("examples/stagehand/README.md", "r") as f: + content = f.read() + + required_sections = [ + "# Stagehand Browser Automation Integration", + "## Overview", + "## Examples", + "## Setup", + "## Use Cases", + "## Best Practices", + ] + + for section in required_sections: + assert section in content, f"Missing section: {section}" + + def test_requirements_file(self): + """Test that requirements file has necessary dependencies.""" + + with open("examples/stagehand/requirements.txt", "r") as f: + content = f.read() + + required_deps = [ + "swarms", + "stagehand", + "python-dotenv", + "pydantic", + "loguru", + ] + + for dep in required_deps: + assert dep in content, f"Missing dependency: {dep}" + + def test_example_files_have_docstrings(self): + """Test that example files have proper docstrings.""" + + example_files = [ + "examples/stagehand/1_stagehand_wrapper_agent.py", + "examples/stagehand/2_stagehand_tools_agent.py", + "examples/stagehand/3_stagehand_mcp_agent.py", + "examples/stagehand/4_stagehand_multi_agent_workflow.py", + ] + + for file_path in example_files: + with open(file_path, "r") as f: + content = f.read() + + # Check for module docstring + assert ( + '"""' in content[:500] + ), f"Missing docstring in {file_path}" + + # Check for main execution block + assert ( + 'if __name__ == "__main__":' in content + ), f"Missing main block in {file_path}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 6cdf3d84c8968d08af41796597d4658428826dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A5=A5=E5=AE=87?= <625024108@qq.com> Date: Thu, 31 Jul 2025 10:07:18 +0800 Subject: [PATCH 27/73] update --- examples/utils/misc/conversation_test.py | 103 +++++++++++++++++++---- 1 file changed, 86 insertions(+), 17 deletions(-) diff --git a/examples/utils/misc/conversation_test.py b/examples/utils/misc/conversation_test.py index ec8a0534..ae34692b 100644 --- a/examples/utils/misc/conversation_test.py +++ b/examples/utils/misc/conversation_test.py @@ -1,22 +1,91 @@ from swarms.structs.conversation import Conversation +from dotenv import load_dotenv -# Create a conversation object -conversation = Conversation(backend="in-memory") -# Add a message to the conversation -conversation.add( - role="user", content="Hello, how are you?", category="input" -) +# Load environment variables from .env file +load_dotenv() -# Add a message to the conversation -conversation.add( - role="assistant", - content="I'm good, thank you!", - category="output", -) - -print( - conversation.export_and_count_categories( - tokenizer_model_name="claude-3-5-sonnet-20240620" +def demonstrate_truncation(): + # Using a smaller context length to clearly see the truncation effect + context_length = 25 + print(f"Creating a conversation instance with context length {context_length}") + + # Using Claude model as the tokenizer model + conversation = Conversation( + context_length=context_length, + tokenizer_model_name="claude-3-7-sonnet-20250219" ) -) + + # Adding first message - short message + short_message = "Hello, I am a user." + print(f"\nAdding short message: '{short_message}'") + conversation.add("user", short_message) + + # Display token count + from swarms.utils.litellm_tokenizer import count_tokens + tokens = count_tokens(short_message, conversation.tokenizer_model_name) + print(f"Short message token count: {tokens}") + + # Adding second message - long message, should be truncated + long_message = "I have a question about artificial intelligence. I want to understand how large language models handle long texts, especially under token constraints. This issue is important because it relates to the model's practicality and effectiveness. I hope to get a detailed answer that helps me understand this complex technical problem." + print(f"\nAdding long message:\n'{long_message}'") + conversation.add("assistant", long_message) + + # Display long message token count + tokens = count_tokens(long_message, conversation.tokenizer_model_name) + print(f"Long message token count: {tokens}") + + # Display current conversation total token count + total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) + for msg in conversation.conversation_history) + print(f"Total token count before truncation: {total_tokens}") + + # Print the complete conversation history before truncation + print("\nConversation history before truncation:") + for i, msg in enumerate(conversation.conversation_history): + print(f"[{i}] {msg['role']}: {msg['content']}") + print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") + + # Execute truncation + print("\nExecuting truncation...") + conversation.truncate_memory_with_tokenizer() + + # Print conversation history after truncation + print("\nConversation history after truncation:") + for i, msg in enumerate(conversation.conversation_history): + print(f"[{i}] {msg['role']}: {msg['content']}") + print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") + + # Display total token count after truncation + total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) + for msg in conversation.conversation_history) + print(f"\nTotal token count after truncation: {total_tokens}") + print(f"Context length limit: {context_length}") + + # Verify if successfully truncated below the limit + if total_tokens <= context_length: + print("✅ Success: Total token count is now less than or equal to context length limit") + else: + print("❌ Failure: Total token count still exceeds context length limit") + + # Test sentence boundary truncation + print("\n\nTesting sentence boundary truncation:") + sentence_test = Conversation(context_length=15, tokenizer_model_name="claude-3-opus-20240229") + test_text = "This is the first sentence. This is the second very long sentence that contains a lot of content. This is the third sentence." + print(f"Original text: '{test_text}'") + print(f"Original token count: {count_tokens(test_text, sentence_test.tokenizer_model_name)}") + + # Using binary search for truncation + truncated = sentence_test._binary_search_truncate(test_text, 10, sentence_test.tokenizer_model_name) + print(f"Truncated text: '{truncated}'") + print(f"Truncated token count: {count_tokens(truncated, sentence_test.tokenizer_model_name)}") + + # Check if truncated at period + if truncated.endswith("."): + print("✅ Success: Text was truncated at sentence boundary") + else: + print("Note: Text was not truncated at sentence boundary") + + +if __name__ == "__main__": + demonstrate_truncation() \ No newline at end of file From 578452fb6c52989c0ffcb602064ed3c3be23ddf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A5=A5=E5=AE=87?= <625024108@qq.com> Date: Fri, 1 Aug 2025 11:47:21 +0800 Subject: [PATCH 28/73] update --- swarms/structs/conversation.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/swarms/structs/conversation.py b/swarms/structs/conversation.py index 3e47cae1..6e11ebdd 100644 --- a/swarms/structs/conversation.py +++ b/swarms/structs/conversation.py @@ -184,7 +184,6 @@ class Conversation: system_prompt: Optional[str] = None, time_enabled: bool = False, autosave: bool = False, # Changed default to False - save_enabled: bool = False, # New parameter to control if saving is enabled save_filepath: str = None, load_filepath: str = None, # New parameter to specify which file to load from context_length: int = 8192, @@ -223,7 +222,6 @@ class Conversation: self.system_prompt = system_prompt self.time_enabled = time_enabled self.autosave = autosave - self.save_enabled = save_enabled self.conversations_dir = conversations_dir self.tokenizer_model_name = tokenizer_model_name self.message_id_on = message_id_on @@ -1021,13 +1019,6 @@ class Conversation: ) return - # Don't save if saving is disabled (你的PR代码) - if not self.save_enabled: - logger.warning( - "An attempt to save the conversation failed: save_enabled is False." - "Please set save_enabled=True when creating a Conversation object to enable saving." - ) - return # Get the full data including metadata and conversation history data = self.get_init_params() @@ -1284,7 +1275,7 @@ class Conversation: Returns: None """ - from swarms.utils.litellm_tokenizer import count_tokens + total_tokens = 0 truncated_history = [] @@ -1350,7 +1341,7 @@ class Conversation: Returns: str: Truncated text with token count not exceeding target_tokens """ - from swarms.utils.litellm_tokenizer import count_tokens + # If text is empty or target tokens is 0, return empty string if not text or target_tokens <= 0: @@ -1390,7 +1381,7 @@ class Conversation: return truncated_at_sentence return best_text - + def clear(self): """Clear the conversation history.""" if self.backend_instance: @@ -1796,4 +1787,4 @@ class Conversation: # # # conversation.add("assistant", "I am doing well, thanks.") # # # # print(conversation.to_json()) # # print(type(conversation.to_dict())) -# # print(conversation.to_yaml()) +# # print(conversation.to_yaml()) \ No newline at end of file From 0ceba80c609c459f612d7fd4e17fd41f8912fe1e Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 11:06:00 +0530 Subject: [PATCH 29/73] fixed links --- docs/swarms_cloud/swarm_types.md | 33 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index f6091501..2e69466d 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -1,30 +1,27 @@ # Multi-Agent Architectures -Each multi-agent architecture type is designed for specific use cases and can be combined to create powerful multi-agent systems. Here's a comprehensive overview of each available swarm: +Each multi-agent architecture type is designed for specific use cases and can be combined to create powerful multi-agent systems. Below is an overview of each available swarm type: | Swarm Type | Description | Learn More | -|---------------------|------------------------------------------------------------------------------|------------| -| AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Optimizes agent performance by dynamically adjusting their roles and positions within the workflow. This architecture is particularly useful when the effectiveness of agents depends on their sequence or arrangement. | [Learn More](/swarms/structs/agent_rearrange) | -| MixtureOfAgents | Creates diverse teams of specialized agents, each bringing unique capabilities to solve complex problems. Each agent contributes unique skills to achieve the overall goal, making it excel at tasks requiring multiple types of expertise or processing. | [Learn More](/swarms/structs/moa) | -| SpreadSheetSwarm | Provides a structured approach to data management and operations, making it ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. | [Learn More](/swarms/structs/spreadsheet_swarm) | -| SequentialWorkflow | Ensures strict process control by executing tasks in a predefined order. Perfect for workflows where each step depends on the completion of previous steps. | [Learn More](/swarms/structs/sequential_workflow) | -| ConcurrentWorkflow | Maximizes efficiency by running independent tasks in parallel, significantly reducing overall processing time for complex operations. Ideal for independent tasks that can be processed simultaneously. | [Learn More](/swarms/structs/concurrentworkflow) | -| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](/swarms/structs/group_chat) | -| MultiAgentRouter | Acts as an intelligent task dispatcher, ensuring optimal distribution of work across available agents based on their capabilities and current workload. | [Learn More](/swarms/structs/multi_agent_router) | -| AutoSwarmBuilder | Simplifies swarm creation by automatically configuring agent architectures based on task requirements and performance metrics. | [Learn More](/swarms/structs/auto_swarm_builder) | -| HiearchicalSwarm | Implements a structured approach to task management, with clear lines of authority and delegation across multiple agent levels. | [Learn More](/swarms/structs/multi_swarm_orchestration) | -| auto | Provides intelligent swarm selection based on context, automatically choosing the most effective architecture for given tasks. | [Learn More](/swarms/concept/how_to_choose_swarms) | -| MajorityVoting | Implements robust decision-making through consensus, particularly useful for tasks requiring collective intelligence or verification. | [Learn More](/swarms/structs/majorityvoting) | -| MALT | Specialized framework for language-based tasks, optimizing agent collaboration for complex language processing operations. | [Learn More](/swarms/structs/malt) | +|----------------------|------------------------------------------------------------------------------|------------| +| AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Useful when agent effectiveness depends on their sequence or arrangement. | [Learn More](swarms_api.md#agentrearrange) | +| MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](swarms_api.md#mixtureofagents) | +| SpreadSheetSwarm | Provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. | [Learn More](swarms_api.md#spreadsheetswarm) | +| SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](swarms_api.md#sequentialworkflow) | +| ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](swarms_api.md#concurrentworkflow) | +| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](swarms_api.md#groupchat) | +| MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](swarms_api.md#multiagentrouter) | +| AutoSwarmBuilder | Automatically configures agent architectures based on task requirements and performance metrics, simplifying swarm creation. | [Learn More](swarms_api.md#autoswarmbuilder) | +| HierarchicalSwarm | Implements a structured, multi-level approach to task management, with clear lines of authority and delegation. | [Learn More](swarms_api.md#hierarchicalswarm) | +| Auto | Intelligently selects the most effective swarm architecture for a given task based on context. | [Learn More](swarms_api.md#auto) | +| MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](swarms_api_tools.md#majorityvoting) | +| MALT | Specialized framework for language-based tasks, optimizing agent collaboration for complex language processing operations. | [Learn More](swarms_api_tools.md#malt) | # Learn More -To learn more about Swarms architecture and how different swarm types work together, visit our comprehensive guides: +To explore Swarms architecture and how different swarm types work together, check out our comprehensive guides: - [Introduction to Multi-Agent Architectures](/swarms/concept/swarm_architectures) - - [How to Choose the Right Multi-Agent Architecture](/swarms/concept/how_to_choose_swarms) - - [Framework Architecture Overview](/swarms/concept/framework_architecture) - - [Building Custom Swarms](/swarms/structs/custom_swarm) From 117f49c3b2339e480c990c986e589d41ad4dfa58 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 12:39:38 +0530 Subject: [PATCH 30/73] seprate docs for each multi-agent structure for api --- docs/mkdocs.yml | 13 ++ docs/swarms_cloud/agent_rearrange.md | 176 ++++++++++++++++ docs/swarms_cloud/auto.md | 203 +++++++++++++++++++ docs/swarms_cloud/auto_swarm_builder.md | 195 ++++++++++++++++++ docs/swarms_cloud/concurrent_workflow.md | 198 ++++++++++++++++++ docs/swarms_cloud/group_chat.md | 204 +++++++++++++++++++ docs/swarms_cloud/hierarchical_swarm.md | 241 ++++++++++++++++++++++ docs/swarms_cloud/index.md | 0 docs/swarms_cloud/majority_voting.md | 240 ++++++++++++++++++++++ docs/swarms_cloud/malt.md | 248 +++++++++++++++++++++++ docs/swarms_cloud/mixture_of_agents.md | 187 +++++++++++++++++ docs/swarms_cloud/multi_agent_router.md | 224 ++++++++++++++++++++ docs/swarms_cloud/sequential_workflow.md | 192 ++++++++++++++++++ docs/swarms_cloud/spreadsheet_swarm.md | 232 +++++++++++++++++++++ docs/swarms_cloud/swarm_types.md | 24 +-- 15 files changed, 2565 insertions(+), 12 deletions(-) create mode 100644 docs/swarms_cloud/agent_rearrange.md create mode 100644 docs/swarms_cloud/auto.md create mode 100644 docs/swarms_cloud/auto_swarm_builder.md create mode 100644 docs/swarms_cloud/concurrent_workflow.md create mode 100644 docs/swarms_cloud/group_chat.md create mode 100644 docs/swarms_cloud/hierarchical_swarm.md create mode 100644 docs/swarms_cloud/index.md create mode 100644 docs/swarms_cloud/majority_voting.md create mode 100644 docs/swarms_cloud/malt.md create mode 100644 docs/swarms_cloud/mixture_of_agents.md create mode 100644 docs/swarms_cloud/multi_agent_router.md create mode 100644 docs/swarms_cloud/sequential_workflow.md create mode 100644 docs/swarms_cloud/spreadsheet_swarm.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 006ff72e..a371119c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -446,6 +446,19 @@ nav: - Tools: "swarms_cloud/swarms_api_tools.md" - Multi-Agent: - Multi Agent Architectures Available: "swarms_cloud/swarm_types.md" + - Swarm Types: + - AgentRearrange: "swarms_cloud/agent_rearrange.md" + - MixtureOfAgents: "swarms_cloud/mixture_of_agents.md" + - SpreadSheetSwarm: "swarms_cloud/spreadsheet_swarm.md" + - SequentialWorkflow: "swarms_cloud/sequential_workflow.md" + - ConcurrentWorkflow: "swarms_cloud/concurrent_workflow.md" + - GroupChat: "swarms_cloud/group_chat.md" + - MultiAgentRouter: "swarms_cloud/multi_agent_router.md" + - AutoSwarmBuilder: "swarms_cloud/auto_swarm_builder.md" + - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" + - Auto: "swarms_cloud/auto.md" + - MajorityVoting: "swarms_cloud/majority_voting.md" + - MALT: "swarms_cloud/malt.md" - Examples: - Medical Swarm: "swarms/examples/swarms_api_medical.md" - Finance Swarm: "swarms/examples/swarms_api_finance.md" diff --git a/docs/swarms_cloud/agent_rearrange.md b/docs/swarms_cloud/agent_rearrange.md new file mode 100644 index 00000000..15c05125 --- /dev/null +++ b/docs/swarms_cloud/agent_rearrange.md @@ -0,0 +1,176 @@ +# AgentRearrange + +*Dynamically reorganizes agents to optimize task performance and efficiency* + +**Swarm Type**: `AgentRearrange` + +## Overview + +The AgentRearrange swarm type dynamically reorganizes the workflow between agents based on task requirements and performance metrics. This architecture is particularly useful when the effectiveness of agents depends on their sequence or arrangement, allowing for optimal task distribution and execution flow. + +Key features: +- **Dynamic Reorganization**: Automatically adjusts agent order based on task needs +- **Performance Optimization**: Optimizes workflow for maximum efficiency +- **Adaptive Sequencing**: Learns from execution patterns to improve arrangement +- **Flexible Task Distribution**: Distributes work based on agent capabilities + +## Use Cases + +- Complex workflows where task order matters +- Multi-step processes requiring optimization +- Tasks where agent performance varies by sequence +- Adaptive workflow management systems + +## API Usage + +### Basic AgentRearrange Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Document Processing Rearrange", + "description": "Process documents with dynamic agent reorganization", + "swarm_type": "AgentRearrange", + "task": "Analyze this legal document and extract key insights, then summarize findings and identify action items", + "agents": [ + { + "agent_name": "Document Analyzer", + "description": "Analyzes document content and structure", + "system_prompt": "You are an expert document analyst. Extract key information, themes, and insights from documents.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Legal Expert", + "description": "Provides legal context and interpretation", + "system_prompt": "You are a legal expert. Analyze documents for legal implications, risks, and compliance issues.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Summarizer", + "description": "Creates concise summaries and action items", + "system_prompt": "You are an expert at creating clear, actionable summaries from complex information.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + } + ], + "rearrange_flow": "Optimize agent sequence based on document complexity and required output quality", + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Document Processing Rearrange", + "description": "Process documents with dynamic agent reorganization", + "swarm_type": "AgentRearrange", + "task": "Analyze this legal document and extract key insights, then summarize findings and identify action items", + "agents": [ + { + "agent_name": "Document Analyzer", + "description": "Analyzes document content and structure", + "system_prompt": "You are an expert document analyst. Extract key information, themes, and insights from documents.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Legal Expert", + "description": "Provides legal context and interpretation", + "system_prompt": "You are a legal expert. Analyze documents for legal implications, risks, and compliance issues.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Summarizer", + "description": "Creates concise summaries and action items", + "system_prompt": "You are an expert at creating clear, actionable summaries from complex information.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + } + ], + "rearrange_flow": "Optimize agent sequence based on document complexity and required output quality", + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("AgentRearrange swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Output: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "document-processing-rearrange", + "swarm_type": "AgentRearrange", + "task": "Analyze this legal document and extract key insights, then summarize findings and identify action items", + "output": { + "analysis": "Document analysis results...", + "legal_insights": "Legal implications and risks...", + "summary": "Concise summary of findings...", + "action_items": ["Action 1", "Action 2", "Action 3"] + }, + "metadata": { + "agent_sequence": ["Legal Expert", "Document Analyzer", "Summarizer"], + "rearrangement_reason": "Optimized for legal document analysis workflow", + "execution_time_seconds": 18.2, + "billing_info": { + "total_cost": 0.045 + } + } +} +``` + +## Configuration Options + +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +| `rearrange_flow` | string | Instructions for how agents should be rearranged | None | +| `agents` | Array | List of agents to be dynamically arranged | Required | +| `max_loops` | integer | Maximum rearrangement iterations | 1 | + +## Best Practices + +- Provide clear `rearrange_flow` instructions for optimal reorganization +- Design agents with complementary but flexible roles +- Use when task complexity requires adaptive sequencing +- Monitor execution patterns to understand rearrangement decisions + +## Related Swarm Types + +- [SequentialWorkflow](sequential_workflow.md) - For fixed sequential processing +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic swarm construction +- [HierarchicalSwarm](hierarchical_swarm.md) - For structured agent hierarchies \ No newline at end of file diff --git a/docs/swarms_cloud/auto.md b/docs/swarms_cloud/auto.md new file mode 100644 index 00000000..262797a1 --- /dev/null +++ b/docs/swarms_cloud/auto.md @@ -0,0 +1,203 @@ +# Auto + +*Intelligently selects the most effective swarm architecture for a given task* + +**Swarm Type**: `auto` (or `Auto`) + +## Overview + +The Auto swarm type intelligently selects the most effective swarm architecture for a given task based on context analysis and task requirements. This intelligent system evaluates the task description and automatically chooses the optimal swarm type from all available architectures, ensuring maximum efficiency and effectiveness. + +Key features: +- **Intelligent Selection**: Automatically chooses the best swarm type for each task +- **Context Analysis**: Analyzes task requirements to make optimal decisions +- **Adaptive Architecture**: Adapts to different types of problems automatically +- **Zero Configuration**: No manual architecture selection required + +## Use Cases + +- When unsure about which swarm type to use +- General-purpose task automation +- Rapid prototyping and experimentation +- Simplified API usage for non-experts + +## API Usage + +### Basic Auto Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Auto Content Creation", + "description": "Let the system choose the best approach for content creation", + "swarm_type": "auto", + "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Auto Content Creation", + "description": "Let the system choose the best approach for content creation", + "swarm_type": "auto", + "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("Auto swarm completed successfully!") + print(f"Selected architecture: {result['metadata']['selected_swarm_type']}") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Content: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "auto-content-creation", + "swarm_type": "SequentialWorkflow", + "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", + "output": { + "research_phase": { + "key_findings": "Sustainable investing has grown 42% in the past two years...", + "market_trends": "ESG funds outperformed traditional funds by 3.2%...", + "statistics": "Global sustainable investment assets reached $35.3 trillion..." + }, + "writing_phase": { + "title": "The Future of Sustainable Investing: A Guide to ESG Strategies", + "content": "Comprehensive blog post with introduction, main sections, and conclusion...", + "word_count": 1850 + }, + "editing_phase": { + "improvements": "Enhanced clarity, improved flow, corrected grammar", + "readability_score": "Grade 8 level - accessible to general audience", + "final_content": "Polished blog post ready for publication..." + }, + "seo_optimization": { + "target_keywords": ["sustainable investing", "ESG funds", "green finance"], + "meta_description": "Discover the future of sustainable investing...", + "optimized_content": "SEO-optimized version with strategic keyword placement" + } + }, + "metadata": { + "auto_selection": { + "selected_swarm_type": "SequentialWorkflow", + "reasoning": "Task requires step-by-step content creation process where each phase builds on the previous", + "analysis": { + "task_complexity": "Medium-High", + "sequential_dependencies": true, + "parallel_opportunities": false, + "collaboration_needs": "Low" + } + }, + "generated_agents": [ + "Research Specialist", + "Content Writer", + "Editor", + "SEO Optimizer" + ], + "execution_time_seconds": 43.2, + "billing_info": { + "total_cost": 0.087 + } + } +} +``` + +### Advanced Auto Usage + +You can provide additional context to help the Auto selection: + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Auto Business Analysis", + "description": "Automatic swarm selection for business analysis", + "swarm_type": "auto", + "task": "Analyze market opportunities for a new AI startup in healthcare", + "rules": "Need multiple perspectives from different business functions, time-sensitive analysis required", + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + swarm_config = { + "name": "Auto Business Analysis", + "description": "Automatic swarm selection for business analysis", + "swarm_type": "auto", + "task": "Analyze market opportunities for a new AI startup in healthcare", + "rules": "Need multiple perspectives from different business functions, time-sensitive analysis required", + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print(f"Auto selected: {result['metadata']['auto_selection']['selected_swarm_type']}") + print(f"Reasoning: {result['metadata']['auto_selection']['reasoning']}") + ``` + +## Selection Logic + +The Auto swarm type analyzes various factors to make its selection: + +| Factor | Consideration | +|--------|---------------| +| **Task Complexity** | Simple → Single agent, Complex → Multi-agent | +| **Sequential Dependencies** | Dependencies → SequentialWorkflow | +| **Parallel Opportunities** | Independent subtasks → ConcurrentWorkflow | +| **Collaboration Needs** | Discussion required → GroupChat | +| **Expertise Diversity** | Multiple domains → MixtureOfAgents | +| **Management Needs** | Oversight required → HierarchicalSwarm | +| **Routing Requirements** | Task distribution → MultiAgentRouter | + +## Best Practices + +- Provide detailed task descriptions for better selection +- Use `rules` parameter to guide selection criteria +- Review the selected architecture in response metadata +- Ideal for users new to swarm architectures + +## Related Swarm Types + +Since Auto can select any swarm type, it's related to all architectures: +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic agent generation +- [SequentialWorkflow](sequential_workflow.md) - Often selected for linear tasks +- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel processing needs +- [MixtureOfAgents](mixture_of_agents.md) - For diverse expertise requirements \ No newline at end of file diff --git a/docs/swarms_cloud/auto_swarm_builder.md b/docs/swarms_cloud/auto_swarm_builder.md new file mode 100644 index 00000000..86799e1e --- /dev/null +++ b/docs/swarms_cloud/auto_swarm_builder.md @@ -0,0 +1,195 @@ +# AutoSwarmBuilder + +*Automatically configures optimal swarm architectures based on task requirements* + +**Swarm Type**: `AutoSwarmBuilder` + +## Overview + +The AutoSwarmBuilder automatically configures optimal agent architectures based on task requirements and performance metrics, simplifying swarm creation. This intelligent system analyzes the given task and automatically generates the most suitable agent configuration, eliminating the need for manual swarm design. + +Key features: +- **Intelligent Configuration**: Automatically designs optimal swarm structures +- **Task-Adaptive**: Adapts architecture based on specific task requirements +- **Performance Optimization**: Selects configurations for maximum efficiency +- **Simplified Setup**: Eliminates manual agent configuration complexity + +## Use Cases + +- Quick prototyping and experimentation +- Unknown or complex task requirements +- Automated swarm optimization +- Simplified swarm creation for non-experts + +## API Usage + +### Basic AutoSwarmBuilder Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Auto Marketing Campaign", + "description": "Automatically build optimal swarm for marketing campaign creation", + "swarm_type": "AutoSwarmBuilder", + "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Auto Marketing Campaign", + "description": "Automatically build optimal swarm for marketing campaign creation", + "swarm_type": "AutoSwarmBuilder", + "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("AutoSwarmBuilder completed successfully!") + print(f"Generated swarm architecture: {result['metadata']['generated_architecture']}") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Campaign output: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "auto-marketing-campaign", + "swarm_type": "AutoSwarmBuilder", + "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", + "output": { + "campaign_strategy": { + "brand_positioning": "Authentic, sustainable fashion for conscious Gen Z consumers", + "key_messaging": "Style that makes a difference - fashion with purpose", + "target_demographics": "Ages 18-26, environmentally conscious, social media active" + }, + "content_strategy": { + "social_platforms": ["TikTok", "Instagram", "Pinterest"], + "content_pillars": ["Sustainability education", "Style inspiration", "Behind-the-scenes"], + "posting_schedule": "Daily posts across platforms with peak engagement timing" + }, + "influencer_strategy": { + "tier_1": "Micro-influencers (10K-100K followers) focused on sustainability", + "tier_2": "Fashion nano-influencers (1K-10K followers) for authentic engagement", + "collaboration_types": ["Product partnerships", "Brand ambassador programs"] + }, + "paid_advertising": { + "platforms": ["Instagram Ads", "TikTok Ads", "Google Ads"], + "budget_allocation": "40% social media, 30% search, 30% video content", + "targeting_strategy": "Interest-based and lookalike audiences" + }, + "metrics_and_kpis": { + "awareness": "Brand mention volume, reach, impressions", + "engagement": "Comments, shares, saves, time spent", + "conversion": "Website traffic, email signups, sales" + } + }, + "metadata": { + "generated_architecture": { + "selected_swarm_type": "MixtureOfAgents", + "generated_agents": [ + "Brand Strategy Expert", + "Gen Z Marketing Specialist", + "Social Media Content Creator", + "Influencer Marketing Manager", + "Digital Advertising Strategist" + ], + "reasoning": "Complex marketing campaign requires diverse expertise working collaboratively" + }, + "auto_optimization": { + "task_complexity": "High", + "required_expertise_areas": 5, + "optimal_architecture": "Collaborative with specialized agents" + }, + "execution_time_seconds": 28.6, + "billing_info": { + "total_cost": 0.071 + } + } +} +``` + +### Advanced Configuration + +You can provide additional guidance to the AutoSwarmBuilder: + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Auto Research Project", + "description": "Auto-build research swarm with specific constraints", + "swarm_type": "AutoSwarmBuilder", + "task": "Conduct comprehensive research on the impact of AI on healthcare outcomes", + "rules": "Focus on peer-reviewed sources, include cost-benefit analysis, ensure balanced perspective on risks and benefits", + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + swarm_config = { + "name": "Auto Research Project", + "description": "Auto-build research swarm with specific constraints", + "swarm_type": "AutoSwarmBuilder", + "task": "Conduct comprehensive research on the impact of AI on healthcare outcomes", + "rules": "Focus on peer-reviewed sources, include cost-benefit analysis, ensure balanced perspective on risks and benefits", + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + ``` + +## Configuration Options + +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +| `task` | string | Task description for automatic optimization | Required | +| `rules` | string | Additional constraints and guidelines | None | +| `max_loops` | integer | Maximum execution rounds | 1 | + +## Best Practices + +- Provide detailed, specific task descriptions for better optimization +- Use `rules` parameter to guide the automatic configuration +- Ideal for rapid prototyping and experimentation +- Review generated architecture in response metadata + +## Related Swarm Types + +- [Auto](auto.md) - For automatic swarm type selection +- [MixtureOfAgents](mixture_of_agents.md) - Often selected by AutoSwarmBuilder +- [HierarchicalSwarm](hierarchical_swarm.md) - For complex structured tasks \ No newline at end of file diff --git a/docs/swarms_cloud/concurrent_workflow.md b/docs/swarms_cloud/concurrent_workflow.md new file mode 100644 index 00000000..289deb84 --- /dev/null +++ b/docs/swarms_cloud/concurrent_workflow.md @@ -0,0 +1,198 @@ +# ConcurrentWorkflow + +*Runs independent tasks in parallel for faster processing* + +**Swarm Type**: `ConcurrentWorkflow` + +## Overview + +The ConcurrentWorkflow swarm type runs independent tasks in parallel, significantly reducing processing time for complex operations. This architecture is ideal for tasks that can be processed simultaneously without dependencies, allowing multiple agents to work on different aspects of a problem at the same time. + +Key features: +- **Parallel Execution**: Multiple agents work simultaneously +- **Reduced Processing Time**: Faster completion through parallelization +- **Independent Tasks**: Agents work on separate, non-dependent subtasks +- **Scalable Performance**: Performance scales with the number of agents + +## Use Cases + +- Independent data analysis tasks +- Parallel content generation +- Multi-source research projects +- Distributed problem solving + +## API Usage + +### Basic ConcurrentWorkflow Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Market Research Concurrent", + "description": "Parallel market research across different sectors", + "swarm_type": "ConcurrentWorkflow", + "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", + "agents": [ + { + "agent_name": "AI Market Analyst", + "description": "Analyzes AI market trends and opportunities", + "system_prompt": "You are an AI market analyst. Focus on artificial intelligence market trends, opportunities, key players, and growth projections.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Healthcare Market Analyst", + "description": "Analyzes healthcare market trends", + "system_prompt": "You are a healthcare market analyst. Focus on healthcare market trends, digital health opportunities, regulatory landscape, and growth areas.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Fintech Market Analyst", + "description": "Analyzes fintech market opportunities", + "system_prompt": "You are a fintech market analyst. Focus on financial technology trends, digital payment systems, blockchain opportunities, and regulatory developments.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "E-commerce Market Analyst", + "description": "Analyzes e-commerce market trends", + "system_prompt": "You are an e-commerce market analyst. Focus on online retail trends, marketplace opportunities, consumer behavior, and emerging platforms.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Market Research Concurrent", + "description": "Parallel market research across different sectors", + "swarm_type": "ConcurrentWorkflow", + "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", + "agents": [ + { + "agent_name": "AI Market Analyst", + "description": "Analyzes AI market trends and opportunities", + "system_prompt": "You are an AI market analyst. Focus on artificial intelligence market trends, opportunities, key players, and growth projections.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Healthcare Market Analyst", + "description": "Analyzes healthcare market trends", + "system_prompt": "You are a healthcare market analyst. Focus on healthcare market trends, digital health opportunities, regulatory landscape, and growth areas.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Fintech Market Analyst", + "description": "Analyzes fintech market opportunities", + "system_prompt": "You are a fintech market analyst. Focus on financial technology trends, digital payment systems, blockchain opportunities, and regulatory developments.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "E-commerce Market Analyst", + "description": "Analyzes e-commerce market trends", + "system_prompt": "You are an e-commerce market analyst. Focus on online retail trends, marketplace opportunities, consumer behavior, and emerging platforms.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("ConcurrentWorkflow swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Parallel results: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "market-research-concurrent", + "swarm_type": "ConcurrentWorkflow", + "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", + "output": { + "ai_market_analysis": { + "market_size": "$150B by 2025", + "growth_rate": "25% CAGR", + "key_opportunities": ["Generative AI", "Edge AI", "AI Infrastructure"] + }, + "healthcare_analysis": { + "market_size": "$350B by 2025", + "growth_rate": "12% CAGR", + "key_opportunities": ["Telemedicine", "AI Diagnostics", "Digital Therapeutics"] + }, + "fintech_analysis": { + "market_size": "$200B by 2025", + "growth_rate": "18% CAGR", + "key_opportunities": ["DeFi", "Digital Banking", "Payment Infrastructure"] + }, + "ecommerce_analysis": { + "market_size": "$8T by 2025", + "growth_rate": "14% CAGR", + "key_opportunities": ["Social Commerce", "B2B Marketplaces", "Sustainable Commerce"] + } + }, + "metadata": { + "parallel_execution": true, + "agents_completed_simultaneously": 4, + "execution_time_seconds": 12.8, + "billing_info": { + "total_cost": 0.052 + } + } +} +``` + +## Best Practices + +- Design independent tasks that don't require sequential dependencies +- Use for tasks that can be parallelized effectively +- Ensure agents have distinct, non-overlapping responsibilities +- Ideal for time-sensitive analysis requiring multiple perspectives + +## Related Swarm Types + +- [SequentialWorkflow](sequential_workflow.md) - For ordered execution +- [MixtureOfAgents](mixture_of_agents.md) - For collaborative analysis +- [MultiAgentRouter](multi_agent_router.md) - For intelligent task distribution \ No newline at end of file diff --git a/docs/swarms_cloud/group_chat.md b/docs/swarms_cloud/group_chat.md new file mode 100644 index 00000000..e3410e1c --- /dev/null +++ b/docs/swarms_cloud/group_chat.md @@ -0,0 +1,204 @@ +# GroupChat + +*Enables dynamic collaboration through chat-based interaction* + +**Swarm Type**: `GroupChat` + +## Overview + +The GroupChat swarm type enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. Agents participate in a conversational workflow where they can build upon each other's contributions, debate ideas, and reach consensus through natural dialogue. + +Key features: +- **Interactive Dialogue**: Agents communicate through natural conversation +- **Dynamic Collaboration**: Real-time information sharing and building upon ideas +- **Consensus Building**: Agents can debate and reach decisions collectively +- **Flexible Participation**: Agents can contribute when relevant to the discussion + +## Use Cases + +- Brainstorming and ideation sessions +- Multi-perspective problem analysis +- Collaborative decision-making processes +- Creative content development + +## API Usage + +### Basic GroupChat Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Product Strategy Discussion", + "description": "Collaborative chat to develop product strategy", + "swarm_type": "GroupChat", + "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", + "agents": [ + { + "agent_name": "Product Manager", + "description": "Leads product strategy and development", + "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.6 + }, + { + "agent_name": "Marketing Strategist", + "description": "Develops marketing and positioning strategy", + "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.7 + }, + { + "agent_name": "Sales Director", + "description": "Provides sales and customer perspective", + "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.5 + }, + { + "agent_name": "UX Researcher", + "description": "Represents user experience and research insights", + "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.4 + } + ], + "max_loops": 3 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Product Strategy Discussion", + "description": "Collaborative chat to develop product strategy", + "swarm_type": "GroupChat", + "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", + "agents": [ + { + "agent_name": "Product Manager", + "description": "Leads product strategy and development", + "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.6 + }, + { + "agent_name": "Marketing Strategist", + "description": "Develops marketing and positioning strategy", + "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.7 + }, + { + "agent_name": "Sales Director", + "description": "Provides sales and customer perspective", + "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.5 + }, + { + "agent_name": "UX Researcher", + "description": "Represents user experience and research insights", + "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", + "model_name": "gpt-4o", + "max_loops": 3, + "temperature": 0.4 + } + ], + "max_loops": 3 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("GroupChat swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Chat discussion: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "product-strategy-discussion", + "swarm_type": "GroupChat", + "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", + "output": { + "chat_transcript": [ + { + "agent": "Product Manager", + "message": "Let's start by defining our target user. What specific pain points do small businesses have with productivity tools?" + }, + { + "agent": "UX Researcher", + "message": "From our research, small businesses struggle with tool complexity and time to value. They need something that works immediately without extensive setup." + }, + { + "agent": "Sales Director", + "message": "I agree. SMBs have limited time and resources. They typically abandon tools that require more than a week to see value. Pricing is also critical - they're very cost-conscious." + }, + { + "agent": "Marketing Strategist", + "message": "This suggests we should focus on 'instant productivity gains' messaging. We could position against complex enterprise tools that overwhelm small teams." + } + ], + "key_decisions": [ + "Target: Small businesses with 5-50 employees", + "Positioning: Simple, immediate productivity gains", + "Pricing: Freemium model with low-cost paid tiers", + "GTM: Self-serve with strong onboarding" + ], + "final_strategy": "Launch with freemium model targeting productivity-focused small businesses through content marketing and self-serve channels..." + }, + "metadata": { + "conversation_rounds": 3, + "total_messages": 12, + "consensus_reached": true, + "execution_time_seconds": 38.7, + "billing_info": { + "total_cost": 0.095 + } + } +} +``` + +## Best Practices + +- Set clear discussion goals and objectives +- Use diverse agent personalities for richer dialogue +- Allow multiple conversation rounds for idea development +- Encourage agents to build upon each other's contributions + +## Related Swarm Types + +- [MixtureOfAgents](mixture_of_agents.md) - For complementary expertise +- [MajorityVoting](majority_voting.md) - For consensus decision-making +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic discussion setup \ No newline at end of file diff --git a/docs/swarms_cloud/hierarchical_swarm.md b/docs/swarms_cloud/hierarchical_swarm.md new file mode 100644 index 00000000..0cd6ec21 --- /dev/null +++ b/docs/swarms_cloud/hierarchical_swarm.md @@ -0,0 +1,241 @@ +# HierarchicalSwarm + +*Implements structured, multi-level task management with clear authority* + +**Swarm Type**: `HierarchicalSwarm` + +## Overview + +The HierarchicalSwarm implements a structured, multi-level approach to task management with clear lines of authority and delegation. This architecture organizes agents in a hierarchical structure where manager agents coordinate and oversee worker agents, enabling efficient task distribution and quality control. + +Key features: +- **Structured Hierarchy**: Clear organizational structure with managers and workers +- **Delegated Authority**: Manager agents distribute tasks to specialized workers +- **Quality Oversight**: Multi-level review and validation processes +- **Scalable Organization**: Efficient coordination of large agent teams + +## Use Cases + +- Complex projects requiring management oversight +- Large-scale content production workflows +- Multi-stage validation and review processes +- Enterprise-level task coordination + +## API Usage + +### Basic HierarchicalSwarm Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Software Development Hierarchy", + "description": "Hierarchical software development team with project manager oversight", + "swarm_type": "HierarchicalSwarm", + "task": "Design and plan a new mobile app for expense tracking targeting freelancers", + "agents": [ + { + "agent_name": "Project Manager", + "description": "Oversees project planning and coordinates team efforts", + "system_prompt": "You are a senior project manager. Coordinate the team, break down tasks, ensure quality, and synthesize outputs. Delegate specific tasks to team members.", + "model_name": "gpt-4o", + "role": "manager", + "max_loops": 2, + "temperature": 0.4 + }, + { + "agent_name": "UX Designer", + "description": "Designs user experience and interface", + "system_prompt": "You are a UX designer specializing in mobile apps. Focus on user flows, wireframes, and interface design. Report findings to the project manager.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.6 + }, + { + "agent_name": "Technical Architect", + "description": "Designs technical architecture and system requirements", + "system_prompt": "You are a technical architect. Focus on system design, technology stack, database design, and technical requirements. Provide technical guidance.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Business Analyst", + "description": "Analyzes business requirements and market fit", + "system_prompt": "You are a business analyst. Focus on requirements gathering, market analysis, feature prioritization, and business logic.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "QA Specialist", + "description": "Ensures quality and validates deliverables", + "system_prompt": "You are a QA specialist. Review all outputs for quality, completeness, and consistency. Identify gaps and suggest improvements.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 2 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Software Development Hierarchy", + "description": "Hierarchical software development team with project manager oversight", + "swarm_type": "HierarchicalSwarm", + "task": "Design and plan a new mobile app for expense tracking targeting freelancers", + "agents": [ + { + "agent_name": "Project Manager", + "description": "Oversees project planning and coordinates team efforts", + "system_prompt": "You are a senior project manager. Coordinate the team, break down tasks, ensure quality, and synthesize outputs. Delegate specific tasks to team members.", + "model_name": "gpt-4o", + "role": "manager", + "max_loops": 2, + "temperature": 0.4 + }, + { + "agent_name": "UX Designer", + "description": "Designs user experience and interface", + "system_prompt": "You are a UX designer specializing in mobile apps. Focus on user flows, wireframes, and interface design. Report findings to the project manager.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.6 + }, + { + "agent_name": "Technical Architect", + "description": "Designs technical architecture and system requirements", + "system_prompt": "You are a technical architect. Focus on system design, technology stack, database design, and technical requirements. Provide technical guidance.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Business Analyst", + "description": "Analyzes business requirements and market fit", + "system_prompt": "You are a business analyst. Focus on requirements gathering, market analysis, feature prioritization, and business logic.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "QA Specialist", + "description": "Ensures quality and validates deliverables", + "system_prompt": "You are a QA specialist. Review all outputs for quality, completeness, and consistency. Identify gaps and suggest improvements.", + "model_name": "gpt-4o", + "role": "worker", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 2 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("HierarchicalSwarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Project plan: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "software-development-hierarchy", + "swarm_type": "HierarchicalSwarm", + "task": "Design and plan a new mobile app for expense tracking targeting freelancers", + "output": { + "project_overview": { + "manager_synthesis": "Comprehensive project plan for freelancer expense tracking app...", + "timeline": "16-week development cycle", + "key_deliverables": ["UX Design", "Technical Architecture", "Business Requirements", "QA Framework"] + }, + "ux_design": { + "user_flows": "Streamlined expense entry and categorization flows...", + "wireframes": "Mobile-first design with dashboard and reporting views...", + "usability_considerations": "One-tap expense entry, photo receipt capture..." + }, + "technical_architecture": { + "tech_stack": "React Native, Node.js, PostgreSQL, AWS", + "system_design": "Microservices architecture with offline capability...", + "security_requirements": "End-to-end encryption, secure authentication..." + }, + "business_requirements": { + "target_market": "Freelancers and independent contractors", + "core_features": ["Expense tracking", "Receipt scanning", "Tax reporting"], + "monetization": "Freemium model with premium reporting features" + }, + "qa_framework": { + "testing_strategy": "Automated testing for core functions...", + "quality_metrics": "Performance, usability, and security benchmarks...", + "validation_checkpoints": "Weekly reviews and milestone validations" + } + }, + "metadata": { + "hierarchy_structure": { + "managers": ["Project Manager"], + "workers": ["UX Designer", "Technical Architect", "Business Analyst", "QA Specialist"] + }, + "coordination_rounds": 2, + "task_delegation": "Manager coordinated 4 specialized work streams", + "execution_time_seconds": 52.4, + "billing_info": { + "total_cost": 0.128 + } + } +} +``` + +## Configuration Options + +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +| `role` | string | Agent role: "manager" or "worker" | "worker" | +| `agents` | Array | Mix of manager and worker agents | Required | +| `max_loops` | integer | Coordination rounds for managers | 1 | + +## Best Practices + +- Clearly define manager and worker roles using the `role` parameter +- Give managers higher `max_loops` for coordination activities +- Design worker agents with specialized, focused responsibilities +- Use for complex projects requiring oversight and coordination + +## Related Swarm Types + +- [SequentialWorkflow](sequential_workflow.md) - For linear task progression +- [MultiAgentRouter](multi_agent_router.md) - For intelligent task routing +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic hierarchy creation \ No newline at end of file diff --git a/docs/swarms_cloud/index.md b/docs/swarms_cloud/index.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/swarms_cloud/majority_voting.md b/docs/swarms_cloud/majority_voting.md new file mode 100644 index 00000000..63f39439 --- /dev/null +++ b/docs/swarms_cloud/majority_voting.md @@ -0,0 +1,240 @@ +# MajorityVoting + +*Implements robust decision-making through consensus and voting* + +**Swarm Type**: `MajorityVoting` + +## Overview + +The MajorityVoting swarm type implements robust decision-making through consensus mechanisms, ideal for tasks requiring collective intelligence or verification. Multiple agents independently analyze the same problem and vote on the best solution, ensuring high-quality, well-validated outcomes through democratic consensus. + +Key features: +- **Consensus-Based Decisions**: Multiple agents vote on the best solution +- **Quality Assurance**: Reduces individual agent bias through collective input +- **Democratic Process**: Fair and transparent decision-making mechanism +- **Robust Validation**: Multiple perspectives ensure thorough analysis + +## Use Cases + +- Critical decision-making requiring validation +- Quality assurance and verification tasks +- Complex problem solving with multiple viable solutions +- Risk assessment and evaluation scenarios + +## API Usage + +### Basic MajorityVoting Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Investment Decision Voting", + "description": "Multiple financial experts vote on investment recommendations", + "swarm_type": "MajorityVoting", + "task": "Evaluate whether to invest $1M in a renewable energy startup. Consider market potential, financial projections, team strength, and competitive landscape.", + "agents": [ + { + "agent_name": "Growth Investor", + "description": "Focuses on growth potential and market opportunity", + "system_prompt": "You are a growth-focused venture capitalist. Evaluate investments based on market size, scalability, and growth potential. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Financial Analyst", + "description": "Analyzes financial metrics and projections", + "system_prompt": "You are a financial analyst specializing in startups. Evaluate financial projections, revenue models, and unit economics. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Technical Due Diligence", + "description": "Evaluates technology and product viability", + "system_prompt": "You are a technical due diligence expert. Assess technology viability, intellectual property, product-market fit, and technical risks. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Market Analyst", + "description": "Analyzes market conditions and competition", + "system_prompt": "You are a market research analyst. Evaluate market dynamics, competitive landscape, regulatory environment, and market timing. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Risk Assessor", + "description": "Identifies and evaluates investment risks", + "system_prompt": "You are a risk assessment specialist. Identify potential risks, evaluate mitigation strategies, and assess overall risk profile. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Investment Decision Voting", + "description": "Multiple financial experts vote on investment recommendations", + "swarm_type": "MajorityVoting", + "task": "Evaluate whether to invest $1M in a renewable energy startup. Consider market potential, financial projections, team strength, and competitive landscape.", + "agents": [ + { + "agent_name": "Growth Investor", + "description": "Focuses on growth potential and market opportunity", + "system_prompt": "You are a growth-focused venture capitalist. Evaluate investments based on market size, scalability, and growth potential. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Financial Analyst", + "description": "Analyzes financial metrics and projections", + "system_prompt": "You are a financial analyst specializing in startups. Evaluate financial projections, revenue models, and unit economics. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Technical Due Diligence", + "description": "Evaluates technology and product viability", + "system_prompt": "You are a technical due diligence expert. Assess technology viability, intellectual property, product-market fit, and technical risks. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Market Analyst", + "description": "Analyzes market conditions and competition", + "system_prompt": "You are a market research analyst. Evaluate market dynamics, competitive landscape, regulatory environment, and market timing. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Risk Assessor", + "description": "Identifies and evaluates investment risks", + "system_prompt": "You are a risk assessment specialist. Identify potential risks, evaluate mitigation strategies, and assess overall risk profile. Provide a recommendation with confidence score.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("MajorityVoting completed successfully!") + print(f"Final decision: {result['output']['consensus_decision']}") + print(f"Vote breakdown: {result['metadata']['vote_breakdown']}") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "investment-decision-voting", + "swarm_type": "MajorityVoting", + "task": "Evaluate whether to invest $1M in a renewable energy startup. Consider market potential, financial projections, team strength, and competitive landscape.", + "output": { + "individual_recommendations": [ + { + "agent": "Growth Investor", + "recommendation": "INVEST", + "confidence": 0.8, + "reasoning": "Strong market growth potential in renewable energy sector, scalable technology platform" + }, + { + "agent": "Financial Analyst", + "recommendation": "INVEST", + "confidence": 0.7, + "reasoning": "Solid financial projections, reasonable burn rate, clear path to profitability" + }, + { + "agent": "Technical Due Diligence", + "recommendation": "INVEST", + "confidence": 0.75, + "reasoning": "Innovative technology with strong IP portfolio, experienced technical team" + }, + { + "agent": "Market Analyst", + "recommendation": "WAIT", + "confidence": 0.6, + "reasoning": "Highly competitive market, regulatory uncertainties may impact timeline" + }, + { + "agent": "Risk Assessor", + "recommendation": "INVEST", + "confidence": 0.65, + "reasoning": "Manageable risks with strong mitigation strategies, experienced leadership team" + } + ], + "consensus_decision": "INVEST", + "consensus_confidence": 0.72, + "consensus_reasoning": "4 out of 5 experts recommend investment with strong market potential and solid fundamentals, despite some market uncertainties" + }, + "metadata": { + "vote_breakdown": { + "INVEST": 4, + "WAIT": 1, + "REJECT": 0 + }, + "vote_percentage": { + "INVEST": "80%", + "WAIT": "20%", + "REJECT": "0%" + }, + "average_confidence": 0.70, + "consensus_threshold": "Simple majority (50%+)", + "execution_time_seconds": 25.8, + "billing_info": { + "total_cost": 0.063 + } + } +} +``` + +## Best Practices + +- Use odd numbers of agents to avoid tie votes +- Design agents with different perspectives for robust evaluation +- Include confidence scores in agent prompts for weighted decisions +- Ideal for high-stakes decisions requiring validation + +## Related Swarm Types + +- [GroupChat](group_chat.md) - For discussion-based consensus +- [MixtureOfAgents](mixture_of_agents.md) - For diverse expertise collaboration +- [HierarchicalSwarm](hierarchical_swarm.md) - For structured decision-making \ No newline at end of file diff --git a/docs/swarms_cloud/malt.md b/docs/swarms_cloud/malt.md new file mode 100644 index 00000000..7539ed58 --- /dev/null +++ b/docs/swarms_cloud/malt.md @@ -0,0 +1,248 @@ +# MALT + +*Specialized framework for complex language-based tasks and processing* + +**Swarm Type**: `MALT` + +## Overview + +MALT (Multi-Agent Language Task) is a specialized framework optimized for complex language-based tasks, optimizing agent collaboration for sophisticated language processing operations. This architecture excels at tasks requiring deep linguistic analysis, natural language understanding, and complex text generation workflows. + +Key features: +- **Language Optimization**: Specifically designed for natural language tasks +- **Linguistic Collaboration**: Agents work together on complex language operations +- **Text Processing Pipeline**: Structured approach to language task workflows +- **Advanced NLP**: Optimized for sophisticated language understanding tasks + +## Use Cases + +- Complex document analysis and processing +- Multi-language translation and localization +- Advanced content generation and editing +- Linguistic research and analysis tasks + +## API Usage + +### Basic MALT Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Legal Document Analysis MALT", + "description": "Advanced linguistic analysis of legal documents using MALT framework", + "swarm_type": "MALT", + "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", + "agents": [ + { + "agent_name": "Syntactic Analyzer", + "description": "Analyzes sentence structure and grammar", + "system_prompt": "You are a syntactic analysis expert. Analyze sentence structure, grammatical patterns, and linguistic complexity in legal texts.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Semantic Analyzer", + "description": "Analyzes meaning and semantic relationships", + "system_prompt": "You are a semantic analysis expert. Extract meaning, identify semantic relationships, and analyze conceptual content in legal documents.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Pragmatic Analyzer", + "description": "Analyzes context and implied meanings", + "system_prompt": "You are a pragmatic analysis expert. Analyze contextual meaning, implied obligations, and pragmatic implications in legal language.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "Discourse Analyzer", + "description": "Analyzes document structure and flow", + "system_prompt": "You are a discourse analysis expert. Analyze document structure, logical flow, and coherence in legal texts.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Risk Language Detector", + "description": "Identifies risk-related language patterns", + "system_prompt": "You are a legal risk language expert. Identify risk indicators, liability language, and potential legal concerns in contract language.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Legal Document Analysis MALT", + "description": "Advanced linguistic analysis of legal documents using MALT framework", + "swarm_type": "MALT", + "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", + "agents": [ + { + "agent_name": "Syntactic Analyzer", + "description": "Analyzes sentence structure and grammar", + "system_prompt": "You are a syntactic analysis expert. Analyze sentence structure, grammatical patterns, and linguistic complexity in legal texts.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Semantic Analyzer", + "description": "Analyzes meaning and semantic relationships", + "system_prompt": "You are a semantic analysis expert. Extract meaning, identify semantic relationships, and analyze conceptual content in legal documents.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Pragmatic Analyzer", + "description": "Analyzes context and implied meanings", + "system_prompt": "You are a pragmatic analysis expert. Analyze contextual meaning, implied obligations, and pragmatic implications in legal language.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "Discourse Analyzer", + "description": "Analyzes document structure and flow", + "system_prompt": "You are a discourse analysis expert. Analyze document structure, logical flow, and coherence in legal texts.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Risk Language Detector", + "description": "Identifies risk-related language patterns", + "system_prompt": "You are a legal risk language expert. Identify risk indicators, liability language, and potential legal concerns in contract language.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("MALT framework completed successfully!") + print(f"Linguistic analysis: {result['output']['linguistic_analysis']}") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "legal-document-analysis-malt", + "swarm_type": "MALT", + "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", + "output": { + "linguistic_analysis": { + "syntactic_analysis": { + "complexity_score": 8.2, + "sentence_structure": "Predominantly complex and compound-complex sentences", + "grammatical_patterns": "Heavy use of passive voice, subordinate clauses, and technical terminology", + "readability": "Graduate level (16+ years of education required)" + }, + "semantic_analysis": { + "key_concepts": ["liability", "indemnification", "force majeure", "intellectual property"], + "semantic_relationships": "Strong hierarchical concept relationships with clear definitional structures", + "conceptual_density": "High - 3.2 legal concepts per sentence average", + "ambiguity_indicators": ["potentially", "reasonable efforts", "material adverse effect"] + }, + "pragmatic_analysis": { + "implied_obligations": [ + "Good faith performance expected", + "Timely notice requirements implied", + "Mutual cooperation assumed" + ], + "power_dynamics": "Balanced with slight advantage to service provider", + "speech_acts": "Predominantly commissives (commitments) and directives (obligations)" + }, + "discourse_analysis": { + "document_structure": "Well-organized with clear section hierarchy", + "logical_flow": "Sequential with appropriate cross-references", + "coherence_score": 8.5, + "transition_patterns": "Formal legal transitions with clause numbering" + }, + "risk_language": { + "high_risk_terms": ["unlimited liability", "personal guarantee", "joint and several"], + "risk_mitigation_language": ["subject to", "limited to", "except as provided"], + "liability_indicators": 23, + "risk_level": "Medium-High" + } + }, + "comprehensive_summary": { + "language_complexity": "High complexity legal document requiring specialized knowledge", + "risk_assessment": "Medium-high risk with standard legal protections", + "readability_concerns": "Requires legal expertise for full comprehension", + "recommendations": [ + "Consider plain language summary for key terms", + "Review unlimited liability clauses", + "Clarify ambiguous terms identified" + ] + } + }, + "metadata": { + "malt_framework": { + "linguistic_layers_analyzed": 5, + "language_processing_depth": "Advanced multi-layer analysis", + "specialized_nlp_operations": [ + "Syntactic parsing", + "Semantic role labeling", + "Pragmatic inference", + "Discourse segmentation", + "Risk pattern recognition" + ] + }, + "execution_time_seconds": 35.7, + "billing_info": { + "total_cost": 0.089 + } + } +} +``` + +## Best Practices + +- Use MALT for sophisticated language processing tasks +- Design agents with complementary linguistic analysis capabilities +- Ideal for tasks requiring deep language understanding +- Consider multiple levels of linguistic analysis (syntax, semantics, pragmatics) + +## Related Swarm Types + +- [SequentialWorkflow](sequential_workflow.md) - For ordered language processing +- [MixtureOfAgents](mixture_of_agents.md) - For diverse linguistic expertise +- [HierarchicalSwarm](hierarchical_swarm.md) - For structured language analysis \ No newline at end of file diff --git a/docs/swarms_cloud/mixture_of_agents.md b/docs/swarms_cloud/mixture_of_agents.md new file mode 100644 index 00000000..e02da9c7 --- /dev/null +++ b/docs/swarms_cloud/mixture_of_agents.md @@ -0,0 +1,187 @@ +# MixtureOfAgents + +*Builds diverse teams of specialized agents for complex problem solving* + +**Swarm Type**: `MixtureOfAgents` + +## Overview + +The MixtureOfAgents swarm type combines multiple agent types with different specializations to tackle diverse aspects of complex problems. Each agent contributes unique skills and perspectives, making this architecture ideal for tasks requiring multiple types of expertise working in harmony. + +Key features: +- **Diverse Expertise**: Combines agents with different specializations +- **Collaborative Problem Solving**: Agents work together leveraging their unique strengths +- **Comprehensive Coverage**: Ensures all aspects of complex tasks are addressed +- **Balanced Perspectives**: Multiple viewpoints for robust decision-making + +## Use Cases + +- Complex research projects requiring multiple disciplines +- Business analysis needing various functional perspectives +- Content creation requiring different expertise areas +- Strategic planning with multiple considerations + +## API Usage + +### Basic MixtureOfAgents Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Business Strategy Mixture", + "description": "Diverse team analyzing business strategy from multiple perspectives", + "swarm_type": "MixtureOfAgents", + "task": "Develop a comprehensive market entry strategy for a new AI product in the healthcare sector", + "agents": [ + { + "agent_name": "Market Research Analyst", + "description": "Analyzes market trends and opportunities", + "system_prompt": "You are a market research expert specializing in healthcare technology. Analyze market size, trends, and competitive landscape.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Financial Analyst", + "description": "Evaluates financial viability and projections", + "system_prompt": "You are a financial analyst expert. Assess financial implications, ROI, and cost structures for business strategies.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Regulatory Expert", + "description": "Analyzes compliance and regulatory requirements", + "system_prompt": "You are a healthcare regulatory expert. Analyze compliance requirements, regulatory pathways, and potential barriers.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + }, + { + "agent_name": "Technology Strategist", + "description": "Evaluates technical feasibility and strategy", + "system_prompt": "You are a technology strategy expert. Assess technical requirements, implementation challenges, and scalability.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Business Strategy Mixture", + "description": "Diverse team analyzing business strategy from multiple perspectives", + "swarm_type": "MixtureOfAgents", + "task": "Develop a comprehensive market entry strategy for a new AI product in the healthcare sector", + "agents": [ + { + "agent_name": "Market Research Analyst", + "description": "Analyzes market trends and opportunities", + "system_prompt": "You are a market research expert specializing in healthcare technology. Analyze market size, trends, and competitive landscape.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Financial Analyst", + "description": "Evaluates financial viability and projections", + "system_prompt": "You are a financial analyst expert. Assess financial implications, ROI, and cost structures for business strategies.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Regulatory Expert", + "description": "Analyzes compliance and regulatory requirements", + "system_prompt": "You are a healthcare regulatory expert. Analyze compliance requirements, regulatory pathways, and potential barriers.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + }, + { + "agent_name": "Technology Strategist", + "description": "Evaluates technical feasibility and strategy", + "system_prompt": "You are a technology strategy expert. Assess technical requirements, implementation challenges, and scalability.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("MixtureOfAgents swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Output: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "business-strategy-mixture", + "swarm_type": "MixtureOfAgents", + "task": "Develop a comprehensive market entry strategy for a new AI product in the healthcare sector", + "output": { + "market_analysis": "Detailed market research findings...", + "financial_assessment": "Financial projections and ROI analysis...", + "regulatory_compliance": "Regulatory requirements and pathways...", + "technology_strategy": "Technical implementation roadmap...", + "integrated_strategy": "Comprehensive market entry strategy combining all perspectives..." + }, + "metadata": { + "agent_contributions": { + "Market Research Analyst": "Market size: $2.3B, Growth rate: 15% CAGR", + "Financial Analyst": "Break-even: 18 months, ROI: 35%", + "Regulatory Expert": "FDA pathway: 510(k), Timeline: 8-12 months", + "Technology Strategist": "MVP timeline: 6 months, Scalability: High" + }, + "execution_time_seconds": 22.1, + "billing_info": { + "total_cost": 0.067 + } + } +} +``` + +## Best Practices + +- Select agents with complementary and diverse expertise +- Ensure each agent has a clear, specialized role +- Use for complex problems requiring multiple perspectives +- Design tasks that benefit from collaborative analysis + +## Related Swarm Types + +- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel task execution +- [GroupChat](group_chat.md) - For collaborative discussion +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic team assembly \ No newline at end of file diff --git a/docs/swarms_cloud/multi_agent_router.md b/docs/swarms_cloud/multi_agent_router.md new file mode 100644 index 00000000..57f5516e --- /dev/null +++ b/docs/swarms_cloud/multi_agent_router.md @@ -0,0 +1,224 @@ +# MultiAgentRouter + +*Intelligent task dispatcher distributing work based on agent capabilities* + +**Swarm Type**: `MultiAgentRouter` + +## Overview + +The MultiAgentRouter acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. This architecture analyzes incoming tasks and automatically routes them to the most suitable agents, optimizing both efficiency and quality of outcomes. + +Key features: +- **Intelligent Routing**: Automatically assigns tasks to best-suited agents +- **Capability Matching**: Matches task requirements with agent specializations +- **Load Balancing**: Distributes workload efficiently across available agents +- **Dynamic Assignment**: Adapts routing based on agent performance and availability + +## Use Cases + +- Customer service request routing +- Content categorization and processing +- Technical support ticket assignment +- Multi-domain question answering + +## API Usage + +### Basic MultiAgentRouter Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Customer Support Router", + "description": "Route customer inquiries to specialized support agents", + "swarm_type": "MultiAgentRouter", + "task": "Handle multiple customer inquiries: 1) Billing question about overcharge, 2) Technical issue with mobile app login, 3) Product recommendation for enterprise client, 4) Return policy question", + "agents": [ + { + "agent_name": "Billing Specialist", + "description": "Handles billing, payments, and account issues", + "system_prompt": "You are a billing specialist. Handle all billing inquiries, payment issues, refunds, and account-related questions with empathy and accuracy.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Technical Support", + "description": "Resolves technical issues and troubleshooting", + "system_prompt": "You are a technical support specialist. Diagnose and resolve technical issues, provide step-by-step troubleshooting, and escalate complex problems.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Sales Consultant", + "description": "Provides product recommendations and sales support", + "system_prompt": "You are a sales consultant. Provide product recommendations, explain features and benefits, and help customers find the right solutions.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "Policy Advisor", + "description": "Explains company policies and procedures", + "system_prompt": "You are a policy advisor. Explain company policies, terms of service, return procedures, and compliance requirements clearly.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Customer Support Router", + "description": "Route customer inquiries to specialized support agents", + "swarm_type": "MultiAgentRouter", + "task": "Handle multiple customer inquiries: 1) Billing question about overcharge, 2) Technical issue with mobile app login, 3) Product recommendation for enterprise client, 4) Return policy question", + "agents": [ + { + "agent_name": "Billing Specialist", + "description": "Handles billing, payments, and account issues", + "system_prompt": "You are a billing specialist. Handle all billing inquiries, payment issues, refunds, and account-related questions with empathy and accuracy.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Technical Support", + "description": "Resolves technical issues and troubleshooting", + "system_prompt": "You are a technical support specialist. Diagnose and resolve technical issues, provide step-by-step troubleshooting, and escalate complex problems.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Sales Consultant", + "description": "Provides product recommendations and sales support", + "system_prompt": "You are a sales consultant. Provide product recommendations, explain features and benefits, and help customers find the right solutions.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "Policy Advisor", + "description": "Explains company policies and procedures", + "system_prompt": "You are a policy advisor. Explain company policies, terms of service, return procedures, and compliance requirements clearly.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("MultiAgentRouter completed successfully!") + print(f"Routing decisions: {result['metadata']['routing_decisions']}") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Customer responses: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "customer-support-router", + "swarm_type": "MultiAgentRouter", + "task": "Handle multiple customer inquiries: 1) Billing question about overcharge, 2) Technical issue with mobile app login, 3) Product recommendation for enterprise client, 4) Return policy question", + "output": { + "inquiry_1_billing": { + "routed_to": "Billing Specialist", + "response": "I understand your concern about the overcharge. Let me review your account and identify the issue. I can see the duplicate charge and will process a refund within 3-5 business days...", + "resolution_status": "Resolved - Refund processed" + }, + "inquiry_2_technical": { + "routed_to": "Technical Support", + "response": "Let's troubleshoot the mobile app login issue. Please try these steps: 1) Clear app cache, 2) Update to latest version, 3) Reset password if needed...", + "resolution_status": "In Progress - Troubleshooting steps provided" + }, + "inquiry_3_sales": { + "routed_to": "Sales Consultant", + "response": "For enterprise clients, I recommend our Professional tier with advanced analytics, dedicated support, and custom integrations. This includes...", + "resolution_status": "Proposal sent - Follow-up scheduled" + }, + "inquiry_4_policy": { + "routed_to": "Policy Advisor", + "response": "Our return policy allows returns within 30 days of purchase for full refund. Items must be in original condition. Here's the complete process...", + "resolution_status": "Information provided - Customer satisfied" + } + }, + "metadata": { + "routing_decisions": [ + { + "inquiry": "Billing question about overcharge", + "routed_to": "Billing Specialist", + "confidence": 0.95, + "reasoning": "Billing-related inquiry requires specialized financial expertise" + }, + { + "inquiry": "Technical issue with mobile app login", + "routed_to": "Technical Support", + "confidence": 0.98, + "reasoning": "Technical troubleshooting requires technical specialist" + }, + { + "inquiry": "Product recommendation for enterprise client", + "routed_to": "Sales Consultant", + "confidence": 0.92, + "reasoning": "Enterprise sales requires specialized sales expertise" + }, + { + "inquiry": "Return policy question", + "routed_to": "Policy Advisor", + "confidence": 0.97, + "reasoning": "Policy questions require policy specialist knowledge" + } + ], + "routing_efficiency": "100% - All inquiries routed to optimal agents", + "execution_time_seconds": 16.4, + "billing_info": { + "total_cost": 0.042 + } + } +} +``` + +## Best Practices + +- Define agents with clear, distinct specializations +- Use descriptive agent names and descriptions for better routing +- Ideal for handling diverse task types that require different expertise +- Monitor routing decisions to optimize agent configurations + +## Related Swarm Types + +- [HierarchicalSwarm](hierarchical_swarm.md) - For structured task management +- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel task processing +- [AutoSwarmBuilder](auto_swarm_builder.md) - For automatic routing setup \ No newline at end of file diff --git a/docs/swarms_cloud/sequential_workflow.md b/docs/swarms_cloud/sequential_workflow.md new file mode 100644 index 00000000..db192e8c --- /dev/null +++ b/docs/swarms_cloud/sequential_workflow.md @@ -0,0 +1,192 @@ +# SequentialWorkflow + +*Executes tasks in a strict, predefined order for step-by-step processing* + +**Swarm Type**: `SequentialWorkflow` + +## Overview + +The SequentialWorkflow swarm type executes tasks in a strict, predefined order where each step depends on the completion of the previous one. This architecture is perfect for workflows that require a linear progression of tasks, ensuring that each agent builds upon the work of the previous agent. + +Key features: +- **Ordered Execution**: Agents execute in a specific, predefined sequence +- **Step Dependencies**: Each step builds upon previous results +- **Predictable Flow**: Clear, linear progression through the workflow +- **Quality Control**: Each agent can validate and enhance previous work + +## Use Cases + +- Document processing pipelines +- Multi-stage analysis workflows +- Content creation and editing processes +- Data transformation and validation pipelines + +## API Usage + +### Basic SequentialWorkflow Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Content Creation Pipeline", + "description": "Sequential content creation from research to final output", + "swarm_type": "SequentialWorkflow", + "task": "Create a comprehensive blog post about the future of renewable energy", + "agents": [ + { + "agent_name": "Research Specialist", + "description": "Conducts thorough research on the topic", + "system_prompt": "You are a research specialist. Gather comprehensive, accurate information on the given topic from reliable sources.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Content Writer", + "description": "Creates engaging written content", + "system_prompt": "You are a skilled content writer. Transform research into engaging, well-structured articles that are informative and readable.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.6 + }, + { + "agent_name": "Editor", + "description": "Reviews and polishes the content", + "system_prompt": "You are a professional editor. Review content for clarity, grammar, flow, and overall quality. Make improvements while maintaining the author's voice.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "SEO Optimizer", + "description": "Optimizes content for search engines", + "system_prompt": "You are an SEO expert. Optimize content for search engines while maintaining readability and quality.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Content Creation Pipeline", + "description": "Sequential content creation from research to final output", + "swarm_type": "SequentialWorkflow", + "task": "Create a comprehensive blog post about the future of renewable energy", + "agents": [ + { + "agent_name": "Research Specialist", + "description": "Conducts thorough research on the topic", + "system_prompt": "You are a research specialist. Gather comprehensive, accurate information on the given topic from reliable sources.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Content Writer", + "description": "Creates engaging written content", + "system_prompt": "You are a skilled content writer. Transform research into engaging, well-structured articles that are informative and readable.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.6 + }, + { + "agent_name": "Editor", + "description": "Reviews and polishes the content", + "system_prompt": "You are a professional editor. Review content for clarity, grammar, flow, and overall quality. Make improvements while maintaining the author's voice.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + }, + { + "agent_name": "SEO Optimizer", + "description": "Optimizes content for search engines", + "system_prompt": "You are an SEO expert. Optimize content for search engines while maintaining readability and quality.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("SequentialWorkflow swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Final output: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "content-creation-pipeline", + "swarm_type": "SequentialWorkflow", + "task": "Create a comprehensive blog post about the future of renewable energy", + "output": { + "research_findings": "Comprehensive research on renewable energy trends...", + "draft_content": "Initial blog post draft...", + "edited_content": "Polished and refined article...", + "final_seo_optimized": "SEO-optimized final blog post ready for publication..." + }, + "metadata": { + "execution_sequence": [ + "Research Specialist", + "Content Writer", + "Editor", + "SEO Optimizer" + ], + "step_outputs": { + "step_1": "Research findings and data", + "step_2": "Draft article content", + "step_3": "Edited and refined content", + "step_4": "SEO-optimized final version" + }, + "execution_time_seconds": 45.3, + "billing_info": { + "total_cost": 0.089 + } + } +} +``` + +## Best Practices + +- Design agents with clear, sequential dependencies +- Ensure each agent builds meaningfully on the previous work +- Use for linear workflows where order matters +- Validate outputs at each step before proceeding + +## Related Swarm Types + +- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel execution +- [AgentRearrange](agent_rearrange.md) - For dynamic sequencing +- [HierarchicalSwarm](hierarchical_swarm.md) - For structured workflows \ No newline at end of file diff --git a/docs/swarms_cloud/spreadsheet_swarm.md b/docs/swarms_cloud/spreadsheet_swarm.md new file mode 100644 index 00000000..538edb48 --- /dev/null +++ b/docs/swarms_cloud/spreadsheet_swarm.md @@ -0,0 +1,232 @@ +# SpreadSheetSwarm + +*Structured approach to data management and operations in spreadsheet-like format* + +**Swarm Type**: `SpreadSheetSwarm` + +## Overview + +The SpreadSheetSwarm provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. This architecture organizes agents to work on data in a tabular format with clear rows, columns, and processing workflows. + +Key features: +- **Structured Data Processing**: Organizes work in spreadsheet-like rows and columns +- **Systematic Operations**: Sequential and methodical data handling +- **Data Transformation**: Efficient processing of structured datasets +- **Collaborative Analysis**: Multiple agents working on different data aspects + +## Use Cases + +- Financial data analysis and reporting +- Customer data processing and segmentation +- Inventory management and tracking +- Research data compilation and analysis + +## API Usage + +### Basic SpreadSheetSwarm Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Financial Analysis Spreadsheet", + "description": "Systematic financial data analysis using spreadsheet structure", + "swarm_type": "SpreadSheetSwarm", + "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", + "agents": [ + { + "agent_name": "Data Validator", + "description": "Validates and cleans financial data", + "system_prompt": "You are a data validation specialist. Clean, validate, and structure financial data ensuring accuracy and consistency.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Revenue Analyst", + "description": "Analyzes revenue trends and patterns", + "system_prompt": "You are a revenue analyst. Focus on revenue trends, growth patterns, and seasonal variations across product lines.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Cost Analyst", + "description": "Analyzes cost structures and margins", + "system_prompt": "You are a cost analyst. Examine cost structures, margin analysis, and expense categorization.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Performance Calculator", + "description": "Calculates KPIs and financial metrics", + "system_prompt": "You are a financial metrics specialist. Calculate KPIs, ratios, and performance indicators from the analyzed data.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + }, + { + "agent_name": "Report Generator", + "description": "Creates structured financial reports", + "system_prompt": "You are a report generator. Create comprehensive, well-structured financial reports with insights and recommendations.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Financial Analysis Spreadsheet", + "description": "Systematic financial data analysis using spreadsheet structure", + "swarm_type": "SpreadSheetSwarm", + "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", + "agents": [ + { + "agent_name": "Data Validator", + "description": "Validates and cleans financial data", + "system_prompt": "You are a data validation specialist. Clean, validate, and structure financial data ensuring accuracy and consistency.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.2 + }, + { + "agent_name": "Revenue Analyst", + "description": "Analyzes revenue trends and patterns", + "system_prompt": "You are a revenue analyst. Focus on revenue trends, growth patterns, and seasonal variations across product lines.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Cost Analyst", + "description": "Analyzes cost structures and margins", + "system_prompt": "You are a cost analyst. Examine cost structures, margin analysis, and expense categorization.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Performance Calculator", + "description": "Calculates KPIs and financial metrics", + "system_prompt": "You are a financial metrics specialist. Calculate KPIs, ratios, and performance indicators from the analyzed data.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.1 + }, + { + "agent_name": "Report Generator", + "description": "Creates structured financial reports", + "system_prompt": "You are a report generator. Create comprehensive, well-structured financial reports with insights and recommendations.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.4 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("SpreadSheetSwarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Structured analysis: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "status": "success", + "swarm_name": "financial-analysis-spreadsheet", + "swarm_type": "SpreadSheetSwarm", + "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", + "output": { + "data_validation": { + "data_quality": "95% accuracy after cleaning", + "missing_values": "Identified and filled 3% missing entries", + "data_structure": "Standardized format across all product lines" + }, + "revenue_analysis": { + "q4_revenue": "$2.4M total revenue", + "growth_rate": "12% quarter-over-quarter growth", + "top_performers": ["Product Line A: +18%", "Product Line C: +15%"], + "seasonal_trends": "Strong holiday season performance" + }, + "cost_analysis": { + "total_costs": "$1.8M operational costs", + "cost_breakdown": "60% COGS, 25% Marketing, 15% Operations", + "margin_analysis": "25% gross margin, 15% net margin", + "cost_optimization": "Identified 8% potential savings in supply chain" + }, + "performance_metrics": { + "roi": "22% return on investment", + "customer_acquisition_cost": "$45 per customer", + "lifetime_value": "$320 average CLV", + "inventory_turnover": "6.2x annual turnover" + }, + "comprehensive_report": { + "executive_summary": "Strong Q4 performance with 12% growth...", + "recommendations": ["Expand Product Line A", "Optimize supply chain", "Increase marketing for underperformers"], + "forecast": "Projected 15% growth for Q1 based on trends" + } + }, + "metadata": { + "processing_structure": { + "rows_processed": 1250, + "columns_analyzed": 18, + "calculations_performed": 47 + }, + "data_pipeline": [ + "Data Validation", + "Revenue Analysis", + "Cost Analysis", + "Performance Calculation", + "Report Generation" + ], + "execution_time_seconds": 34.2, + "billing_info": { + "total_cost": 0.078 + } + } +} +``` + +## Best Practices + +- Structure data in clear, logical formats before processing +- Use systematic, step-by-step analysis approaches +- Ideal for quantitative analysis and reporting tasks +- Ensure data validation before proceeding with analysis + +## Related Swarm Types + +- [SequentialWorkflow](sequential_workflow.md) - For ordered data processing +- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel data analysis +- [HierarchicalSwarm](hierarchical_swarm.md) - For complex data projects \ No newline at end of file diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index 2e69466d..8ab22abd 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -4,18 +4,18 @@ Each multi-agent architecture type is designed for specific use cases and can be | Swarm Type | Description | Learn More | |----------------------|------------------------------------------------------------------------------|------------| -| AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Useful when agent effectiveness depends on their sequence or arrangement. | [Learn More](swarms_api.md#agentrearrange) | -| MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](swarms_api.md#mixtureofagents) | -| SpreadSheetSwarm | Provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. | [Learn More](swarms_api.md#spreadsheetswarm) | -| SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](swarms_api.md#sequentialworkflow) | -| ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](swarms_api.md#concurrentworkflow) | -| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](swarms_api.md#groupchat) | -| MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](swarms_api.md#multiagentrouter) | -| AutoSwarmBuilder | Automatically configures agent architectures based on task requirements and performance metrics, simplifying swarm creation. | [Learn More](swarms_api.md#autoswarmbuilder) | -| HierarchicalSwarm | Implements a structured, multi-level approach to task management, with clear lines of authority and delegation. | [Learn More](swarms_api.md#hierarchicalswarm) | -| Auto | Intelligently selects the most effective swarm architecture for a given task based on context. | [Learn More](swarms_api.md#auto) | -| MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](swarms_api_tools.md#majorityvoting) | -| MALT | Specialized framework for language-based tasks, optimizing agent collaboration for complex language processing operations. | [Learn More](swarms_api_tools.md#malt) | +| AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Useful when agent effectiveness depends on their sequence or arrangement. | [Learn More](agent_rearrange.md) | +| MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](mixture_of_agents.md) | +| SpreadSheetSwarm | Provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. | [Learn More](spreadsheet_swarm.md) | +| SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](sequential_workflow.md) | +| ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](concurrent_workflow.md) | +| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](group_chat.md) | +| MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](multi_agent_router.md) | +| AutoSwarmBuilder | Automatically configures agent architectures based on task requirements and performance metrics, simplifying swarm creation. | [Learn More](auto_swarm_builder.md) | +| HierarchicalSwarm | Implements a structured, multi-level approach to task management, with clear lines of authority and delegation. | [Learn More](hierarchical_swarm.md) | +| Auto | Intelligently selects the most effective swarm architecture for a given task based on context. | [Learn More](auto.md) | +| MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | +| MALT | Specialized framework for language-based tasks, optimizing agent collaboration for complex language processing operations. | [Learn More](malt.md) | # Learn More From cfc7c11d2c9057ccce8d0dc283797038d3f784e2 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 20:08:27 +0530 Subject: [PATCH 31/73] fixes and cleanup ! --- docs/mkdocs.yml | 12 +- docs/swarms_cloud/agent_rearrange.md | 66 ++++--- docs/swarms_cloud/auto.md | 150 +--------------- docs/swarms_cloud/auto_swarm_builder.md | 151 +--------------- docs/swarms_cloud/concurrent_workflow.md | 82 +++++---- docs/swarms_cloud/group_chat.md | 166 ------------------ docs/swarms_cloud/hierarchical_swarm.md | 195 --------------------- docs/swarms_cloud/majority_voting.md | 123 +++++++------- docs/swarms_cloud/malt.md | 208 ----------------------- docs/swarms_cloud/mixture_of_agents.md | 79 ++++++--- docs/swarms_cloud/multi_agent_router.md | 101 +++++------ docs/swarms_cloud/sequential_workflow.md | 74 +++++--- docs/swarms_cloud/spreadsheet_swarm.md | 191 --------------------- docs/swarms_cloud/swarm_types.md | 12 +- 14 files changed, 325 insertions(+), 1285 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index a371119c..e6fe1aff 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -449,16 +449,16 @@ nav: - Swarm Types: - AgentRearrange: "swarms_cloud/agent_rearrange.md" - MixtureOfAgents: "swarms_cloud/mixture_of_agents.md" - - SpreadSheetSwarm: "swarms_cloud/spreadsheet_swarm.md" + # - SpreadSheetSwarm: "swarms_cloud/spreadsheet_swarm.md" - SequentialWorkflow: "swarms_cloud/sequential_workflow.md" - ConcurrentWorkflow: "swarms_cloud/concurrent_workflow.md" - - GroupChat: "swarms_cloud/group_chat.md" + # - GroupChat: "swarms_cloud/group_chat.md" - MultiAgentRouter: "swarms_cloud/multi_agent_router.md" - - AutoSwarmBuilder: "swarms_cloud/auto_swarm_builder.md" - - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" - - Auto: "swarms_cloud/auto.md" + # - AutoSwarmBuilder: "swarms_cloud/auto_swarm_builder.md" + # - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" + # - Auto: "swarms_cloud/auto.md" - MajorityVoting: "swarms_cloud/majority_voting.md" - - MALT: "swarms_cloud/malt.md" + # - MALT: "swarms_cloud/malt.md" - Examples: - Medical Swarm: "swarms/examples/swarms_api_medical.md" - Finance Swarm: "swarms/examples/swarms_api_finance.md" diff --git a/docs/swarms_cloud/agent_rearrange.md b/docs/swarms_cloud/agent_rearrange.md index 15c05125..73293d90 100644 --- a/docs/swarms_cloud/agent_rearrange.md +++ b/docs/swarms_cloud/agent_rearrange.md @@ -61,7 +61,7 @@ Key features: "temperature": 0.4 } ], - "rearrange_flow": "Optimize agent sequence based on document complexity and required output quality", + "rearrange_flow": "Summarizer -> Legal Expert -> Document Analyzer", "max_loops": 1 }' ``` @@ -110,7 +110,7 @@ Key features: "temperature": 0.4 } ], - "rearrange_flow": "Optimize agent sequence based on document complexity and required output quality", + "rearrange_flow": "Summarizer -> Legal Expert -> Document Analyzer", "max_loops": 1 } @@ -133,24 +133,52 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "document-processing-rearrange", - "swarm_type": "AgentRearrange", - "task": "Analyze this legal document and extract key insights, then summarize findings and identify action items", - "output": { - "analysis": "Document analysis results...", - "legal_insights": "Legal implications and risks...", - "summary": "Concise summary of findings...", - "action_items": ["Action 1", "Action 2", "Action 3"] - }, - "metadata": { - "agent_sequence": ["Legal Expert", "Document Analyzer", "Summarizer"], - "rearrangement_reason": "Optimized for legal document analysis workflow", - "execution_time_seconds": 18.2, - "billing_info": { - "total_cost": 0.045 + "job_id": "swarms-Uc8R7UcepLmNNPwcU7JC6YPy5wiI", + "status": "success", + "swarm_name": "Document Processing Rearrange", + "description": "Process documents with dynamic agent reorganization", + "swarm_type": "AgentRearrange", + "output": [ + { + "role": "Summarizer", + "content": "\"Of course! Please provide the legal document you would like me to analyze, and I'll help extract key insights, summarize findings, and identify any action items.\"" + }, + { + "role": "Legal Expert", + "content": "\"\"Absolutely! Please upload or describe the legal document you need assistance with, and I'll provide an analysis that highlights key insights, summarizes the findings, and identifies any action items that may be necessary.\"\"" + }, + { + "role": "Document Analyzer", + "content": "\"Of course! Please provide the legal document you would like me to analyze, and I'll help extract key insights, summarize findings, and identify any action items.\"" + } + ], + "number_of_agents": 3, + "service_tier": "standard", + "execution_time": 7.898931264877319, + "usage": { + "input_tokens": 22, + "output_tokens": 144, + "total_tokens": 166, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.03, + "input_token_cost": 0.000066, + "output_token_cost": 0.00216, + "token_counts": { + "total_input_tokens": 22, + "total_output_tokens": 144, + "total_tokens": 166 + }, + "num_agents": 3, + "service_tier": "standard", + "night_time_discount_applied": true + }, + "total_cost": 0.032226, + "discount_active": true, + "discount_type": "night_time", + "discount_percentage": 75 + } } - } } ``` diff --git a/docs/swarms_cloud/auto.md b/docs/swarms_cloud/auto.md index 262797a1..e5e30df7 100644 --- a/docs/swarms_cloud/auto.md +++ b/docs/swarms_cloud/auto.md @@ -23,155 +23,7 @@ Key features: ## API Usage -### Basic Auto Example - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Auto Content Creation", - "description": "Let the system choose the best approach for content creation", - "swarm_type": "auto", - "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", - "max_loops": 1 - }' - ``` - -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Auto Content Creation", - "description": "Let the system choose the best approach for content creation", - "swarm_type": "auto", - "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("Auto swarm completed successfully!") - print(f"Selected architecture: {result['metadata']['selected_swarm_type']}") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - print(f"Content: {result['output']}") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "auto-content-creation", - "swarm_type": "SequentialWorkflow", - "task": "Create a comprehensive blog post about sustainable investing, including research, writing, editing, and SEO optimization", - "output": { - "research_phase": { - "key_findings": "Sustainable investing has grown 42% in the past two years...", - "market_trends": "ESG funds outperformed traditional funds by 3.2%...", - "statistics": "Global sustainable investment assets reached $35.3 trillion..." - }, - "writing_phase": { - "title": "The Future of Sustainable Investing: A Guide to ESG Strategies", - "content": "Comprehensive blog post with introduction, main sections, and conclusion...", - "word_count": 1850 - }, - "editing_phase": { - "improvements": "Enhanced clarity, improved flow, corrected grammar", - "readability_score": "Grade 8 level - accessible to general audience", - "final_content": "Polished blog post ready for publication..." - }, - "seo_optimization": { - "target_keywords": ["sustainable investing", "ESG funds", "green finance"], - "meta_description": "Discover the future of sustainable investing...", - "optimized_content": "SEO-optimized version with strategic keyword placement" - } - }, - "metadata": { - "auto_selection": { - "selected_swarm_type": "SequentialWorkflow", - "reasoning": "Task requires step-by-step content creation process where each phase builds on the previous", - "analysis": { - "task_complexity": "Medium-High", - "sequential_dependencies": true, - "parallel_opportunities": false, - "collaboration_needs": "Low" - } - }, - "generated_agents": [ - "Research Specialist", - "Content Writer", - "Editor", - "SEO Optimizer" - ], - "execution_time_seconds": 43.2, - "billing_info": { - "total_cost": 0.087 - } - } -} -``` - -### Advanced Auto Usage - -You can provide additional context to help the Auto selection: - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Auto Business Analysis", - "description": "Automatic swarm selection for business analysis", - "swarm_type": "auto", - "task": "Analyze market opportunities for a new AI startup in healthcare", - "rules": "Need multiple perspectives from different business functions, time-sensitive analysis required", - "max_loops": 1 - }' - ``` - -=== "Python (requests)" - ```python - swarm_config = { - "name": "Auto Business Analysis", - "description": "Automatic swarm selection for business analysis", - "swarm_type": "auto", - "task": "Analyze market opportunities for a new AI startup in healthcare", - "rules": "Need multiple perspectives from different business functions, time-sensitive analysis required", - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print(f"Auto selected: {result['metadata']['auto_selection']['selected_swarm_type']}") - print(f"Reasoning: {result['metadata']['auto_selection']['reasoning']}") - ``` + ## Selection Logic diff --git a/docs/swarms_cloud/auto_swarm_builder.md b/docs/swarms_cloud/auto_swarm_builder.md index 86799e1e..895a6f77 100644 --- a/docs/swarms_cloud/auto_swarm_builder.md +++ b/docs/swarms_cloud/auto_swarm_builder.md @@ -1,4 +1,4 @@ -# AutoSwarmBuilder +# AutoSwarmBuilder [ Needs an Fix ] *Automatically configures optimal swarm architectures based on task requirements* @@ -23,155 +23,6 @@ Key features: ## API Usage -### Basic AutoSwarmBuilder Example - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Auto Marketing Campaign", - "description": "Automatically build optimal swarm for marketing campaign creation", - "swarm_type": "AutoSwarmBuilder", - "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", - "max_loops": 1 - }' - ``` - -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Auto Marketing Campaign", - "description": "Automatically build optimal swarm for marketing campaign creation", - "swarm_type": "AutoSwarmBuilder", - "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("AutoSwarmBuilder completed successfully!") - print(f"Generated swarm architecture: {result['metadata']['generated_architecture']}") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - print(f"Campaign output: {result['output']}") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "auto-marketing-campaign", - "swarm_type": "AutoSwarmBuilder", - "task": "Create a comprehensive digital marketing campaign for a new sustainable fashion brand targeting Gen Z consumers", - "output": { - "campaign_strategy": { - "brand_positioning": "Authentic, sustainable fashion for conscious Gen Z consumers", - "key_messaging": "Style that makes a difference - fashion with purpose", - "target_demographics": "Ages 18-26, environmentally conscious, social media active" - }, - "content_strategy": { - "social_platforms": ["TikTok", "Instagram", "Pinterest"], - "content_pillars": ["Sustainability education", "Style inspiration", "Behind-the-scenes"], - "posting_schedule": "Daily posts across platforms with peak engagement timing" - }, - "influencer_strategy": { - "tier_1": "Micro-influencers (10K-100K followers) focused on sustainability", - "tier_2": "Fashion nano-influencers (1K-10K followers) for authentic engagement", - "collaboration_types": ["Product partnerships", "Brand ambassador programs"] - }, - "paid_advertising": { - "platforms": ["Instagram Ads", "TikTok Ads", "Google Ads"], - "budget_allocation": "40% social media, 30% search, 30% video content", - "targeting_strategy": "Interest-based and lookalike audiences" - }, - "metrics_and_kpis": { - "awareness": "Brand mention volume, reach, impressions", - "engagement": "Comments, shares, saves, time spent", - "conversion": "Website traffic, email signups, sales" - } - }, - "metadata": { - "generated_architecture": { - "selected_swarm_type": "MixtureOfAgents", - "generated_agents": [ - "Brand Strategy Expert", - "Gen Z Marketing Specialist", - "Social Media Content Creator", - "Influencer Marketing Manager", - "Digital Advertising Strategist" - ], - "reasoning": "Complex marketing campaign requires diverse expertise working collaboratively" - }, - "auto_optimization": { - "task_complexity": "High", - "required_expertise_areas": 5, - "optimal_architecture": "Collaborative with specialized agents" - }, - "execution_time_seconds": 28.6, - "billing_info": { - "total_cost": 0.071 - } - } -} -``` - -### Advanced Configuration - -You can provide additional guidance to the AutoSwarmBuilder: - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Auto Research Project", - "description": "Auto-build research swarm with specific constraints", - "swarm_type": "AutoSwarmBuilder", - "task": "Conduct comprehensive research on the impact of AI on healthcare outcomes", - "rules": "Focus on peer-reviewed sources, include cost-benefit analysis, ensure balanced perspective on risks and benefits", - "max_loops": 1 - }' - ``` - -=== "Python (requests)" - ```python - swarm_config = { - "name": "Auto Research Project", - "description": "Auto-build research swarm with specific constraints", - "swarm_type": "AutoSwarmBuilder", - "task": "Conduct comprehensive research on the impact of AI on healthcare outcomes", - "rules": "Focus on peer-reviewed sources, include cost-benefit analysis, ensure balanced perspective on risks and benefits", - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - ``` ## Configuration Options diff --git a/docs/swarms_cloud/concurrent_workflow.md b/docs/swarms_cloud/concurrent_workflow.md index 289deb84..aac81991 100644 --- a/docs/swarms_cloud/concurrent_workflow.md +++ b/docs/swarms_cloud/concurrent_workflow.md @@ -147,40 +147,56 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "market-research-concurrent", - "swarm_type": "ConcurrentWorkflow", - "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", - "output": { - "ai_market_analysis": { - "market_size": "$150B by 2025", - "growth_rate": "25% CAGR", - "key_opportunities": ["Generative AI", "Edge AI", "AI Infrastructure"] - }, - "healthcare_analysis": { - "market_size": "$350B by 2025", - "growth_rate": "12% CAGR", - "key_opportunities": ["Telemedicine", "AI Diagnostics", "Digital Therapeutics"] - }, - "fintech_analysis": { - "market_size": "$200B by 2025", - "growth_rate": "18% CAGR", - "key_opportunities": ["DeFi", "Digital Banking", "Payment Infrastructure"] - }, - "ecommerce_analysis": { - "market_size": "$8T by 2025", - "growth_rate": "14% CAGR", - "key_opportunities": ["Social Commerce", "B2B Marketplaces", "Sustainable Commerce"] - } - }, - "metadata": { - "parallel_execution": true, - "agents_completed_simultaneously": 4, - "execution_time_seconds": 12.8, - "billing_info": { - "total_cost": 0.052 + "job_id": "swarms-S17nZFDesmLHxCRoeyF3NVYvPaXk", + "status": "success", + "swarm_name": "Market Research Concurrent", + "description": "Parallel market research across different sectors", + "swarm_type": "ConcurrentWorkflow", + "output": [ + { + "role": "E-commerce Market Analyst", + "content": "To analyze market opportunities in the AI, healthcare, fintech, and e-commerce sectors, we can break down each sector's current trends, consumer behavior, and emerging platforms. Here's an overview of each sector with a focus on e-commerce....." + }, + { + "role": "AI Market Analyst", + "content": "The artificial intelligence (AI) landscape presents numerous opportunities across various sectors, particularly in healthcare, fintech, and e-commerce. Here's a detailed analysis of each sector:\n\n### Healthcare....." + }, + { + "role": "Healthcare Market Analyst", + "content": "As a Healthcare Market Analyst, I will focus on analyzing market opportunities within the healthcare sector, particularly in the realm of AI and digital health. The intersection of healthcare with fintech and e-commerce also presents unique opportunities. Here's an overview of key trends and growth areas:...." + }, + { + "role": "Fintech Market Analyst", + "content": "Certainly! Let's break down the market opportunities in the fintech sector, focusing on financial technology trends, digital payment systems, blockchain opportunities, and regulatory developments:\n\n### 1. Financial Technology Trends....." + } + ], + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 23.360230922698975, + "usage": { + "input_tokens": 35, + "output_tokens": 2787, + "total_tokens": 2822, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.000105, + "output_token_cost": 0.041805, + "token_counts": { + "total_input_tokens": 35, + "total_output_tokens": 2787, + "total_tokens": 2822 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": true + }, + "total_cost": 0.08191, + "discount_active": true, + "discount_type": "night_time", + "discount_percentage": 75 + } } - } } ``` diff --git a/docs/swarms_cloud/group_chat.md b/docs/swarms_cloud/group_chat.md index e3410e1c..fa6cab9d 100644 --- a/docs/swarms_cloud/group_chat.md +++ b/docs/swarms_cloud/group_chat.md @@ -23,172 +23,6 @@ Key features: ## API Usage -### Basic GroupChat Example - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Product Strategy Discussion", - "description": "Collaborative chat to develop product strategy", - "swarm_type": "GroupChat", - "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", - "agents": [ - { - "agent_name": "Product Manager", - "description": "Leads product strategy and development", - "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.6 - }, - { - "agent_name": "Marketing Strategist", - "description": "Develops marketing and positioning strategy", - "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.7 - }, - { - "agent_name": "Sales Director", - "description": "Provides sales and customer perspective", - "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.5 - }, - { - "agent_name": "UX Researcher", - "description": "Represents user experience and research insights", - "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.4 - } - ], - "max_loops": 3 - }' - ``` - -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Product Strategy Discussion", - "description": "Collaborative chat to develop product strategy", - "swarm_type": "GroupChat", - "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", - "agents": [ - { - "agent_name": "Product Manager", - "description": "Leads product strategy and development", - "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.6 - }, - { - "agent_name": "Marketing Strategist", - "description": "Develops marketing and positioning strategy", - "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.7 - }, - { - "agent_name": "Sales Director", - "description": "Provides sales and customer perspective", - "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.5 - }, - { - "agent_name": "UX Researcher", - "description": "Represents user experience and research insights", - "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", - "model_name": "gpt-4o", - "max_loops": 3, - "temperature": 0.4 - } - ], - "max_loops": 3 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("GroupChat swarm completed successfully!") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - print(f"Chat discussion: {result['output']}") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "product-strategy-discussion", - "swarm_type": "GroupChat", - "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", - "output": { - "chat_transcript": [ - { - "agent": "Product Manager", - "message": "Let's start by defining our target user. What specific pain points do small businesses have with productivity tools?" - }, - { - "agent": "UX Researcher", - "message": "From our research, small businesses struggle with tool complexity and time to value. They need something that works immediately without extensive setup." - }, - { - "agent": "Sales Director", - "message": "I agree. SMBs have limited time and resources. They typically abandon tools that require more than a week to see value. Pricing is also critical - they're very cost-conscious." - }, - { - "agent": "Marketing Strategist", - "message": "This suggests we should focus on 'instant productivity gains' messaging. We could position against complex enterprise tools that overwhelm small teams." - } - ], - "key_decisions": [ - "Target: Small businesses with 5-50 employees", - "Positioning: Simple, immediate productivity gains", - "Pricing: Freemium model with low-cost paid tiers", - "GTM: Self-serve with strong onboarding" - ], - "final_strategy": "Launch with freemium model targeting productivity-focused small businesses through content marketing and self-serve channels..." - }, - "metadata": { - "conversation_rounds": 3, - "total_messages": 12, - "consensus_reached": true, - "execution_time_seconds": 38.7, - "billing_info": { - "total_cost": 0.095 - } - } -} -``` ## Best Practices diff --git a/docs/swarms_cloud/hierarchical_swarm.md b/docs/swarms_cloud/hierarchical_swarm.md index 0cd6ec21..f65c7a46 100644 --- a/docs/swarms_cloud/hierarchical_swarm.md +++ b/docs/swarms_cloud/hierarchical_swarm.md @@ -23,201 +23,6 @@ Key features: ## API Usage -### Basic HierarchicalSwarm Example - -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Software Development Hierarchy", - "description": "Hierarchical software development team with project manager oversight", - "swarm_type": "HierarchicalSwarm", - "task": "Design and plan a new mobile app for expense tracking targeting freelancers", - "agents": [ - { - "agent_name": "Project Manager", - "description": "Oversees project planning and coordinates team efforts", - "system_prompt": "You are a senior project manager. Coordinate the team, break down tasks, ensure quality, and synthesize outputs. Delegate specific tasks to team members.", - "model_name": "gpt-4o", - "role": "manager", - "max_loops": 2, - "temperature": 0.4 - }, - { - "agent_name": "UX Designer", - "description": "Designs user experience and interface", - "system_prompt": "You are a UX designer specializing in mobile apps. Focus on user flows, wireframes, and interface design. Report findings to the project manager.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.6 - }, - { - "agent_name": "Technical Architect", - "description": "Designs technical architecture and system requirements", - "system_prompt": "You are a technical architect. Focus on system design, technology stack, database design, and technical requirements. Provide technical guidance.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Business Analyst", - "description": "Analyzes business requirements and market fit", - "system_prompt": "You are a business analyst. Focus on requirements gathering, market analysis, feature prioritization, and business logic.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.4 - }, - { - "agent_name": "QA Specialist", - "description": "Ensures quality and validates deliverables", - "system_prompt": "You are a QA specialist. Review all outputs for quality, completeness, and consistency. Identify gaps and suggest improvements.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.2 - } - ], - "max_loops": 2 - }' - ``` - -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Software Development Hierarchy", - "description": "Hierarchical software development team with project manager oversight", - "swarm_type": "HierarchicalSwarm", - "task": "Design and plan a new mobile app for expense tracking targeting freelancers", - "agents": [ - { - "agent_name": "Project Manager", - "description": "Oversees project planning and coordinates team efforts", - "system_prompt": "You are a senior project manager. Coordinate the team, break down tasks, ensure quality, and synthesize outputs. Delegate specific tasks to team members.", - "model_name": "gpt-4o", - "role": "manager", - "max_loops": 2, - "temperature": 0.4 - }, - { - "agent_name": "UX Designer", - "description": "Designs user experience and interface", - "system_prompt": "You are a UX designer specializing in mobile apps. Focus on user flows, wireframes, and interface design. Report findings to the project manager.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.6 - }, - { - "agent_name": "Technical Architect", - "description": "Designs technical architecture and system requirements", - "system_prompt": "You are a technical architect. Focus on system design, technology stack, database design, and technical requirements. Provide technical guidance.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Business Analyst", - "description": "Analyzes business requirements and market fit", - "system_prompt": "You are a business analyst. Focus on requirements gathering, market analysis, feature prioritization, and business logic.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.4 - }, - { - "agent_name": "QA Specialist", - "description": "Ensures quality and validates deliverables", - "system_prompt": "You are a QA specialist. Review all outputs for quality, completeness, and consistency. Identify gaps and suggest improvements.", - "model_name": "gpt-4o", - "role": "worker", - "max_loops": 1, - "temperature": 0.2 - } - ], - "max_loops": 2 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("HierarchicalSwarm completed successfully!") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - print(f"Project plan: {result['output']}") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "software-development-hierarchy", - "swarm_type": "HierarchicalSwarm", - "task": "Design and plan a new mobile app for expense tracking targeting freelancers", - "output": { - "project_overview": { - "manager_synthesis": "Comprehensive project plan for freelancer expense tracking app...", - "timeline": "16-week development cycle", - "key_deliverables": ["UX Design", "Technical Architecture", "Business Requirements", "QA Framework"] - }, - "ux_design": { - "user_flows": "Streamlined expense entry and categorization flows...", - "wireframes": "Mobile-first design with dashboard and reporting views...", - "usability_considerations": "One-tap expense entry, photo receipt capture..." - }, - "technical_architecture": { - "tech_stack": "React Native, Node.js, PostgreSQL, AWS", - "system_design": "Microservices architecture with offline capability...", - "security_requirements": "End-to-end encryption, secure authentication..." - }, - "business_requirements": { - "target_market": "Freelancers and independent contractors", - "core_features": ["Expense tracking", "Receipt scanning", "Tax reporting"], - "monetization": "Freemium model with premium reporting features" - }, - "qa_framework": { - "testing_strategy": "Automated testing for core functions...", - "quality_metrics": "Performance, usability, and security benchmarks...", - "validation_checkpoints": "Weekly reviews and milestone validations" - } - }, - "metadata": { - "hierarchy_structure": { - "managers": ["Project Manager"], - "workers": ["UX Designer", "Technical Architect", "Business Analyst", "QA Specialist"] - }, - "coordination_rounds": 2, - "task_delegation": "Manager coordinated 4 specialized work streams", - "execution_time_seconds": 52.4, - "billing_info": { - "total_cost": 0.128 - } - } -} -``` ## Configuration Options diff --git a/docs/swarms_cloud/majority_voting.md b/docs/swarms_cloud/majority_voting.md index 63f39439..5a95dfa0 100644 --- a/docs/swarms_cloud/majority_voting.md +++ b/docs/swarms_cloud/majority_voting.md @@ -164,65 +164,74 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "investment-decision-voting", - "swarm_type": "MajorityVoting", - "task": "Evaluate whether to invest $1M in a renewable energy startup. Consider market potential, financial projections, team strength, and competitive landscape.", - "output": { - "individual_recommendations": [ - { - "agent": "Growth Investor", - "recommendation": "INVEST", - "confidence": 0.8, - "reasoning": "Strong market growth potential in renewable energy sector, scalable technology platform" - }, - { - "agent": "Financial Analyst", - "recommendation": "INVEST", - "confidence": 0.7, - "reasoning": "Solid financial projections, reasonable burn rate, clear path to profitability" - }, - { - "agent": "Technical Due Diligence", - "recommendation": "INVEST", - "confidence": 0.75, - "reasoning": "Innovative technology with strong IP portfolio, experienced technical team" - }, - { - "agent": "Market Analyst", - "recommendation": "WAIT", - "confidence": 0.6, - "reasoning": "Highly competitive market, regulatory uncertainties may impact timeline" - }, - { - "agent": "Risk Assessor", - "recommendation": "INVEST", - "confidence": 0.65, - "reasoning": "Manageable risks with strong mitigation strategies, experienced leadership team" - } + "job_id": "swarms-1WFsSJU2KcvY11lxRMjdQNWFHArI", + "status": "success", + "swarm_name": "Investment Decision Voting", + "description": "Multiple financial experts vote on investment recommendations", + "swarm_type": "MajorityVoting", + "output": [ + { + "role": "Financial Analyst", + "content": [ + "To evaluate the potential investment in a renewable energy startup, we will assess the technology viability, intellectual property, product-market fit, and technical risks, along with the additional factors of market ....." + ] + }, + { + "role": "Technical Due Diligence", + "content": [ + "To evaluate the potential investment in a renewable energy startup, we will analyze the relevant market dynamics, competitive landscape, regulatory environment, and market timing. Here's the breakdown of the assessment......." + ] + }, + { + "role": "Market Analyst", + "content": [ + "To evaluate the potential investment in a renewable energy startup, let's break down the key factors:\n\n1. **Market Potential........" + ] + }, + { + "role": "Growth Investor", + "content": [ + "To evaluate the potential investment in a renewable energy startup, we need to assess various risk factors and mitigation strategies across several key areas: market potential, financial projections, team strength, and competitive landscape.\n\n### 1. Market Potential\n**Risks:**\n- **Regulatory Changes................" + ] + }, + { + "role": "Risk Assessor", + "content": [ + "To provide a comprehensive evaluation of whether to invest $1M in the renewable energy startup, let's break down the key areas.........." + ] + }, + { + "role": "Risk Assessor", + "content": "To evaluate the potential investment in a renewable energy startup, we need to assess various risk factors and mitigation strategies across several key areas....." + } ], - "consensus_decision": "INVEST", - "consensus_confidence": 0.72, - "consensus_reasoning": "4 out of 5 experts recommend investment with strong market potential and solid fundamentals, despite some market uncertainties" - }, - "metadata": { - "vote_breakdown": { - "INVEST": 4, - "WAIT": 1, - "REJECT": 0 - }, - "vote_percentage": { - "INVEST": "80%", - "WAIT": "20%", - "REJECT": "0%" - }, - "average_confidence": 0.70, - "consensus_threshold": "Simple majority (50%+)", - "execution_time_seconds": 25.8, - "billing_info": { - "total_cost": 0.063 + "number_of_agents": 5, + "service_tier": "standard", + "execution_time": 61.74853563308716, + "usage": { + "input_tokens": 39, + "output_tokens": 8468, + "total_tokens": 8507, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.05, + "input_token_cost": 0.000117, + "output_token_cost": 0.12702, + "token_counts": { + "total_input_tokens": 39, + "total_output_tokens": 8468, + "total_tokens": 8507 + }, + "num_agents": 5, + "service_tier": "standard", + "night_time_discount_applied": false + }, + "total_cost": 0.177137, + "discount_active": false, + "discount_type": "none", + "discount_percentage": 0 + } } - } } ``` diff --git a/docs/swarms_cloud/malt.md b/docs/swarms_cloud/malt.md index 7539ed58..3247e854 100644 --- a/docs/swarms_cloud/malt.md +++ b/docs/swarms_cloud/malt.md @@ -23,215 +23,7 @@ Key features: ## API Usage -### Basic MALT Example -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Legal Document Analysis MALT", - "description": "Advanced linguistic analysis of legal documents using MALT framework", - "swarm_type": "MALT", - "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", - "agents": [ - { - "agent_name": "Syntactic Analyzer", - "description": "Analyzes sentence structure and grammar", - "system_prompt": "You are a syntactic analysis expert. Analyze sentence structure, grammatical patterns, and linguistic complexity in legal texts.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - }, - { - "agent_name": "Semantic Analyzer", - "description": "Analyzes meaning and semantic relationships", - "system_prompt": "You are a semantic analysis expert. Extract meaning, identify semantic relationships, and analyze conceptual content in legal documents.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Pragmatic Analyzer", - "description": "Analyzes context and implied meanings", - "system_prompt": "You are a pragmatic analysis expert. Analyze contextual meaning, implied obligations, and pragmatic implications in legal language.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.4 - }, - { - "agent_name": "Discourse Analyzer", - "description": "Analyzes document structure and flow", - "system_prompt": "You are a discourse analysis expert. Analyze document structure, logical flow, and coherence in legal texts.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Risk Language Detector", - "description": "Identifies risk-related language patterns", - "system_prompt": "You are a legal risk language expert. Identify risk indicators, liability language, and potential legal concerns in contract language.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - } - ], - "max_loops": 1 - }' - ``` - -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Legal Document Analysis MALT", - "description": "Advanced linguistic analysis of legal documents using MALT framework", - "swarm_type": "MALT", - "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", - "agents": [ - { - "agent_name": "Syntactic Analyzer", - "description": "Analyzes sentence structure and grammar", - "system_prompt": "You are a syntactic analysis expert. Analyze sentence structure, grammatical patterns, and linguistic complexity in legal texts.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - }, - { - "agent_name": "Semantic Analyzer", - "description": "Analyzes meaning and semantic relationships", - "system_prompt": "You are a semantic analysis expert. Extract meaning, identify semantic relationships, and analyze conceptual content in legal documents.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Pragmatic Analyzer", - "description": "Analyzes context and implied meanings", - "system_prompt": "You are a pragmatic analysis expert. Analyze contextual meaning, implied obligations, and pragmatic implications in legal language.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.4 - }, - { - "agent_name": "Discourse Analyzer", - "description": "Analyzes document structure and flow", - "system_prompt": "You are a discourse analysis expert. Analyze document structure, logical flow, and coherence in legal texts.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Risk Language Detector", - "description": "Identifies risk-related language patterns", - "system_prompt": "You are a legal risk language expert. Identify risk indicators, liability language, and potential legal concerns in contract language.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - } - ], - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("MALT framework completed successfully!") - print(f"Linguistic analysis: {result['output']['linguistic_analysis']}") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "legal-document-analysis-malt", - "swarm_type": "MALT", - "task": "Perform comprehensive linguistic analysis of a complex legal contract including sentiment analysis, risk identification, clause categorization, and language complexity assessment", - "output": { - "linguistic_analysis": { - "syntactic_analysis": { - "complexity_score": 8.2, - "sentence_structure": "Predominantly complex and compound-complex sentences", - "grammatical_patterns": "Heavy use of passive voice, subordinate clauses, and technical terminology", - "readability": "Graduate level (16+ years of education required)" - }, - "semantic_analysis": { - "key_concepts": ["liability", "indemnification", "force majeure", "intellectual property"], - "semantic_relationships": "Strong hierarchical concept relationships with clear definitional structures", - "conceptual_density": "High - 3.2 legal concepts per sentence average", - "ambiguity_indicators": ["potentially", "reasonable efforts", "material adverse effect"] - }, - "pragmatic_analysis": { - "implied_obligations": [ - "Good faith performance expected", - "Timely notice requirements implied", - "Mutual cooperation assumed" - ], - "power_dynamics": "Balanced with slight advantage to service provider", - "speech_acts": "Predominantly commissives (commitments) and directives (obligations)" - }, - "discourse_analysis": { - "document_structure": "Well-organized with clear section hierarchy", - "logical_flow": "Sequential with appropriate cross-references", - "coherence_score": 8.5, - "transition_patterns": "Formal legal transitions with clause numbering" - }, - "risk_language": { - "high_risk_terms": ["unlimited liability", "personal guarantee", "joint and several"], - "risk_mitigation_language": ["subject to", "limited to", "except as provided"], - "liability_indicators": 23, - "risk_level": "Medium-High" - } - }, - "comprehensive_summary": { - "language_complexity": "High complexity legal document requiring specialized knowledge", - "risk_assessment": "Medium-high risk with standard legal protections", - "readability_concerns": "Requires legal expertise for full comprehension", - "recommendations": [ - "Consider plain language summary for key terms", - "Review unlimited liability clauses", - "Clarify ambiguous terms identified" - ] - } - }, - "metadata": { - "malt_framework": { - "linguistic_layers_analyzed": 5, - "language_processing_depth": "Advanced multi-layer analysis", - "specialized_nlp_operations": [ - "Syntactic parsing", - "Semantic role labeling", - "Pragmatic inference", - "Discourse segmentation", - "Risk pattern recognition" - ] - }, - "execution_time_seconds": 35.7, - "billing_info": { - "total_cost": 0.089 - } - } -} ``` ## Best Practices diff --git a/docs/swarms_cloud/mixture_of_agents.md b/docs/swarms_cloud/mixture_of_agents.md index e02da9c7..d90e225b 100644 --- a/docs/swarms_cloud/mixture_of_agents.md +++ b/docs/swarms_cloud/mixture_of_agents.md @@ -147,29 +147,64 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "business-strategy-mixture", - "swarm_type": "MixtureOfAgents", - "task": "Develop a comprehensive market entry strategy for a new AI product in the healthcare sector", - "output": { - "market_analysis": "Detailed market research findings...", - "financial_assessment": "Financial projections and ROI analysis...", - "regulatory_compliance": "Regulatory requirements and pathways...", - "technology_strategy": "Technical implementation roadmap...", - "integrated_strategy": "Comprehensive market entry strategy combining all perspectives..." - }, - "metadata": { - "agent_contributions": { - "Market Research Analyst": "Market size: $2.3B, Growth rate: 15% CAGR", - "Financial Analyst": "Break-even: 18 months, ROI: 35%", - "Regulatory Expert": "FDA pathway: 510(k), Timeline: 8-12 months", - "Technology Strategist": "MVP timeline: 6 months, Scalability: High" - }, - "execution_time_seconds": 22.1, - "billing_info": { - "total_cost": 0.067 + "job_id": "swarms-kBZaJg1uGTkRbLCAsGztL2jrp5Mj", + "status": "success", + "swarm_name": "Business Strategy Mixture", + "description": "Diverse team analyzing business strategy from multiple perspectives", + "swarm_type": "MixtureOfAgents", + "output": [ + { + "role": "System", + "content": "Team Name: Business Strategy Mixture\nTeam Description: Diverse team analyzing business strategy from multiple perspectives\nThese are the agents in your team. Each agent has a specific role and expertise to contribute to the team's objectives.\nTotal Agents: 4\n\nBelow is a summary of your team members and their primary responsibilities:\n| Agent Name | Description |\n|------------|-------------|\n| Market Research Analyst | Analyzes market trends and opportunities |\n| Financial Analyst | Evaluates financial viability and projections |\n| Regulatory Expert | Analyzes compliance and regulatory requirements |\n| Technology Strategist | Evaluates technical feasibility and strategy |\n\nEach agent is designed to handle tasks within their area of expertise. Collaborate effectively by assigning tasks according to these roles." + }, + { + "role": "Market Research Analyst", + "content": "To develop a comprehensive market entry strategy for a new AI product in the healthcare sector, we will leverage the expertise of each team member to cover all critical aspects of the strategy. Here's how each agent will contribute......." + }, + { + "role": "Technology Strategist", + "content": "To develop a comprehensive market entry strategy for a new AI product in the healthcare sector, we'll need to collaborate effectively with the team, leveraging each member's expertise. Here's how each agent can contribute to the strategy, along with a focus on the technical requirements, implementation challenges, and scalability from the technology strategist's perspective....." + }, + { + "role": "Financial Analyst", + "content": "Developing a comprehensive market entry strategy for a new AI product in the healthcare sector involves a multidisciplinary approach. Each agent in the Business Strategy Mixture team will play a crucial role in ensuring a successful market entry. Here's how the team can collaborate........" + }, + { + "role": "Regulatory Expert", + "content": "To develop a comprehensive market entry strategy for a new AI product in the healthcare sector, we need to leverage the expertise of each agent in the Business Strategy Mixture team. Below is an outline of how each team member can contribute to this strategy......" + }, + { + "role": "Aggregator Agent", + "content": "As the Aggregator Agent, I've observed and analyzed the responses from the Business Strategy Mixture team regarding the development of a comprehensive market entry strategy for a new AI product in the healthcare sector. Here's a summary of the key points ......" + } + ], + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 30.230480670928955, + "usage": { + "input_tokens": 30, + "output_tokens": 3401, + "total_tokens": 3431, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.00009, + "output_token_cost": 0.051015, + "token_counts": { + "total_input_tokens": 30, + "total_output_tokens": 3401, + "total_tokens": 3431 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": true + }, + "total_cost": 0.091105, + "discount_active": true, + "discount_type": "night_time", + "discount_percentage": 75 + } } - } } ``` diff --git a/docs/swarms_cloud/multi_agent_router.md b/docs/swarms_cloud/multi_agent_router.md index 57f5516e..b69641a1 100644 --- a/docs/swarms_cloud/multi_agent_router.md +++ b/docs/swarms_cloud/multi_agent_router.md @@ -148,65 +148,52 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "customer-support-router", - "swarm_type": "MultiAgentRouter", - "task": "Handle multiple customer inquiries: 1) Billing question about overcharge, 2) Technical issue with mobile app login, 3) Product recommendation for enterprise client, 4) Return policy question", - "output": { - "inquiry_1_billing": { - "routed_to": "Billing Specialist", - "response": "I understand your concern about the overcharge. Let me review your account and identify the issue. I can see the duplicate charge and will process a refund within 3-5 business days...", - "resolution_status": "Resolved - Refund processed" - }, - "inquiry_2_technical": { - "routed_to": "Technical Support", - "response": "Let's troubleshoot the mobile app login issue. Please try these steps: 1) Clear app cache, 2) Update to latest version, 3) Reset password if needed...", - "resolution_status": "In Progress - Troubleshooting steps provided" - }, - "inquiry_3_sales": { - "routed_to": "Sales Consultant", - "response": "For enterprise clients, I recommend our Professional tier with advanced analytics, dedicated support, and custom integrations. This includes...", - "resolution_status": "Proposal sent - Follow-up scheduled" - }, - "inquiry_4_policy": { - "routed_to": "Policy Advisor", - "response": "Our return policy allows returns within 30 days of purchase for full refund. Items must be in original condition. Here's the complete process...", - "resolution_status": "Information provided - Customer satisfied" - } - }, - "metadata": { - "routing_decisions": [ - { - "inquiry": "Billing question about overcharge", - "routed_to": "Billing Specialist", - "confidence": 0.95, - "reasoning": "Billing-related inquiry requires specialized financial expertise" - }, - { - "inquiry": "Technical issue with mobile app login", - "routed_to": "Technical Support", - "confidence": 0.98, - "reasoning": "Technical troubleshooting requires technical specialist" - }, - { - "inquiry": "Product recommendation for enterprise client", - "routed_to": "Sales Consultant", - "confidence": 0.92, - "reasoning": "Enterprise sales requires specialized sales expertise" - }, - { - "inquiry": "Return policy question", - "routed_to": "Policy Advisor", - "confidence": 0.97, - "reasoning": "Policy questions require policy specialist knowledge" - } + "job_id": "swarms-OvOZHubprE3thzLmRdNBZAxA6om4", + "status": "success", + "swarm_name": "Customer Support Router", + "description": "Route customer inquiries to specialized support agents", + "swarm_type": "MultiAgentRouter", + "output": [ + { + "role": "user", + "content": "Handle multiple customer inquiries: 1) Billing question about overcharge, 2) Technical issue with mobile app login, 3) Product recommendation for enterprise client, 4) Return policy question" + }, + { + "role": "Agent Router", + "content": "selected_agent='Billing Specialist' reasoning='The task involves multiple inquiries, but the first one is about a billing question regarding an overcharge. Billing issues often require immediate attention to ensure customer satisfaction and prevent further complications. Therefore, the Billing Specialist is the most appropriate agent to handle this task. They can address the billing question directly and potentially coordinate with other agents for the remaining inquiries.' modified_task='Billing question about overcharge'" + }, + { + "role": "Billing Specialist", + "content": "Of course, I'd be happy to help you with your billing question regarding an overcharge. Could you please provide me with more details about the charge in question, such as the date it occurred and the amount? This information will help me look into your account and resolve the issue as quickly as possible." + } ], - "routing_efficiency": "100% - All inquiries routed to optimal agents", - "execution_time_seconds": 16.4, - "billing_info": { - "total_cost": 0.042 + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 7.800086975097656, + "usage": { + "input_tokens": 28, + "output_tokens": 221, + "total_tokens": 249, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.000084, + "output_token_cost": 0.003315, + "token_counts": { + "total_input_tokens": 28, + "total_output_tokens": 221, + "total_tokens": 249 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": true + }, + "total_cost": 0.043399, + "discount_active": true, + "discount_type": "night_time", + "discount_percentage": 75 + } } - } } ``` diff --git a/docs/swarms_cloud/sequential_workflow.md b/docs/swarms_cloud/sequential_workflow.md index db192e8c..1754e6ca 100644 --- a/docs/swarms_cloud/sequential_workflow.md +++ b/docs/swarms_cloud/sequential_workflow.md @@ -147,34 +147,56 @@ Key features: **Example Response**: ```json { - "status": "success", - "swarm_name": "content-creation-pipeline", - "swarm_type": "SequentialWorkflow", - "task": "Create a comprehensive blog post about the future of renewable energy", - "output": { - "research_findings": "Comprehensive research on renewable energy trends...", - "draft_content": "Initial blog post draft...", - "edited_content": "Polished and refined article...", - "final_seo_optimized": "SEO-optimized final blog post ready for publication..." - }, - "metadata": { - "execution_sequence": [ - "Research Specialist", - "Content Writer", - "Editor", - "SEO Optimizer" + "job_id": "swarms-pbM8wqUwxq8afGeROV2A4xAcncd1", + "status": "success", + "swarm_name": "Content Creation Pipeline", + "description": "Sequential content creation from research to final output", + "swarm_type": "SequentialWorkflow", + "output": [ + { + "role": "Research Specialist", + "content": "\"**Title: The Future of Renewable Energy: Charting a Sustainable Path Forward**\n\nAs we navigate the complexities of the 21st century, the transition to renewable energy stands out as a critical endeavor to ensure a sustainable future......" + }, + { + "role": "SEO Optimizer", + "content": "\"**Title: The Future of Renewable Energy: Charting a Sustainable Path Forward**\n\nThe transition to renewable energy is crucial as we face the challenges of the 21st century, including climate change and dwindling fossil fuel resources......." + }, + { + "role": "Editor", + "content": "\"**Title: The Future of Renewable Energy: Charting a Sustainable Path Forward**\n\nAs we confront the challenges of the 21st century, transitioning to renewable energy is essential for a sustainable future. With climate change concerns escalating and fossil fuel reserves depleting, renewable energy is not just an option but a necessity...." + }, + { + "role": "Content Writer", + "content": "\"**Title: The Future of Renewable Energy: Charting a Sustainable Path Forward**\n\nAs we face the multifaceted challenges of the 21st century, transitioning to renewable energy emerges as not just an option but an essential step toward a sustainable future...." + } ], - "step_outputs": { - "step_1": "Research findings and data", - "step_2": "Draft article content", - "step_3": "Edited and refined content", - "step_4": "SEO-optimized final version" - }, - "execution_time_seconds": 45.3, - "billing_info": { - "total_cost": 0.089 + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 72.23084282875061, + "usage": { + "input_tokens": 28, + "output_tokens": 3012, + "total_tokens": 3040, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.000084, + "output_token_cost": 0.04518, + "token_counts": { + "total_input_tokens": 28, + "total_output_tokens": 3012, + "total_tokens": 3040 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": true + }, + "total_cost": 0.085264, + "discount_active": true, + "discount_type": "night_time", + "discount_percentage": 75 + } } - } } ``` diff --git a/docs/swarms_cloud/spreadsheet_swarm.md b/docs/swarms_cloud/spreadsheet_swarm.md index 538edb48..47beafaf 100644 --- a/docs/swarms_cloud/spreadsheet_swarm.md +++ b/docs/swarms_cloud/spreadsheet_swarm.md @@ -25,198 +25,7 @@ Key features: ### Basic SpreadSheetSwarm Example -=== "Shell (curl)" - ```bash - curl -X POST "https://api.swarms.world/v1/swarm/completions" \ - -H "x-api-key: $SWARMS_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Financial Analysis Spreadsheet", - "description": "Systematic financial data analysis using spreadsheet structure", - "swarm_type": "SpreadSheetSwarm", - "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", - "agents": [ - { - "agent_name": "Data Validator", - "description": "Validates and cleans financial data", - "system_prompt": "You are a data validation specialist. Clean, validate, and structure financial data ensuring accuracy and consistency.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - }, - { - "agent_name": "Revenue Analyst", - "description": "Analyzes revenue trends and patterns", - "system_prompt": "You are a revenue analyst. Focus on revenue trends, growth patterns, and seasonal variations across product lines.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Cost Analyst", - "description": "Analyzes cost structures and margins", - "system_prompt": "You are a cost analyst. Examine cost structures, margin analysis, and expense categorization.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Performance Calculator", - "description": "Calculates KPIs and financial metrics", - "system_prompt": "You are a financial metrics specialist. Calculate KPIs, ratios, and performance indicators from the analyzed data.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.1 - }, - { - "agent_name": "Report Generator", - "description": "Creates structured financial reports", - "system_prompt": "You are a report generator. Create comprehensive, well-structured financial reports with insights and recommendations.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.4 - } - ], - "max_loops": 1 - }' - ``` -=== "Python (requests)" - ```python - import requests - import json - - API_BASE_URL = "https://api.swarms.world" - API_KEY = "your_api_key_here" - - headers = { - "x-api-key": API_KEY, - "Content-Type": "application/json" - } - - swarm_config = { - "name": "Financial Analysis Spreadsheet", - "description": "Systematic financial data analysis using spreadsheet structure", - "swarm_type": "SpreadSheetSwarm", - "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", - "agents": [ - { - "agent_name": "Data Validator", - "description": "Validates and cleans financial data", - "system_prompt": "You are a data validation specialist. Clean, validate, and structure financial data ensuring accuracy and consistency.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.2 - }, - { - "agent_name": "Revenue Analyst", - "description": "Analyzes revenue trends and patterns", - "system_prompt": "You are a revenue analyst. Focus on revenue trends, growth patterns, and seasonal variations across product lines.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Cost Analyst", - "description": "Analyzes cost structures and margins", - "system_prompt": "You are a cost analyst. Examine cost structures, margin analysis, and expense categorization.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.3 - }, - { - "agent_name": "Performance Calculator", - "description": "Calculates KPIs and financial metrics", - "system_prompt": "You are a financial metrics specialist. Calculate KPIs, ratios, and performance indicators from the analyzed data.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.1 - }, - { - "agent_name": "Report Generator", - "description": "Creates structured financial reports", - "system_prompt": "You are a report generator. Create comprehensive, well-structured financial reports with insights and recommendations.", - "model_name": "gpt-4o", - "max_loops": 1, - "temperature": 0.4 - } - ], - "max_loops": 1 - } - - response = requests.post( - f"{API_BASE_URL}/v1/swarm/completions", - headers=headers, - json=swarm_config - ) - - if response.status_code == 200: - result = response.json() - print("SpreadSheetSwarm completed successfully!") - print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") - print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") - print(f"Structured analysis: {result['output']}") - else: - print(f"Error: {response.status_code} - {response.text}") - ``` - -**Example Response**: -```json -{ - "status": "success", - "swarm_name": "financial-analysis-spreadsheet", - "swarm_type": "SpreadSheetSwarm", - "task": "Analyze quarterly financial performance data for a retail company with multiple product lines and create comprehensive insights", - "output": { - "data_validation": { - "data_quality": "95% accuracy after cleaning", - "missing_values": "Identified and filled 3% missing entries", - "data_structure": "Standardized format across all product lines" - }, - "revenue_analysis": { - "q4_revenue": "$2.4M total revenue", - "growth_rate": "12% quarter-over-quarter growth", - "top_performers": ["Product Line A: +18%", "Product Line C: +15%"], - "seasonal_trends": "Strong holiday season performance" - }, - "cost_analysis": { - "total_costs": "$1.8M operational costs", - "cost_breakdown": "60% COGS, 25% Marketing, 15% Operations", - "margin_analysis": "25% gross margin, 15% net margin", - "cost_optimization": "Identified 8% potential savings in supply chain" - }, - "performance_metrics": { - "roi": "22% return on investment", - "customer_acquisition_cost": "$45 per customer", - "lifetime_value": "$320 average CLV", - "inventory_turnover": "6.2x annual turnover" - }, - "comprehensive_report": { - "executive_summary": "Strong Q4 performance with 12% growth...", - "recommendations": ["Expand Product Line A", "Optimize supply chain", "Increase marketing for underperformers"], - "forecast": "Projected 15% growth for Q1 based on trends" - } - }, - "metadata": { - "processing_structure": { - "rows_processed": 1250, - "columns_analyzed": 18, - "calculations_performed": 47 - }, - "data_pipeline": [ - "Data Validation", - "Revenue Analysis", - "Cost Analysis", - "Performance Calculation", - "Report Generation" - ], - "execution_time_seconds": 34.2, - "billing_info": { - "total_cost": 0.078 - } - } -} -``` ## Best Practices diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index 8ab22abd..f61277b9 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -6,16 +6,16 @@ Each multi-agent architecture type is designed for specific use cases and can be |----------------------|------------------------------------------------------------------------------|------------| | AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Useful when agent effectiveness depends on their sequence or arrangement. | [Learn More](agent_rearrange.md) | | MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](mixture_of_agents.md) | -| SpreadSheetSwarm | Provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. | [Learn More](spreadsheet_swarm.md) | + | SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](sequential_workflow.md) | | ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](concurrent_workflow.md) | -| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](group_chat.md) | + | MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](multi_agent_router.md) | -| AutoSwarmBuilder | Automatically configures agent architectures based on task requirements and performance metrics, simplifying swarm creation. | [Learn More](auto_swarm_builder.md) | -| HierarchicalSwarm | Implements a structured, multi-level approach to task management, with clear lines of authority and delegation. | [Learn More](hierarchical_swarm.md) | -| Auto | Intelligently selects the most effective swarm architecture for a given task based on context. | [Learn More](auto.md) | + + | MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | -| MALT | Specialized framework for language-based tasks, optimizing agent collaboration for complex language processing operations. | [Learn More](malt.md) | + # Learn More From 224e8eb58d0bcaf6cab6d124991bfd9c2c2149fc Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 20:10:34 +0530 Subject: [PATCH 32/73] updates! --- docs/swarms_cloud/swarm_types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index f61277b9..e28bace4 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -6,15 +6,15 @@ Each multi-agent architecture type is designed for specific use cases and can be |----------------------|------------------------------------------------------------------------------|------------| | AgentRearrange | Dynamically reorganizes agents to optimize task performance and efficiency. Useful when agent effectiveness depends on their sequence or arrangement. | [Learn More](agent_rearrange.md) | | MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](mixture_of_agents.md) | - | SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](sequential_workflow.md) | | ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](concurrent_workflow.md) | - | MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](multi_agent_router.md) | +| MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | + + -| MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | # Learn More From 1444a719c43c9f38b1a2d60085e2d2e2860c2b16 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 21:27:39 +0530 Subject: [PATCH 33/73] fixes !! --- docs/mkdocs.yml | 2 +- docs/swarms_cloud/group_chat.md | 151 +++++++++++++++++++++++++++++++ docs/swarms_cloud/swarm_types.md | 4 +- 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e6fe1aff..729dd70c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -452,7 +452,7 @@ nav: # - SpreadSheetSwarm: "swarms_cloud/spreadsheet_swarm.md" - SequentialWorkflow: "swarms_cloud/sequential_workflow.md" - ConcurrentWorkflow: "swarms_cloud/concurrent_workflow.md" - # - GroupChat: "swarms_cloud/group_chat.md" + - GroupChat: "swarms_cloud/group_chat.md" - MultiAgentRouter: "swarms_cloud/multi_agent_router.md" # - AutoSwarmBuilder: "swarms_cloud/auto_swarm_builder.md" # - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" diff --git a/docs/swarms_cloud/group_chat.md b/docs/swarms_cloud/group_chat.md index fa6cab9d..3c3ee17c 100644 --- a/docs/swarms_cloud/group_chat.md +++ b/docs/swarms_cloud/group_chat.md @@ -23,6 +23,157 @@ Key features: ## API Usage +### Basic GroupChat Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Product Strategy Discussion", + "description": "Collaborative chat to develop product strategy", + "swarm_type": "GroupChat", + "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", + "agents": [ + { + "agent_name": "Product Manager", + "description": "Leads product strategy and development", + "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "Marketing Strategist", + "description": "Develops marketing and positioning strategy", + "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "Sales Director", + "description": "Provides sales and customer perspective", + "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "UX Researcher", + "description": "Represents user experience and research insights", + "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", + "model_name": "gpt-4o", + "max_loops": 3, + } + ], + "max_loops": 3 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Product Strategy Discussion", + "description": "Collaborative chat to develop product strategy", + "swarm_type": "GroupChat", + "task": "Discuss and develop a go-to-market strategy for a new AI-powered productivity tool targeting small businesses", + "agents": [ + { + "agent_name": "Product Manager", + "description": "Leads product strategy and development", + "system_prompt": "You are a senior product manager. Focus on product positioning, features, user needs, and market fit. Ask probing questions and build on others ideas.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "Marketing Strategist", + "description": "Develops marketing and positioning strategy", + "system_prompt": "You are a marketing strategist. Focus on target audience, messaging, channels, and competitive positioning. Contribute marketing insights to the discussion.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "Sales Director", + "description": "Provides sales and customer perspective", + "system_prompt": "You are a sales director with small business experience. Focus on pricing, sales process, customer objections, and market adoption. Share practical sales insights.", + "model_name": "gpt-4o", + "max_loops": 3, + }, + { + "agent_name": "UX Researcher", + "description": "Represents user experience and research insights", + "system_prompt": "You are a UX researcher specializing in small business tools. Focus on user behavior, usability, adoption barriers, and design considerations.", + "model_name": "gpt-4o", + "max_loops": 3, + } + ], + "max_loops": 3 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("GroupChat swarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Chat discussion: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "job_id": "swarms-2COVtf3k0Fz7jU1BOOHF3b5nuL2x", + "status": "success", + "swarm_name": "Product Strategy Discussion", + "description": "Collaborative chat to develop product strategy", + "swarm_type": "GroupChat", + "output": "User: \n\nSystem: \n Group Chat Name: Product Strategy Discussion\nGroup Chat Description: Collaborative chat to develop product strategy\n Agents in your Group Chat: Available Agents for Team: None\n\n\n\n[Agent 1]\nName: Product Manager\nDescription: Leads product strategy and development\nRole.....", + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 47.36732482910156, + "usage": { + "input_tokens": 30, + "output_tokens": 1633, + "total_tokens": 1663, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.00009, + "output_token_cost": 0.024495, + "token_counts": { + "total_input_tokens": 30, + "total_output_tokens": 1633, + "total_tokens": 1663 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": false + }, + "total_cost": 0.064585, + "discount_active": false, + "discount_type": "none", + "discount_percentage": 0 + } + } +} +``` ## Best Practices diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index e28bace4..d964ce0c 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -8,10 +8,12 @@ Each multi-agent architecture type is designed for specific use cases and can be | MixtureOfAgents | Builds diverse teams of specialized agents, each contributing unique skills to solve complex problems. Excels at tasks requiring multiple types of expertise. | [Learn More](mixture_of_agents.md) | | SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](sequential_workflow.md) | | ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](concurrent_workflow.md) | +| GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](group_chat.md) | | MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](multi_agent_router.md) | | MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | + - + From cf7357fd8e2eb4e15d7a683c445fe5bd67ea4f51 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 23:14:53 +0530 Subject: [PATCH 34/73] fixed HiearchicalSwarm & cleanups ! --- docs/mkdocs.yml | 6 +- docs/swarms_cloud/hierarchical_swarm.md | 212 +++++++++++++++++++++++- docs/swarms_cloud/malt.md | 40 ----- docs/swarms_cloud/spreadsheet_swarm.md | 41 ----- docs/swarms_cloud/swarm_types.md | 5 +- 5 files changed, 213 insertions(+), 91 deletions(-) delete mode 100644 docs/swarms_cloud/malt.md delete mode 100644 docs/swarms_cloud/spreadsheet_swarm.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 729dd70c..0fad0e31 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -449,16 +449,14 @@ nav: - Swarm Types: - AgentRearrange: "swarms_cloud/agent_rearrange.md" - MixtureOfAgents: "swarms_cloud/mixture_of_agents.md" - # - SpreadSheetSwarm: "swarms_cloud/spreadsheet_swarm.md" - SequentialWorkflow: "swarms_cloud/sequential_workflow.md" - ConcurrentWorkflow: "swarms_cloud/concurrent_workflow.md" - GroupChat: "swarms_cloud/group_chat.md" - MultiAgentRouter: "swarms_cloud/multi_agent_router.md" + - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" + - MajorityVoting: "swarms_cloud/majority_voting.md" # - AutoSwarmBuilder: "swarms_cloud/auto_swarm_builder.md" - # - HierarchicalSwarm: "swarms_cloud/hierarchical_swarm.md" # - Auto: "swarms_cloud/auto.md" - - MajorityVoting: "swarms_cloud/majority_voting.md" - # - MALT: "swarms_cloud/malt.md" - Examples: - Medical Swarm: "swarms/examples/swarms_api_medical.md" - Finance Swarm: "swarms/examples/swarms_api_finance.md" diff --git a/docs/swarms_cloud/hierarchical_swarm.md b/docs/swarms_cloud/hierarchical_swarm.md index f65c7a46..70c8792e 100644 --- a/docs/swarms_cloud/hierarchical_swarm.md +++ b/docs/swarms_cloud/hierarchical_swarm.md @@ -1,12 +1,12 @@ -# HierarchicalSwarm +# HiearchicalSwarm *Implements structured, multi-level task management with clear authority* -**Swarm Type**: `HierarchicalSwarm` +**Swarm Type**: `HiearchicalSwarm` ## Overview -The HierarchicalSwarm implements a structured, multi-level approach to task management with clear lines of authority and delegation. This architecture organizes agents in a hierarchical structure where manager agents coordinate and oversee worker agents, enabling efficient task distribution and quality control. +The HiearchicalSwarm implements a structured, multi-level approach to task management with clear lines of authority and delegation. This architecture organizes agents in a hierarchical structure where manager agents coordinate and oversee worker agents, enabling efficient task distribution and quality control. Key features: - **Structured Hierarchy**: Clear organizational structure with managers and workers @@ -23,6 +23,212 @@ Key features: ## API Usage +### Basic HiearchicalSwarm Example + +=== "Shell (curl)" + ```bash + curl -X POST "https://api.swarms.world/v1/swarm/completions" \ + -H "x-api-key: $SWARMS_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Market Research ", + "description": "Parallel market research across different sectors", + "swarm_type": "HiearchicalSwarm", + "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", + "agents": [ + { + "agent_name": "AI Market Analyst", + "description": "Analyzes AI market trends and opportunities", + "system_prompt": "You are an AI market analyst. Focus on artificial intelligence market trends, opportunities, key players, and growth projections.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Healthcare Market Analyst", + "description": "Analyzes healthcare market trends", + "system_prompt": "You are a healthcare market analyst. Focus on healthcare market trends, digital health opportunities, regulatory landscape, and growth areas.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Fintech Market Analyst", + "description": "Analyzes fintech market opportunities", + "system_prompt": "You are a fintech market analyst. Focus on financial technology trends, digital payment systems, blockchain opportunities, and regulatory developments.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "E-commerce Market Analyst", + "description": "Analyzes e-commerce market trends", + "system_prompt": "You are an e-commerce market analyst. Focus on online retail trends, marketplace opportunities, consumer behavior, and emerging platforms.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + }' + ``` + +=== "Python (requests)" + ```python + import requests + import json + + API_BASE_URL = "https://api.swarms.world" + API_KEY = "your_api_key_here" + + headers = { + "x-api-key": API_KEY, + "Content-Type": "application/json" + } + + swarm_config = { + "name": "Market Research ", + "description": "Parallel market research across different sectors", + "swarm_type": "HiearchicalSwarm", + "task": "Research and analyze market opportunities in AI, healthcare, fintech, and e-commerce sectors", + "agents": [ + { + "agent_name": "AI Market Analyst", + "description": "Analyzes AI market trends and opportunities", + "system_prompt": "You are an AI market analyst. Focus on artificial intelligence market trends, opportunities, key players, and growth projections.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Healthcare Market Analyst", + "description": "Analyzes healthcare market trends", + "system_prompt": "You are a healthcare market analyst. Focus on healthcare market trends, digital health opportunities, regulatory landscape, and growth areas.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "Fintech Market Analyst", + "description": "Analyzes fintech market opportunities", + "system_prompt": "You are a fintech market analyst. Focus on financial technology trends, digital payment systems, blockchain opportunities, and regulatory developments.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + }, + { + "agent_name": "E-commerce Market Analyst", + "description": "Analyzes e-commerce market trends", + "system_prompt": "You are an e-commerce market analyst. Focus on online retail trends, marketplace opportunities, consumer behavior, and emerging platforms.", + "model_name": "gpt-4o", + "max_loops": 1, + "temperature": 0.3 + } + ], + "max_loops": 1 + } + + response = requests.post( + f"{API_BASE_URL}/v1/swarm/completions", + headers=headers, + json=swarm_config + ) + + if response.status_code == 200: + result = response.json() + print("HiearchicalSwarm completed successfully!") + print(f"Cost: ${result['metadata']['billing_info']['total_cost']}") + print(f"Execution time: {result['metadata']['execution_time_seconds']} seconds") + print(f"Project plan: {result['output']}") + else: + print(f"Error: {response.status_code} - {response.text}") + ``` + +**Example Response**: +```json +{ + "job_id": "swarms-JIrcIAfs2d75xrXGaAL94uWyYJ8V", + "status": "success", + "swarm_name": "Market Research Auto", + "description": "Parallel market research across different sectors", + "swarm_type": "HiearchicalSwarm", + "output": [ + { + "role": "System", + "content": "These are the agents in your team. Each agent has a specific role and expertise to contribute to the team's objectives.\nTotal Agents: 4\n\nBelow is a summary of your team members and their primary responsibilities:\n| Agent Name | Description |\n|------------|-------------|\n| AI Market Analyst | Analyzes AI market trends and opportunities |\n| Healthcare Market Analyst | Analyzes healthcare market trends |\n| Fintech Market Analyst | Analyzes fintech market opportunities |\n| E-commerce Market Analyst | Analyzes e-commerce market trends |\n\nEach agent is designed to handle tasks within their area of expertise. Collaborate effectively by assigning tasks according to these roles." + }, + { + "role": "Director", + "content": [ + { + "role": "Director", + "content": [ + { + "function": { + "arguments": "{\"plan\":\"Conduct a comprehensive analysis of market opportunities in the AI, healthcare, fintech, and e-commerce sectors. Each market analyst will focus on their respective sector, gathering data on current trends, growth opportunities, and potential challenges. The findings will be compiled into a report for strategic decision-making.\",\"orders\":[{\"agent_name\":\"AI Market Analyst\",\"task\":\"Research current trends in the AI market, identify growth opportunities, and analyze potential challenges.\"},{\"agent_name\":\"Healthcare Market Analyst\",\"task\":\"Analyze the healthcare market for emerging trends, growth opportunities, and possible challenges.\"},{\"agent_name\":\"Fintech Market Analyst\",\"task\":\"Investigate the fintech sector for current trends, identify opportunities for growth, and assess challenges.\"},{\"agent_name\":\"E-commerce Market Analyst\",\"task\":\"Examine e-commerce market trends, identify growth opportunities, and analyze potential challenges.\"}]}", + "name": "ModelMetaclass" + }, + "id": "call_GxiyzIRb2oGQXokbbkeaeVry", + "type": "function" + } + ] + } + ] + }, + { + "role": "AI Market Analyst", + "content": "### AI Market Analysis: Trends, Opportunities, and Challenges\n\n#### Current Trends in the AI Market:\n\n1. **Increased Adoption Across Industries**..." + }, + { + "role": "Healthcare Market Analyst", + "content": "### Healthcare Market Analysis: Trends, Opportunities, and Challenges\n\n#### Current Trends in the Healthcare Market:\n\n1. **Telehealth Expansion**..." + }, + { + "role": "Fintech Market Analyst", + "content": "### Fintech Market Analysis: Trends, Opportunities, and Challenges\n\n#### Current Trends in the Fintech Market:\n\n1. **Digital Payments Proliferation**...." + }, + { + "role": "E-commerce Market Analyst", + "content": "### E-commerce Market Analysis: Trends, Opportunities, and Challenges\n\n#### Current Trends in the E-commerce Market:\n\n1. **Omnichannel Retailing**...." + }, + { + "role": "Director", + "content": "### Feedback for Worker Agents\n\n#### AI Market Analyst\n\n**Strengths:**\n- Comprehensive coverage of current trends, growth opportunities, and challenges in the AI market.\n- Clear categorization of insights, making it easy to follow and understand.\n\n**Weaknesses....." + }, + { + "role": "System", + "content": "--- Loop 1/1 completed ---" + } + ], + "number_of_agents": 4, + "service_tier": "standard", + "execution_time": 94.07934331893921, + "usage": { + "input_tokens": 35, + "output_tokens": 3827, + "total_tokens": 3862, + "billing_info": { + "cost_breakdown": { + "agent_cost": 0.04, + "input_token_cost": 0.000105, + "output_token_cost": 0.057405, + "token_counts": { + "total_input_tokens": 35, + "total_output_tokens": 3827, + "total_tokens": 3862 + }, + "num_agents": 4, + "service_tier": "standard", + "night_time_discount_applied": false + }, + "total_cost": 0.09751, + "discount_active": false, + "discount_type": "none", + "discount_percentage": 0 + } + } +} +``` ## Configuration Options diff --git a/docs/swarms_cloud/malt.md b/docs/swarms_cloud/malt.md deleted file mode 100644 index 3247e854..00000000 --- a/docs/swarms_cloud/malt.md +++ /dev/null @@ -1,40 +0,0 @@ -# MALT - -*Specialized framework for complex language-based tasks and processing* - -**Swarm Type**: `MALT` - -## Overview - -MALT (Multi-Agent Language Task) is a specialized framework optimized for complex language-based tasks, optimizing agent collaboration for sophisticated language processing operations. This architecture excels at tasks requiring deep linguistic analysis, natural language understanding, and complex text generation workflows. - -Key features: -- **Language Optimization**: Specifically designed for natural language tasks -- **Linguistic Collaboration**: Agents work together on complex language operations -- **Text Processing Pipeline**: Structured approach to language task workflows -- **Advanced NLP**: Optimized for sophisticated language understanding tasks - -## Use Cases - -- Complex document analysis and processing -- Multi-language translation and localization -- Advanced content generation and editing -- Linguistic research and analysis tasks - -## API Usage - - -``` - -## Best Practices - -- Use MALT for sophisticated language processing tasks -- Design agents with complementary linguistic analysis capabilities -- Ideal for tasks requiring deep language understanding -- Consider multiple levels of linguistic analysis (syntax, semantics, pragmatics) - -## Related Swarm Types - -- [SequentialWorkflow](sequential_workflow.md) - For ordered language processing -- [MixtureOfAgents](mixture_of_agents.md) - For diverse linguistic expertise -- [HierarchicalSwarm](hierarchical_swarm.md) - For structured language analysis \ No newline at end of file diff --git a/docs/swarms_cloud/spreadsheet_swarm.md b/docs/swarms_cloud/spreadsheet_swarm.md deleted file mode 100644 index 47beafaf..00000000 --- a/docs/swarms_cloud/spreadsheet_swarm.md +++ /dev/null @@ -1,41 +0,0 @@ -# SpreadSheetSwarm - -*Structured approach to data management and operations in spreadsheet-like format* - -**Swarm Type**: `SpreadSheetSwarm` - -## Overview - -The SpreadSheetSwarm provides a structured approach to data management and operations, ideal for tasks involving data analysis, transformation, and systematic processing in a spreadsheet-like structure. This architecture organizes agents to work on data in a tabular format with clear rows, columns, and processing workflows. - -Key features: -- **Structured Data Processing**: Organizes work in spreadsheet-like rows and columns -- **Systematic Operations**: Sequential and methodical data handling -- **Data Transformation**: Efficient processing of structured datasets -- **Collaborative Analysis**: Multiple agents working on different data aspects - -## Use Cases - -- Financial data analysis and reporting -- Customer data processing and segmentation -- Inventory management and tracking -- Research data compilation and analysis - -## API Usage - -### Basic SpreadSheetSwarm Example - - - -## Best Practices - -- Structure data in clear, logical formats before processing -- Use systematic, step-by-step analysis approaches -- Ideal for quantitative analysis and reporting tasks -- Ensure data validation before proceeding with analysis - -## Related Swarm Types - -- [SequentialWorkflow](sequential_workflow.md) - For ordered data processing -- [ConcurrentWorkflow](concurrent_workflow.md) - For parallel data analysis -- [HierarchicalSwarm](hierarchical_swarm.md) - For complex data projects \ No newline at end of file diff --git a/docs/swarms_cloud/swarm_types.md b/docs/swarms_cloud/swarm_types.md index d964ce0c..5773237b 100644 --- a/docs/swarms_cloud/swarm_types.md +++ b/docs/swarms_cloud/swarm_types.md @@ -9,15 +9,14 @@ Each multi-agent architecture type is designed for specific use cases and can be | SequentialWorkflow | Executes tasks in a strict, predefined order. Perfect for workflows where each step depends on the completion of the previous one. | [Learn More](sequential_workflow.md) | | ConcurrentWorkflow | Runs independent tasks in parallel, significantly reducing processing time for complex operations. Ideal for tasks that can be processed simultaneously. | [Learn More](concurrent_workflow.md) | | GroupChat | Enables dynamic collaboration between agents through a chat-based interface, facilitating real-time information sharing and decision-making. | [Learn More](group_chat.md) | +| HierarchicalSwarm | Implements a structured, multi-level approach to task management, with clear lines of authority and delegation. | [Learn More](hierarchical_swarm.md) | | MultiAgentRouter | Acts as an intelligent task dispatcher, distributing work across agents based on their capabilities and current workload. | [Learn More](multi_agent_router.md) | | MajorityVoting | Implements robust decision-making through consensus, ideal for tasks requiring collective intelligence or verification. | [Learn More](majority_voting.md) | - - + # Learn More From 0271ad1597bba3693fff5523c764efdebed578c2 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 23:26:45 +0530 Subject: [PATCH 35/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8d0c7d3f..6c740d56 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -89,7 +89,7 @@ extra: url: "https://github.com/kyegomez/awesome-multi-agent-papers" - "Popular Tools Integration": + "Tools Use Case": - title: "Tools and MCP" url: "https://docs.swarms.world/en/latest/swarms/tools/tools_examples/" - title: "MCP (Model Context Protocol)" From 09938b383037b4a7c3254411884d6014b863df0b Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 23:33:23 +0530 Subject: [PATCH 36/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6c740d56..163b3dda 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -107,7 +107,7 @@ extra: - title: "Yahoo Finance" url: "https://docs.swarms.world/en/latest/swarms/examples/yahoo_finance/" - "Applications Use Case": + "Use Cases": - title: "Examples Overview" url: "https://docs.swarms.world/en/latest/examples/index/" - title: "Templates & Applications" From 8864f5853d7e3e21bbf02f63660e4d3c79125755 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Fri, 1 Aug 2025 23:35:19 +0530 Subject: [PATCH 37/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 163b3dda..d9bb1aa4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -107,7 +107,7 @@ extra: - title: "Yahoo Finance" url: "https://docs.swarms.world/en/latest/swarms/examples/yahoo_finance/" - "Use Cases": + "Use Case": - title: "Examples Overview" url: "https://docs.swarms.world/en/latest/examples/index/" - title: "Templates & Applications" From c9ed0dba2701c78926fff7fab1c5129fbe1d38b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A5=A5=E5=AE=87?= <625024108@qq.com> Date: Sat, 2 Aug 2025 02:10:20 +0800 Subject: [PATCH 38/73] update --- examples/utils/misc/conversation_test.py | 103 +++--------------- .../utils/misc/conversation_test_truncate.py | 91 ++++++++++++++++ 2 files changed, 108 insertions(+), 86 deletions(-) create mode 100644 examples/utils/misc/conversation_test_truncate.py diff --git a/examples/utils/misc/conversation_test.py b/examples/utils/misc/conversation_test.py index ae34692b..a7a6750e 100644 --- a/examples/utils/misc/conversation_test.py +++ b/examples/utils/misc/conversation_test.py @@ -1,91 +1,22 @@ from swarms.structs.conversation import Conversation -from dotenv import load_dotenv +# Create a conversation object +conversation = Conversation(backend="in-memory") -# Load environment variables from .env file -load_dotenv() - -def demonstrate_truncation(): - # Using a smaller context length to clearly see the truncation effect - context_length = 25 - print(f"Creating a conversation instance with context length {context_length}") - - # Using Claude model as the tokenizer model - conversation = Conversation( - context_length=context_length, - tokenizer_model_name="claude-3-7-sonnet-20250219" - ) - - # Adding first message - short message - short_message = "Hello, I am a user." - print(f"\nAdding short message: '{short_message}'") - conversation.add("user", short_message) - - # Display token count - from swarms.utils.litellm_tokenizer import count_tokens - tokens = count_tokens(short_message, conversation.tokenizer_model_name) - print(f"Short message token count: {tokens}") - - # Adding second message - long message, should be truncated - long_message = "I have a question about artificial intelligence. I want to understand how large language models handle long texts, especially under token constraints. This issue is important because it relates to the model's practicality and effectiveness. I hope to get a detailed answer that helps me understand this complex technical problem." - print(f"\nAdding long message:\n'{long_message}'") - conversation.add("assistant", long_message) - - # Display long message token count - tokens = count_tokens(long_message, conversation.tokenizer_model_name) - print(f"Long message token count: {tokens}") - - # Display current conversation total token count - total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) - for msg in conversation.conversation_history) - print(f"Total token count before truncation: {total_tokens}") - - # Print the complete conversation history before truncation - print("\nConversation history before truncation:") - for i, msg in enumerate(conversation.conversation_history): - print(f"[{i}] {msg['role']}: {msg['content']}") - print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") - - # Execute truncation - print("\nExecuting truncation...") - conversation.truncate_memory_with_tokenizer() - - # Print conversation history after truncation - print("\nConversation history after truncation:") - for i, msg in enumerate(conversation.conversation_history): - print(f"[{i}] {msg['role']}: {msg['content']}") - print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") - - # Display total token count after truncation - total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) - for msg in conversation.conversation_history) - print(f"\nTotal token count after truncation: {total_tokens}") - print(f"Context length limit: {context_length}") - - # Verify if successfully truncated below the limit - if total_tokens <= context_length: - print("✅ Success: Total token count is now less than or equal to context length limit") - else: - print("❌ Failure: Total token count still exceeds context length limit") - - # Test sentence boundary truncation - print("\n\nTesting sentence boundary truncation:") - sentence_test = Conversation(context_length=15, tokenizer_model_name="claude-3-opus-20240229") - test_text = "This is the first sentence. This is the second very long sentence that contains a lot of content. This is the third sentence." - print(f"Original text: '{test_text}'") - print(f"Original token count: {count_tokens(test_text, sentence_test.tokenizer_model_name)}") - - # Using binary search for truncation - truncated = sentence_test._binary_search_truncate(test_text, 10, sentence_test.tokenizer_model_name) - print(f"Truncated text: '{truncated}'") - print(f"Truncated token count: {count_tokens(truncated, sentence_test.tokenizer_model_name)}") - - # Check if truncated at period - if truncated.endswith("."): - print("✅ Success: Text was truncated at sentence boundary") - else: - print("Note: Text was not truncated at sentence boundary") +# Add a message to the conversation +conversation.add( + role="user", content="Hello, how are you?", category="input" +) +# Add a message to the conversation +conversation.add( + role="assistant", + content="I'm good, thank you!", + category="output", +) -if __name__ == "__main__": - demonstrate_truncation() \ No newline at end of file +print( + conversation.export_and_count_categories( + tokenizer_model_name="claude-3-5-sonnet-20240620" + ) +) \ No newline at end of file diff --git a/examples/utils/misc/conversation_test_truncate.py b/examples/utils/misc/conversation_test_truncate.py new file mode 100644 index 00000000..ae34692b --- /dev/null +++ b/examples/utils/misc/conversation_test_truncate.py @@ -0,0 +1,91 @@ +from swarms.structs.conversation import Conversation +from dotenv import load_dotenv + + +# Load environment variables from .env file +load_dotenv() + +def demonstrate_truncation(): + # Using a smaller context length to clearly see the truncation effect + context_length = 25 + print(f"Creating a conversation instance with context length {context_length}") + + # Using Claude model as the tokenizer model + conversation = Conversation( + context_length=context_length, + tokenizer_model_name="claude-3-7-sonnet-20250219" + ) + + # Adding first message - short message + short_message = "Hello, I am a user." + print(f"\nAdding short message: '{short_message}'") + conversation.add("user", short_message) + + # Display token count + from swarms.utils.litellm_tokenizer import count_tokens + tokens = count_tokens(short_message, conversation.tokenizer_model_name) + print(f"Short message token count: {tokens}") + + # Adding second message - long message, should be truncated + long_message = "I have a question about artificial intelligence. I want to understand how large language models handle long texts, especially under token constraints. This issue is important because it relates to the model's practicality and effectiveness. I hope to get a detailed answer that helps me understand this complex technical problem." + print(f"\nAdding long message:\n'{long_message}'") + conversation.add("assistant", long_message) + + # Display long message token count + tokens = count_tokens(long_message, conversation.tokenizer_model_name) + print(f"Long message token count: {tokens}") + + # Display current conversation total token count + total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) + for msg in conversation.conversation_history) + print(f"Total token count before truncation: {total_tokens}") + + # Print the complete conversation history before truncation + print("\nConversation history before truncation:") + for i, msg in enumerate(conversation.conversation_history): + print(f"[{i}] {msg['role']}: {msg['content']}") + print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") + + # Execute truncation + print("\nExecuting truncation...") + conversation.truncate_memory_with_tokenizer() + + # Print conversation history after truncation + print("\nConversation history after truncation:") + for i, msg in enumerate(conversation.conversation_history): + print(f"[{i}] {msg['role']}: {msg['content']}") + print(f" Token count: {count_tokens(msg['content'], conversation.tokenizer_model_name)}") + + # Display total token count after truncation + total_tokens = sum(count_tokens(msg["content"], conversation.tokenizer_model_name) + for msg in conversation.conversation_history) + print(f"\nTotal token count after truncation: {total_tokens}") + print(f"Context length limit: {context_length}") + + # Verify if successfully truncated below the limit + if total_tokens <= context_length: + print("✅ Success: Total token count is now less than or equal to context length limit") + else: + print("❌ Failure: Total token count still exceeds context length limit") + + # Test sentence boundary truncation + print("\n\nTesting sentence boundary truncation:") + sentence_test = Conversation(context_length=15, tokenizer_model_name="claude-3-opus-20240229") + test_text = "This is the first sentence. This is the second very long sentence that contains a lot of content. This is the third sentence." + print(f"Original text: '{test_text}'") + print(f"Original token count: {count_tokens(test_text, sentence_test.tokenizer_model_name)}") + + # Using binary search for truncation + truncated = sentence_test._binary_search_truncate(test_text, 10, sentence_test.tokenizer_model_name) + print(f"Truncated text: '{truncated}'") + print(f"Truncated token count: {count_tokens(truncated, sentence_test.tokenizer_model_name)}") + + # Check if truncated at period + if truncated.endswith("."): + print("✅ Success: Text was truncated at sentence boundary") + else: + print("Note: Text was not truncated at sentence boundary") + + +if __name__ == "__main__": + demonstrate_truncation() \ No newline at end of file From 9576957adf7934e1d3bc5ddf0f8c0deef962f973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A5=A5=E5=AE=87?= <625024108@qq.com> Date: Sat, 2 Aug 2025 02:17:48 +0800 Subject: [PATCH 39/73] update --- examples/utils/misc/conversation_test_truncate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/utils/misc/conversation_test_truncate.py b/examples/utils/misc/conversation_test_truncate.py index ae34692b..f660ff45 100644 --- a/examples/utils/misc/conversation_test_truncate.py +++ b/examples/utils/misc/conversation_test_truncate.py @@ -1,6 +1,6 @@ from swarms.structs.conversation import Conversation from dotenv import load_dotenv - +from swarms.utils.litellm_tokenizer import count_tokens # Load environment variables from .env file load_dotenv() @@ -22,7 +22,7 @@ def demonstrate_truncation(): conversation.add("user", short_message) # Display token count - from swarms.utils.litellm_tokenizer import count_tokens + tokens = count_tokens(short_message, conversation.tokenizer_model_name) print(f"Short message token count: {tokens}") From def507cf5ca8a6f024e7c08db33c3e96ccbc0b01 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Fri, 1 Aug 2025 23:25:52 +0300 Subject: [PATCH 40/73] Update board_of_directors_example.py --- .../board_of_directors_example.py | 440 ++++-------------- 1 file changed, 100 insertions(+), 340 deletions(-) diff --git a/examples/multi_agent/board_of_directors/board_of_directors_example.py b/examples/multi_agent/board_of_directors/board_of_directors_example.py index 425d460b..bc043733 100644 --- a/examples/multi_agent/board_of_directors/board_of_directors_example.py +++ b/examples/multi_agent/board_of_directors/board_of_directors_example.py @@ -5,19 +5,25 @@ This example demonstrates how to use the Board of Directors swarm feature in the Swarms Framework. It shows how to create a board, configure it, and use it to orchestrate tasks across multiple agents. -The example includes: -1. Basic Board of Directors setup and usage -2. Custom board member configuration -3. Task execution and feedback -4. Configuration management +To run this example: +1. Make sure you're in the root directory of the swarms project +2. Run: python examples/multi_agent/board_of_directors/board_of_directors_example.py """ import os import sys -from typing import List, Optional - -# Add the parent directory to the path to import swarms -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) +from typing import List + +# Add the root directory to the Python path if running from examples directory +current_dir = os.path.dirname(os.path.abspath(__file__)) +if 'examples' in current_dir: + root_dir = current_dir + while os.path.basename(root_dir) != 'examples' and root_dir != os.path.dirname(root_dir): + root_dir = os.path.dirname(root_dir) + if os.path.basename(root_dir) == 'examples': + root_dir = os.path.dirname(root_dir) + if root_dir not in sys.path: + sys.path.insert(0, root_dir) from swarms.structs.board_of_directors_swarm import ( BoardOfDirectorsSwarm, @@ -25,413 +31,167 @@ from swarms.structs.board_of_directors_swarm import ( BoardMemberRole, ) from swarms.structs.agent import Agent -from swarms.config.board_config import ( - enable_board_feature, - disable_board_feature, - is_board_feature_enabled, - create_default_config_file, - set_board_size, - set_decision_threshold, - set_board_model, - enable_verbose_logging, - disable_verbose_logging, -) - - -def enable_board_directors_feature() -> None: - """ - Enable the Board of Directors feature. - - This function demonstrates how to enable the Board of Directors feature - globally and create a default configuration file. - """ - print("🔧 Enabling Board of Directors feature...") - - try: - # Create a default configuration file - create_default_config_file("swarms_board_config.yaml") - - # Enable the feature - enable_board_feature("swarms_board_config.yaml") - - # Configure some default settings - set_board_size(3) - set_decision_threshold(0.6) - set_board_model("gpt-4o-mini") - enable_verbose_logging("swarms_board_config.yaml") - - print("✅ Board of Directors feature enabled successfully!") - print("📁 Configuration file created: swarms_board_config.yaml") - - except Exception as e: - print(f"❌ Failed to enable Board of Directors feature: {e}") - raise -def create_custom_board_members() -> List[BoardMember]: - """ - Create custom board members with specific roles and expertise. - - This function demonstrates how to create a custom board with - specialized roles and expertise areas. +def create_board_members() -> List[BoardMember]: + """Create board members with specific roles.""" - Returns: - List[BoardMember]: List of custom board members - """ - print("👥 Creating custom board members...") - - # Create specialized board members chairman = Agent( - agent_name="Executive_Chairman", - agent_description="Executive Chairman with strategic vision and leadership expertise", + agent_name="Chairman", + agent_description="Executive Chairman with strategic vision", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are the Executive Chairman of the Board. Your role is to: -1. Provide strategic leadership and vision -2. Facilitate high-level decision-making -3. Ensure board effectiveness and governance -4. Represent the organization's interests -5. Guide long-term strategic planning - -You should be visionary, strategic, and focused on organizational success.""", + system_prompt="You are the Executive Chairman. Provide strategic leadership and facilitate decision-making.", ) cto = Agent( agent_name="CTO", - agent_description="Chief Technology Officer with deep technical expertise", + agent_description="Chief Technology Officer with technical expertise", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are the Chief Technology Officer. Your role is to: -1. Provide technical leadership and strategy -2. Evaluate technology solutions and architectures -3. Ensure technical feasibility of proposed solutions -4. Guide technology-related decisions -5. Maintain technical standards and best practices - -You should be technically proficient, innovative, and focused on technical excellence.""", + system_prompt="You are the CTO. Provide technical leadership and evaluate technology solutions.", ) cfo = Agent( agent_name="CFO", - agent_description="Chief Financial Officer with financial and risk management expertise", + agent_description="Chief Financial Officer with financial expertise", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are the Chief Financial Officer. Your role is to: -1. Provide financial analysis and insights -2. Evaluate financial implications of decisions -3. Ensure financial sustainability and risk management -4. Guide resource allocation and budgeting -5. Maintain financial controls and compliance - -You should be financially astute, risk-aware, and focused on financial health.""", + system_prompt="You are the CFO. Provide financial analysis and ensure fiscal responsibility.", ) - # Create BoardMember objects with roles and expertise - board_members = [ + return [ BoardMember( agent=chairman, role=BoardMemberRole.CHAIRMAN, voting_weight=2.0, - expertise_areas=["strategic_planning", "leadership", "governance", "business_strategy"] + expertise_areas=["leadership", "strategy"] ), BoardMember( agent=cto, role=BoardMemberRole.EXECUTIVE_DIRECTOR, voting_weight=1.5, - expertise_areas=["technology", "architecture", "innovation", "technical_strategy"] + expertise_areas=["technology", "innovation"] ), BoardMember( agent=cfo, role=BoardMemberRole.EXECUTIVE_DIRECTOR, voting_weight=1.5, - expertise_areas=["finance", "risk_management", "budgeting", "financial_analysis"] + expertise_areas=["finance", "risk_management"] ), ] - - print(f"✅ Created {len(board_members)} custom board members") - for member in board_members: - print(f" - {member.agent.agent_name} ({member.role.value})") - - return board_members def create_worker_agents() -> List[Agent]: - """ - Create worker agents for the swarm. - - This function creates specialized worker agents that will be - managed by the Board of Directors. + """Create worker agents for the swarm.""" - Returns: - List[Agent]: List of worker agents - """ - print("🛠️ Creating worker agents...") - - # Create specialized worker agents researcher = Agent( - agent_name="Research_Analyst", - agent_description="Research analyst specializing in market research and data analysis", + agent_name="Researcher", + agent_description="Research analyst for data analysis", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are a Research Analyst. Your responsibilities include: -1. Conducting thorough research on assigned topics -2. Analyzing data and market trends -3. Preparing comprehensive research reports -4. Providing data-driven insights and recommendations -5. Maintaining high standards of research quality - -You should be analytical, thorough, and evidence-based in your work.""", + system_prompt="You are a Research Analyst. Conduct thorough research and provide data-driven insights.", ) developer = Agent( - agent_name="Software_Developer", - agent_description="Software developer with expertise in system design and implementation", + agent_name="Developer", + agent_description="Software developer for implementation", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are a Software Developer. Your responsibilities include: -1. Designing and implementing software solutions -2. Writing clean, maintainable code -3. Conducting code reviews and testing -4. Collaborating with team members -5. Following best practices and coding standards - -You should be technically skilled, detail-oriented, and focused on quality.""", + system_prompt="You are a Software Developer. Design and implement software solutions.", ) marketer = Agent( - agent_name="Marketing_Specialist", - agent_description="Marketing specialist with expertise in digital marketing and brand strategy", + agent_name="Marketer", + agent_description="Marketing specialist for strategy", model_name="gpt-4o-mini", max_loops=1, - system_prompt="""You are a Marketing Specialist. Your responsibilities include: -1. Developing marketing strategies and campaigns -2. Creating compelling content and messaging -3. Analyzing market trends and customer behavior -4. Managing brand presence and reputation -5. Measuring and optimizing marketing performance - -You should be creative, strategic, and customer-focused in your approach.""", + system_prompt="You are a Marketing Specialist. Develop marketing strategies and campaigns.", ) - agents = [researcher, developer, marketer] - - print(f"✅ Created {len(agents)} worker agents") - for agent in agents: - print(f" - {agent.agent_name}: {agent.agent_description}") - - return agents + return [researcher, developer, marketer] -def run_board_of_directors_example() -> None: - """ - Run a comprehensive Board of Directors example. +def run_board_example() -> None: + """Run a Board of Directors example.""" - This function demonstrates the complete workflow of using - the Board of Directors swarm to orchestrate tasks. - """ - print("\n" + "="*60) - print("🏛️ BOARD OF DIRECTORS SWARM EXAMPLE") - print("="*60) + # Create board members and worker agents + board_members = create_board_members() + worker_agents = create_worker_agents() - try: - # Check if Board of Directors feature is enabled - if not is_board_feature_enabled(): - print("⚠️ Board of Directors feature is not enabled. Enabling now...") - enable_board_directors_feature() - - # Create custom board members - board_members = create_custom_board_members() - - # Create worker agents - worker_agents = create_worker_agents() - - # Create the Board of Directors swarm - print("\n🏛️ Creating Board of Directors swarm...") - board_swarm = BoardOfDirectorsSwarm( - name="Executive_Board_Swarm", - description="Executive board with specialized roles for strategic decision-making", - board_members=board_members, - agents=worker_agents, - max_loops=2, - verbose=True, - decision_threshold=0.6, - enable_voting=True, - enable_consensus=True, - ) - - print("✅ Board of Directors swarm created successfully!") - - # Display board summary - summary = board_swarm.get_board_summary() - print(f"\n📊 Board Summary:") - print(f" Board Name: {summary['board_name']}") - print(f" Total Members: {summary['total_members']}") - print(f" Total Agents: {summary['total_agents']}") - print(f" Max Loops: {summary['max_loops']}") - print(f" Decision Threshold: {summary['decision_threshold']}") - - print(f"\n👥 Board Members:") - for member in summary['members']: - print(f" - {member['name']} ({member['role']}) - Weight: {member['voting_weight']}") - print(f" Expertise: {', '.join(member['expertise_areas'])}") - - # Define a complex task for the board to handle - task = """ - Develop a comprehensive strategy for launching a new AI-powered product in the market. - - The task involves: - 1. Market research and competitive analysis - 2. Technical architecture and development planning - 3. Marketing strategy and go-to-market plan - 4. Financial projections and risk assessment - - Please coordinate the efforts of all team members to create a cohesive strategy. - """ - - print(f"\n📋 Executing task: {task.strip()[:100]}...") - - # Execute the task using the Board of Directors swarm - result = board_swarm.run(task=task) - - print("\n✅ Task completed successfully!") - print(f"📄 Result type: {type(result)}") - - # Display conversation history - if hasattr(result, 'get') and callable(result.get): - conversation_history = result.get('conversation_history', []) - print(f"\n💬 Conversation History ({len(conversation_history)} messages):") - for i, message in enumerate(conversation_history[-5:], 1): # Show last 5 messages - role = message.get('role', 'Unknown') - content = message.get('content', '')[:100] + "..." if len(message.get('content', '')) > 100 else message.get('content', '') - print(f" {i}. {role}: {content}") - else: - print(f"\n📝 Result: {str(result)[:200]}...") - - print("\n🎉 Board of Directors example completed successfully!") - - except Exception as e: - print(f"❌ Error in Board of Directors example: {e}") - import traceback - traceback.print_exc() - - -def run_simple_board_example() -> None: - """ - Run a simple Board of Directors example with default settings. + # Create the Board of Directors swarm + board_swarm = BoardOfDirectorsSwarm( + name="Executive_Board", + board_members=board_members, + agents=worker_agents, + max_loops=2, + verbose=True, + decision_threshold=0.6, + ) - This function demonstrates a basic usage of the Board of Directors - swarm with minimal configuration. + # Define task + task = """ + Develop a strategy for launching a new AI-powered product in the market. + Include market research, technical planning, marketing strategy, and financial projections. """ - print("\n" + "="*60) - print("🏛️ SIMPLE BOARD OF DIRECTORS EXAMPLE") - print("="*60) - try: - # Create simple worker agents - print("🛠️ Creating simple worker agents...") - - analyst = Agent( - agent_name="Data_Analyst", - agent_description="Data analyst for processing and analyzing information", - model_name="gpt-4o-mini", - max_loops=1, - ) - - writer = Agent( - agent_name="Content_Writer", - agent_description="Content writer for creating reports and documentation", - model_name="gpt-4o-mini", - max_loops=1, - ) - - agents = [analyst, writer] - - # Create Board of Directors swarm with default settings - print("🏛️ Creating Board of Directors swarm with default settings...") - board_swarm = BoardOfDirectorsSwarm( - name="Simple_Board_Swarm", - agents=agents, - verbose=True, - ) - - print("✅ Simple Board of Directors swarm created!") - - # Simple task - task = "Analyze the current market trends and create a summary report with recommendations." - - print(f"\n📋 Executing simple task: {task}") - - # Execute the task - result = board_swarm.run(task=task) - - print("\n✅ Simple task completed successfully!") - print(f"📄 Result type: {type(result)}") - - if hasattr(result, 'get') and callable(result.get): - conversation_history = result.get('conversation_history', []) - print(f"\n💬 Conversation History ({len(conversation_history)} messages):") - for i, message in enumerate(conversation_history[-3:], 1): # Show last 3 messages - role = message.get('role', 'Unknown') - content = message.get('content', '')[:80] + "..." if len(message.get('content', '')) > 80 else message.get('content', '') - print(f" {i}. {role}: {content}") - else: - print(f"\n📝 Result: {str(result)[:150]}...") - - print("\n🎉 Simple Board of Directors example completed!") - - except Exception as e: - print(f"❌ Error in simple Board of Directors example: {e}") - import traceback - traceback.print_exc() + # Execute the task + result = board_swarm.run(task=task) + + print("Task completed successfully!") + print(f"Result: {result}") -def check_environment() -> bool: - """ - Check if the environment is properly set up for the example. +def run_simple_example() -> None: + """Run a simple Board of Directors example.""" - Returns: - bool: True if environment is ready, False otherwise - """ - # Check for OpenAI API key - if not os.getenv("OPENAI_API_KEY"): - print("⚠️ Warning: OPENAI_API_KEY environment variable not set.") - print(" The example may not work without a valid API key.") - print(" Please set your OpenAI API key: export OPENAI_API_KEY='your-key-here'") - return False + # Create simple agents + analyst = Agent( + agent_name="Analyst", + agent_description="Data analyst", + model_name="gpt-4o-mini", + max_loops=1, + ) - return True + writer = Agent( + agent_name="Writer", + agent_description="Content writer", + model_name="gpt-4o-mini", + max_loops=1, + ) + + # Create swarm with default settings + board_swarm = BoardOfDirectorsSwarm( + name="Simple_Board", + agents=[analyst, writer], + verbose=True, + ) + + # Execute simple task + task = "Analyze current market trends and create a summary report." + result = board_swarm.run(task=task) + + print("Simple example completed!") + print(f"Result: {result}") def main() -> None: - """ - Main function to run the Board of Directors examples. - """ - print("🚀 Board of Directors Swarm Examples") - print("="*50) + """Main function to run the examples.""" - # Check environment - if not check_environment(): - print("\n⚠️ Environment check failed. Please set up your environment properly.") + if not os.getenv("OPENAI_API_KEY"): + print("Warning: OPENAI_API_KEY not set. Example may not work.") return try: - # Run simple example first - run_simple_board_example() - - # Run comprehensive example - run_board_of_directors_example() + print("Running simple Board of Directors example...") + run_simple_example() - print("\n" + "="*60) - print("🎉 All Board of Directors examples completed successfully!") - print("="*60) + print("\nRunning comprehensive Board of Directors example...") + run_board_example() - except KeyboardInterrupt: - print("\n⚠️ Examples interrupted by user.") except Exception as e: - print(f"\n❌ Unexpected error: {e}") - import traceback - traceback.print_exc() + print(f"Error: {e}") if __name__ == "__main__": - main() \ No newline at end of file + main() From eb413d350a5e4ffb54b39bca7bf2ce9f1f43e1b7 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Fri, 1 Aug 2025 17:21:53 -0700 Subject: [PATCH 41/73] [WORKSHOP][New Examples and demos] --- cron_job_example.py | 54 --- docs/swarms/structs/conversation.md | 122 ++---- .../cron_job_examples/cron_job_example.py | 247 +++++++++++ ...on_job_figma_stock_swarms_tools_example.py | 105 +++++ .../cron_job_examples/figma_stock_example.py | 79 ++++ .../cron_job_examples/solana_price_tracker.py | 257 ++++++++++++ .../graph_workflow_example.py | 30 +- .../heavy_swarm_example.py | 38 +- .../medical_heavy_swarm_example.py | 34 ++ ...y => sector_analysis_hiearchical_swarm.py} | 25 +- .../senator_assembly/senator_simulation.py | 318 +++++++------- swarms/structs/hiearchical_swarm.py | 387 +++++++++++++----- 12 files changed, 1245 insertions(+), 451 deletions(-) delete mode 100644 cron_job_example.py create mode 100644 examples/cron_job_examples/cron_job_example.py create mode 100644 examples/cron_job_examples/cron_job_figma_stock_swarms_tools_example.py create mode 100644 examples/cron_job_examples/figma_stock_example.py create mode 100644 examples/cron_job_examples/solana_price_tracker.py create mode 100644 examples/multi_agent/heavy_swarm_examples/medical_heavy_swarm_example.py rename examples/multi_agent/hiearchical_swarm/{hiearchical_swarm.py => sector_analysis_hiearchical_swarm.py} (62%) diff --git a/cron_job_example.py b/cron_job_example.py deleted file mode 100644 index 855a9f31..00000000 --- a/cron_job_example.py +++ /dev/null @@ -1,54 +0,0 @@ -from swarms import Agent, CronJob -from loguru import logger - - -# Example usage -if __name__ == "__main__": - # Initialize the agent - agent = Agent( - agent_name="Quantitative-Trading-Agent", - agent_description="Advanced quantitative trading and algorithmic analysis agent", - system_prompt="""You are an expert quantitative trading agent with deep expertise in: - - Algorithmic trading strategies and implementation - - Statistical arbitrage and market making - - Risk management and portfolio optimization - - High-frequency trading systems - - Market microstructure analysis - - Quantitative research methodologies - - Financial mathematics and stochastic processes - - Machine learning applications in trading - - Your core responsibilities include: - 1. Developing and backtesting trading strategies - 2. Analyzing market data and identifying alpha opportunities - 3. Implementing risk management frameworks - 4. Optimizing portfolio allocations - 5. Conducting quantitative research - 6. Monitoring market microstructure - 7. Evaluating trading system performance - - You maintain strict adherence to: - - Mathematical rigor in all analyses - - Statistical significance in strategy development - - Risk-adjusted return optimization - - Market impact minimization - - Regulatory compliance - - Transaction cost analysis - - Performance attribution - - You communicate in precise, technical terms while maintaining clarity for stakeholders.""", - max_loops=1, - model_name="gpt-4.1", - dynamic_temperature_enabled=True, - output_type="str-all-except-first", - streaming_on=True, - print_on=True, - telemetry_enable=False, - ) - - # Example 1: Basic usage with just a task - logger.info("Starting example cron job") - cron_job = CronJob(agent=agent, interval="10seconds") - cron_job.run( - task="What are the best top 3 etfs for gold coverage?" - ) diff --git a/docs/swarms/structs/conversation.md b/docs/swarms/structs/conversation.md index 0e3b9bf0..e1fd7c7a 100644 --- a/docs/swarms/structs/conversation.md +++ b/docs/swarms/structs/conversation.md @@ -6,96 +6,33 @@ The `Conversation` class is a powerful and flexible tool for managing conversati ### Key Features -- **Multiple Storage Backends**: Support for various storage solutions: - - In-memory: Fast, temporary storage for testing and development - - Supabase: PostgreSQL-based cloud storage with real-time capabilities - - Redis: High-performance caching and persistence - - SQLite: Local file-based storage - - DuckDB: Analytical workloads and columnar storage - - Pulsar: Event streaming for distributed systems - - Mem0: Memory-based storage with mem0 integration - -- **Token Management**: - - Built-in token counting with configurable models - - Automatic token tracking for input/output messages - - Token usage analytics and reporting - - Context length management - -- **Metadata and Categories**: - - Support for message metadata - - Message categorization (input/output) - - Role-based message tracking - - Custom message IDs - -- **Data Export/Import**: - - JSON and YAML export formats - - Automatic saving and loading - - Conversation history management - - Batch operations support - -- **Advanced Features**: - - Message search and filtering - - Conversation analytics - - Multi-agent support - - Error handling and fallbacks - - Type hints and validation +| Feature Category | Features / Description | +|----------------------------|-------------------------------------------------------------------------------------------------------------| +| **Multiple Storage Backends** | - In-memory: Fast, temporary storage for testing and development
- Supabase: PostgreSQL-based cloud storage with real-time capabilities
- Redis: High-performance caching and persistence
- SQLite: Local file-based storage
- DuckDB: Analytical workloads and columnar storage
- Pulsar: Event streaming for distributed systems
- Mem0: Memory-based storage with mem0 integration | +| **Token Management** | - Built-in token counting with configurable models
- Automatic token tracking for input/output messages
- Token usage analytics and reporting
- Context length management | +| **Metadata and Categories** | - Support for message metadata
- Message categorization (input/output)
- Role-based message tracking
- Custom message IDs | +| **Data Export/Import** | - JSON and YAML export formats
- Automatic saving and loading
- Conversation history management
- Batch operations support | +| **Advanced Features** | - Message search and filtering
- Conversation analytics
- Multi-agent support
- Error handling and fallbacks
- Type hints and validation | ### Use Cases -1. **Chatbot Development**: - - Store and manage conversation history - - Track token usage and context length - - Analyze conversation patterns - -2. **Multi-Agent Systems**: - - Coordinate multiple AI agents - - Track agent interactions - - Store agent outputs and metadata - -3. **Analytics Applications**: - - Track conversation metrics - - Generate usage reports - - Analyze user interactions - -4. **Production Systems**: - - Persistent storage with various backends - - Error handling and recovery - - Scalable conversation management - -5. **Development and Testing**: - - Fast in-memory storage - - Debugging support - - Easy export/import of test data +| Use Case | Features / Description | +|----------------------------|--------------------------------------------------------------------------------------------------------| +| **Chatbot Development** | - Store and manage conversation history
- Track token usage and context length
- Analyze conversation patterns | +| **Multi-Agent Systems** | - Coordinate multiple AI agents
- Track agent interactions
- Store agent outputs and metadata | +| **Analytics Applications** | - Track conversation metrics
- Generate usage reports
- Analyze user interactions | +| **Production Systems** | - Persistent storage with various backends
- Error handling and recovery
- Scalable conversation management | +| **Development and Testing**| - Fast in-memory storage
- Debugging support
- Easy export/import of test data | ### Best Practices -1. **Storage Selection**: - - Use in-memory for testing and development - - Choose Supabase for multi-user cloud applications - - Use Redis for high-performance requirements - - Select SQLite for single-user local applications - - Pick DuckDB for analytical workloads - - Opt for Pulsar in distributed systems - -2. **Token Management**: - - Enable token counting for production use - - Set appropriate context lengths - - Monitor token usage with export_and_count_categories() - -3. **Error Handling**: - - Implement proper fallback mechanisms - - Use type hints for better code reliability - - Monitor and log errors appropriately - -4. **Data Management**: - - Use appropriate export formats (JSON/YAML) - - Implement regular backup strategies - - Clean up old conversations when needed - -5. **Security**: - - Use environment variables for sensitive credentials - - Implement proper access controls - - Validate input data +| Category | Best Practices | +|---------------------|------------------------------------------------------------------------------------------------------------------------| +| **Storage Selection** | - Use in-memory for testing and development
- Choose Supabase for multi-user cloud applications
- Use Redis for high-performance requirements
- Select SQLite for single-user local applications
- Pick DuckDB for analytical workloads
- Opt for Pulsar in distributed systems | +| **Token Management** | - Enable token counting for production use
- Set appropriate context lengths
- Monitor token usage with `export_and_count_categories()` | +| **Error Handling** | - Implement proper fallback mechanisms
- Use type hints for better code reliability
- Monitor and log errors appropriately | +| **Data Management** | - Use appropriate export formats (JSON/YAML)
- Implement regular backup strategies
- Clean up old conversations when needed | +| **Security** | - Use environment variables for sensitive credentials
- Implement proper access controls
- Validate input data | ## Table of Contents @@ -113,13 +50,15 @@ The `Conversation` class is designed to manage conversations by keeping track of **New in this version**: The class now supports multiple storage backends for persistent conversation storage: -- **"in-memory"**: Default memory-based storage (no persistence) -- **"mem0"**: Memory-based storage with mem0 integration (requires: `pip install mem0ai`) -- **"supabase"**: PostgreSQL-based storage using Supabase (requires: `pip install supabase`) -- **"redis"**: Redis-based storage (requires: `pip install redis`) -- **"sqlite"**: SQLite-based storage (built-in to Python) -- **"duckdb"**: DuckDB-based storage (requires: `pip install duckdb`) -- **"pulsar"**: Apache Pulsar messaging backend (requires: `pip install pulsar-client`) +| Backend | Description | Requirements | +|--------------|-------------------------------------------------------------------------------------------------------------|------------------------------------| +| **in-memory**| Default memory-based storage (no persistence) | None (built-in) | +| **mem0** | Memory-based storage with mem0 integration | `pip install mem0ai` | +| **supabase** | PostgreSQL-based storage using Supabase | `pip install supabase` | +| **redis** | Redis-based storage | `pip install redis` | +| **sqlite** | SQLite-based storage (local file) | None (built-in) | +| **duckdb** | DuckDB-based storage (analytical workloads, columnar storage) | `pip install duckdb` | +| **pulsar** | Apache Pulsar messaging backend | `pip install pulsar-client` | All backends use **lazy loading** - database dependencies are only imported when the specific backend is instantiated. Each backend provides helpful error messages if required packages are not installed. @@ -132,7 +71,6 @@ All backends use **lazy loading** - database dependencies are only imported when | system_prompt | Optional[str] | System prompt for the conversation | | time_enabled | bool | Flag to enable time tracking for messages | | autosave | bool | Flag to enable automatic saving | -| save_enabled | bool | Flag to control if saving is enabled | | save_filepath | str | File path for saving conversation history | | load_filepath | str | File path for loading conversation history | | conversation_history | list | List storing conversation messages | diff --git a/examples/cron_job_examples/cron_job_example.py b/examples/cron_job_examples/cron_job_example.py new file mode 100644 index 00000000..b7c0d501 --- /dev/null +++ b/examples/cron_job_examples/cron_job_example.py @@ -0,0 +1,247 @@ +from loguru import logger +import yfinance as yf +import json + + +def get_figma_stock_data(stock: str) -> str: + """ + Fetches comprehensive stock data for Figma (FIG) using Yahoo Finance. + + Returns: + Dict[str, Any]: A dictionary containing comprehensive Figma stock data including: + - Current price and market data + - Company information + - Financial metrics + - Historical data summary + - Trading statistics + + Raises: + Exception: If there's an error fetching the data from Yahoo Finance + """ + try: + # Initialize Figma stock ticker + figma = yf.Ticker(stock) + + # Get current stock info + info = figma.info + + # Get recent historical data (last 30 days) + hist = figma.history(period="30d") + + # Get real-time fast info + fast_info = figma.fast_info + + # Compile comprehensive data + figma_data = { + "company_info": { + "name": info.get("longName", "Figma Inc."), + "symbol": "FIG", + "sector": info.get("sector", "N/A"), + "industry": info.get("industry", "N/A"), + "website": info.get("website", "N/A"), + "description": info.get("longBusinessSummary", "N/A"), + }, + "current_market_data": { + "current_price": info.get("currentPrice", "N/A"), + "previous_close": info.get("previousClose", "N/A"), + "open": info.get("open", "N/A"), + "day_low": info.get("dayLow", "N/A"), + "day_high": info.get("dayHigh", "N/A"), + "volume": info.get("volume", "N/A"), + "market_cap": info.get("marketCap", "N/A"), + "price_change": ( + info.get("currentPrice", 0) + - info.get("previousClose", 0) + if info.get("currentPrice") + and info.get("previousClose") + else "N/A" + ), + "price_change_percent": info.get( + "regularMarketChangePercent", "N/A" + ), + }, + "financial_metrics": { + "pe_ratio": info.get("trailingPE", "N/A"), + "forward_pe": info.get("forwardPE", "N/A"), + "price_to_book": info.get("priceToBook", "N/A"), + "price_to_sales": info.get( + "priceToSalesTrailing12Months", "N/A" + ), + "enterprise_value": info.get( + "enterpriseValue", "N/A" + ), + "beta": info.get("beta", "N/A"), + "dividend_yield": info.get("dividendYield", "N/A"), + "payout_ratio": info.get("payoutRatio", "N/A"), + }, + "trading_statistics": { + "fifty_day_average": info.get( + "fiftyDayAverage", "N/A" + ), + "two_hundred_day_average": info.get( + "twoHundredDayAverage", "N/A" + ), + "fifty_two_week_low": info.get( + "fiftyTwoWeekLow", "N/A" + ), + "fifty_two_week_high": info.get( + "fiftyTwoWeekHigh", "N/A" + ), + "shares_outstanding": info.get( + "sharesOutstanding", "N/A" + ), + "float_shares": info.get("floatShares", "N/A"), + "shares_short": info.get("sharesShort", "N/A"), + "short_ratio": info.get("shortRatio", "N/A"), + }, + "recent_performance": { + "last_30_days": { + "start_price": ( + hist.iloc[0]["Close"] + if not hist.empty + else "N/A" + ), + "end_price": ( + hist.iloc[-1]["Close"] + if not hist.empty + else "N/A" + ), + "total_return": ( + ( + hist.iloc[-1]["Close"] + - hist.iloc[0]["Close"] + ) + / hist.iloc[0]["Close"] + * 100 + if not hist.empty + else "N/A" + ), + "highest_price": ( + hist["High"].max() + if not hist.empty + else "N/A" + ), + "lowest_price": ( + hist["Low"].min() if not hist.empty else "N/A" + ), + "average_volume": ( + hist["Volume"].mean() + if not hist.empty + else "N/A" + ), + } + }, + "real_time_data": { + "last_price": ( + fast_info.last_price + if hasattr(fast_info, "last_price") + else "N/A" + ), + "last_volume": ( + fast_info.last_volume + if hasattr(fast_info, "last_volume") + else "N/A" + ), + "bid": ( + fast_info.bid + if hasattr(fast_info, "bid") + else "N/A" + ), + "ask": ( + fast_info.ask + if hasattr(fast_info, "ask") + else "N/A" + ), + "bid_size": ( + fast_info.bid_size + if hasattr(fast_info, "bid_size") + else "N/A" + ), + "ask_size": ( + fast_info.ask_size + if hasattr(fast_info, "ask_size") + else "N/A" + ), + }, + } + + logger.info("Successfully fetched Figma (FIG) stock data") + return json.dumps(figma_data, indent=4) + + except Exception as e: + logger.error(f"Error fetching Figma stock data: {e}") + raise Exception(f"Failed to fetch Figma stock data: {e}") + + +# # Example usage +# # Initialize the quantitative trading agent +# agent = Agent( +# agent_name="Quantitative-Trading-Agent", +# agent_description="Advanced quantitative trading and algorithmic analysis agent specializing in stock analysis and trading strategies", +# system_prompt=f"""You are an expert quantitative trading agent with deep expertise in: +# - Algorithmic trading strategies and implementation +# - Statistical arbitrage and market making +# - Risk management and portfolio optimization +# - High-frequency trading systems +# - Market microstructure analysis +# - Quantitative research methodologies +# - Financial mathematics and stochastic processes +# - Machine learning applications in trading +# - Technical analysis and chart patterns +# - Fundamental analysis and valuation models +# - Options trading and derivatives +# - Market sentiment analysis + +# Your core responsibilities include: +# 1. Developing and backtesting trading strategies +# 2. Analyzing market data and identifying alpha opportunities +# 3. Implementing risk management frameworks +# 4. Optimizing portfolio allocations +# 5. Conducting quantitative research +# 6. Monitoring market microstructure +# 7. Evaluating trading system performance +# 8. Performing comprehensive stock analysis +# 9. Generating trading signals and recommendations +# 10. Risk assessment and position sizing + +# When analyzing stocks, you should: +# - Evaluate technical indicators and chart patterns +# - Assess fundamental metrics and valuation ratios +# - Analyze market sentiment and momentum +# - Consider macroeconomic factors +# - Provide risk-adjusted return projections +# - Suggest optimal entry/exit points +# - Calculate position sizing recommendations +# - Identify potential catalysts and risks + +# You maintain strict adherence to: +# - Mathematical rigor in all analyses +# - Statistical significance in strategy development +# - Risk-adjusted return optimization +# - Market impact minimization +# - Regulatory compliance +# - Transaction cost analysis +# - Performance attribution +# - Data-driven decision making + +# You communicate in precise, technical terms while maintaining clarity for stakeholders. +# Data: {get_figma_stock_data('FIG')} + +# """, +# max_loops=1, +# model_name="gpt-4o-mini", +# dynamic_temperature_enabled=True, +# output_type="str-all-except-first", +# streaming_on=True, +# print_on=True, +# telemetry_enable=False, +# ) + +# # Example 1: Basic usage with just a task +# logger.info("Starting quantitative analysis cron job for Figma (FIG)") +# cron_job = CronJob(agent=agent, interval="10seconds") +# cron_job.run( +# task="Analyze the Figma (FIG) stock comprehensively using the available stock data. Provide a detailed quantitative analysis" +# ) + +print(get_figma_stock_data("FIG")) diff --git a/examples/cron_job_examples/cron_job_figma_stock_swarms_tools_example.py b/examples/cron_job_examples/cron_job_figma_stock_swarms_tools_example.py new file mode 100644 index 00000000..da914c63 --- /dev/null +++ b/examples/cron_job_examples/cron_job_figma_stock_swarms_tools_example.py @@ -0,0 +1,105 @@ +""" +Example script demonstrating how to fetch Figma (FIG) stock data using swarms_tools Yahoo Finance API. +This shows the alternative approach using the existing swarms_tools package. +""" + +from swarms import Agent +from swarms.prompts.finance_agent_sys_prompt import ( + FINANCIAL_AGENT_SYS_PROMPT, +) +from swarms_tools import yahoo_finance_api +from loguru import logger +import json + + +def get_figma_data_with_swarms_tools(): + """ + Fetches Figma stock data using the swarms_tools Yahoo Finance API. + + Returns: + dict: Figma stock data from swarms_tools + """ + try: + logger.info("Fetching Figma stock data using swarms_tools...") + figma_data = yahoo_finance_api(["FIG"]) + return figma_data + except Exception as e: + logger.error(f"Error fetching data with swarms_tools: {e}") + raise + + +def analyze_figma_with_agent(): + """ + Uses a Swarms agent to analyze Figma stock data. + """ + try: + # Initialize the agent with Yahoo Finance tool + agent = Agent( + agent_name="Figma-Analysis-Agent", + agent_description="Specialized agent for analyzing Figma stock data", + system_prompt=FINANCIAL_AGENT_SYS_PROMPT, + max_loops=1, + model_name="gpt-4o-mini", + tools=[yahoo_finance_api], + dynamic_temperature_enabled=True, + ) + + # Ask the agent to analyze Figma + analysis = agent.run( + "Analyze the current stock data for Figma (FIG) and provide insights on its performance, valuation metrics, and recent trends." + ) + + return analysis + + except Exception as e: + logger.error(f"Error in agent analysis: {e}") + raise + + +def main(): + """ + Main function to demonstrate different approaches for Figma stock data. + """ + logger.info("Starting Figma stock analysis with swarms_tools") + + try: + # Method 1: Direct API call + print("\n" + "=" * 60) + print("METHOD 1: Direct swarms_tools API call") + print("=" * 60) + + figma_data = get_figma_data_with_swarms_tools() + print("Raw data from swarms_tools:") + print(json.dumps(figma_data, indent=2, default=str)) + + # Method 2: Agent-based analysis + print("\n" + "=" * 60) + print("METHOD 2: Agent-based analysis") + print("=" * 60) + + analysis = analyze_figma_with_agent() + print("Agent analysis:") + print(analysis) + + # Method 3: Comparison with custom function + print("\n" + "=" * 60) + print("METHOD 3: Comparison with custom function") + print("=" * 60) + + from cron_job_examples.cron_job_example import ( + get_figma_stock_data_simple, + ) + + custom_data = get_figma_stock_data_simple() + print("Custom function output:") + print(custom_data) + + logger.info("All methods completed successfully!") + + except Exception as e: + logger.error(f"Error in main function: {e}") + print(f"Error: {e}") + + +if __name__ == "__main__": + main() diff --git a/examples/cron_job_examples/figma_stock_example.py b/examples/cron_job_examples/figma_stock_example.py new file mode 100644 index 00000000..f9760462 --- /dev/null +++ b/examples/cron_job_examples/figma_stock_example.py @@ -0,0 +1,79 @@ +""" +Example script demonstrating how to fetch Figma (FIG) stock data using Yahoo Finance. +""" + +from cron_job_examples.cron_job_example import ( + get_figma_stock_data, + get_figma_stock_data_simple, +) +from loguru import logger +import json + + +def main(): + """ + Main function to demonstrate Figma stock data fetching. + """ + logger.info("Starting Figma stock data demonstration") + + try: + # Example 1: Get comprehensive data as dictionary + logger.info("Fetching comprehensive Figma stock data...") + figma_data = get_figma_stock_data() + + # Print the data in a structured format + print("\n" + "=" * 50) + print("COMPREHENSIVE FIGMA STOCK DATA") + print("=" * 50) + print(json.dumps(figma_data, indent=2, default=str)) + + # Example 2: Get simple formatted data + logger.info("Fetching simple formatted Figma stock data...") + simple_data = get_figma_stock_data_simple() + + print("\n" + "=" * 50) + print("SIMPLE FORMATTED FIGMA STOCK DATA") + print("=" * 50) + print(simple_data) + + # Example 3: Access specific data points + logger.info("Accessing specific data points...") + + current_price = figma_data["current_market_data"][ + "current_price" + ] + market_cap = figma_data["current_market_data"]["market_cap"] + pe_ratio = figma_data["financial_metrics"]["pe_ratio"] + + print("\nKey Metrics:") + print(f"Current Price: ${current_price}") + print(f"Market Cap: ${market_cap:,}") + print(f"P/E Ratio: {pe_ratio}") + + # Example 4: Check if stock is performing well + price_change = figma_data["current_market_data"][ + "price_change" + ] + if isinstance(price_change, (int, float)): + if price_change > 0: + print( + f"\n📈 Figma stock is up ${price_change:.2f} today!" + ) + elif price_change < 0: + print( + f"\n📉 Figma stock is down ${abs(price_change):.2f} today." + ) + else: + print("\n➡️ Figma stock is unchanged today.") + + logger.info( + "Figma stock data demonstration completed successfully!" + ) + + except Exception as e: + logger.error(f"Error in main function: {e}") + print(f"Error: {e}") + + +if __name__ == "__main__": + main() diff --git a/examples/cron_job_examples/solana_price_tracker.py b/examples/cron_job_examples/solana_price_tracker.py new file mode 100644 index 00000000..0ae048ab --- /dev/null +++ b/examples/cron_job_examples/solana_price_tracker.py @@ -0,0 +1,257 @@ +from swarms import Agent, CronJob +from loguru import logger +import requests +import json +from datetime import datetime + + +def get_solana_price() -> str: + """ + Fetches comprehensive Solana (SOL) price data using CoinGecko API. + + Returns: + str: A JSON formatted string containing Solana's current price and market data including: + - Current price in USD + - Market cap + - 24h volume + - 24h price change + - Last updated timestamp + + Raises: + Exception: If there's an error fetching the data from CoinGecko API + """ + try: + # CoinGecko API endpoint for simple price data + url = "https://api.coingecko.com/api/v3/simple/price" + params = { + "ids": "solana", # Solana's CoinGecko ID + "vs_currencies": "usd", + "include_market_cap": True, + "include_24hr_vol": True, + "include_24hr_change": True, + "include_last_updated_at": True, + } + + # Make API request with timeout + response = requests.get(url, params=params, timeout=10) + response.raise_for_status() + + # Parse response data + data = response.json() + + if "solana" not in data: + raise Exception("Solana data not found in API response") + + solana_data = data["solana"] + + # Compile comprehensive data + solana_info = { + "timestamp": datetime.now().isoformat(), + "coin_info": { + "name": "Solana", + "symbol": "SOL", + "coin_id": "solana", + }, + "price_data": { + "current_price_usd": solana_data.get("usd", "N/A"), + "market_cap_usd": solana_data.get( + "usd_market_cap", "N/A" + ), + "volume_24h_usd": solana_data.get( + "usd_24h_vol", "N/A" + ), + "price_change_24h_percent": solana_data.get( + "usd_24h_change", "N/A" + ), + "last_updated_at": solana_data.get( + "last_updated_at", "N/A" + ), + }, + "formatted_data": { + "price_formatted": ( + f"${solana_data.get('usd', 'N/A'):,.2f}" + if solana_data.get("usd") + else "N/A" + ), + "market_cap_formatted": ( + f"${solana_data.get('usd_market_cap', 'N/A'):,.0f}" + if solana_data.get("usd_market_cap") + else "N/A" + ), + "volume_formatted": ( + f"${solana_data.get('usd_24h_vol', 'N/A'):,.0f}" + if solana_data.get("usd_24h_vol") + else "N/A" + ), + "change_formatted": ( + f"{solana_data.get('usd_24h_change', 'N/A'):+.2f}%" + if solana_data.get("usd_24h_change") is not None + else "N/A" + ), + }, + } + + logger.info( + f"Successfully fetched Solana price: ${solana_data.get('usd', 'N/A')}" + ) + return json.dumps(solana_info, indent=4) + + except requests.RequestException as e: + error_msg = f"API request failed: {e}" + logger.error(error_msg) + return json.dumps( + { + "error": error_msg, + "timestamp": datetime.now().isoformat(), + "status": "failed", + }, + indent=4, + ) + except Exception as e: + error_msg = f"Error fetching Solana price data: {e}" + logger.error(error_msg) + return json.dumps( + { + "error": error_msg, + "timestamp": datetime.now().isoformat(), + "status": "failed", + }, + indent=4, + ) + + +def analyze_solana_data(data: str) -> str: + """ + Analyzes Solana price data and provides insights. + + Args: + data (str): JSON string containing Solana price data + + Returns: + str: Analysis and insights about the current Solana market data + """ + try: + # Parse the data + solana_data = json.loads(data) + + if "error" in solana_data: + return f"❌ Error in data: {solana_data['error']}" + + price_data = solana_data.get("price_data", {}) + formatted_data = solana_data.get("formatted_data", {}) + + # Extract key metrics + current_price = price_data.get("current_price_usd") + price_change = price_data.get("price_change_24h_percent") + volume_24h = price_data.get("volume_24h_usd") + market_cap = price_data.get("market_cap_usd") + + # Generate analysis + analysis = f""" +🔍 **Solana (SOL) Market Analysis** - {solana_data.get('timestamp', 'N/A')} + +💰 **Current Price**: {formatted_data.get('price_formatted', 'N/A')} +📊 **24h Change**: {formatted_data.get('change_formatted', 'N/A')} +💎 **Market Cap**: {formatted_data.get('market_cap_formatted', 'N/A')} +📈 **24h Volume**: {formatted_data.get('volume_formatted', 'N/A')} + +""" + + # Add sentiment analysis based on price change + if price_change is not None: + if price_change > 5: + analysis += "🚀 **Sentiment**: Strongly Bullish - Significant positive momentum\n" + elif price_change > 1: + analysis += "📈 **Sentiment**: Bullish - Positive price action\n" + elif price_change > -1: + analysis += ( + "➡️ **Sentiment**: Neutral - Sideways movement\n" + ) + elif price_change > -5: + analysis += "📉 **Sentiment**: Bearish - Negative price action\n" + else: + analysis += "🔻 **Sentiment**: Strongly Bearish - Significant decline\n" + + # Add volume analysis + if volume_24h and market_cap: + try: + volume_market_cap_ratio = ( + volume_24h / market_cap + ) * 100 + if volume_market_cap_ratio > 10: + analysis += "🔥 **Volume**: High trading activity - Strong market interest\n" + elif volume_market_cap_ratio > 5: + analysis += ( + "📊 **Volume**: Moderate trading activity\n" + ) + else: + analysis += "😴 **Volume**: Low trading activity - Limited market movement\n" + except (TypeError, ZeroDivisionError): + analysis += "📊 **Volume**: Unable to calculate volume/market cap ratio\n" + + analysis += f"\n⏰ **Last Updated**: {price_data.get('last_updated_at', 'N/A')}" + + return analysis + + except json.JSONDecodeError as e: + return f"❌ Error parsing data: {e}" + except Exception as e: + return f"❌ Error analyzing data: {e}" + + +# Initialize the Solana analysis agent +agent = Agent( + agent_name="Solana-Price-Analyzer", + agent_description="Specialized agent for analyzing Solana (SOL) cryptocurrency price data and market trends", + system_prompt=f"""You are an expert cryptocurrency analyst specializing in Solana (SOL) analysis. Your expertise includes: + +- Technical analysis and chart patterns +- Market sentiment analysis +- Volume and liquidity analysis +- Price action interpretation +- Market cap and valuation metrics +- Cryptocurrency market dynamics +- DeFi ecosystem analysis +- Blockchain technology trends + +When analyzing Solana data, you should: +- Evaluate price movements and trends +- Assess market sentiment and momentum +- Consider volume and liquidity factors +- Analyze market cap positioning +- Provide actionable insights +- Identify potential catalysts or risks +- Consider broader market context + +You communicate clearly and provide practical analysis that helps users understand Solana's current market position and potential future movements. + +Current Solana Data: {get_solana_price()} +""", + max_loops=1, + model_name="gpt-4o-mini", + dynamic_temperature_enabled=True, + output_type="str-all-except-first", + streaming_on=False, # need to fix this bug where streaming is working but makes copies of the border when you scroll on the terminal + print_on=True, + telemetry_enable=False, +) + + +def main(): + """ + Main function to run the Solana price tracking cron job. + """ + logger.info("🚀 Starting Solana price tracking cron job") + logger.info("📊 Fetching Solana price every 10 seconds...") + + # Create cron job that runs every 10 seconds + cron_job = CronJob(agent=agent, interval="30seconds") + + # Run the cron job with analysis task + cron_job.run( + task="Analyze the current Solana (SOL) price data comprehensively. Provide detailed market analysis including price trends, volume analysis, market sentiment, and actionable insights. Format your response clearly with emojis and structured sections." + ) + + +if __name__ == "__main__": + main() diff --git a/examples/multi_agent/graphworkflow_examples/graph_workflow_example.py b/examples/multi_agent/graphworkflow_examples/graph_workflow_example.py index 75aa8b4d..22e4b0a9 100644 --- a/examples/multi_agent/graphworkflow_examples/graph_workflow_example.py +++ b/examples/multi_agent/graphworkflow_examples/graph_workflow_example.py @@ -1,5 +1,4 @@ -from swarms import Agent -from swarms.structs.graph_workflow import GraphWorkflow +from swarms import Agent, GraphWorkflow from swarms.prompts.multi_agent_collab_prompt import ( MULTI_AGENT_COLLAB_PROMPT_TWO, ) @@ -11,6 +10,7 @@ agent1 = Agent( max_loops=1, system_prompt=MULTI_AGENT_COLLAB_PROMPT_TWO, # Set collaboration prompt ) + agent2 = Agent( agent_name="ResearchAgent2", model_name="gpt-4.1", @@ -19,7 +19,11 @@ agent2 = Agent( ) # Build the workflow with only agents as nodes -workflow = GraphWorkflow() +workflow = GraphWorkflow( + name="Research Workflow", + description="A workflow for researching the best arbitrage trading strategies for altcoins", + auto_compile=True, +) workflow.add_node(agent1) workflow.add_node(agent2) @@ -27,27 +31,15 @@ workflow.add_node(agent2) workflow.add_edge(agent1.agent_name, agent2.agent_name) # Visualize the workflow using Graphviz -print("\n📊 Creating workflow visualization...") -try: - viz_output = workflow.visualize( - output_path="simple_workflow_graph", - format="png", - view=True, # Auto-open the generated image - show_parallel_patterns=True, - ) - print(f"✅ Workflow visualization saved to: {viz_output}") -except Exception as e: - print(f"⚠️ Graphviz not available, using text visualization: {e}") - workflow.visualize() +workflow.visualize() + +workflow.compile() # Export workflow to JSON workflow_json = workflow.to_json() -print( - f"\n💾 Workflow exported to JSON ({len(workflow_json)} characters)" -) +print(workflow_json) # Run the workflow and print results -print("\n🚀 Executing workflow...") results = workflow.run( task="What are the best arbitrage trading strategies for altcoins? Give me research papers and articles on the topic." ) diff --git a/examples/multi_agent/heavy_swarm_examples/heavy_swarm_example.py b/examples/multi_agent/heavy_swarm_examples/heavy_swarm_example.py index 57715ff7..588e3f3e 100644 --- a/examples/multi_agent/heavy_swarm_examples/heavy_swarm_example.py +++ b/examples/multi_agent/heavy_swarm_examples/heavy_swarm_example.py @@ -1,16 +1,32 @@ -from swarms.structs.heavy_swarm import HeavySwarm +from swarms import HeavySwarm -swarm = HeavySwarm( - worker_model_name="claude-3-5-sonnet-20240620", - show_dashboard=True, - question_agent_model_name="gpt-4.1", - loops_per_agent=1, -) +def main(): + """ + Run a HeavySwarm query to find the best 3 gold ETFs. + This function initializes a HeavySwarm instance and queries it to provide + the top 3 gold exchange-traded funds (ETFs), requesting clear, structured results. + """ + swarm = HeavySwarm( + name="Gold ETF Research Team", + description="A team of agents that research the best gold ETFs", + worker_model_name="claude-sonnet-4-latest", + show_dashboard=True, + question_agent_model_name="gpt-4.1", + loops_per_agent=1, + ) -out = swarm.run( - "Provide 3 publicly traded biotech companies that are currently trading below their cash value. For each company identified, provide available data or projections for the next 6 months, including any relevant financial metrics, upcoming catalysts, or events that could impact valuation. Present your findings in a clear, structured format. Be very specific and provide their ticker symbol, name, and the current price, cash value, and the percentage difference between the two." -) + prompt = ( + "Find the best 3 gold ETFs. For each ETF, provide the ticker symbol, " + "full name, current price, expense ratio, assets under management, and " + "a brief explanation of why it is considered among the best. Present the information " + "in a clear, structured format suitable for investors." + ) -print(out) + out = swarm.run(prompt) + print(out) + + +if __name__ == "__main__": + main() diff --git a/examples/multi_agent/heavy_swarm_examples/medical_heavy_swarm_example.py b/examples/multi_agent/heavy_swarm_examples/medical_heavy_swarm_example.py new file mode 100644 index 00000000..ad460310 --- /dev/null +++ b/examples/multi_agent/heavy_swarm_examples/medical_heavy_swarm_example.py @@ -0,0 +1,34 @@ +from swarms import HeavySwarm + + +def main(): + """ + Run a HeavySwarm query to find the best and most promising treatments for diabetes. + + This function initializes a HeavySwarm instance and queries it to provide + the top current and theoretical treatments for diabetes, requesting clear, + structured, and evidence-based results suitable for medical research or clinical review. + """ + swarm = HeavySwarm( + name="Diabetes Treatment Research Team", + description="A team of agents that research the best and most promising treatments for diabetes, including theoretical approaches.", + worker_model_name="claude-sonnet-4-20250514", + show_dashboard=True, + question_agent_model_name="gpt-4.1", + loops_per_agent=1, + ) + + prompt = ( + "Identify the best and most promising treatments for diabetes, including both current standard therapies and theoretical or experimental approaches. " + "For each treatment, provide: the treatment name, type (e.g., medication, lifestyle intervention, device, gene therapy, etc.), " + "mechanism of action, current stage of research or approval status, key clinical evidence or rationale, " + "potential benefits and risks, and a brief summary of why it is considered promising. " + "Present the information in a clear, structured format suitable for medical professionals or researchers." + ) + + out = swarm.run(prompt) + print(out) + + +if __name__ == "__main__": + main() diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm.py b/examples/multi_agent/hiearchical_swarm/sector_analysis_hiearchical_swarm.py similarity index 62% rename from examples/multi_agent/hiearchical_swarm/hiearchical_swarm.py rename to examples/multi_agent/hiearchical_swarm/sector_analysis_hiearchical_swarm.py index ee4d1d60..7312d90b 100644 --- a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm.py +++ b/examples/multi_agent/hiearchical_swarm/sector_analysis_hiearchical_swarm.py @@ -1,5 +1,4 @@ -from swarms import Agent -from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms import Agent, HierarchicalSwarm # Initialize agents for a $50B portfolio analysis @@ -9,24 +8,27 @@ agents = [ agent_description="Senior financial analyst at BlackRock.", system_prompt="You are a financial analyst tasked with optimizing asset allocations for a $50B portfolio. Provide clear, quantitative recommendations for each sector.", max_loops=1, - model_name="groq/deepseek-r1-distill-qwen-32b", + model_name="gpt-4.1", max_tokens=3000, + streaming_on=True, ), Agent( agent_name="Sector-Risk-Analyst", agent_description="Expert risk management analyst.", system_prompt="You are a risk analyst responsible for advising on risk allocation within a $50B portfolio. Provide detailed insights on risk exposures for each sector.", max_loops=1, - model_name="groq/deepseek-r1-distill-qwen-32b", + model_name="gpt-4.1", max_tokens=3000, + streaming_on=True, ), Agent( agent_name="Tech-Sector-Analyst", agent_description="Technology sector analyst.", system_prompt="You are a tech sector analyst focused on capital and risk allocations. Provide data-backed insights for the tech sector.", max_loops=1, - model_name="groq/deepseek-r1-distill-qwen-32b", + model_name="gpt-4.1", max_tokens=3000, + streaming_on=True, ), ] @@ -35,14 +37,19 @@ majority_voting = HierarchicalSwarm( name="Sector-Investment-Advisory-System", description="System for sector analysis and optimal allocations.", agents=agents, - # director=director_agent, - max_loops=1, + max_loops=2, output_type="dict", ) -# Run the analysis + result = majority_voting.run( - task="Evaluate market sectors and determine optimal allocation for a $50B portfolio. Include a detailed table of allocations, risk assessments, and a consolidated strategy." + task=( + "Simulate the allocation of a $50B fund specifically for the pharmaceutical sector. " + "Provide specific tickers (e.g., PFE, MRK, JNJ, LLY, BMY, etc.) and a clear rationale for why funds should be allocated to each company. " + "Present a table showing each ticker, company name, allocation percentage, and allocation amount in USD. " + "Include a brief summary of the overall allocation strategy and the reasoning behind the choices." + "Only call the Sector-Financial-Analyst agent to do the analysis. Nobody else should do the analysis." + ) ) print(result) diff --git a/simulations/senator_assembly/senator_simulation.py b/simulations/senator_assembly/senator_simulation.py index b03a7762..75a2ee61 100644 --- a/simulations/senator_assembly/senator_simulation.py +++ b/simulations/senator_assembly/senator_simulation.py @@ -6,11 +6,11 @@ each with detailed backgrounds, political positions, and comprehensive system pr that reflect their real-world characteristics, voting patterns, and policy priorities. """ +import random +from typing import Dict, List, Optional, Union + from swarms import Agent from swarms.structs.multi_agent_exec import run_agents_concurrently -from typing import Dict, List, Optional, Union -import json -import random class SenatorSimulation: @@ -3490,159 +3490,159 @@ class SenatorSimulation: } -# Example usage and demonstration -def main(): - """ - Demonstrate the Senate simulation with various scenarios. - """ - print("🏛️ US Senate Simulation Initializing...") - - # Create the simulation - senate = SenatorSimulation() - - print("\n📊 Senate Composition:") - composition = senate.get_senate_composition() - print(json.dumps(composition, indent=2)) - - print(f"\n🎭 Available Senators ({len(senate.senators)}):") - for name in senate.senators.keys(): - party = senate._get_senator_party(name) - print(f" - {name} ({party})") - - # Example 1: Individual senator response - print("\n🗣️ Example: Senator Response") - senator = senate.get_senator("Katie Britt") - response = senator.run( - "What is your position on infrastructure spending and how would you pay for it?" - ) - print(f"Senator Katie Britt: {response}") - - # Example 2: Simulate a debate - print("\n💬 Example: Senate Debate on Climate Change") - debate = senate.simulate_debate( - "Climate change legislation and carbon pricing", - [ - "Katie Britt", - "Mark Kelly", - "Lisa Murkowski", - "Alex Padilla", - ], - ) - - for entry in debate["transcript"]: - print(f"\n{entry['senator']} ({entry['party']}):") - print(f" {entry['position'][:200]}...") - - # Example 3: Simulate a vote - print("\n🗳️ Example: Senate Vote on Infrastructure Bill") - vote = senate.simulate_vote( - "A $1.2 trillion infrastructure bill including roads, bridges, broadband, and clean energy projects", - [ - "Katie Britt", - "Mark Kelly", - "Lisa Murkowski", - "Alex Padilla", - "Tom Cotton", - ], - ) - - print("Vote Results:") - for senator, vote_choice in vote["votes"].items(): - print(f" {senator}: {vote_choice}") - - print(f"\nFinal Result: {vote['results']['outcome']}") - print( - f"YEA: {vote['results']['yea']}, NAY: {vote['results']['nay']}, PRESENT: {vote['results']['present']}" - ) - - # Example 4: Committee hearing - print("\n🏛️ Example: Committee Hearing") - hearing = senate.run_committee_hearing( - "Armed Services", - "Military readiness and defense spending", - ["Secretary of Defense", "Joint Chiefs Chairman"], - ) - - print("Armed Services Committee Hearing on Military Readiness") - for entry in hearing["transcript"][:3]: # Show first 3 entries - print( - f"\n{entry['type'].title()}: {entry['senator'] if 'senator' in entry else entry['witness']}" - ) - print(f" {entry['content'][:150]}...") - - # Example 5: Run all senators concurrently on a single task - print("\n🚀 Example: All Senators Concurrent Response") - all_senators_results = senate.run( - "What is your position on federal student loan forgiveness and how should we address the student debt crisis?" - ) - - print(f"\nTask: {all_senators_results['task']}") - print( - f"Selection Method: {all_senators_results['selection_method']}" - ) - print( - f"Total Participants: {all_senators_results['total_participants']}" - ) - - print("\n📊 Party Breakdown:") - for party, senators in all_senators_results[ - "party_breakdown" - ].items(): - if senators: - print(f"\n{party} ({len(senators)} senators):") - for senator_data in senators: - print(f" - {senator_data['senator']}") - - # Example 6: Run 50% of senators randomly - print("\n🎲 Example: Random 50% of Senators") - random_results = senate.run( - "What is your position on climate change legislation and carbon pricing?", - participants=0.5, # 50% of all senators - ) - - print(f"\nTask: {random_results['task']}") - print(f"Selection Method: {random_results['selection_method']}") - print( - f"Total Participants: {random_results['total_participants']}" - ) - - print("\n📋 Selected Senators:") - for senator in random_results["participants"]: - party = senate._get_senator_party(senator) - print(f" - {senator} ({party})") - - print("\n📊 Party Breakdown:") - for party, senators in random_results["party_breakdown"].items(): - if senators: - print(f"\n{party} ({len(senators)} senators):") - for senator_data in senators: - print(f" - {senator_data['senator']}") - - # Example 7: Run specific senators - print("\n🎯 Example: Specific Senators") - specific_results = senate.run( - "What is your position on military spending and defense policy?", - participants=[ - "Katie Britt", - "Mark Kelly", - "Lisa Murkowski", - "Alex Padilla", - "Tom Cotton", - ], - ) - - print(f"\nTask: {specific_results['task']}") - print(f"Selection Method: {specific_results['selection_method']}") - print( - f"Total Participants: {specific_results['total_participants']}" - ) - - print("\n📋 Responses by Senator:") - for senator, response in specific_results["responses"].items(): - party = senate._get_senator_party(senator) - print(f"\n{senator} ({party}):") - print(f" {response[:200]}...") - - -if __name__ == "__main__": - main() +# # Example usage and demonstration +# def main(): +# """ +# Demonstrate the Senate simulation with various scenarios. +# """ +# print("🏛️ US Senate Simulation Initializing...") + +# # Create the simulation +# senate = SenatorSimulation() + +# print("\n📊 Senate Composition:") +# composition = senate.get_senate_composition() +# print(json.dumps(composition, indent=2)) + +# print(f"\n🎭 Available Senators ({len(senate.senators)}):") +# for name in senate.senators.keys(): +# party = senate._get_senator_party(name) +# print(f" - {name} ({party})") + +# # Example 1: Individual senator response +# print("\n🗣️ Example: Senator Response") +# senator = senate.get_senator("Katie Britt") +# response = senator.run( +# "What is your position on infrastructure spending and how would you pay for it?" +# ) +# print(f"Senator Katie Britt: {response}") + +# # Example 2: Simulate a debate +# print("\n💬 Example: Senate Debate on Climate Change") +# debate = senate.simulate_debate( +# "Climate change legislation and carbon pricing", +# [ +# "Katie Britt", +# "Mark Kelly", +# "Lisa Murkowski", +# "Alex Padilla", +# ], +# ) + +# for entry in debate["transcript"]: +# print(f"\n{entry['senator']} ({entry['party']}):") +# print(f" {entry['position'][:200]}...") + +# # Example 3: Simulate a vote +# print("\n🗳️ Example: Senate Vote on Infrastructure Bill") +# vote = senate.simulate_vote( +# "A $1.2 trillion infrastructure bill including roads, bridges, broadband, and clean energy projects", +# [ +# "Katie Britt", +# "Mark Kelly", +# "Lisa Murkowski", +# "Alex Padilla", +# "Tom Cotton", +# ], +# ) + +# print("Vote Results:") +# for senator, vote_choice in vote["votes"].items(): +# print(f" {senator}: {vote_choice}") + +# print(f"\nFinal Result: {vote['results']['outcome']}") +# print( +# f"YEA: {vote['results']['yea']}, NAY: {vote['results']['nay']}, PRESENT: {vote['results']['present']}" +# ) + +# # Example 4: Committee hearing +# print("\n🏛️ Example: Committee Hearing") +# hearing = senate.run_committee_hearing( +# "Armed Services", +# "Military readiness and defense spending", +# ["Secretary of Defense", "Joint Chiefs Chairman"], +# ) + +# print("Armed Services Committee Hearing on Military Readiness") +# for entry in hearing["transcript"][:3]: # Show first 3 entries +# print( +# f"\n{entry['type'].title()}: {entry['senator'] if 'senator' in entry else entry['witness']}" +# ) +# print(f" {entry['content'][:150]}...") + +# # Example 5: Run all senators concurrently on a single task +# print("\n🚀 Example: All Senators Concurrent Response") +# all_senators_results = senate.run( +# "What is your position on federal student loan forgiveness and how should we address the student debt crisis?" +# ) + +# print(f"\nTask: {all_senators_results['task']}") +# print( +# f"Selection Method: {all_senators_results['selection_method']}" +# ) +# print( +# f"Total Participants: {all_senators_results['total_participants']}" +# ) + +# print("\n📊 Party Breakdown:") +# for party, senators in all_senators_results[ +# "party_breakdown" +# ].items(): +# if senators: +# print(f"\n{party} ({len(senators)} senators):") +# for senator_data in senators: +# print(f" - {senator_data['senator']}") + +# # Example 6: Run 50% of senators randomly +# print("\n🎲 Example: Random 50% of Senators") +# random_results = senate.run( +# "What is your position on climate change legislation and carbon pricing?", +# participants=0.5, # 50% of all senators +# ) + +# print(f"\nTask: {random_results['task']}") +# print(f"Selection Method: {random_results['selection_method']}") +# print( +# f"Total Participants: {random_results['total_participants']}" +# ) + +# print("\n📋 Selected Senators:") +# for senator in random_results["participants"]: +# party = senate._get_senator_party(senator) +# print(f" - {senator} ({party})") + +# print("\n📊 Party Breakdown:") +# for party, senators in random_results["party_breakdown"].items(): +# if senators: +# print(f"\n{party} ({len(senators)} senators):") +# for senator_data in senators: +# print(f" - {senator_data['senator']}") + +# # Example 7: Run specific senators +# print("\n🎯 Example: Specific Senators") +# specific_results = senate.run( +# "What is your position on military spending and defense policy?", +# participants=[ +# "Katie Britt", +# "Mark Kelly", +# "Lisa Murkowski", +# "Alex Padilla", +# "Tom Cotton", +# ], +# ) + +# print(f"\nTask: {specific_results['task']}") +# print(f"Selection Method: {specific_results['selection_method']}") +# print( +# f"Total Participants: {specific_results['total_participants']}" +# ) + +# print("\n📋 Responses by Senator:") +# for senator, response in specific_results["responses"].items(): +# party = senate._get_senator_party(senator) +# print(f"\n{senator} ({party}):") +# print(f" {response[:200]}...") + + +# if __name__ == "__main__": +# main() diff --git a/swarms/structs/hiearchical_swarm.py b/swarms/structs/hiearchical_swarm.py index 2ff30a06..85a720cf 100644 --- a/swarms/structs/hiearchical_swarm.py +++ b/swarms/structs/hiearchical_swarm.py @@ -1,6 +1,10 @@ """ -Flow: +Hierarchical Swarm Implementation + +This module provides a hierarchical swarm architecture where a director agent coordinates +multiple worker agents to execute complex tasks through a structured workflow. +Flow: 1. User provides a task 2. Director creates a plan 3. Director distributes orders to agents individually or multiple tasks at once @@ -8,10 +12,21 @@ Flow: 5. Director evaluates results and issues new orders if needed (up to max_loops) 6. All context and conversation history is preserved throughout the process +Todo + +- Add layers of management -- a list of list of agents that act as departments +- Auto build agents from input prompt - and then add them to the swarm +- Create an interactive and dynamic UI like we did with heavy swarm +- Make it faster and more high performance + +Classes: + HierarchicalOrder: Represents a single task assignment to a specific agent + SwarmSpec: Contains the overall plan and list of orders for the swarm + HierarchicalSwarm: Main swarm orchestrator that manages director and worker agents """ import traceback -from typing import Any, Callable, List, Literal, Optional, Union +from typing import Any, Callable, List, Optional, Union from pydantic import BaseModel, Field @@ -19,7 +34,6 @@ from swarms.prompts.hiearchical_system_prompt import ( HIEARCHICAL_SWARM_SYSTEM_PROMPT, ) from swarms.structs.agent import Agent -from swarms.structs.base_swarm import BaseSwarm from swarms.structs.conversation import Conversation from swarms.structs.ma_utils import list_all_agents from swarms.tools.base_tool import BaseTool @@ -33,6 +47,20 @@ logger = initialize_logger(log_folder="hierarchical_swarm") class HierarchicalOrder(BaseModel): + """ + Represents a single task assignment within the hierarchical swarm. + + This class defines the structure for individual task orders that the director + distributes to worker agents. Each order specifies which agent should execute + what specific task. + + Attributes: + agent_name (str): The name of the agent assigned to execute the task. + Must match an existing agent in the swarm. + task (str): The specific task description to be executed by the assigned agent. + Should be clear and actionable. + """ + agent_name: str = Field( ..., description="Specifies the name of the agent to which the task is assigned. This is a crucial element in the hierarchical structure of the swarm, as it determines the specific agent responsible for the task execution.", @@ -44,6 +72,20 @@ class HierarchicalOrder(BaseModel): class SwarmSpec(BaseModel): + """ + Defines the complete specification for a hierarchical swarm execution. + + This class contains the overall plan and all individual orders that the director + creates to coordinate the swarm's activities. It serves as the structured output + format for the director agent. + + Attributes: + plan (str): A comprehensive plan outlining the sequence of actions and strategy + for the entire swarm to accomplish the given task. + orders (List[HierarchicalOrder]): A list of specific task assignments to + individual agents within the swarm. + """ + plan: str = Field( ..., description="Outlines the sequence of actions to be taken by the swarm. This plan is a detailed roadmap that guides the swarm's behavior and decision-making.", @@ -54,50 +96,34 @@ class SwarmSpec(BaseModel): ) -SwarmType = Literal[ - "AgentRearrange", - "MixtureOfAgents", - "SpreadSheetSwarm", - "SequentialWorkflow", - "ConcurrentWorkflow", - "GroupChat", - "MultiAgentRouter", - "AutoSwarmBuilder", - "HiearchicalSwarm", - "auto", - "MajorityVoting", - "MALT", - "DeepResearchSwarm", - "CouncilAsAJudge", - "InteractiveGroupChat", -] - - -class SwarmRouterCall(BaseModel): - goal: str = Field( - ..., - description="The goal of the swarm router call. This is the goal that the swarm router will use to determine the best swarm to use.", - ) - swarm_type: SwarmType = Field( - ..., - description="The type of swarm to use. This is the type of swarm that the swarm router will use to determine the best swarm to use.", - ) - - task: str = Field( - ..., - description="The task to be executed by the swarm router. This is the task that the swarm router will use to determine the best swarm to use.", - ) - - -class HierarchicalSwarm(BaseSwarm): +class HierarchicalSwarm: """ - _Representer a hierarchical swarm of agents, with a director that orchestrates tasks among the agents. - The workflow follows a hierarchical pattern: - 1. Task is received and sent to the director - 2. Director creates a plan and distributes orders to agents - 3. Agents execute tasks and report back to the director - 4. Director evaluates results and issues new orders if needed (up to max_loops) - 5. All context and conversation history is preserved throughout the process + A hierarchical swarm orchestrator that coordinates multiple agents through a director. + + This class implements a hierarchical architecture where a director agent creates + plans and distributes tasks to worker agents. The director can provide feedback + and iterate on results through multiple loops to achieve the desired outcome. + + The swarm maintains conversation history throughout the process, allowing for + context-aware decision making and iterative refinement of results. + + Attributes: + name (str): The name identifier for this swarm instance. + description (str): A description of the swarm's purpose and capabilities. + director (Optional[Union[Agent, Callable, Any]]): The director agent that + coordinates the swarm. + agents (List[Union[Agent, Callable, Any]]): List of worker agents available + for task execution. + max_loops (int): Maximum number of feedback loops the swarm can perform. + output_type (OutputType): Format for the final output of the swarm. + feedback_director_model_name (str): Model name for the feedback director. + director_name (str): Name identifier for the director agent. + director_model_name (str): Model name for the main director agent. + verbose (bool): Whether to enable detailed logging and progress tracking. + add_collaboration_prompt (bool): Whether to add collaboration prompts to agents. + planning_director_agent (Optional[Union[Agent, Callable, Any]]): Optional + planning agent. + director_feedback_on (bool): Whether director feedback is enabled. """ def __init__( @@ -121,22 +147,33 @@ class HierarchicalSwarm(BaseSwarm): **kwargs, ): """ - Initializes the HierarchicalSwarm with the given parameters. - - :param name: The name of the swarm. - :param description: A description of the swarm. - :param director: The director agent that orchestrates tasks. - :param agents: A list of agents within the swarm. - :param max_loops: The maximum number of feedback loops between the director and agents. - :param output_type: The format in which to return the output (dict, str, or list). - :param verbose: Enable detailed logging with loguru. + Initialize a new HierarchicalSwarm instance. + + Args: + name (str): The name identifier for this swarm instance. + description (str): A description of the swarm's purpose. + director (Optional[Union[Agent, Callable, Any]]): The director agent. + If None, a default director will be created. + agents (List[Union[Agent, Callable, Any]]): List of worker agents. + Must not be empty. + max_loops (int): Maximum number of feedback loops (must be > 0). + output_type (OutputType): Format for the final output. + feedback_director_model_name (str): Model name for feedback director. + director_name (str): Name identifier for the director agent. + director_model_name (str): Model name for the main director agent. + verbose (bool): Whether to enable detailed logging. + add_collaboration_prompt (bool): Whether to add collaboration prompts. + planning_director_agent (Optional[Union[Agent, Callable, Any]]): + Optional planning agent for enhanced planning capabilities. + director_feedback_on (bool): Whether director feedback is enabled. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Raises: + ValueError: If no agents are provided or max_loops is invalid. """ - super().__init__( - name=name, - description=description, - agents=agents, - ) self.name = name + self.description = description self.director = director self.agents = agents self.max_loops = max_loops @@ -155,33 +192,47 @@ class HierarchicalSwarm(BaseSwarm): def init_swarm(self): """ - Initializes the swarm. + Initialize the swarm with proper configuration and validation. + + This method performs the following initialization steps: + 1. Sets up logging if verbose mode is enabled + 2. Creates a conversation instance for history tracking + 3. Performs reliability checks on the configuration + 4. Adds agent context to the director + + Raises: + ValueError: If the swarm configuration is invalid. """ # Initialize logger only if verbose is enabled if self.verbose: logger.info( f"🚀 Initializing HierarchicalSwarm: {self.name}" ) - logger.info( - f"📊 Configuration - Max loops: {self.max_loops}" - ) self.conversation = Conversation(time_enabled=False) # Reliability checks self.reliability_checks() - self.director = self.setup_director() - self.add_context_to_director() if self.verbose: logger.success( - f"✅ HierarchicalSwarm initialized successfully: Name {self.name}" + f"✅ HierarchicalSwarm: {self.name} initialized successfully." ) def add_context_to_director(self): - """Add agent context to the director's conversation.""" + """ + Add agent context and collaboration information to the director's conversation. + + This method ensures that the director has complete information about all + available agents, their capabilities, and how they can collaborate. This + context is essential for the director to make informed decisions about + task distribution. + + Raises: + Exception: If adding context fails due to agent configuration issues. + """ try: if self.verbose: logger.info("📝 Adding agent context to director") @@ -207,7 +258,18 @@ class HierarchicalSwarm(BaseSwarm): ) def setup_director(self): - """Set up the director agent with proper configuration.""" + """ + Set up the director agent with proper configuration and tools. + + Creates a new director agent with the SwarmSpec schema for structured + output, enabling it to create plans and distribute orders effectively. + + Returns: + Agent: A configured director agent ready to coordinate the swarm. + + Raises: + Exception: If director setup fails due to configuration issues. + """ try: if self.verbose: logger.info("🎯 Setting up director agent") @@ -217,17 +279,6 @@ class HierarchicalSwarm(BaseSwarm): if self.verbose: logger.debug(f"📋 Director schema: {schema}") - # if self.director is not None: - # # if litellm_check_for_tools(self.director.model_name) is True: - # self.director.add_tool_schema([schema]) - - # if self.verbose: - # logger.success( - # "✅ Director agent setup completed successfully" - # ) - - # return self.director - # else: return Agent( agent_name=self.director_name, agent_description="A director agent that can create a plan and distribute orders to agents", @@ -244,13 +295,20 @@ class HierarchicalSwarm(BaseSwarm): def reliability_checks(self): """ - Checks if there are any agents and a director set for the swarm. - Raises ValueError if either condition is not met. + Perform validation checks to ensure the swarm is properly configured. + + This method validates: + 1. That at least one agent is provided + 2. That max_loops is greater than 0 + 3. That a director is available (creates default if needed) + + Raises: + ValueError: If the swarm configuration is invalid. """ try: if self.verbose: logger.info( - f"🔍 Running reliability checks for swarm: {self.name}" + f"Hiearchical Swarm: {self.name} Reliability checks in progress..." ) if not self.agents or len(self.agents) == 0: @@ -263,17 +321,12 @@ class HierarchicalSwarm(BaseSwarm): "Max loops must be greater than 0. Please set a valid number of loops." ) - if not self.director: - raise ValueError( - "Director not set for the swarm. A director agent is required to coordinate and orchestrate tasks among the agents." - ) + if self.director is None: + self.director = self.setup_director() if self.verbose: logger.success( - f"✅ Reliability checks passed for swarm: {self.name}" - ) - logger.info( - f"📊 Swarm stats - Agents: {len(self.agents)}, Max loops: {self.max_loops}" + f"Hiearchical Swarm: {self.name} Reliability checks passed..." ) except Exception as e: @@ -286,11 +339,22 @@ class HierarchicalSwarm(BaseSwarm): img: str = None, ) -> SwarmSpec: """ - Runs a task through the director agent with the current conversation context. + Execute the director agent with the given task and conversation context. + + This method runs the director agent to create a plan and distribute orders + based on the current task and conversation history. If a planning director + agent is configured, it will first create a detailed plan before the main + director processes the task. + + Args: + task (str): The task to be executed by the director. + img (str, optional): Optional image input for the task. + + Returns: + SwarmSpec: The director's output containing the plan and orders. - :param task: The task to be executed by the director. - :param img: Optional image to be used with the task. - :return: The SwarmSpec containing the director's orders. + Raises: + Exception: If director execution fails. """ try: if self.verbose: @@ -330,7 +394,25 @@ class HierarchicalSwarm(BaseSwarm): def step(self, task: str, img: str = None, *args, **kwargs): """ - Runs a single step of the hierarchical swarm. + Execute a single step of the hierarchical swarm workflow. + + This method performs one complete iteration of the swarm's workflow: + 1. Run the director to create a plan and orders + 2. Parse the director's output to extract plan and orders + 3. Execute all orders by calling the appropriate agents + 4. Optionally generate director feedback on the results + + Args: + task (str): The task to be processed in this step. + img (str, optional): Optional image input for the task. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + Any: The results from this step, either agent outputs or director feedback. + + Raises: + Exception: If step execution fails. """ try: if self.verbose: @@ -370,13 +452,27 @@ class HierarchicalSwarm(BaseSwarm): def run(self, task: str, img: str = None, *args, **kwargs): """ - Executes the hierarchical swarm for a specified number of feedback loops. + Execute the hierarchical swarm for the specified number of feedback loops. + + This method orchestrates the complete swarm execution, performing multiple + iterations based on the max_loops configuration. Each iteration builds upon + the previous results, allowing for iterative refinement and improvement. - :param task: The initial task to be processed by the swarm. - :param img: Optional image input for the agents. - :param args: Additional positional arguments. - :param kwargs: Additional keyword arguments. - :return: The formatted conversation history as output. + The method maintains conversation history throughout all loops and provides + context from previous iterations to subsequent ones. + + Args: + task (str): The initial task to be processed by the swarm. + img (str, optional): Optional image input for the agents. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + Any: The formatted conversation history as output, formatted according + to the output_type configuration. + + Raises: + Exception: If swarm execution fails. """ try: current_loop = 0 @@ -448,7 +544,23 @@ class HierarchicalSwarm(BaseSwarm): logger.error(error_msg) def feedback_director(self, outputs: list): - """Provide feedback from the director based on agent outputs.""" + """ + Generate feedback from the director based on agent outputs. + + This method creates a feedback director agent that analyzes the results + from worker agents and provides specific, actionable feedback for improvement. + The feedback is added to the conversation history and can be used in + subsequent iterations. + + Args: + outputs (list): List of outputs from worker agents that need feedback. + + Returns: + str: The director's feedback on the agent outputs. + + Raises: + Exception: If feedback generation fails. + """ try: if self.verbose: logger.info("📝 Generating director feedback") @@ -491,7 +603,24 @@ class HierarchicalSwarm(BaseSwarm): self, agent_name: str, task: str, *args, **kwargs ): """ - Calls a single agent with the given task. + Call a single agent by name to execute a specific task. + + This method locates an agent by name and executes the given task with + the current conversation context. The agent's output is added to the + conversation history for future reference. + + Args: + agent_name (str): The name of the agent to call. + task (str): The task to be executed by the agent. + *args: Additional positional arguments for the agent. + **kwargs: Additional keyword arguments for the agent. + + Returns: + Any: The output from the agent's execution. + + Raises: + ValueError: If the specified agent is not found in the swarm. + Exception: If agent execution fails. """ try: if self.verbose: @@ -537,7 +666,22 @@ class HierarchicalSwarm(BaseSwarm): def parse_orders(self, output): """ - Parses the orders from the director's output. + Parse the director's output to extract plan and orders. + + This method handles various output formats from the director agent and + extracts the plan and hierarchical orders. It supports both direct + dictionary formats and function call formats with JSON arguments. + + Args: + output: The raw output from the director agent. + + Returns: + tuple: A tuple containing (plan, orders) where plan is a string + and orders is a list of HierarchicalOrder objects. + + Raises: + ValueError: If the output format is unexpected or cannot be parsed. + Exception: If parsing fails due to other errors. """ try: if self.verbose: @@ -666,7 +810,20 @@ class HierarchicalSwarm(BaseSwarm): def execute_orders(self, orders: list): """ - Executes the orders from the director's output. + Execute all orders from the director's output. + + This method iterates through all hierarchical orders and calls the + appropriate agents to execute their assigned tasks. Each agent's + output is collected and returned as a list. + + Args: + orders (list): List of HierarchicalOrder objects to execute. + + Returns: + list: List of outputs from all executed orders. + + Raises: + Exception: If order execution fails. """ try: if self.verbose: @@ -699,7 +856,23 @@ class HierarchicalSwarm(BaseSwarm): self, tasks: List[str], img: str = None, *args, **kwargs ): """ - Executes the hierarchical swarm for a list of tasks. + Execute the hierarchical swarm for multiple tasks in sequence. + + This method processes a list of tasks sequentially, running the complete + swarm workflow for each task. Each task is processed independently with + its own conversation context and results. + + Args: + tasks (List[str]): List of tasks to be processed by the swarm. + img (str, optional): Optional image input for the tasks. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + list: List of results for each processed task. + + Raises: + Exception: If batched execution fails. """ try: if self.verbose: From 9b0b47153af0b0216dfbec2d585c3d3c6ad09086 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Sun, 3 Aug 2025 07:15:20 +0530 Subject: [PATCH 42/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 960408d3..7cb9b489 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -89,7 +89,7 @@ extra: url: "https://github.com/kyegomez/awesome-multi-agent-papers" - "Tools Use Case": + "Tools": - title: "Tools and MCP" url: "https://docs.swarms.world/en/latest/swarms/tools/tools_examples/" - title: "MCP (Model Context Protocol)" From db46c0958b0e3fe93ffa9aa1fce9ae7da4fc64ac Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Sun, 3 Aug 2025 07:36:10 +0530 Subject: [PATCH 43/73] Update mkdocs.yml --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7cb9b489..e1015db9 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -107,7 +107,7 @@ extra: - title: "Yahoo Finance" url: "https://docs.swarms.world/en/latest/swarms/examples/yahoo_finance/" - "Use Case": + "Use Cases": - title: "Examples Overview" url: "https://docs.swarms.world/en/latest/examples/index/" - title: "Templates & Applications" From c77a1b350dc044ec4d71a525114754b48ba95f33 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 3 Aug 2025 17:36:47 -0700 Subject: [PATCH 44/73] fix readme --- README.md | 21 +- docs/api/graph_workflow_api_endpoint.md | 631 ++++++++++++++++++ .../test_graphworlfolw_validation.py | 31 +- swarms/structs/cron_job.py | 1 - swarms/structs/graph_workflow.py | 131 ++-- 5 files changed, 752 insertions(+), 63 deletions(-) create mode 100644 docs/api/graph_workflow_api_endpoint.md diff --git a/README.md b/README.md index bfcf4264..1f0f4c6c 100644 --- a/README.md +++ b/README.md @@ -217,17 +217,16 @@ print(final_post) | **Architecture** | **Description** | **Best For** | |---|---|---| -| **[SequentialWorkflow](https://docs.swarms.world/en/latest/swarms/structs/sequential_workflow/)** | Agents execute tasks in a linear chain; one agent's output is the next one's input. | Step-by-step processes like data transformation pipelines, report generation. | -| **[ConcurrentWorkflow](https://docs.swarms.world/en/latest/swarms/structs/concurrent_workflow/)** | Agents run tasks simultaneously for maximum efficiency. | High-throughput tasks like batch processing, parallel data analysis. | -| **[AgentRearrange](https://docs.swarms.world/en/latest/swarms/structs/agent_rearrange/)** | Dynamically maps complex relationships (e.g., `a -> b, c`) between agents. | Flexible and adaptive workflows, task distribution, dynamic routing. | -| **[GraphWorkflow](https://docs.swarms.world/en/latest/swarms/structs/graph_workflow/)** | Orchestrates agents as nodes in a Directed Acyclic Graph (DAG). | Complex projects with intricate dependencies, like software builds. | -| **[MixtureOfAgents (MoA)](https://docs.swarms.world/en/latest/swarms/structs/moa/)** | Utilizes multiple expert agents in parallel and synthesizes their outputs. | Complex problem-solving, achieving state-of-the-art performance through collaboration. | -| **[GroupChat](https://docs.swarms.world/en/latest/swarms/structs/group_chat/)** | Agents collaborate and make decisions through a conversational interface. | Real-time collaborative decision-making, negotiations, brainstorming. | -| **[ForestSwarm](https://docs.swarms.world/en/latest/swarms/structs/forest_swarm/)** | Dynamically selects the most suitable agent or tree of agents for a given task. | Task routing, optimizing for expertise, complex decision-making trees. | - -| **[HierarchicalSwarm](https://docs.swarms.world/en/latest/swarms/structs/hiearchical_swarm/)** | Orchestrates agents with a director that creates plans and distributes tasks to specialized worker agents. | Complex project management, team coordination, hierarchical decision-making with feedback loops. | -| **[HeavySwarm](https://docs.swarms.world/en/latest/swarms/structs/heavy_swarm/)** | Implements a 5-phase workflow with specialized agents (Research, Analysis, Alternatives, Verification) for comprehensive task analysis. | Complex research and analysis tasks, financial analysis, strategic planning, comprehensive reporting. | -| **[SwarmRouter](https://docs.swarms.world/en/latest/swarms/structs/swarm_router/)** | Universal orchestrator that provides a single interface to run any type of swarm with dynamic selection. | Simplifying complex workflows, switching between swarm strategies, unified multi-agent management. | +| **[SequentialWorkflow](https://docs.swarms.world/en/latest/swarms/structs/sequential_workflow/)** | Agents execute tasks in a linear chain; the output of one agent becomes the input for the next. | Step-by-step processes such as data transformation pipelines and report generation. | +| **[ConcurrentWorkflow](https://docs.swarms.world/en/latest/swarms/structs/concurrent_workflow/)** | Agents run tasks simultaneously for maximum efficiency. | High-throughput tasks such as batch processing and parallel data analysis. | +| **[AgentRearrange](https://docs.swarms.world/en/latest/swarms/structs/agent_rearrange/)** | Dynamically maps complex relationships (e.g., `a -> b, c`) between agents. | Flexible and adaptive workflows, task distribution, and dynamic routing. | +| **[GraphWorkflow](https://docs.swarms.world/en/latest/swarms/structs/graph_workflow/)** | Orchestrates agents as nodes in a Directed Acyclic Graph (DAG). | Complex projects with intricate dependencies, such as software builds. | +| **[MixtureOfAgents (MoA)](https://docs.swarms.world/en/latest/swarms/structs/moa/)** | Utilizes multiple expert agents in parallel and synthesizes their outputs. | Complex problem-solving and achieving state-of-the-art performance through collaboration. | +| **[GroupChat](https://docs.swarms.world/en/latest/swarms/structs/group_chat/)** | Agents collaborate and make decisions through a conversational interface. | Real-time collaborative decision-making, negotiations, and brainstorming. | +| **[ForestSwarm](https://docs.swarms.world/en/latest/swarms/structs/forest_swarm/)** | Dynamically selects the most suitable agent or tree of agents for a given task. | Task routing, optimizing for expertise, and complex decision-making trees. | +| **[HierarchicalSwarm](https://docs.swarms.world/en/latest/swarms/structs/hiearchical_swarm/)** | Orchestrates agents with a director who creates plans and distributes tasks to specialized worker agents. | Complex project management, team coordination, and hierarchical decision-making with feedback loops. | +| **[HeavySwarm](https://docs.swarms.world/en/latest/swarms/structs/heavy_swarm/)** | Implements a five-phase workflow with specialized agents (Research, Analysis, Alternatives, Verification) for comprehensive task analysis. | Complex research and analysis tasks, financial analysis, strategic planning, and comprehensive reporting. | +| **[SwarmRouter](https://docs.swarms.world/en/latest/swarms/structs/swarm_router/)** | A universal orchestrator that provides a single interface to run any type of swarm with dynamic selection. | Simplifying complex workflows, switching between swarm strategies, and unified multi-agent management. | ----- diff --git a/docs/api/graph_workflow_api_endpoint.md b/docs/api/graph_workflow_api_endpoint.md new file mode 100644 index 00000000..7b3b2ffb --- /dev/null +++ b/docs/api/graph_workflow_api_endpoint.md @@ -0,0 +1,631 @@ +# GraphWorkflow API Endpoint Design + +## Overview + +This document outlines the design for a single API endpoint that allows users to create, configure, and execute GraphWorkflow instances. The endpoint provides a comprehensive interface for leveraging the GraphWorkflow functionality with minimal setup. + +## Base URL + +``` +POST /api/v1/graph-workflow/execute +``` + +## Request Schema + +### Main Request Body + +```json +{ + "workflow_config": { + "name": "string", + "description": "string", + "max_loops": 1, + "auto_compile": true, + "verbose": false + }, + "agents": [ + { + "id": "string", + "agent_name": "string", + "model_name": "string", + "system_prompt": "string", + "temperature": 0.7, + "max_tokens": 4000, + "max_loops": 1, + "metadata": {} + } + ], + "connections": [ + { + "type": "simple", + "source": "string", + "target": "string", + "metadata": {} + } + ], + "entry_points": ["string"], + "end_points": ["string"], + "task": "string", + "options": { + "include_conversation": false, + "include_runtime_state": false, + "visualization": { + "enabled": false, + "format": "png", + "show_summary": true + } + } +} +``` + +### Detailed Schema Definitions + +#### WorkflowConfig +```json +{ + "name": "Investment Analysis Workflow", + "description": "Multi-agent workflow for comprehensive investment analysis", + "max_loops": 1, + "auto_compile": true, + "verbose": false +} +``` + +#### Agent Definition +```json +{ + "id": "fundamental_analyst", + "agent_name": "Fundamental Analysis Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a fundamental analysis expert specializing in financial analysis...", + "temperature": 0.7, + "max_tokens": 4000, + "max_loops": 1, + "autosave": true, + "dashboard": false, + "metadata": { + "specialization": "financial_analysis", + "expertise_level": "expert" + } +} +``` + +#### Connection Types + +##### Simple Connection +```json +{ + "type": "simple", + "source": "data_gatherer", + "target": "fundamental_analyst", + "metadata": { + "priority": "high" + } +} +``` + +##### Fan-out Connection +```json +{ + "type": "fan_out", + "source": "data_gatherer", + "targets": ["fundamental_analyst", "technical_analyst", "sentiment_analyst"], + "metadata": { + "parallel_execution": true + } +} +``` + +##### Fan-in Connection +```json +{ + "type": "fan_in", + "sources": ["fundamental_analyst", "technical_analyst", "sentiment_analyst"], + "target": "synthesis_agent", + "metadata": { + "aggregation_method": "combine_all" + } +} +``` + +##### Parallel Chain +```json +{ + "type": "parallel_chain", + "sources": ["data_gatherer_1", "data_gatherer_2"], + "targets": ["analyst_1", "analyst_2", "analyst_3"], + "metadata": { + "full_mesh": true + } +} +``` + +## Response Schema + +### Success Response +```json +{ + "status": "success", + "workflow_id": "uuid-string", + "execution_time": 45.23, + "results": { + "fundamental_analyst": "Analysis output from fundamental analyst...", + "technical_analyst": "Technical analysis results...", + "synthesis_agent": "Combined analysis and recommendations..." + }, + "conversation": { + "history": [ + { + "role": "fundamental_analyst", + "content": "Analysis output...", + "timestamp": "2024-01-15T10:30:00Z" + } + ] + }, + "metrics": { + "total_agents": 5, + "total_connections": 6, + "execution_layers": 3, + "parallel_efficiency": 85.5 + }, + "visualization": { + "url": "https://api.example.com/visualizations/workflow_123.png", + "format": "png" + }, + "workflow_summary": { + "name": "Investment Analysis Workflow", + "description": "Multi-agent workflow for comprehensive investment analysis", + "entry_points": ["data_gatherer"], + "end_points": ["synthesis_agent"], + "compilation_status": "compiled" + } +} +``` + +### Error Response +```json +{ + "status": "error", + "error_code": "VALIDATION_ERROR", + "message": "Invalid workflow configuration", + "details": { + "field": "connections", + "issue": "Source node 'invalid_node' does not exist", + "suggestions": ["Check node IDs in connections", "Verify all referenced nodes exist"] + }, + "timestamp": "2024-01-15T10:30:00Z" +} +``` + +## Implementation Example + +### Python FastAPI Implementation + +```python +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel, Field +from typing import List, Dict, Any, Optional +import uuid +import time +from swarms import Agent, GraphWorkflow, Node, NodeType, Edge + +app = FastAPI(title="GraphWorkflow API", version="1.0.0") + +# Pydantic Models +class WorkflowConfig(BaseModel): + name: str = "Graph-Workflow-01" + description: str = "A customizable workflow system" + max_loops: int = 1 + auto_compile: bool = True + verbose: bool = False + +class AgentDefinition(BaseModel): + id: str + agent_name: str + model_name: str = "gpt-4o-mini" + system_prompt: Optional[str] = None + temperature: float = 0.7 + max_tokens: int = 4000 + max_loops: int = 1 + autosave: bool = True + dashboard: bool = False + metadata: Dict[str, Any] = Field(default_factory=dict) + +class SimpleConnection(BaseModel): + type: str = "simple" + source: str + target: str + metadata: Dict[str, Any] = Field(default_factory=dict) + +class FanOutConnection(BaseModel): + type: str = "fan_out" + source: str + targets: List[str] + metadata: Dict[str, Any] = Field(default_factory=dict) + +class FanInConnection(BaseModel): + type: str = "fan_in" + sources: List[str] + target: str + metadata: Dict[str, Any] = Field(default_factory=dict) + +class ParallelChainConnection(BaseModel): + type: str = "parallel_chain" + sources: List[str] + targets: List[str] + metadata: Dict[str, Any] = Field(default_factory=dict) + +class VisualizationOptions(BaseModel): + enabled: bool = False + format: str = "png" + show_summary: bool = True + +class WorkflowOptions(BaseModel): + include_conversation: bool = False + include_runtime_state: bool = False + visualization: VisualizationOptions = Field(default_factory=VisualizationOptions) + +class GraphWorkflowRequest(BaseModel): + workflow_config: WorkflowConfig + agents: List[AgentDefinition] + connections: List[Dict[str, Any]] # Union of all connection types + entry_points: Optional[List[str]] = None + end_points: Optional[List[str]] = None + task: str + options: WorkflowOptions = Field(default_factory=WorkflowOptions) + +@app.post("/api/v1/graph-workflow/execute") +async def execute_graph_workflow(request: GraphWorkflowRequest): + """ + Execute a GraphWorkflow with the provided configuration. + + This endpoint creates a workflow from the provided agents and connections, + executes it with the given task, and returns the results. + """ + start_time = time.time() + workflow_id = str(uuid.uuid4()) + + try: + # Create agents from definitions + agent_instances = {} + for agent_def in request.agents: + agent = Agent( + agent_name=agent_def.agent_name, + model_name=agent_def.model_name, + system_prompt=agent_def.system_prompt, + temperature=agent_def.temperature, + max_tokens=agent_def.max_tokens, + max_loops=agent_def.max_loops, + autosave=agent_def.autosave, + dashboard=agent_def.dashboard, + ) + agent_instances[agent_def.id] = agent + + # Create workflow + workflow = GraphWorkflow( + id=workflow_id, + name=request.workflow_config.name, + description=request.workflow_config.description, + max_loops=request.workflow_config.max_loops, + auto_compile=request.workflow_config.auto_compile, + verbose=request.workflow_config.verbose, + ) + + # Add agents to workflow + for agent_def in request.agents: + workflow.add_node(agent_instances[agent_def.id]) + + # Add connections + for connection in request.connections: + conn_type = connection.get("type", "simple") + + if conn_type == "simple": + workflow.add_edge(connection["source"], connection["target"]) + elif conn_type == "fan_out": + workflow.add_edges_from_source( + connection["source"], + connection["targets"] + ) + elif conn_type == "fan_in": + workflow.add_edges_to_target( + connection["sources"], + connection["target"] + ) + elif conn_type == "parallel_chain": + workflow.add_parallel_chain( + connection["sources"], + connection["targets"] + ) + + # Set entry and end points + if request.entry_points: + workflow.set_entry_points(request.entry_points) + else: + workflow.auto_set_entry_points() + + if request.end_points: + workflow.set_end_points(request.end_points) + else: + workflow.auto_set_end_points() + + # Execute workflow + results = workflow.run(request.task) + + # Prepare response + execution_time = time.time() - start_time + + response = { + "status": "success", + "workflow_id": workflow_id, + "execution_time": execution_time, + "results": results, + "metrics": { + "total_agents": len(workflow.nodes), + "total_connections": len(workflow.edges), + "execution_layers": len(workflow._sorted_layers) if workflow._compiled else 0, + "parallel_efficiency": calculate_parallel_efficiency(workflow) + }, + "workflow_summary": { + "name": workflow.name, + "description": workflow.description, + "entry_points": workflow.entry_points, + "end_points": workflow.end_points, + "compilation_status": "compiled" if workflow._compiled else "not_compiled" + } + } + + # Add conversation if requested + if request.options.include_conversation and workflow.conversation: + response["conversation"] = { + "history": workflow.conversation.history + } + + # Add visualization if requested + if request.options.visualization.enabled: + try: + viz_path = workflow.visualize( + format=request.options.visualization.format, + view=False, + show_summary=request.options.visualization.show_summary + ) + response["visualization"] = { + "url": f"/api/v1/visualizations/{workflow_id}.{request.options.visualization.format}", + "format": request.options.visualization.format, + "local_path": viz_path + } + except Exception as e: + response["visualization"] = { + "error": str(e), + "fallback": workflow.visualize_simple() + } + + return response + + except Exception as e: + execution_time = time.time() - start_time + raise HTTPException( + status_code=400, + detail={ + "status": "error", + "error_code": "EXECUTION_ERROR", + "message": str(e), + "execution_time": execution_time, + "workflow_id": workflow_id + } + ) + +def calculate_parallel_efficiency(workflow): + """Calculate parallel execution efficiency percentage.""" + if not workflow._compiled or not workflow._sorted_layers: + return 0.0 + + total_nodes = len(workflow.nodes) + max_parallel = max(len(layer) for layer in workflow._sorted_layers) + + if total_nodes == 0: + return 0.0 + + return (max_parallel / total_nodes) * 100 + +# Additional endpoints for workflow management +@app.get("/api/v1/graph-workflow/{workflow_id}/status") +async def get_workflow_status(workflow_id: str): + """Get the status of a workflow execution.""" + # Implementation for retrieving workflow status + pass + +@app.delete("/api/v1/graph-workflow/{workflow_id}") +async def delete_workflow(workflow_id: str): + """Delete a workflow and its associated resources.""" + # Implementation for cleaning up workflow resources + pass +``` + +## Usage Examples + +### Basic Investment Analysis Workflow + +```json +{ + "workflow_config": { + "name": "Investment Analysis Workflow", + "description": "Multi-agent workflow for comprehensive investment analysis", + "max_loops": 1, + "verbose": false + }, + "agents": [ + { + "id": "data_gatherer", + "agent_name": "Data Gathering Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a financial data gathering specialist. Collect relevant financial data, news, and market information.", + "temperature": 0.3 + }, + { + "id": "fundamental_analyst", + "agent_name": "Fundamental Analysis Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a fundamental analysis expert. Analyze company financials, business model, and competitive position.", + "temperature": 0.5 + }, + { + "id": "technical_analyst", + "agent_name": "Technical Analysis Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a technical analysis specialist. Analyze price charts, trends, and trading patterns.", + "temperature": 0.5 + }, + { + "id": "synthesis_agent", + "agent_name": "Synthesis Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a synthesis expert. Combine all analysis outputs into comprehensive investment recommendations.", + "temperature": 0.7 + } + ], + "connections": [ + { + "type": "fan_out", + "source": "data_gatherer", + "targets": ["fundamental_analyst", "technical_analyst"] + }, + { + "type": "fan_in", + "sources": ["fundamental_analyst", "technical_analyst"], + "target": "synthesis_agent" + } + ], + "task": "Analyze the investment potential of Tesla (TSLA) stock based on current market conditions, financial performance, and technical indicators. Provide a comprehensive recommendation with risk assessment.", + "options": { + "include_conversation": true, + "visualization": { + "enabled": true, + "format": "png", + "show_summary": true + } + } +} +``` + +### Content Creation Workflow + +```json +{ + "workflow_config": { + "name": "Content Creation Workflow", + "description": "Multi-stage content creation with research, writing, and review", + "max_loops": 1 + }, + "agents": [ + { + "id": "researcher", + "agent_name": "Research Agent", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a research specialist. Gather comprehensive information on the given topic.", + "temperature": 0.3 + }, + { + "id": "writer", + "agent_name": "Content Writer", + "model_name": "gpt-4o-mini", + "system_prompt": "You are a professional content writer. Create engaging, well-structured content based on research.", + "temperature": 0.7 + }, + { + "id": "editor", + "agent_name": "Editor", + "model_name": "gpt-4o-mini", + "system_prompt": "You are an expert editor. Review and improve content for clarity, accuracy, and engagement.", + "temperature": 0.5 + } + ], + "connections": [ + { + "type": "simple", + "source": "researcher", + "target": "writer" + }, + { + "type": "simple", + "source": "writer", + "target": "editor" + } + ], + "task": "Create a comprehensive blog post about the future of artificial intelligence in healthcare, including current applications, challenges, and future prospects.", + "options": { + "include_conversation": true + } +} +``` + +## Error Handling + +### Common Error Codes + +- `VALIDATION_ERROR`: Invalid workflow configuration +- `AGENT_CREATION_ERROR`: Failed to create agent instances +- `CONNECTION_ERROR`: Invalid connections between agents +- `EXECUTION_ERROR`: Workflow execution failed +- `VISUALIZATION_ERROR`: Failed to generate visualization +- `TIMEOUT_ERROR`: Workflow execution timed out + +### Error Response Format + +```json +{ + "status": "error", + "error_code": "VALIDATION_ERROR", + "message": "Invalid workflow configuration", + "details": { + "field": "connections", + "issue": "Source node 'invalid_node' does not exist", + "suggestions": [ + "Check node IDs in connections", + "Verify all referenced nodes exist" + ] + }, + "timestamp": "2024-01-15T10:30:00Z", + "workflow_id": "uuid-string" +} +``` + +## Rate Limiting and Quotas + +- **Rate Limit**: 10 requests per minute per API key +- **Timeout**: 300 seconds (5 minutes) for workflow execution +- **Max Agents**: 50 agents per workflow +- **Max Connections**: 200 connections per workflow +- **Payload Size**: 10MB maximum request size + +## Authentication + +The API requires authentication using API keys: + +``` +Authorization: Bearer your-api-key-here +``` + +## Monitoring and Logging + +- All workflow executions are logged with execution time and results +- Failed executions are logged with detailed error information +- Performance metrics are collected for optimization +- Workflow visualizations are cached for 24 hours + +## Best Practices + +1. **Agent Design**: Use clear, specific system prompts for each agent +2. **Connection Patterns**: Leverage fan-out and fan-in patterns for parallel processing +3. **Task Definition**: Provide clear, specific tasks for better results +4. **Error Handling**: Always check the response status and handle errors appropriately +5. **Resource Management**: Clean up workflows when no longer needed +6. **Testing**: Test workflows with smaller datasets before scaling up + +## Future Enhancements + +- **Streaming Responses**: Real-time workflow execution updates +- **Workflow Templates**: Pre-built workflow configurations +- **Scheduling**: Automated workflow execution on schedules +- **Versioning**: Workflow version control and rollback +- **Collaboration**: Multi-user workflow editing and sharing +- **Advanced Analytics**: Detailed performance and efficiency metrics \ No newline at end of file diff --git a/examples/multi_agent/graphworkflow_examples/test_graphworlfolw_validation.py b/examples/multi_agent/graphworkflow_examples/test_graphworlfolw_validation.py index 70e00ae4..13c2347c 100644 --- a/examples/multi_agent/graphworkflow_examples/test_graphworlfolw_validation.py +++ b/examples/multi_agent/graphworkflow_examples/test_graphworlfolw_validation.py @@ -13,10 +13,19 @@ print("Creating simple workflow...") wf = GraphWorkflow(name="Demo-Workflow", verbose=True) -agent1 = Agent(agent_name="DataCollector", model_name="claude-3-7-sonnet-20250219") -agent2 = Agent(agent_name="Analyzer", model_name="claude-3-7-sonnet-20250219") -agent3 = Agent(agent_name="Reporter", model_name="claude-3-7-sonnet-20250219") -agent4 = Agent(agent_name="Isolated", model_name="claude-3-7-sonnet-20250219") # Isolated node +agent1 = Agent( + agent_name="DataCollector", + model_name="claude-3-7-sonnet-20250219", +) +agent2 = Agent( + agent_name="Analyzer", model_name="claude-3-7-sonnet-20250219" +) +agent3 = Agent( + agent_name="Reporter", model_name="claude-3-7-sonnet-20250219" +) +agent4 = Agent( + agent_name="Isolated", model_name="claude-3-7-sonnet-20250219" +) # Isolated node wf.add_node(agent1) @@ -50,9 +59,15 @@ print("\n\nCreating workflow with cycles...") wf2 = GraphWorkflow(name="Cyclic-Workflow", verbose=True) -wf2.add_node(Agent(agent_name="A", model_name="claude-3-7-sonnet-20250219")) -wf2.add_node(Agent(agent_name="B", model_name="claude-3-7-sonnet-20250219")) -wf2.add_node(Agent(agent_name="C", model_name="claude-3-7-sonnet-20250219")) +wf2.add_node( + Agent(agent_name="A", model_name="claude-3-7-sonnet-20250219") +) +wf2.add_node( + Agent(agent_name="B", model_name="claude-3-7-sonnet-20250219") +) +wf2.add_node( + Agent(agent_name="C", model_name="claude-3-7-sonnet-20250219") +) wf2.add_edge("A", "B") @@ -65,4 +80,4 @@ result = wf2.validate() print(f"Workflow is valid: {result['is_valid']}") print(f"Warnings: {result['warnings']}") if "cycles" in result: - print(f"Detected cycles: {result['cycles']}") \ No newline at end of file + print(f"Detected cycles: {result['cycles']}") diff --git a/swarms/structs/cron_job.py b/swarms/structs/cron_job.py index 79bd1090..6bdd0826 100644 --- a/swarms/structs/cron_job.py +++ b/swarms/structs/cron_job.py @@ -10,7 +10,6 @@ from loguru import logger # from swarms import Agent - class CronJobError(Exception): """Base exception class for CronJob errors.""" diff --git a/swarms/structs/graph_workflow.py b/swarms/structs/graph_workflow.py index 6cdccfaa..4a2b0c90 100644 --- a/swarms/structs/graph_workflow.py +++ b/swarms/structs/graph_workflow.py @@ -2176,136 +2176,181 @@ class GraphWorkflow: f"Failed to load GraphWorkflow from {filepath}: {e}" ) raise e + def validate(self, auto_fix=False) -> Dict[str, Any]: """ Validate the workflow structure, checking for potential issues such as isolated nodes, cyclic dependencies, etc. - + Args: auto_fix (bool): Whether to automatically fix some simple issues (like auto-setting entry/exit points) - + Returns: Dict[str, Any]: Dictionary containing validation results, including validity, warnings and errors """ if self.verbose: - logger.debug(f"Validating GraphWorkflow structure (auto_fix={auto_fix})") - + logger.debug( + f"Validating GraphWorkflow structure (auto_fix={auto_fix})" + ) + result = { "is_valid": True, "warnings": [], "errors": [], - "fixed": [] + "fixed": [], } - + try: # Check for empty graph if not self.nodes: result["errors"].append("Workflow has no nodes") result["is_valid"] = False return result - + if not self.edges: - result["warnings"].append("Workflow has no edges between nodes") - + result["warnings"].append( + "Workflow has no edges between nodes" + ) + # Check for node agent instance validity invalid_agents = [] for node_id, node in self.nodes.items(): if node.agent is None: invalid_agents.append(node_id) - + if invalid_agents: - result["errors"].append(f"Found {len(invalid_agents)} nodes with invalid agent instances: {invalid_agents}") + result["errors"].append( + f"Found {len(invalid_agents)} nodes with invalid agent instances: {invalid_agents}" + ) result["is_valid"] = False - + # Check for isolated nodes (no incoming or outgoing edges) - isolated = [n for n in self.nodes if self.graph.in_degree(n) == 0 and self.graph.out_degree(n) == 0] + isolated = [ + n + for n in self.nodes + if self.graph.in_degree(n) == 0 + and self.graph.out_degree(n) == 0 + ] if isolated: - result["warnings"].append(f"Found {len(isolated)} isolated nodes: {isolated}") - + result["warnings"].append( + f"Found {len(isolated)} isolated nodes: {isolated}" + ) + # Check for cyclic dependencies try: cycles = list(nx.simple_cycles(self.graph)) if cycles: - result["warnings"].append(f"Found {len(cycles)} cycles in workflow") + result["warnings"].append( + f"Found {len(cycles)} cycles in workflow" + ) result["cycles"] = cycles except Exception as e: - result["warnings"].append(f"Could not check for cycles: {e}") - + result["warnings"].append( + f"Could not check for cycles: {e}" + ) + # Check entry points if not self.entry_points: result["warnings"].append("No entry points defined") if auto_fix: self.auto_set_entry_points() result["fixed"].append("Auto-set entry points") - + # Check exit points if not self.end_points: result["warnings"].append("No end points defined") if auto_fix: self.auto_set_end_points() result["fixed"].append("Auto-set end points") - + # Check for unreachable nodes (not reachable from entry points) if self.entry_points: reachable = set() for entry in self.entry_points: - reachable.update(nx.descendants(self.graph, entry)) + reachable.update( + nx.descendants(self.graph, entry) + ) reachable.add(entry) - + unreachable = set(self.nodes.keys()) - reachable if unreachable: - result["warnings"].append(f"Found {len(unreachable)} nodes unreachable from entry points: {unreachable}") + result["warnings"].append( + f"Found {len(unreachable)} nodes unreachable from entry points: {unreachable}" + ) if auto_fix and unreachable: # Add unreachable nodes as entry points - updated_entries = self.entry_points + list(unreachable) + updated_entries = self.entry_points + list( + unreachable + ) self.set_entry_points(updated_entries) - result["fixed"].append(f"Added {len(unreachable)} unreachable nodes to entry points") - + result["fixed"].append( + f"Added {len(unreachable)} unreachable nodes to entry points" + ) + # Check for dead-end nodes (cannot reach any exit point) if self.end_points: reverse_graph = self.graph.reverse() reachable_to_exit = set() for exit_point in self.end_points: - reachable_to_exit.update(nx.descendants(reverse_graph, exit_point)) + reachable_to_exit.update( + nx.descendants(reverse_graph, exit_point) + ) reachable_to_exit.add(exit_point) - + dead_ends = set(self.nodes.keys()) - reachable_to_exit if dead_ends: - result["warnings"].append(f"Found {len(dead_ends)} nodes that cannot reach any exit point: {dead_ends}") + result["warnings"].append( + f"Found {len(dead_ends)} nodes that cannot reach any exit point: {dead_ends}" + ) if auto_fix and dead_ends: # Add dead-end nodes as exit points - updated_exits = self.end_points + list(dead_ends) + updated_exits = self.end_points + list( + dead_ends + ) self.set_end_points(updated_exits) - result["fixed"].append(f"Added {len(dead_ends)} dead-end nodes to exit points") - + result["fixed"].append( + f"Added {len(dead_ends)} dead-end nodes to exit points" + ) + # Check for serious warnings has_serious_warnings = any( - "cycle" in warning.lower() or "unreachable" in warning.lower() + "cycle" in warning.lower() + or "unreachable" in warning.lower() for warning in result["warnings"] ) - + # If there are errors or serious warnings without fixes, the workflow is invalid - if result["errors"] or (has_serious_warnings and not auto_fix): + if result["errors"] or ( + has_serious_warnings and not auto_fix + ): result["is_valid"] = False - + if self.verbose: if result["is_valid"]: if result["warnings"]: - logger.warning(f"Validation found {len(result['warnings'])} warnings but workflow is still valid") + logger.warning( + f"Validation found {len(result['warnings'])} warnings but workflow is still valid" + ) else: - logger.success("Workflow validation completed with no issues") + logger.success( + "Workflow validation completed with no issues" + ) else: - logger.error(f"Validation found workflow to be invalid with {len(result['errors'])} errors and {len(result['warnings'])} warnings") - + logger.error( + f"Validation found workflow to be invalid with {len(result['errors'])} errors and {len(result['warnings'])} warnings" + ) + if result["fixed"]: - logger.info(f"Auto-fixed {len(result['fixed'])} issues: {', '.join(result['fixed'])}") - + logger.info( + f"Auto-fixed {len(result['fixed'])} issues: {', '.join(result['fixed'])}" + ) + return result except Exception as e: result["is_valid"] = False result["errors"].append(str(e)) logger.exception(f"Error during workflow validation: {e}") - return result + return result def export_summary(self) -> Dict[str, Any]: """ From e71bd68509fda171ff0e95a9cce720ec4e7c6857 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 3 Aug 2025 17:42:23 -0700 Subject: [PATCH 45/73] readme --- docs/api/graph_workflow_api_endpoint.md | 631 ------------------------ 1 file changed, 631 deletions(-) delete mode 100644 docs/api/graph_workflow_api_endpoint.md diff --git a/docs/api/graph_workflow_api_endpoint.md b/docs/api/graph_workflow_api_endpoint.md deleted file mode 100644 index 7b3b2ffb..00000000 --- a/docs/api/graph_workflow_api_endpoint.md +++ /dev/null @@ -1,631 +0,0 @@ -# GraphWorkflow API Endpoint Design - -## Overview - -This document outlines the design for a single API endpoint that allows users to create, configure, and execute GraphWorkflow instances. The endpoint provides a comprehensive interface for leveraging the GraphWorkflow functionality with minimal setup. - -## Base URL - -``` -POST /api/v1/graph-workflow/execute -``` - -## Request Schema - -### Main Request Body - -```json -{ - "workflow_config": { - "name": "string", - "description": "string", - "max_loops": 1, - "auto_compile": true, - "verbose": false - }, - "agents": [ - { - "id": "string", - "agent_name": "string", - "model_name": "string", - "system_prompt": "string", - "temperature": 0.7, - "max_tokens": 4000, - "max_loops": 1, - "metadata": {} - } - ], - "connections": [ - { - "type": "simple", - "source": "string", - "target": "string", - "metadata": {} - } - ], - "entry_points": ["string"], - "end_points": ["string"], - "task": "string", - "options": { - "include_conversation": false, - "include_runtime_state": false, - "visualization": { - "enabled": false, - "format": "png", - "show_summary": true - } - } -} -``` - -### Detailed Schema Definitions - -#### WorkflowConfig -```json -{ - "name": "Investment Analysis Workflow", - "description": "Multi-agent workflow for comprehensive investment analysis", - "max_loops": 1, - "auto_compile": true, - "verbose": false -} -``` - -#### Agent Definition -```json -{ - "id": "fundamental_analyst", - "agent_name": "Fundamental Analysis Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a fundamental analysis expert specializing in financial analysis...", - "temperature": 0.7, - "max_tokens": 4000, - "max_loops": 1, - "autosave": true, - "dashboard": false, - "metadata": { - "specialization": "financial_analysis", - "expertise_level": "expert" - } -} -``` - -#### Connection Types - -##### Simple Connection -```json -{ - "type": "simple", - "source": "data_gatherer", - "target": "fundamental_analyst", - "metadata": { - "priority": "high" - } -} -``` - -##### Fan-out Connection -```json -{ - "type": "fan_out", - "source": "data_gatherer", - "targets": ["fundamental_analyst", "technical_analyst", "sentiment_analyst"], - "metadata": { - "parallel_execution": true - } -} -``` - -##### Fan-in Connection -```json -{ - "type": "fan_in", - "sources": ["fundamental_analyst", "technical_analyst", "sentiment_analyst"], - "target": "synthesis_agent", - "metadata": { - "aggregation_method": "combine_all" - } -} -``` - -##### Parallel Chain -```json -{ - "type": "parallel_chain", - "sources": ["data_gatherer_1", "data_gatherer_2"], - "targets": ["analyst_1", "analyst_2", "analyst_3"], - "metadata": { - "full_mesh": true - } -} -``` - -## Response Schema - -### Success Response -```json -{ - "status": "success", - "workflow_id": "uuid-string", - "execution_time": 45.23, - "results": { - "fundamental_analyst": "Analysis output from fundamental analyst...", - "technical_analyst": "Technical analysis results...", - "synthesis_agent": "Combined analysis and recommendations..." - }, - "conversation": { - "history": [ - { - "role": "fundamental_analyst", - "content": "Analysis output...", - "timestamp": "2024-01-15T10:30:00Z" - } - ] - }, - "metrics": { - "total_agents": 5, - "total_connections": 6, - "execution_layers": 3, - "parallel_efficiency": 85.5 - }, - "visualization": { - "url": "https://api.example.com/visualizations/workflow_123.png", - "format": "png" - }, - "workflow_summary": { - "name": "Investment Analysis Workflow", - "description": "Multi-agent workflow for comprehensive investment analysis", - "entry_points": ["data_gatherer"], - "end_points": ["synthesis_agent"], - "compilation_status": "compiled" - } -} -``` - -### Error Response -```json -{ - "status": "error", - "error_code": "VALIDATION_ERROR", - "message": "Invalid workflow configuration", - "details": { - "field": "connections", - "issue": "Source node 'invalid_node' does not exist", - "suggestions": ["Check node IDs in connections", "Verify all referenced nodes exist"] - }, - "timestamp": "2024-01-15T10:30:00Z" -} -``` - -## Implementation Example - -### Python FastAPI Implementation - -```python -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel, Field -from typing import List, Dict, Any, Optional -import uuid -import time -from swarms import Agent, GraphWorkflow, Node, NodeType, Edge - -app = FastAPI(title="GraphWorkflow API", version="1.0.0") - -# Pydantic Models -class WorkflowConfig(BaseModel): - name: str = "Graph-Workflow-01" - description: str = "A customizable workflow system" - max_loops: int = 1 - auto_compile: bool = True - verbose: bool = False - -class AgentDefinition(BaseModel): - id: str - agent_name: str - model_name: str = "gpt-4o-mini" - system_prompt: Optional[str] = None - temperature: float = 0.7 - max_tokens: int = 4000 - max_loops: int = 1 - autosave: bool = True - dashboard: bool = False - metadata: Dict[str, Any] = Field(default_factory=dict) - -class SimpleConnection(BaseModel): - type: str = "simple" - source: str - target: str - metadata: Dict[str, Any] = Field(default_factory=dict) - -class FanOutConnection(BaseModel): - type: str = "fan_out" - source: str - targets: List[str] - metadata: Dict[str, Any] = Field(default_factory=dict) - -class FanInConnection(BaseModel): - type: str = "fan_in" - sources: List[str] - target: str - metadata: Dict[str, Any] = Field(default_factory=dict) - -class ParallelChainConnection(BaseModel): - type: str = "parallel_chain" - sources: List[str] - targets: List[str] - metadata: Dict[str, Any] = Field(default_factory=dict) - -class VisualizationOptions(BaseModel): - enabled: bool = False - format: str = "png" - show_summary: bool = True - -class WorkflowOptions(BaseModel): - include_conversation: bool = False - include_runtime_state: bool = False - visualization: VisualizationOptions = Field(default_factory=VisualizationOptions) - -class GraphWorkflowRequest(BaseModel): - workflow_config: WorkflowConfig - agents: List[AgentDefinition] - connections: List[Dict[str, Any]] # Union of all connection types - entry_points: Optional[List[str]] = None - end_points: Optional[List[str]] = None - task: str - options: WorkflowOptions = Field(default_factory=WorkflowOptions) - -@app.post("/api/v1/graph-workflow/execute") -async def execute_graph_workflow(request: GraphWorkflowRequest): - """ - Execute a GraphWorkflow with the provided configuration. - - This endpoint creates a workflow from the provided agents and connections, - executes it with the given task, and returns the results. - """ - start_time = time.time() - workflow_id = str(uuid.uuid4()) - - try: - # Create agents from definitions - agent_instances = {} - for agent_def in request.agents: - agent = Agent( - agent_name=agent_def.agent_name, - model_name=agent_def.model_name, - system_prompt=agent_def.system_prompt, - temperature=agent_def.temperature, - max_tokens=agent_def.max_tokens, - max_loops=agent_def.max_loops, - autosave=agent_def.autosave, - dashboard=agent_def.dashboard, - ) - agent_instances[agent_def.id] = agent - - # Create workflow - workflow = GraphWorkflow( - id=workflow_id, - name=request.workflow_config.name, - description=request.workflow_config.description, - max_loops=request.workflow_config.max_loops, - auto_compile=request.workflow_config.auto_compile, - verbose=request.workflow_config.verbose, - ) - - # Add agents to workflow - for agent_def in request.agents: - workflow.add_node(agent_instances[agent_def.id]) - - # Add connections - for connection in request.connections: - conn_type = connection.get("type", "simple") - - if conn_type == "simple": - workflow.add_edge(connection["source"], connection["target"]) - elif conn_type == "fan_out": - workflow.add_edges_from_source( - connection["source"], - connection["targets"] - ) - elif conn_type == "fan_in": - workflow.add_edges_to_target( - connection["sources"], - connection["target"] - ) - elif conn_type == "parallel_chain": - workflow.add_parallel_chain( - connection["sources"], - connection["targets"] - ) - - # Set entry and end points - if request.entry_points: - workflow.set_entry_points(request.entry_points) - else: - workflow.auto_set_entry_points() - - if request.end_points: - workflow.set_end_points(request.end_points) - else: - workflow.auto_set_end_points() - - # Execute workflow - results = workflow.run(request.task) - - # Prepare response - execution_time = time.time() - start_time - - response = { - "status": "success", - "workflow_id": workflow_id, - "execution_time": execution_time, - "results": results, - "metrics": { - "total_agents": len(workflow.nodes), - "total_connections": len(workflow.edges), - "execution_layers": len(workflow._sorted_layers) if workflow._compiled else 0, - "parallel_efficiency": calculate_parallel_efficiency(workflow) - }, - "workflow_summary": { - "name": workflow.name, - "description": workflow.description, - "entry_points": workflow.entry_points, - "end_points": workflow.end_points, - "compilation_status": "compiled" if workflow._compiled else "not_compiled" - } - } - - # Add conversation if requested - if request.options.include_conversation and workflow.conversation: - response["conversation"] = { - "history": workflow.conversation.history - } - - # Add visualization if requested - if request.options.visualization.enabled: - try: - viz_path = workflow.visualize( - format=request.options.visualization.format, - view=False, - show_summary=request.options.visualization.show_summary - ) - response["visualization"] = { - "url": f"/api/v1/visualizations/{workflow_id}.{request.options.visualization.format}", - "format": request.options.visualization.format, - "local_path": viz_path - } - except Exception as e: - response["visualization"] = { - "error": str(e), - "fallback": workflow.visualize_simple() - } - - return response - - except Exception as e: - execution_time = time.time() - start_time - raise HTTPException( - status_code=400, - detail={ - "status": "error", - "error_code": "EXECUTION_ERROR", - "message": str(e), - "execution_time": execution_time, - "workflow_id": workflow_id - } - ) - -def calculate_parallel_efficiency(workflow): - """Calculate parallel execution efficiency percentage.""" - if not workflow._compiled or not workflow._sorted_layers: - return 0.0 - - total_nodes = len(workflow.nodes) - max_parallel = max(len(layer) for layer in workflow._sorted_layers) - - if total_nodes == 0: - return 0.0 - - return (max_parallel / total_nodes) * 100 - -# Additional endpoints for workflow management -@app.get("/api/v1/graph-workflow/{workflow_id}/status") -async def get_workflow_status(workflow_id: str): - """Get the status of a workflow execution.""" - # Implementation for retrieving workflow status - pass - -@app.delete("/api/v1/graph-workflow/{workflow_id}") -async def delete_workflow(workflow_id: str): - """Delete a workflow and its associated resources.""" - # Implementation for cleaning up workflow resources - pass -``` - -## Usage Examples - -### Basic Investment Analysis Workflow - -```json -{ - "workflow_config": { - "name": "Investment Analysis Workflow", - "description": "Multi-agent workflow for comprehensive investment analysis", - "max_loops": 1, - "verbose": false - }, - "agents": [ - { - "id": "data_gatherer", - "agent_name": "Data Gathering Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a financial data gathering specialist. Collect relevant financial data, news, and market information.", - "temperature": 0.3 - }, - { - "id": "fundamental_analyst", - "agent_name": "Fundamental Analysis Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a fundamental analysis expert. Analyze company financials, business model, and competitive position.", - "temperature": 0.5 - }, - { - "id": "technical_analyst", - "agent_name": "Technical Analysis Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a technical analysis specialist. Analyze price charts, trends, and trading patterns.", - "temperature": 0.5 - }, - { - "id": "synthesis_agent", - "agent_name": "Synthesis Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a synthesis expert. Combine all analysis outputs into comprehensive investment recommendations.", - "temperature": 0.7 - } - ], - "connections": [ - { - "type": "fan_out", - "source": "data_gatherer", - "targets": ["fundamental_analyst", "technical_analyst"] - }, - { - "type": "fan_in", - "sources": ["fundamental_analyst", "technical_analyst"], - "target": "synthesis_agent" - } - ], - "task": "Analyze the investment potential of Tesla (TSLA) stock based on current market conditions, financial performance, and technical indicators. Provide a comprehensive recommendation with risk assessment.", - "options": { - "include_conversation": true, - "visualization": { - "enabled": true, - "format": "png", - "show_summary": true - } - } -} -``` - -### Content Creation Workflow - -```json -{ - "workflow_config": { - "name": "Content Creation Workflow", - "description": "Multi-stage content creation with research, writing, and review", - "max_loops": 1 - }, - "agents": [ - { - "id": "researcher", - "agent_name": "Research Agent", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a research specialist. Gather comprehensive information on the given topic.", - "temperature": 0.3 - }, - { - "id": "writer", - "agent_name": "Content Writer", - "model_name": "gpt-4o-mini", - "system_prompt": "You are a professional content writer. Create engaging, well-structured content based on research.", - "temperature": 0.7 - }, - { - "id": "editor", - "agent_name": "Editor", - "model_name": "gpt-4o-mini", - "system_prompt": "You are an expert editor. Review and improve content for clarity, accuracy, and engagement.", - "temperature": 0.5 - } - ], - "connections": [ - { - "type": "simple", - "source": "researcher", - "target": "writer" - }, - { - "type": "simple", - "source": "writer", - "target": "editor" - } - ], - "task": "Create a comprehensive blog post about the future of artificial intelligence in healthcare, including current applications, challenges, and future prospects.", - "options": { - "include_conversation": true - } -} -``` - -## Error Handling - -### Common Error Codes - -- `VALIDATION_ERROR`: Invalid workflow configuration -- `AGENT_CREATION_ERROR`: Failed to create agent instances -- `CONNECTION_ERROR`: Invalid connections between agents -- `EXECUTION_ERROR`: Workflow execution failed -- `VISUALIZATION_ERROR`: Failed to generate visualization -- `TIMEOUT_ERROR`: Workflow execution timed out - -### Error Response Format - -```json -{ - "status": "error", - "error_code": "VALIDATION_ERROR", - "message": "Invalid workflow configuration", - "details": { - "field": "connections", - "issue": "Source node 'invalid_node' does not exist", - "suggestions": [ - "Check node IDs in connections", - "Verify all referenced nodes exist" - ] - }, - "timestamp": "2024-01-15T10:30:00Z", - "workflow_id": "uuid-string" -} -``` - -## Rate Limiting and Quotas - -- **Rate Limit**: 10 requests per minute per API key -- **Timeout**: 300 seconds (5 minutes) for workflow execution -- **Max Agents**: 50 agents per workflow -- **Max Connections**: 200 connections per workflow -- **Payload Size**: 10MB maximum request size - -## Authentication - -The API requires authentication using API keys: - -``` -Authorization: Bearer your-api-key-here -``` - -## Monitoring and Logging - -- All workflow executions are logged with execution time and results -- Failed executions are logged with detailed error information -- Performance metrics are collected for optimization -- Workflow visualizations are cached for 24 hours - -## Best Practices - -1. **Agent Design**: Use clear, specific system prompts for each agent -2. **Connection Patterns**: Leverage fan-out and fan-in patterns for parallel processing -3. **Task Definition**: Provide clear, specific tasks for better results -4. **Error Handling**: Always check the response status and handle errors appropriately -5. **Resource Management**: Clean up workflows when no longer needed -6. **Testing**: Test workflows with smaller datasets before scaling up - -## Future Enhancements - -- **Streaming Responses**: Real-time workflow execution updates -- **Workflow Templates**: Pre-built workflow configurations -- **Scheduling**: Automated workflow execution on schedules -- **Versioning**: Workflow version control and rollback -- **Collaboration**: Multi-user workflow editing and sharing -- **Advanced Analytics**: Detailed performance and efficiency metrics \ No newline at end of file From 80f5bb3fbacf2fd86b1035b76edaa4dba4a9f246 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 00:35:49 +0530 Subject: [PATCH 46/73] docs for deployments ! --- .../cloudflare_workers.md | 581 ++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 docs/deployment_solutions/cloudflare_workers.md diff --git a/docs/deployment_solutions/cloudflare_workers.md b/docs/deployment_solutions/cloudflare_workers.md new file mode 100644 index 00000000..c1d12bb9 --- /dev/null +++ b/docs/deployment_solutions/cloudflare_workers.md @@ -0,0 +1,581 @@ +# Cloudflare Workers Deployment with Swarms API + +Deploy intelligent agent swarms on Cloudflare's edge network for maximum performance, global availability, and automatic scaling. This guide covers deploying both API endpoints and scheduled cron job agents using the Swarms API. + +## Overview + +Cloudflare Workers provide a serverless execution environment that runs on Cloudflare's global network, offering: + +- **Edge Computing**: Deploy agents close to users worldwide +- **Automatic Scaling**: Handle traffic spikes without configuration +- **Zero Cold Starts**: Instant response times +- **Cost Effective**: Pay only for what you use +- **Built-in Cron Jobs**: Schedule automated agent tasks + +Perfect for deploying AI agents that need global reach, low latency, and scheduled execution capabilities. + +## Prerequisites + +!!! info "Requirements" + + - Cloudflare account (free tier available) + - Wrangler CLI installed (`npm install -g wrangler`) + - Swarms API key from [Swarms Platform](https://swarms.world/platform/api-keys) + - Node.js 16+ for local development + +## Quick Start + +### 1. Project Setup + +```bash +# Create new Cloudflare Worker +npx create-cloudflare my-swarms-worker worker + +# Navigate to project +cd my-swarms-worker + +# Install dependencies +npm install +``` + +### 2. Configure Environment Variables + +Add your Swarms API key to `wrangler.toml`: + +```toml +name = "my-swarms-worker" +main = "src/index.js" +compatibility_date = "2024-01-01" + +[env.production.vars] +SWARMS_API_KEY = "your-api-key-here" + +[env.production] +# For scheduled agents +[[env.production.triggers.crons]] +cron = "0 9 * * MON-FRI" # Weekdays at 9 AM UTC +``` + +### 3. Basic Agent Worker + +=== "JavaScript" + + ```javascript + export default { + async fetch(request, env, ctx) { + // Handle CORS preflight + if (request.method === 'OPTIONS') { + return handleCORS(); + } + + try { + const { pathname } = new URL(request.url); + + if (pathname === '/api/agent' && request.method === 'POST') { + return await handleAgentRequest(request, env); + } + + if (pathname === '/api/health' && request.method === 'GET') { + return new Response(JSON.stringify({ + status: 'healthy', + service: 'Swarms Agent API', + version: '1.0.0' + }), { + status: 200, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + + return new Response('Not Found', { status: 404 }); + } catch (error) { + console.error('Worker error:', error); + return new Response(JSON.stringify({ + error: error.message + }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + }, + + // Scheduled event handler for cron jobs + async scheduled(event, env, ctx) { + ctx.waitUntil(handleScheduledEvent(event, env)); + } + }; + + async function handleAgentRequest(request, env) { + const requestData = await request.json(); + + const agentConfig = { + agent_config: { + agent_name: requestData.agent_name || "CloudflareAgent", + description: requestData.description || "Agent running on Cloudflare Workers", + system_prompt: requestData.system_prompt || "You are a helpful AI assistant.", + model_name: requestData.model_name || "gpt-4o-mini", + max_tokens: requestData.max_tokens || 2000, + temperature: requestData.temperature || 0.7 + }, + task: requestData.task + }; + + const response = await fetch('https://api.swarms.world/v1/agent/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(agentConfig) + }); + + const result = await response.json(); + + return new Response(JSON.stringify(result), { + status: response.status, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + + function getCORSHeaders() { + return { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }; + } + + function handleCORS() { + return new Response(null, { + status: 200, + headers: getCORSHeaders() + }); + } + ``` + +=== "TypeScript" + + ```typescript + interface AgentRequest { + agent_name?: string; + description?: string; + system_prompt?: string; + model_name?: string; + max_tokens?: number; + temperature?: number; + task: string; + } + + interface AgentConfig { + agent_config: { + agent_name: string; + description: string; + system_prompt: string; + model_name: string; + max_tokens: number; + temperature: number; + }; + task: string; + } + + interface Env { + SWARMS_API_KEY: string; + } + + export interface ScheduledEvent { + scheduledTime: number; + cron: string; + } + + export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + if (request.method === 'OPTIONS') { + return handleCORS(); + } + + try { + const { pathname } = new URL(request.url); + + if (pathname === '/api/agent' && request.method === 'POST') { + return await handleAgentRequest(request, env); + } + + if (pathname === '/api/health' && request.method === 'GET') { + return new Response(JSON.stringify({ + status: 'healthy', + service: 'Swarms Agent API', + version: '1.0.0' + }), { + status: 200, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + + return new Response('Not Found', { status: 404 }); + } catch (error) { + console.error('Worker error:', error); + return new Response(JSON.stringify({ + error: (error as Error).message + }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + }, + + async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise { + ctx.waitUntil(handleScheduledEvent(event, env)); + } + }; + + async function handleAgentRequest(request: Request, env: Env): Promise { + const requestData: AgentRequest = await request.json(); + + const agentConfig: AgentConfig = { + agent_config: { + agent_name: requestData.agent_name || "CloudflareAgent", + description: requestData.description || "Agent running on Cloudflare Workers", + system_prompt: requestData.system_prompt || "You are a helpful AI assistant.", + model_name: requestData.model_name || "gpt-4o-mini", + max_tokens: requestData.max_tokens || 2000, + temperature: requestData.temperature || 0.7 + }, + task: requestData.task + }; + + const response = await fetch('https://api.swarms.world/v1/agent/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(agentConfig) + }); + + const result = await response.json(); + + return new Response(JSON.stringify(result), { + status: response.status, + headers: { + 'Content-Type': 'application/json', + ...getCORSHeaders() + } + }); + } + + function getCORSHeaders(): Record { + return { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }; + } + + function handleCORS(): Response { + return new Response(null, { + status: 200, + headers: getCORSHeaders() + }); + } + ``` + +### 4. Deploy Your Worker + +```bash +# Deploy to Cloudflare +wrangler deploy + +# View logs +wrangler tail +``` + +## Scheduled Cron Job Agents + +Cloudflare Workers support scheduled events (cron jobs) that can trigger agent tasks automatically. This is perfect for periodic analysis, monitoring, reporting, and automated decision-making. + +### Stock Market Analysis Agent + +Deploy a cron job agent that analyzes stock market trends and generates daily reports: + +=== "JavaScript" + + ```javascript + // wrangler.toml configuration for stock market agent + /* + [[env.production.triggers.crons]] + cron = "0 16 * * MON-FRI" # 4 PM UTC (after US market close) + */ + + async function handleScheduledEvent(event, env) { + console.log('Stock market analysis triggered at:', new Date(event.scheduledTime)); + + try { + // Stock analysis swarm configuration + const swarmConfig = { + name: "Stock Market Analysis Swarm", + description: "Daily stock market analysis and trading insights", + agents: [ + { + agent_name: "Market Data Analyst", + description: "Analyzes market trends and price movements", + system_prompt: `You are an expert financial market analyst specializing in equity markets. + Analyze market trends, volume patterns, and price movements. + Provide technical analysis insights and identify key support/resistance levels. + Focus on major indices (S&P 500, NASDAQ, DOW) and sector performance.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.3, + role: "worker" + }, + { + agent_name: "Economic News Analyzer", + description: "Analyzes economic news impact on markets", + system_prompt: `You are a financial news analyst with expertise in market sentiment analysis. + Analyze recent economic news, earnings reports, and market-moving events. + Assess their potential impact on stock prices and market sentiment. + Identify key catalysts and risk factors for the next trading day.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.3, + role: "worker" + }, + { + agent_name: "Trading Strategy Advisor", + description: "Provides trading recommendations and risk assessment", + system_prompt: `You are a quantitative trading strategist with expertise in risk management. + Based on technical analysis and market sentiment, provide actionable trading insights. + Include risk assessment, position sizing recommendations, and key levels to watch. + Focus on risk-adjusted returns and downside protection strategies.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.4, + role: "worker" + } + ], + swarm_type: "SequentialWorkflow", + max_loops: 1, + task: `Analyze today's stock market performance and provide insights for tomorrow's trading session. + Include: 1) Market overview and key movers 2) Technical analysis of major indices + 3) Economic news impact assessment 4) Trading opportunities and risks + 5) Key levels to watch tomorrow. Format as a professional daily market report.`, + service_tier: "standard" + }; + + // Execute the swarm + const response = await fetch('https://api.swarms.world/v1/swarm/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(swarmConfig) + }); + + const result = await response.json(); + + if (response.ok) { + console.log('Stock analysis completed successfully'); + console.log('Cost:', result.metadata?.billing_info?.total_cost); + + // Store results in KV storage for retrieval + if (env.STOCK_REPORTS) { + const reportKey = `stock-report-${new Date().toISOString().split('T')[0]}`; + await env.STOCK_REPORTS.put(reportKey, JSON.stringify({ + timestamp: new Date().toISOString(), + analysis: result.output, + cost: result.metadata?.billing_info?.total_cost + })); + } + + // Optional: Send to webhook, email, or notification service + await sendNotification(env, result.output); + + } else { + console.error('Stock analysis failed:', result); + } + + } catch (error) { + console.error('Error in scheduled stock analysis:', error); + } + } + + async function sendNotification(env, analysis) { + // Example: Send to Slack webhook + if (env.SLACK_WEBHOOK_URL) { + try { + await fetch(env.SLACK_WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: "📈 Daily Stock Market Analysis Ready", + attachments: [{ + color: "good", + text: typeof analysis === 'string' ? analysis.substring(0, 500) + '...' : 'Analysis completed', + footer: "Swarms Stock Analysis Agent" + }] + }) + }); + } catch (error) { + console.error('Failed to send Slack notification:', error); + } + } + } + + // API endpoint to retrieve stock reports + async function getStockReport(request, env) { + const url = new URL(request.url); + const date = url.searchParams.get('date') || new Date().toISOString().split('T')[0]; + const reportKey = `stock-report-${date}`; + + if (env.STOCK_REPORTS) { + const report = await env.STOCK_REPORTS.get(reportKey); + if (report) { + return new Response(report, { + headers: { 'Content-Type': 'application/json' } + }); + } + } + + return new Response(JSON.stringify({ error: 'Report not found' }), { + status: 404, + headers: { 'Content-Type': 'application/json' } + }); + } + ``` + +=== "TypeScript" + + ```typescript + interface StockAnalysisResult { + timestamp: string; + analysis: any; + cost: number; + } + + async function handleScheduledEvent(event: ScheduledEvent, env: Env): Promise { + console.log('Stock market analysis triggered at:', new Date(event.scheduledTime)); + + try { + const swarmConfig = { + name: "Stock Market Analysis Swarm", + description: "Daily stock market analysis and trading insights", + agents: [ + { + agent_name: "Market Data Analyst", + description: "Analyzes market trends and price movements", + system_prompt: `You are an expert financial market analyst specializing in equity markets. + Analyze market trends, volume patterns, and price movements. + Provide technical analysis insights and identify key support/resistance levels. + Focus on major indices (S&P 500, NASDAQ, DOW) and sector performance.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.3, + role: "worker" + }, + { + agent_name: "Economic News Analyzer", + description: "Analyzes economic news impact on markets", + system_prompt: `You are a financial news analyst with expertise in market sentiment analysis. + Analyze recent economic news, earnings reports, and market-moving events. + Assess their potential impact on stock prices and market sentiment. + Identify key catalysts and risk factors for the next trading day.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.3, + role: "worker" + }, + { + agent_name: "Trading Strategy Advisor", + description: "Provides trading recommendations and risk assessment", + system_prompt: `You are a quantitative trading strategist with expertise in risk management. + Based on technical analysis and market sentiment, provide actionable trading insights. + Include risk assessment, position sizing recommendations, and key levels to watch. + Focus on risk-adjusted returns and downside protection strategies.`, + model_name: "gpt-4o", + max_tokens: 3000, + temperature: 0.4, + role: "worker" + } + ], + swarm_type: "SequentialWorkflow" as const, + max_loops: 1, + task: `Analyze today's stock market performance and provide insights for tomorrow's trading session. + Include: 1) Market overview and key movers 2) Technical analysis of major indices + 3) Economic news impact assessment 4) Trading opportunities and risks + 5) Key levels to watch tomorrow. Format as a professional daily market report.`, + service_tier: "standard" + }; + + const response = await fetch('https://api.swarms.world/v1/swarm/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(swarmConfig) + }); + + const result = await response.json(); + + if (response.ok) { + console.log('Stock analysis completed successfully'); + console.log('Cost:', result.metadata?.billing_info?.total_cost); + + if (env.STOCK_REPORTS) { + const reportKey = `stock-report-${new Date().toISOString().split('T')[0]}`; + const reportData: StockAnalysisResult = { + timestamp: new Date().toISOString(), + analysis: result.output, + cost: result.metadata?.billing_info?.total_cost + }; + + await env.STOCK_REPORTS.put(reportKey, JSON.stringify(reportData)); + } + + await sendNotification(env, result.output); + + } else { + console.error('Stock analysis failed:', result); + } + + } catch (error) { + console.error('Error in scheduled stock analysis:', error); + } + } + + async function sendNotification(env: Env, analysis: any): Promise { + if (env.SLACK_WEBHOOK_URL) { + try { + await fetch(env.SLACK_WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: "📈 Daily Stock Market Analysis Ready", + attachments: [{ + color: "good", + text: typeof analysis === 'string' ? analysis.substring(0, 500) + '...' : 'Analysis completed', + footer: "Swarms Stock Analysis Agent" + }] + }) + }); + } catch (error) { + console.error('Failed to send Slack notification:', error); + } + } + } + ``` + From cacc277013ac6eb6dd078c5572f1e884976c6ec1 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 00:43:14 +0530 Subject: [PATCH 47/73] updates --- docs/mkdocs.yml | 1 + .../{deployment_solutions => swarms_cloud}/cloudflare_workers.md | 0 2 files changed, 1 insertion(+) rename docs/{deployment_solutions => swarms_cloud}/cloudflare_workers.md (100%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 646acdd2..8bb7ec3e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -345,6 +345,7 @@ nav: - Deploy on Google Cloud Run: "swarms_cloud/cloud_run.md" - Deploy on Phala: "swarms_cloud/phala_deploy.md" - CronJob: "swarms/structs/cron_job.md" + - Deploy on Cloudflare Workers: "swarms_cloud/cloudflare_workers.md" # - Deploy on FastAPI: "swarms_cloud/fastapi_deploy.md" diff --git a/docs/deployment_solutions/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md similarity index 100% rename from docs/deployment_solutions/cloudflare_workers.md rename to docs/swarms_cloud/cloudflare_workers.md From c532c3c909a1abf5f705f285728b8077e35dce7d Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 01:05:13 +0530 Subject: [PATCH 48/73] updated the docs !! --- docs/swarms_cloud/cloudflare_workers.md | 946 +++++++++++------------- 1 file changed, 426 insertions(+), 520 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index c1d12bb9..085ca78b 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -1,581 +1,487 @@ -# Cloudflare Workers Deployment with Swarms API +# Cloudflare Workers with Swarms: Automated Cron Job Agents -Deploy intelligent agent swarms on Cloudflare's edge network for maximum performance, global availability, and automatic scaling. This guide covers deploying both API endpoints and scheduled cron job agents using the Swarms API. +Deploy scheduled AI agents on Cloudflare's global edge network for automated stock analysis and healthcare monitoring. This guide focuses on cron job agents that run automatically at scheduled intervals. ## Overview -Cloudflare Workers provide a serverless execution environment that runs on Cloudflare's global network, offering: +Cloudflare Workers with cron triggers enable automated agent execution for: +- **Stock Market Analysis**: Daily market reports and trading insights +- **Healthcare Monitoring**: Patient data analysis and alerts +- **Automated Reporting**: Scheduled business intelligence +- **Global Deployment**: Edge computing with zero cold starts -- **Edge Computing**: Deploy agents close to users worldwide -- **Automatic Scaling**: Handle traffic spikes without configuration -- **Zero Cold Starts**: Instant response times -- **Cost Effective**: Pay only for what you use -- **Built-in Cron Jobs**: Schedule automated agent tasks +## Quick Setup -Perfect for deploying AI agents that need global reach, low latency, and scheduled execution capabilities. - -## Prerequisites - -!!! info "Requirements" - - - Cloudflare account (free tier available) - - Wrangler CLI installed (`npm install -g wrangler`) - - Swarms API key from [Swarms Platform](https://swarms.world/platform/api-keys) - - Node.js 16+ for local development - -## Quick Start - -### 1. Project Setup +### 1. Create Worker Project ```bash -# Create new Cloudflare Worker -npx create-cloudflare my-swarms-worker worker - -# Navigate to project -cd my-swarms-worker - -# Install dependencies -npm install +npx create-cloudflare stock-agent worker +cd stock-agent ``` -### 2. Configure Environment Variables +### 2. Configure Cron Schedule -Add your Swarms API key to `wrangler.toml`: +Edit `wrangler.toml`: ```toml -name = "my-swarms-worker" +name = "stock-analysis-agent" main = "src/index.js" compatibility_date = "2024-01-01" [env.production.vars] SWARMS_API_KEY = "your-api-key-here" +SLACK_WEBHOOK_URL = "optional-slack-webhook" -[env.production] -# For scheduled agents +# Stock market analysis - after market close [[env.production.triggers.crons]] -cron = "0 9 * * MON-FRI" # Weekdays at 9 AM UTC -``` +cron = "0 21 * * MON-FRI" # 9 PM UTC (4 PM EST) -### 3. Basic Agent Worker - -=== "JavaScript" +# Healthcare monitoring - every 4 hours +[[env.production.triggers.crons]] +cron = "0 */4 * * *" +``` - ```javascript - export default { - async fetch(request, env, ctx) { - // Handle CORS preflight - if (request.method === 'OPTIONS') { - return handleCORS(); +## Stock Market Analysis Agent + +Automated daily stock analysis after market close: + +```javascript +export default { + // Cron job handler - runs automatically + async scheduled(event, env, ctx) { + ctx.waitUntil(handleStockAnalysis(event, env)); + } +}; + +async function handleStockAnalysis(event, env) { + try { + // Step 1: Fetch real market data from multiple sources + const marketData = await fetchMarketData(env); + + // Step 2: Get market news + const marketNews = await fetchMarketNews(env); + + // Step 3: Send real data to Swarms agents for analysis + const swarmConfig = { + name: "Real-Time Stock Analysis", + description: "Live market data analysis with AI agents", + agents: [ + { + agent_name: "Technical Analyst", + system_prompt: `You are a professional technical analyst. Analyze the provided real market data: + - Calculate key technical indicators (RSI, MACD, Moving Averages) + - Identify support and resistance levels + - Determine market trends and momentum + - Provide trading signals and price targets + Format your analysis professionally with specific price levels.`, + model_name: "gpt-4o-mini", + max_tokens: 3000, + temperature: 0.2 + }, + { + agent_name: "Fundamental Analyst", + system_prompt: `You are a fundamental market analyst. Using the provided market news and data: + - Analyze earnings impact and company fundamentals + - Evaluate economic indicators and Fed policy effects + - Assess sector rotation and market sentiment + - Identify value opportunities and risks + Provide investment recommendations with risk assessment.`, + model_name: "gpt-4o-mini", + max_tokens: 3000, + temperature: 0.3 } + ], + swarm_type: "ConcurrentWorkflow", + task: `Analyze the following real market data and news: + +MARKET DATA: +${JSON.stringify(marketData, null, 2)} + +MARKET NEWS: +${marketNews} + +Provide comprehensive analysis with: +1. Technical analysis with key levels +2. Fundamental analysis with catalysts +3. Trading recommendations +4. Risk assessment +5. Tomorrow's key levels to watch`, + max_loops: 1 + }; - try { - const { pathname } = new URL(request.url); - - if (pathname === '/api/agent' && request.method === 'POST') { - return await handleAgentRequest(request, env); - } - - if (pathname === '/api/health' && request.method === 'GET') { - return new Response(JSON.stringify({ - status: 'healthy', - service: 'Swarms Agent API', - version: '1.0.0' - }), { - status: 200, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); - } - - return new Response('Not Found', { status: 404 }); - } catch (error) { - console.error('Worker error:', error); - return new Response(JSON.stringify({ - error: error.message - }), { - status: 500, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); - } + const response = await fetch('https://api.swarms.world/v1/swarm/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' }, - - // Scheduled event handler for cron jobs - async scheduled(event, env, ctx) { - ctx.waitUntil(handleScheduledEvent(event, env)); + body: JSON.stringify(swarmConfig) + }); + + const result = await response.json(); + + if (response.ok) { + console.log('✅ Real-time stock analysis completed'); + console.log('💰 Cost:', result.metadata?.billing_info?.total_cost); + + // Send email directly + await sendEmailReport(env, result.output, marketData); + } + } catch (error) { + console.error('❌ Real-time stock analysis failed:', error); + } +} + +// Fetch real market data from Alpha Vantage API +async function fetchMarketData(env) { + const symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'TSLA', 'NVDA']; + const marketData = {}; + + for (const symbol of symbols) { + try { + // Get daily prices + const priceResponse = await fetch( + `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${symbol}&apikey=${env.STOCK_API_KEY}` + ); + const priceData = await priceResponse.json(); + + // Get technical indicators (RSI) + const rsiResponse = await fetch( + `https://www.alphavantage.co/query?function=RSI&symbol=${symbol}&interval=daily&time_period=14&series_type=close&apikey=${env.STOCK_API_KEY}` + ); + const rsiData = await rsiResponse.json(); + + if (priceData['Time Series (Daily)'] && rsiData['Technical Analysis: RSI']) { + const latestDate = Object.keys(priceData['Time Series (Daily)'])[0]; + const latestPrice = priceData['Time Series (Daily)'][latestDate]; + const latestRSI = Object.values(rsiData['Technical Analysis: RSI'])[0]; + + marketData[symbol] = { + price: parseFloat(latestPrice['4. close']), + open: parseFloat(latestPrice['1. open']), + high: parseFloat(latestPrice['2. high']), + low: parseFloat(latestPrice['3. low']), + volume: parseInt(latestPrice['5. volume']), + change: parseFloat(latestPrice['4. close']) - parseFloat(latestPrice['1. open']), + change_percent: ((parseFloat(latestPrice['4. close']) - parseFloat(latestPrice['1. open'])) / parseFloat(latestPrice['1. open']) * 100).toFixed(2), + rsi: parseFloat(latestRSI?.RSI || 50), + date: latestDate + }; } - }; - - async function handleAgentRequest(request, env) { - const requestData = await request.json(); - const agentConfig = { - agent_config: { - agent_name: requestData.agent_name || "CloudflareAgent", - description: requestData.description || "Agent running on Cloudflare Workers", - system_prompt: requestData.system_prompt || "You are a helpful AI assistant.", - model_name: requestData.model_name || "gpt-4o-mini", - max_tokens: requestData.max_tokens || 2000, - temperature: requestData.temperature || 0.7 - }, - task: requestData.task - }; - - const response = await fetch('https://api.swarms.world/v1/agent/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(agentConfig) - }); - - const result = await response.json(); + // Rate limiting - Alpha Vantage allows 5 requests per minute on free tier + await new Promise(resolve => setTimeout(resolve, 12000)); - return new Response(JSON.stringify(result), { - status: response.status, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); + } catch (error) { + console.error(`Error fetching data for ${symbol}:`, error); + marketData[symbol] = { error: 'Failed to fetch data' }; } - - function getCORSHeaders() { - return { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }; + } + + return marketData; +} + +// Fetch market news from Financial Modeling Prep (free tier available) +async function fetchMarketNews(env) { + try { + const newsResponse = await fetch( + `https://financialmodelingprep.com/api/v3/stock_news?tickers=AAPL,MSFT,TSLA,NVDA&limit=10&apikey=${env.FMP_API_KEY || 'demo'}` + ); + const newsData = await newsResponse.json(); + + if (Array.isArray(newsData)) { + return newsData.slice(0, 5).map(article => ({ + title: article.title, + text: article.text?.substring(0, 300) + '...', + publishedDate: article.publishedDate, + symbol: article.symbol, + url: article.url + })); } + } catch (error) { + console.error('Error fetching news:', error); + } + + return "Market news temporarily unavailable"; +} + +// Send email report using Mailgun API +async function sendEmailReport(env, analysis, marketData) { + // Extract key market movers for email subject + const movers = Object.entries(marketData) + .filter(([symbol, data]) => data.change_percent && Math.abs(parseFloat(data.change_percent)) > 2) + .map(([symbol, data]) => `${symbol}: ${data.change_percent}%`) + .join(', '); + + const emailSubject = `📊 Daily Stock Analysis - ${new Date().toLocaleDateString()}`; + const emailBody = ` +

Daily Market Analysis Report

+

Date: ${new Date().toLocaleString()}

+

Key Market Movers: ${movers || 'Market stable'}

+ +

AI Agent Analysis:

+
+
${analysis}
+
+ +

Market Data Summary:

+ + + + + ${Object.entries(marketData).map(([symbol, data]) => ` + + + + + + + + `).join('')} +
SymbolPriceChange %VolumeRSI
${symbol}$${data.price?.toFixed(2) || 'N/A'} + ${data.change_percent}% + ${data.volume?.toLocaleString() || 'N/A'}${data.rsi?.toFixed(1) || 'N/A'}
+ +

Generated by Swarms AI Agent System

+ `; + + // Send via Mailgun + const formData = new FormData(); + formData.append('from', `Stock Analysis Agent `); + formData.append('to', env.RECIPIENT_EMAIL); + formData.append('subject', emailSubject); + formData.append('html', emailBody); + + try { + const response = await fetch(`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`, { + method: 'POST', + headers: { + 'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}` + }, + body: formData + }); - function handleCORS() { - return new Response(null, { - status: 200, - headers: getCORSHeaders() - }); - } - ``` - -=== "TypeScript" - - ```typescript - interface AgentRequest { - agent_name?: string; - description?: string; - system_prompt?: string; - model_name?: string; - max_tokens?: number; - temperature?: number; - task: string; + if (response.ok) { + console.log('✅ Email report sent successfully'); + } else { + console.error('❌ Failed to send email:', await response.text()); } + } catch (error) { + console.error('❌ Email sending error:', error); + } +} + +async function sendSlackNotification(webhookUrl, analysis) { + await fetch(webhookUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: "📈 Daily Stock Market Analysis", + blocks: [{ + type: "section", + text: { + type: "mrkdwn", + text: `*Market Analysis Complete*\n\`\`\`${analysis.substring(0, 500)}...\`\`\`` + } + }] + }) + }); +} +``` - interface AgentConfig { - agent_config: { - agent_name: string; - description: string; - system_prompt: string; - model_name: string; - max_tokens: number; - temperature: number; - }; - task: string; - } +## Healthcare Monitoring Agent - interface Env { - SWARMS_API_KEY: string; - } +Automated patient monitoring and health alerts: - export interface ScheduledEvent { - scheduledTime: number; - cron: string; +```javascript +export default { + async scheduled(event, env, ctx) { + // Determine which agent to run based on cron schedule + const hour = new Date().getHours(); + + if (hour % 4 === 0) { + // Every 4 hours - patient monitoring + ctx.waitUntil(handlePatientMonitoring(event, env)); } - - export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - if (request.method === 'OPTIONS') { - return handleCORS(); - } - - try { - const { pathname } = new URL(request.url); - - if (pathname === '/api/agent' && request.method === 'POST') { - return await handleAgentRequest(request, env); - } - - if (pathname === '/api/health' && request.method === 'GET') { - return new Response(JSON.stringify({ - status: 'healthy', - service: 'Swarms Agent API', - version: '1.0.0' - }), { - status: 200, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); - } - - return new Response('Not Found', { status: 404 }); - } catch (error) { - console.error('Worker error:', error); - return new Response(JSON.stringify({ - error: (error as Error).message - }), { - status: 500, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); - } + } +}; + +async function handlePatientMonitoring(event, env) { + const healthcareSwarm = { + name: "Patient Monitoring System", + description: "Automated patient health analysis and alerting", + agents: [ + { + agent_name: "Vital Signs Analyst", + system_prompt: `Analyze patient vital signs data for abnormalities: + - Heart rate patterns and irregularities + - Blood pressure trends + - Oxygen saturation levels + - Temperature variations + Flag critical conditions requiring immediate attention.`, + model_name: "gpt-4o-mini", + max_tokens: 2000, + temperature: 0.1 }, - - async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise { - ctx.waitUntil(handleScheduledEvent(event, env)); + { + agent_name: "Risk Assessment Specialist", + system_prompt: `Evaluate patient risk factors and health trends: + - Medication interactions + - Chronic condition management + - Recovery progress assessment + - Early warning signs detection + Prioritize patients needing urgent care.`, + model_name: "gpt-4o-mini", + max_tokens: 2000, + temperature: 0.2 } - }; - - async function handleAgentRequest(request: Request, env: Env): Promise { - const requestData: AgentRequest = await request.json(); - - const agentConfig: AgentConfig = { - agent_config: { - agent_name: requestData.agent_name || "CloudflareAgent", - description: requestData.description || "Agent running on Cloudflare Workers", - system_prompt: requestData.system_prompt || "You are a helpful AI assistant.", - model_name: requestData.model_name || "gpt-4o-mini", - max_tokens: requestData.max_tokens || 2000, - temperature: requestData.temperature || 0.7 - }, - task: requestData.task - }; - - const response = await fetch('https://api.swarms.world/v1/agent/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(agentConfig) - }); + ], + swarm_type: "ConcurrentWorkflow", + task: "Analyze current patient monitoring data and generate health status alerts for medical staff.", + max_loops: 1 + }; + + try { + const response = await fetch('https://api.swarms.world/v1/swarm/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(healthcareSwarm) + }); - const result = await response.json(); + const result = await response.json(); + + if (response.ok) { + console.log('🏥 Health monitoring completed'); - return new Response(JSON.stringify(result), { - status: response.status, - headers: { - 'Content-Type': 'application/json', - ...getCORSHeaders() - } - }); - } - - function getCORSHeaders(): Record { - return { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }; + // Send email alerts for all monitoring results + await sendHealthEmailAlert(env, result.output); } + } catch (error) { + console.error('❌ Health monitoring failed:', error); + } +} + +// Send healthcare email alerts +async function sendHealthEmailAlert(env, analysis) { + const severity = extractSeverity(analysis); + const isUrgent = severity === 'critical' || severity === 'urgent'; + + const emailSubject = `${isUrgent ? '🚨 URGENT' : '🏥'} Health Monitoring Alert - ${new Date().toLocaleString()}`; + const emailBody = ` +

Patient Monitoring Report

+

Timestamp: ${new Date().toLocaleString()}

+

Severity Level: ${severity.toUpperCase()}

+ +

AI Health Analysis:

+
+
${analysis}
+
+ + ${isUrgent ? '

⚠️ IMMEDIATE ATTENTION REQUIRED

' : ''} + +

Generated by Swarms Healthcare Monitoring Agent

+ `; + + // Send email using Mailgun + const formData = new FormData(); + formData.append('from', `Healthcare Monitor `); + formData.append('to', env.MEDICAL_TEAM_EMAIL); + formData.append('subject', emailSubject); + formData.append('html', emailBody); + + try { + const response = await fetch(`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`, { + method: 'POST', + headers: { + 'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}` + }, + body: formData + }); - function handleCORS(): Response { - return new Response(null, { - status: 200, - headers: getCORSHeaders() - }); + if (response.ok) { + console.log('✅ Healthcare email alert sent successfully'); + } else { + console.error('❌ Failed to send healthcare email:', await response.text()); } - ``` + } catch (error) { + console.error('❌ Healthcare email error:', error); + } +} + +function extractSeverity(analysis) { + if (analysis.includes('CRITICAL')) return 'critical'; + if (analysis.includes('URGENT')) return 'urgent'; + if (analysis.includes('WARNING')) return 'warning'; + return 'normal'; +} + +function getSeverityColor(severity) { + switch(severity) { + case 'critical': return 'red'; + case 'urgent': return 'orange'; + case 'warning': return 'yellow'; + default: return 'green'; + } +} +``` -### 4. Deploy Your Worker +## Deployment ```bash -# Deploy to Cloudflare +# Deploy your cron job agents wrangler deploy -# View logs +# Monitor logs wrangler tail -``` - -## Scheduled Cron Job Agents -Cloudflare Workers support scheduled events (cron jobs) that can trigger agent tasks automatically. This is perfect for periodic analysis, monitoring, reporting, and automated decision-making. - -### Stock Market Analysis Agent - -Deploy a cron job agent that analyzes stock market trends and generates daily reports: +# Test cron trigger manually +wrangler triggers cron "0 21 * * MON-FRI" +``` -=== "JavaScript" +## Cost Optimization - ```javascript - // wrangler.toml configuration for stock market agent - /* - [[env.production.triggers.crons]] - cron = "0 16 * * MON-FRI" # 4 PM UTC (after US market close) - */ +- Use `gpt-4o-mini` for cost-effective analysis +- Set appropriate `max_tokens` limits +- Configure cron schedules to avoid unnecessary runs +- Implement error handling to prevent API waste - async function handleScheduledEvent(event, env) { - console.log('Stock market analysis triggered at:', new Date(event.scheduledTime)); - - try { - // Stock analysis swarm configuration - const swarmConfig = { - name: "Stock Market Analysis Swarm", - description: "Daily stock market analysis and trading insights", - agents: [ - { - agent_name: "Market Data Analyst", - description: "Analyzes market trends and price movements", - system_prompt: `You are an expert financial market analyst specializing in equity markets. - Analyze market trends, volume patterns, and price movements. - Provide technical analysis insights and identify key support/resistance levels. - Focus on major indices (S&P 500, NASDAQ, DOW) and sector performance.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.3, - role: "worker" - }, - { - agent_name: "Economic News Analyzer", - description: "Analyzes economic news impact on markets", - system_prompt: `You are a financial news analyst with expertise in market sentiment analysis. - Analyze recent economic news, earnings reports, and market-moving events. - Assess their potential impact on stock prices and market sentiment. - Identify key catalysts and risk factors for the next trading day.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.3, - role: "worker" - }, - { - agent_name: "Trading Strategy Advisor", - description: "Provides trading recommendations and risk assessment", - system_prompt: `You are a quantitative trading strategist with expertise in risk management. - Based on technical analysis and market sentiment, provide actionable trading insights. - Include risk assessment, position sizing recommendations, and key levels to watch. - Focus on risk-adjusted returns and downside protection strategies.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.4, - role: "worker" - } - ], - swarm_type: "SequentialWorkflow", - max_loops: 1, - task: `Analyze today's stock market performance and provide insights for tomorrow's trading session. - Include: 1) Market overview and key movers 2) Technical analysis of major indices - 3) Economic news impact assessment 4) Trading opportunities and risks - 5) Key levels to watch tomorrow. Format as a professional daily market report.`, - service_tier: "standard" - }; +## Environment Variables - // Execute the swarm - const response = await fetch('https://api.swarms.world/v1/swarm/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(swarmConfig) - }); - - const result = await response.json(); - - if (response.ok) { - console.log('Stock analysis completed successfully'); - console.log('Cost:', result.metadata?.billing_info?.total_cost); - - // Store results in KV storage for retrieval - if (env.STOCK_REPORTS) { - const reportKey = `stock-report-${new Date().toISOString().split('T')[0]}`; - await env.STOCK_REPORTS.put(reportKey, JSON.stringify({ - timestamp: new Date().toISOString(), - analysis: result.output, - cost: result.metadata?.billing_info?.total_cost - })); - } - - // Optional: Send to webhook, email, or notification service - await sendNotification(env, result.output); - - } else { - console.error('Stock analysis failed:', result); - } - - } catch (error) { - console.error('Error in scheduled stock analysis:', error); - } - } +Add to `wrangler.toml`: - async function sendNotification(env, analysis) { - // Example: Send to Slack webhook - if (env.SLACK_WEBHOOK_URL) { - try { - await fetch(env.SLACK_WEBHOOK_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - text: "📈 Daily Stock Market Analysis Ready", - attachments: [{ - color: "good", - text: typeof analysis === 'string' ? analysis.substring(0, 500) + '...' : 'Analysis completed', - footer: "Swarms Stock Analysis Agent" - }] - }) - }); - } catch (error) { - console.error('Failed to send Slack notification:', error); - } - } - } - - // API endpoint to retrieve stock reports - async function getStockReport(request, env) { - const url = new URL(request.url); - const date = url.searchParams.get('date') || new Date().toISOString().split('T')[0]; - const reportKey = `stock-report-${date}`; - - if (env.STOCK_REPORTS) { - const report = await env.STOCK_REPORTS.get(reportKey); - if (report) { - return new Response(report, { - headers: { 'Content-Type': 'application/json' } - }); - } - } - - return new Response(JSON.stringify({ error: 'Report not found' }), { - status: 404, - headers: { 'Content-Type': 'application/json' } - }); - } - ``` +```toml +[env.production.vars] +SWARMS_API_KEY = "your-swarms-api-key" -=== "TypeScript" +# Stock API Keys (get free keys from these providers) +STOCK_API_KEY = "your-alpha-vantage-key" # Free: https://www.alphavantage.co/support/#api-key +FMP_API_KEY = "your-fmp-key" # Free: https://financialmodelingprep.com/developer/docs - ```typescript - interface StockAnalysisResult { - timestamp: string; - analysis: any; - cost: number; - } +# Email Configuration (Mailgun) +MAILGUN_API_KEY = "your-mailgun-api-key" # Free: https://www.mailgun.com/ +MAILGUN_DOMAIN = "your-domain.com" +RECIPIENT_EMAIL = "investor@yourcompany.com" +MEDICAL_TEAM_EMAIL = "medical-team@hospital.com" +``` - async function handleScheduledEvent(event: ScheduledEvent, env: Env): Promise { - console.log('Stock market analysis triggered at:', new Date(event.scheduledTime)); - - try { - const swarmConfig = { - name: "Stock Market Analysis Swarm", - description: "Daily stock market analysis and trading insights", - agents: [ - { - agent_name: "Market Data Analyst", - description: "Analyzes market trends and price movements", - system_prompt: `You are an expert financial market analyst specializing in equity markets. - Analyze market trends, volume patterns, and price movements. - Provide technical analysis insights and identify key support/resistance levels. - Focus on major indices (S&P 500, NASDAQ, DOW) and sector performance.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.3, - role: "worker" - }, - { - agent_name: "Economic News Analyzer", - description: "Analyzes economic news impact on markets", - system_prompt: `You are a financial news analyst with expertise in market sentiment analysis. - Analyze recent economic news, earnings reports, and market-moving events. - Assess their potential impact on stock prices and market sentiment. - Identify key catalysts and risk factors for the next trading day.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.3, - role: "worker" - }, - { - agent_name: "Trading Strategy Advisor", - description: "Provides trading recommendations and risk assessment", - system_prompt: `You are a quantitative trading strategist with expertise in risk management. - Based on technical analysis and market sentiment, provide actionable trading insights. - Include risk assessment, position sizing recommendations, and key levels to watch. - Focus on risk-adjusted returns and downside protection strategies.`, - model_name: "gpt-4o", - max_tokens: 3000, - temperature: 0.4, - role: "worker" - } - ], - swarm_type: "SequentialWorkflow" as const, - max_loops: 1, - task: `Analyze today's stock market performance and provide insights for tomorrow's trading session. - Include: 1) Market overview and key movers 2) Technical analysis of major indices - 3) Economic news impact assessment 4) Trading opportunities and risks - 5) Key levels to watch tomorrow. Format as a professional daily market report.`, - service_tier: "standard" - }; +## API Endpoints Used - const response = await fetch('https://api.swarms.world/v1/swarm/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(swarmConfig) - }); - - const result = await response.json(); - - if (response.ok) { - console.log('Stock analysis completed successfully'); - console.log('Cost:', result.metadata?.billing_info?.total_cost); - - if (env.STOCK_REPORTS) { - const reportKey = `stock-report-${new Date().toISOString().split('T')[0]}`; - const reportData: StockAnalysisResult = { - timestamp: new Date().toISOString(), - analysis: result.output, - cost: result.metadata?.billing_info?.total_cost - }; - - await env.STOCK_REPORTS.put(reportKey, JSON.stringify(reportData)); - } - - await sendNotification(env, result.output); - - } else { - console.error('Stock analysis failed:', result); - } - - } catch (error) { - console.error('Error in scheduled stock analysis:', error); - } - } +### Stock Data APIs +- **Alpha Vantage**: Real-time stock prices, technical indicators (RSI, MACD) +- **Financial Modeling Prep**: Market news, earnings data, company fundamentals +- **Free Tier Limits**: Alpha Vantage (5 calls/min), FMP (250 calls/day) - async function sendNotification(env: Env, analysis: any): Promise { - if (env.SLACK_WEBHOOK_URL) { - try { - await fetch(env.SLACK_WEBHOOK_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - text: "📈 Daily Stock Market Analysis Ready", - attachments: [{ - color: "good", - text: typeof analysis === 'string' ? analysis.substring(0, 500) + '...' : 'Analysis completed', - footer: "Swarms Stock Analysis Agent" - }] - }) - }); - } catch (error) { - console.error('Failed to send Slack notification:', error); - } - } - } - ``` +### Real Market Data Flow +1. **Fetch Live Data**: Current prices, volume, technical indicators +2. **Get Market News**: Recent earnings, economic events, analyst reports +3. **AI Analysis**: Swarms agents analyze real data for actionable insights +4. **Email Reports**: Professional HTML emails with analysis and data tables +### Email Features +- **Stock Reports**: Daily market analysis with data tables and key movers +- **Healthcare Alerts**: Color-coded severity levels with immediate attention flags +- **HTML Formatting**: Professional email templates with styling +- **Mailgun Integration**: Reliable email delivery service From fdba766e08779661a5ac85bb0a33494c44eb5886 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 03:08:50 +0530 Subject: [PATCH 49/73] updated docs ! --- docs/swarms_cloud/cloudflare_workers.md | 638 +++++++++--------------- 1 file changed, 243 insertions(+), 395 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index 085ca78b..ad73cce0 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -1,111 +1,176 @@ -# Cloudflare Workers with Swarms: Automated Cron Job Agents +# Cloudflare Workers with Swarms: Production AI Agents -Deploy scheduled AI agents on Cloudflare's global edge network for automated stock analysis and healthcare monitoring. This guide focuses on cron job agents that run automatically at scheduled intervals. +Deploy AI agents on Cloudflare's edge network with automatic cron scheduling and real-time data integration. This guide shows a production-ready implementation with both HTTP endpoints and scheduled triggers. -## Overview +## Architecture Overview -Cloudflare Workers with cron triggers enable automated agent execution for: -- **Stock Market Analysis**: Daily market reports and trading insights -- **Healthcare Monitoring**: Patient data analysis and alerts -- **Automated Reporting**: Scheduled business intelligence -- **Global Deployment**: Edge computing with zero cold starts +The Cloudflare Workers pattern uses two main handlers: +- **`fetch()`**: HTTP requests for testing and manual triggers +- **`scheduled()`**: Cron jobs for automated execution + +```javascript +export default { + // HTTP handler for manual testing + async fetch(request, env, ctx) { + // Handle web interface and API endpoints + }, + + // Cron handler for scheduled execution + async scheduled(event, env, ctx) { + ctx.waitUntil(handleStockAnalysis(event, env)); + } +}; +``` ## Quick Setup ### 1. Create Worker Project ```bash -npx create-cloudflare stock-agent worker +npm create cloudflare@latest stock-agent cd stock-agent ``` ### 2. Configure Cron Schedule -Edit `wrangler.toml`: - -```toml -name = "stock-analysis-agent" -main = "src/index.js" -compatibility_date = "2024-01-01" - -[env.production.vars] -SWARMS_API_KEY = "your-api-key-here" -SLACK_WEBHOOK_URL = "optional-slack-webhook" - -# Stock market analysis - after market close -[[env.production.triggers.crons]] -cron = "0 21 * * MON-FRI" # 9 PM UTC (4 PM EST) - -# Healthcare monitoring - every 4 hours -[[env.production.triggers.crons]] -cron = "0 */4 * * *" +Edit `wrangler.jsonc`: + +```jsonc +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "stock-agent", + "main": "src/index.js", + "compatibility_date": "2025-08-03", + "observability": { + "enabled": true + }, + "triggers": { + "crons": [ + "0 */3 * * *" // Every 3 hours + ] + }, + "vars": { + "SWARMS_API_KEY": "your-api-key" + } +} ``` -## Stock Market Analysis Agent +## Complete Minimal Implementation -Automated daily stock analysis after market close: +Create `src/index.js`: ```javascript export default { - // Cron job handler - runs automatically + // HTTP handler - provides web interface and manual triggers + async fetch(request, env, ctx) { + const url = new URL(request.url); + + // Web interface for testing + if (url.pathname === '/') { + return new Response(` + + + + Stock Analysis Agent + + + +

📈 Stock Analysis Agent

+
Status: Online ✅
+ + +
+ + + + + `, { + headers: { 'Content-Type': 'text/html' } + }); + } + + // Manual trigger endpoint + if (url.pathname === '/trigger') { + try { + const result = await handleStockAnalysis(null, env); + return new Response(JSON.stringify({ + message: 'Analysis triggered', + timestamp: new Date().toISOString(), + result + }), { + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + return new Response(JSON.stringify({ + error: error.message + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + } + + return new Response('Not Found', { status: 404 }); + }, + + // Cron handler - runs automatically on schedule async scheduled(event, env, ctx) { ctx.waitUntil(handleStockAnalysis(event, env)); } }; +// Main analysis function used by both HTTP and cron triggers async function handleStockAnalysis(event, env) { + console.log('🚀 Starting stock analysis...'); + try { - // Step 1: Fetch real market data from multiple sources - const marketData = await fetchMarketData(env); + // Step 1: Fetch real market data (using Yahoo Finance - no API key needed) + const marketData = await fetchMarketData(); - // Step 2: Get market news - const marketNews = await fetchMarketNews(env); + // Check if we got valid data + const validSymbols = Object.keys(marketData).filter(symbol => !marketData[symbol].error); + if (validSymbols.length === 0) { + throw new Error('No valid market data retrieved'); + } - // Step 3: Send real data to Swarms agents for analysis + // Step 2: Send to Swarms AI agents const swarmConfig = { - name: "Real-Time Stock Analysis", - description: "Live market data analysis with AI agents", + name: "Stock Analysis", + description: "Real-time market analysis", agents: [ { agent_name: "Technical Analyst", - system_prompt: `You are a professional technical analyst. Analyze the provided real market data: - - Calculate key technical indicators (RSI, MACD, Moving Averages) - - Identify support and resistance levels - - Determine market trends and momentum - - Provide trading signals and price targets - Format your analysis professionally with specific price levels.`, + system_prompt: \`Analyze the provided stock data: + - Identify trends and key levels + - Provide trading signals + - Calculate technical indicators + Format analysis professionally.\`, model_name: "gpt-4o-mini", - max_tokens: 3000, + max_tokens: 1500, temperature: 0.2 - }, - { - agent_name: "Fundamental Analyst", - system_prompt: `You are a fundamental market analyst. Using the provided market news and data: - - Analyze earnings impact and company fundamentals - - Evaluate economic indicators and Fed policy effects - - Assess sector rotation and market sentiment - - Identify value opportunities and risks - Provide investment recommendations with risk assessment.`, - model_name: "gpt-4o-mini", - max_tokens: 3000, - temperature: 0.3 } ], - swarm_type: "ConcurrentWorkflow", - task: `Analyze the following real market data and news: - -MARKET DATA: -${JSON.stringify(marketData, null, 2)} - -MARKET NEWS: -${marketNews} - -Provide comprehensive analysis with: -1. Technical analysis with key levels -2. Fundamental analysis with catalysts -3. Trading recommendations -4. Risk assessment -5. Tomorrow's key levels to watch`, + swarm_type: "SequentialWorkflow", + task: \`Analyze this market data: \\${JSON.stringify(marketData, null, 2)}\`, max_loops: 1 }; @@ -118,370 +183,153 @@ Provide comprehensive analysis with: body: JSON.stringify(swarmConfig) }); - const result = await response.json(); - - if (response.ok) { - console.log('✅ Real-time stock analysis completed'); - console.log('💰 Cost:', result.metadata?.billing_info?.total_cost); - - // Send email directly - await sendEmailReport(env, result.output, marketData); + if (!response.ok) { + throw new Error(\`Swarms API error: \\${response.status}\`); } + + const result = await response.json(); + console.log('✅ Analysis completed'); + + return { + success: true, + analysis: result.output, + symbolsAnalyzed: validSymbols.length, + cost: result.usage?.billing_info?.total_cost + }; + } catch (error) { - console.error('❌ Real-time stock analysis failed:', error); + console.error('❌ Analysis failed:', error.message); + return { + success: false, + error: error.message + }; } } -// Fetch real market data from Alpha Vantage API -async function fetchMarketData(env) { - const symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'TSLA', 'NVDA']; +// Fetch market data from Yahoo Finance (free, no API key required) +async function fetchMarketData() { + const symbols = ['SPY', 'AAPL', 'MSFT', 'TSLA']; const marketData = {}; - for (const symbol of symbols) { + const promises = symbols.map(async (symbol) => { try { - // Get daily prices - const priceResponse = await fetch( - `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${symbol}&apikey=${env.STOCK_API_KEY}` - ); - const priceData = await priceResponse.json(); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 8000); - // Get technical indicators (RSI) - const rsiResponse = await fetch( - `https://www.alphavantage.co/query?function=RSI&symbol=${symbol}&interval=daily&time_period=14&series_type=close&apikey=${env.STOCK_API_KEY}` + const response = await fetch( + \`https://query1.finance.yahoo.com/v8/finance/chart/\\${symbol}\`, + { + signal: controller.signal, + headers: { 'User-Agent': 'Mozilla/5.0' } + } ); - const rsiData = await rsiResponse.json(); - - if (priceData['Time Series (Daily)'] && rsiData['Technical Analysis: RSI']) { - const latestDate = Object.keys(priceData['Time Series (Daily)'])[0]; - const latestPrice = priceData['Time Series (Daily)'][latestDate]; - const latestRSI = Object.values(rsiData['Technical Analysis: RSI'])[0]; - - marketData[symbol] = { - price: parseFloat(latestPrice['4. close']), - open: parseFloat(latestPrice['1. open']), - high: parseFloat(latestPrice['2. high']), - low: parseFloat(latestPrice['3. low']), - volume: parseInt(latestPrice['5. volume']), - change: parseFloat(latestPrice['4. close']) - parseFloat(latestPrice['1. open']), - change_percent: ((parseFloat(latestPrice['4. close']) - parseFloat(latestPrice['1. open'])) / parseFloat(latestPrice['1. open']) * 100).toFixed(2), - rsi: parseFloat(latestRSI?.RSI || 50), - date: latestDate - }; - } + clearTimeout(timeout); - // Rate limiting - Alpha Vantage allows 5 requests per minute on free tier - await new Promise(resolve => setTimeout(resolve, 12000)); + if (!response.ok) throw new Error(\`HTTP \\${response.status}\`); - } catch (error) { - console.error(`Error fetching data for ${symbol}:`, error); - marketData[symbol] = { error: 'Failed to fetch data' }; - } - } - - return marketData; -} - -// Fetch market news from Financial Modeling Prep (free tier available) -async function fetchMarketNews(env) { - try { - const newsResponse = await fetch( - `https://financialmodelingprep.com/api/v3/stock_news?tickers=AAPL,MSFT,TSLA,NVDA&limit=10&apikey=${env.FMP_API_KEY || 'demo'}` - ); - const newsData = await newsResponse.json(); - - if (Array.isArray(newsData)) { - return newsData.slice(0, 5).map(article => ({ - title: article.title, - text: article.text?.substring(0, 300) + '...', - publishedDate: article.publishedDate, - symbol: article.symbol, - url: article.url - })); - } - } catch (error) { - console.error('Error fetching news:', error); - } - - return "Market news temporarily unavailable"; -} - -// Send email report using Mailgun API -async function sendEmailReport(env, analysis, marketData) { - // Extract key market movers for email subject - const movers = Object.entries(marketData) - .filter(([symbol, data]) => data.change_percent && Math.abs(parseFloat(data.change_percent)) > 2) - .map(([symbol, data]) => `${symbol}: ${data.change_percent}%`) - .join(', '); - - const emailSubject = `📊 Daily Stock Analysis - ${new Date().toLocaleDateString()}`; - const emailBody = ` -

Daily Market Analysis Report

-

Date: ${new Date().toLocaleString()}

-

Key Market Movers: ${movers || 'Market stable'}

- -

AI Agent Analysis:

-
-
${analysis}
-
- -

Market Data Summary:

- - - - - ${Object.entries(marketData).map(([symbol, data]) => ` - - - - - - - - `).join('')} -
SymbolPriceChange %VolumeRSI
${symbol}$${data.price?.toFixed(2) || 'N/A'} - ${data.change_percent}% - ${data.volume?.toLocaleString() || 'N/A'}${data.rsi?.toFixed(1) || 'N/A'}
- -

Generated by Swarms AI Agent System

- `; - - // Send via Mailgun - const formData = new FormData(); - formData.append('from', `Stock Analysis Agent `); - formData.append('to', env.RECIPIENT_EMAIL); - formData.append('subject', emailSubject); - formData.append('html', emailBody); - - try { - const response = await fetch(`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`, { - method: 'POST', - headers: { - 'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}` - }, - body: formData - }); + const data = await response.json(); + const result = data.chart.result[0]; + const meta = result.meta; + + const currentPrice = meta.regularMarketPrice; + const previousClose = meta.previousClose; + const change = currentPrice - previousClose; + const changePercent = ((change / previousClose) * 100).toFixed(2); + + return [symbol, { + price: currentPrice, + change: change, + change_percent: changePercent, + volume: meta.regularMarketVolume, + currency: meta.currency + }]; - if (response.ok) { - console.log('✅ Email report sent successfully'); - } else { - console.error('❌ Failed to send email:', await response.text()); + } catch (error) { + return [symbol, { error: error.message }]; } - } catch (error) { - console.error('❌ Email sending error:', error); - } -} - -async function sendSlackNotification(webhookUrl, analysis) { - await fetch(webhookUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - text: "📈 Daily Stock Market Analysis", - blocks: [{ - type: "section", - text: { - type: "mrkdwn", - text: `*Market Analysis Complete*\n\`\`\`${analysis.substring(0, 500)}...\`\`\`` - } - }] - }) }); -} -``` - -## Healthcare Monitoring Agent - -Automated patient monitoring and health alerts: -```javascript -export default { - async scheduled(event, env, ctx) { - // Determine which agent to run based on cron schedule - const hour = new Date().getHours(); - - if (hour % 4 === 0) { - // Every 4 hours - patient monitoring - ctx.waitUntil(handlePatientMonitoring(event, env)); + const results = await Promise.allSettled(promises); + results.forEach((result) => { + if (result.status === 'fulfilled' && result.value) { + const [symbol, data] = result.value; + marketData[symbol] = data; } - } -}; - -async function handlePatientMonitoring(event, env) { - const healthcareSwarm = { - name: "Patient Monitoring System", - description: "Automated patient health analysis and alerting", - agents: [ - { - agent_name: "Vital Signs Analyst", - system_prompt: `Analyze patient vital signs data for abnormalities: - - Heart rate patterns and irregularities - - Blood pressure trends - - Oxygen saturation levels - - Temperature variations - Flag critical conditions requiring immediate attention.`, - model_name: "gpt-4o-mini", - max_tokens: 2000, - temperature: 0.1 - }, - { - agent_name: "Risk Assessment Specialist", - system_prompt: `Evaluate patient risk factors and health trends: - - Medication interactions - - Chronic condition management - - Recovery progress assessment - - Early warning signs detection - Prioritize patients needing urgent care.`, - model_name: "gpt-4o-mini", - max_tokens: 2000, - temperature: 0.2 - } - ], - swarm_type: "ConcurrentWorkflow", - task: "Analyze current patient monitoring data and generate health status alerts for medical staff.", - max_loops: 1 - }; - - try { - const response = await fetch('https://api.swarms.world/v1/swarm/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(healthcareSwarm) - }); + }); - const result = await response.json(); - - if (response.ok) { - console.log('🏥 Health monitoring completed'); - - // Send email alerts for all monitoring results - await sendHealthEmailAlert(env, result.output); - } - } catch (error) { - console.error('❌ Health monitoring failed:', error); - } + return marketData; } +``` -// Send healthcare email alerts -async function sendHealthEmailAlert(env, analysis) { - const severity = extractSeverity(analysis); - const isUrgent = severity === 'critical' || severity === 'urgent'; - - const emailSubject = `${isUrgent ? '🚨 URGENT' : '🏥'} Health Monitoring Alert - ${new Date().toLocaleString()}`; - const emailBody = ` -

Patient Monitoring Report

-

Timestamp: ${new Date().toLocaleString()}

-

Severity Level: ${severity.toUpperCase()}

- -

AI Health Analysis:

-
-
${analysis}
-
- - ${isUrgent ? '

⚠️ IMMEDIATE ATTENTION REQUIRED

' : ''} - -

Generated by Swarms Healthcare Monitoring Agent

- `; - - // Send email using Mailgun - const formData = new FormData(); - formData.append('from', `Healthcare Monitor `); - formData.append('to', env.MEDICAL_TEAM_EMAIL); - formData.append('subject', emailSubject); - formData.append('html', emailBody); +## Key Features Explained - try { - const response = await fetch(`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`, { - method: 'POST', - headers: { - 'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}` - }, - body: formData - }); +### 1. **Dual Handler Pattern** +- **`fetch()`**: Handles HTTP requests, provides web UI for testing +- **`scheduled()`**: Executes on cron schedule automatically +- **Shared Logic**: Both use the same `handleStockAnalysis()` function - if (response.ok) { - console.log('✅ Healthcare email alert sent successfully'); - } else { - console.error('❌ Failed to send healthcare email:', await response.text()); - } - } catch (error) { - console.error('❌ Healthcare email error:', error); - } +### 2. **Cron Configuration** +```jsonc +"triggers": { + "crons": [ + "0 */3 * * *" // Every 3 hours + "0 9 * * MON-FRI" // Weekdays at 9 AM + ] } +``` -function extractSeverity(analysis) { - if (analysis.includes('CRITICAL')) return 'critical'; - if (analysis.includes('URGENT')) return 'urgent'; - if (analysis.includes('WARNING')) return 'warning'; - return 'normal'; -} +### 3. **Real Data Integration** +- **Yahoo Finance API**: Free, no API key required +- **Error Handling**: Timeout management, fallback responses +- **Parallel Processing**: Fetch multiple symbols simultaneously -function getSeverityColor(severity) { - switch(severity) { - case 'critical': return 'red'; - case 'urgent': return 'orange'; - case 'warning': return 'yellow'; - default: return 'green'; - } -} -``` +### 4. **Production Features** +- **Web Interface**: Test manually via browser +- **Structured Responses**: Consistent JSON format +- **Error Recovery**: Graceful failure handling +- **Logging**: Console output for debugging ## Deployment ```bash -# Deploy your cron job agents +# Deploy to Cloudflare wrangler deploy -# Monitor logs +# View logs wrangler tail -# Test cron trigger manually -wrangler triggers cron "0 21 * * MON-FRI" +# Test cron manually +wrangler triggers cron "0 */3 * * *" ``` -## Cost Optimization - -- Use `gpt-4o-mini` for cost-effective analysis -- Set appropriate `max_tokens` limits -- Configure cron schedules to avoid unnecessary runs -- Implement error handling to prevent API waste - ## Environment Variables -Add to `wrangler.toml`: - -```toml -[env.production.vars] -SWARMS_API_KEY = "your-swarms-api-key" +Add to `wrangler.jsonc`: -# Stock API Keys (get free keys from these providers) -STOCK_API_KEY = "your-alpha-vantage-key" # Free: https://www.alphavantage.co/support/#api-key -FMP_API_KEY = "your-fmp-key" # Free: https://financialmodelingprep.com/developer/docs - -# Email Configuration (Mailgun) -MAILGUN_API_KEY = "your-mailgun-api-key" # Free: https://www.mailgun.com/ -MAILGUN_DOMAIN = "your-domain.com" -RECIPIENT_EMAIL = "investor@yourcompany.com" -MEDICAL_TEAM_EMAIL = "medical-team@hospital.com" +```jsonc +{ + "vars": { + "SWARMS_API_KEY": "your-swarms-api-key", + "MAILGUN_API_KEY": "optional-for-emails", + "MAILGUN_DOMAIN": "your-domain.com", + "RECIPIENT_EMAIL": "alerts@company.com" + } +} ``` -## API Endpoints Used +## Testing + +1. **Deploy**: `wrangler deploy` +2. **Visit URL**: Open your worker URL to see the web interface +3. **Manual Test**: Click "Start Analysis" button +4. **Cron Test**: `wrangler triggers cron "0 */3 * * *"` -### Stock Data APIs -- **Alpha Vantage**: Real-time stock prices, technical indicators (RSI, MACD) -- **Financial Modeling Prep**: Market news, earnings data, company fundamentals -- **Free Tier Limits**: Alpha Vantage (5 calls/min), FMP (250 calls/day) +## Production Tips -### Real Market Data Flow -1. **Fetch Live Data**: Current prices, volume, technical indicators -2. **Get Market News**: Recent earnings, economic events, analyst reports -3. **AI Analysis**: Swarms agents analyze real data for actionable insights -4. **Email Reports**: Professional HTML emails with analysis and data tables +- **Error Handling**: Always wrap API calls in try-catch +- **Timeouts**: Use AbortController for external API calls +- **Logging**: Use console.log for debugging in Cloudflare dashboard +- **Rate Limits**: Yahoo Finance is free but has rate limits +- **Cost Control**: Set appropriate `max_tokens` in agent config -### Email Features -- **Stock Reports**: Daily market analysis with data tables and key movers -- **Healthcare Alerts**: Color-coded severity levels with immediate attention flags -- **HTML Formatting**: Professional email templates with styling -- **Mailgun Integration**: Reliable email delivery service +This minimal implementation provides a solid foundation for production AI agents on Cloudflare Workers with automated scheduling and real-time data integration. \ No newline at end of file From 45f72f5a3307a9735ce8661d25c00a55c96664c2 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 03:27:35 +0530 Subject: [PATCH 50/73] fixed docs ! --- docs/swarms_cloud/cloudflare_workers.md | 935 ++++++++++++++++++++---- 1 file changed, 781 insertions(+), 154 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index ad73cce0..a28171fc 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -1,44 +1,59 @@ -# Cloudflare Workers with Swarms: Production AI Agents +# Deploy Autonomous Cron Agents with Swarms API on Cloudflare Workers -Deploy AI agents on Cloudflare's edge network with automatic cron scheduling and real-time data integration. This guide shows a production-ready implementation with both HTTP endpoints and scheduled triggers. +Deploy intelligent, self-executing AI agents powered by Swarms API on Cloudflare's global edge network. Build production-ready autonomous agents that run on automated schedules, fetch real-time data, perform sophisticated analysis using Swarms AI, and take automated actions across 330+ cities worldwide. -## Architecture Overview +## What Are Autonomous Cron Agents? -The Cloudflare Workers pattern uses two main handlers: -- **`fetch()`**: HTTP requests for testing and manual triggers -- **`scheduled()`**: Cron jobs for automated execution +Autonomous cron agents combine **Swarms API intelligence** with **Cloudflare Workers edge computing** to create AI-powered systems that: +- **Execute automatically** on predefined schedules without human intervention +- **Fetch real-time data** from external sources (APIs, databases, IoT sensors) +- **Perform intelligent analysis** using specialized Swarms AI agents +- **Take automated actions** based on analysis findings (alerts, reports, decisions) +- **Scale globally** on Cloudflare's edge network with sub-100ms response times worldwide -```javascript -export default { - // HTTP handler for manual testing - async fetch(request, env, ctx) { - // Handle web interface and API endpoints - }, - - // Cron handler for scheduled execution - async scheduled(event, env, ctx) { - ctx.waitUntil(handleStockAnalysis(event, env)); - } -}; +## Architecture Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Cloudflare │ │ Data Sources │ │ Swarms API │ +│ Workers Cron │ │ │ │ │ +│ "0 */3 * * *" │───▶│ Yahoo Finance │───▶│ Multi-Agent │ +│ │ │ Medical APIs │ │ Intelligence │ +│ scheduled() │ │ News Feeds │ │ Autonomous │ +│ Global Edge │ │ IoT Sensors │ │ Actions │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ ``` -## Quick Setup +**Key Benefits:** +- **24/7 Autonomous Operation**: Zero human intervention required +- **Global Edge Deployment**: Cloudflare's 330+ city network for ultra-low latency +- **Swarms AI Intelligence**: Live data analysis with specialized AI agents +- **Automated Decision Making**: Smart actions based on Swarms agent insights +- **Enterprise Reliability**: Production-grade error handling and monitoring -### 1. Create Worker Project +## Quick Start: Deploy Autonomous Stock Analysis Agent + +Create your first autonomous financial intelligence agent powered by Swarms API and deployed on Cloudflare Workers edge network. + +### 1. Cloudflare Workers Project Setup ```bash -npm create cloudflare@latest stock-agent -cd stock-agent +# Create new Cloudflare Workers project +npm create cloudflare@latest autonomous-stock-agent +cd autonomous-stock-agent + +# Install dependencies for Swarms API integration +npm run start ``` -### 2. Configure Cron Schedule +### 2. Configure Cloudflare Workers Cron Schedule -Edit `wrangler.jsonc`: +Edit `wrangler.jsonc` to set up autonomous execution: ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", - "name": "stock-agent", + "name": "autonomous-stock-agent", "main": "src/index.js", "compatibility_date": "2025-08-03", "observability": { @@ -46,58 +61,116 @@ Edit `wrangler.jsonc`: }, "triggers": { "crons": [ - "0 */3 * * *" // Every 3 hours + "0 */3 * * *" // Cloudflare Workers cron: autonomous analysis every 3 hours ] }, "vars": { - "SWARMS_API_KEY": "your-api-key" + "SWARMS_API_KEY": "your-swarms-api-key" // Your Swarms API key } } ``` -## Complete Minimal Implementation +### 3. Cloudflare Workers + Swarms API Implementation Create `src/index.js`: ```javascript export default { - // HTTP handler - provides web interface and manual triggers + // Cloudflare Workers fetch handler - Web interface for monitoring async fetch(request, env, ctx) { const url = new URL(request.url); - // Web interface for testing if (url.pathname === '/') { return new Response(` - Stock Analysis Agent + Autonomous Stock Intelligence Agent -

📈 Stock Analysis Agent

-
Status: Online ✅
- - -
+
+
+

🤖 Autonomous Stock Intelligence Agent

+

Powered by Swarms API • Running on Cloudflare Workers

+
+ Swarms AI + Cloudflare Edge + Real-time + Autonomous +
+
+ +
+ 🚀 Status: Swarms AI agents active on Cloudflare edge, monitoring markets 24/7 +
+ +
+ +
+ +
+
+

Swarms AI agents analyzing live market data on Cloudflare edge...

+
+ +
+
@@ -108,12 +181,11 @@ export default { }); } - // Manual trigger endpoint - if (url.pathname === '/trigger') { + if (url.pathname === '/execute') { try { - const result = await handleStockAnalysis(null, env); + const result = await executeAutonomousAnalysis(null, env); return new Response(JSON.stringify({ - message: 'Analysis triggered', + message: 'Autonomous analysis executed successfully', timestamp: new Date().toISOString(), result }), { @@ -121,7 +193,9 @@ export default { }); } catch (error) { return new Response(JSON.stringify({ - error: error.message + error: error.message, + timestamp: new Date().toISOString(), + system: 'autonomous-agent' }), { status: 500, headers: { 'Content-Type': 'application/json' } @@ -129,207 +203,760 @@ export default { } } - return new Response('Not Found', { status: 404 }); + return new Response('Autonomous Agent Endpoint Not Found', { status: 404 }); }, - // Cron handler - runs automatically on schedule + // Cloudflare Workers cron handler - triggered by scheduled events async scheduled(event, env, ctx) { - ctx.waitUntil(handleStockAnalysis(event, env)); + console.log('🚀 Cloudflare Workers cron triggered - executing Swarms AI analysis'); + ctx.waitUntil(executeAutonomousAnalysis(event, env)); } }; -// Main analysis function used by both HTTP and cron triggers -async function handleStockAnalysis(event, env) { - console.log('🚀 Starting stock analysis...'); +// Core function combining Cloudflare Workers execution with Swarms API intelligence +async function executeAutonomousAnalysis(event, env) { + console.log('🤖 Cloudflare Workers executing Swarms AI stock intelligence analysis...'); try { - // Step 1: Fetch real market data (using Yahoo Finance - no API key needed) - const marketData = await fetchMarketData(); + // Step 1: Autonomous data collection from multiple sources + console.log('📊 Executing autonomous data collection...'); + const marketIntelligence = await collectMarketIntelligence(); - // Check if we got valid data - const validSymbols = Object.keys(marketData).filter(symbol => !marketData[symbol].error); - if (validSymbols.length === 0) { - throw new Error('No valid market data retrieved'); + const validData = Object.keys(marketIntelligence).filter(symbol => !marketIntelligence[symbol].error); + if (validData.length === 0) { + throw new Error('Autonomous data collection failed - no valid market intelligence gathered'); } - // Step 2: Send to Swarms AI agents - const swarmConfig = { - name: "Stock Analysis", - description: "Real-time market analysis", + console.log(\`✅ Autonomous data collection successful: \${validData.length} sources\`); + + // Step 2: Deploy Swarms AI agents for autonomous analysis + console.log('🧠 Deploying Swarms AI agents for autonomous intelligence generation...'); + const swarmConfiguration = { + name: "Autonomous Market Intelligence Swarm", + description: "Self-executing financial analysis and decision support system", agents: [ { - agent_name: "Technical Analyst", - system_prompt: \`Analyze the provided stock data: - - Identify trends and key levels - - Provide trading signals - - Calculate technical indicators - Format analysis professionally.\`, + agent_name: "Autonomous Technical Intelligence Agent", + system_prompt: \`You are an autonomous technical analysis AI agent operating 24/7. Provide: + - Real-time trend identification and momentum analysis + - Dynamic support/resistance level calculations + - Technical indicator signals (RSI, MACD, moving averages) + - Autonomous price targets and risk assessments + - Self-executing trading signal recommendations + + Format your analysis as a professional autonomous intelligence briefing for automated systems.\`, model_name: "gpt-4o-mini", - max_tokens: 1500, + max_tokens: 2500, temperature: 0.2 + }, + { + agent_name: "Autonomous Market Sentiment Agent", + system_prompt: \`You are an autonomous market sentiment analysis AI agent. Continuously evaluate: + - Real-time market psychology and investor behavior patterns + - Volume analysis and institutional activity detection + - Risk-on vs risk-off sentiment shifts + - Autonomous sector rotation and leadership identification + - Self-executing market timing recommendations + + Provide actionable intelligence for autonomous decision-making systems.\`, + model_name: "gpt-4o-mini", + max_tokens: 2500, + temperature: 0.3 } ], - swarm_type: "SequentialWorkflow", - task: \`Analyze this market data: \\${JSON.stringify(marketData, null, 2)}\`, + swarm_type: "ConcurrentWorkflow", + task: \`Execute autonomous analysis of real-time market intelligence: + + LIVE MARKET INTELLIGENCE: + \${JSON.stringify(marketIntelligence, null, 2)} + + Generate comprehensive autonomous intelligence report including: + 1. Technical analysis with specific autonomous entry/exit recommendations + 2. Market sentiment assessment with timing signals for automated systems + 3. Risk management protocols for autonomous execution + 4. Self-executing action recommendations + 5. Key monitoring parameters for next autonomous cycle + + Focus on actionable intelligence for autonomous trading systems and automated decision making.\`, max_loops: 1 }; + // Execute Swarms API call from Cloudflare Workers edge const response = await fetch('https://api.swarms.world/v1/swarm/completions', { method: 'POST', headers: { 'x-api-key': env.SWARMS_API_KEY, 'Content-Type': 'application/json' }, - body: JSON.stringify(swarmConfig) + body: JSON.stringify(swarmConfiguration) }); if (!response.ok) { - throw new Error(\`Swarms API error: \\${response.status}\`); + const errorText = await response.text(); + throw new Error(\`Swarms API autonomous execution failed: \${response.status} - \${errorText}\`); } - const result = await response.json(); - console.log('✅ Analysis completed'); + const analysisResult = await response.json(); + const intelligenceReport = analysisResult.output; + + console.log('✅ Autonomous Swarms AI analysis completed successfully'); + console.log(\`💰 Autonomous execution cost: \${analysisResult.usage?.billing_info?.total_cost || 'N/A'}\`); + + // Step 3: Execute autonomous actions based on intelligence + if (env.AUTONOMOUS_ALERTS_EMAIL) { + console.log('📧 Executing autonomous alert system...'); + await executeAutonomousAlerts(env, intelligenceReport, marketIntelligence); + } return { success: true, - analysis: result.output, - symbolsAnalyzed: validSymbols.length, - cost: result.usage?.billing_info?.total_cost + analysis: intelligenceReport, + symbolsAnalyzed: validData.length, + cost: analysisResult.usage?.billing_info?.total_cost || analysisResult.metadata?.billing_info?.total_cost, + executionTime: new Date().toISOString(), + nextExecution: 'Scheduled for next autonomous cron trigger', + autonomousSystem: 'Swarms AI Agents' }; } catch (error) { - console.error('❌ Analysis failed:', error.message); + console.error('❌ Autonomous analysis execution failed:', error.message); return { success: false, - error: error.message + error: error.message, + executionTime: new Date().toISOString(), + autonomousSystem: 'Error in autonomous pipeline' }; } } -// Fetch market data from Yahoo Finance (free, no API key required) -async function fetchMarketData() { - const symbols = ['SPY', 'AAPL', 'MSFT', 'TSLA']; - const marketData = {}; +// Autonomous market intelligence collection +async function collectMarketIntelligence() { + const targetSymbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'NVDA', 'TSLA']; + const marketIntelligence = {}; + + console.log('🎯 Executing autonomous multi-source data collection...'); - const promises = symbols.map(async (symbol) => { + const dataCollectionPromises = targetSymbols.map(async (symbol) => { try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 8000); + const timeout = setTimeout(() => controller.abort(), 10000); + // Autonomous data collection from Yahoo Finance API const response = await fetch( - \`https://query1.finance.yahoo.com/v8/finance/chart/\\${symbol}\`, + \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}\`, { signal: controller.signal, - headers: { 'User-Agent': 'Mozilla/5.0' } + headers: { + 'User-Agent': 'Mozilla/5.0 (autonomous-swarms-agent) AppleWebKit/537.36' + } } ); clearTimeout(timeout); - if (!response.ok) throw new Error(\`HTTP \\${response.status}\`); + if (!response.ok) { + throw new Error(\`Autonomous data collection failed: HTTP \${response.status}\`); + } const data = await response.json(); - const result = data.chart.result[0]; - const meta = result.meta; + const chartResult = data.chart.result[0]; + const meta = chartResult.meta; + + if (!meta) { + throw new Error('Invalid market intelligence structure received'); + } const currentPrice = meta.regularMarketPrice; const previousClose = meta.previousClose; - const change = currentPrice - previousClose; - const changePercent = ((change / previousClose) * 100).toFixed(2); + const dayChange = currentPrice - previousClose; + const changePercent = ((dayChange / previousClose) * 100).toFixed(2); + + console.log(\`📈 \${symbol}: $\${currentPrice} (\${changePercent}%) - Autonomous collection successful\`); return [symbol, { price: currentPrice, - change: change, + change: dayChange, change_percent: changePercent, - volume: meta.regularMarketVolume, - currency: meta.currency + volume: meta.regularMarketVolume || 0, + market_cap: meta.marketCap || 0, + pe_ratio: meta.trailingPE || 0, + day_high: meta.regularMarketDayHigh, + day_low: meta.regularMarketDayLow, + fifty_two_week_high: meta.fiftyTwoWeekHigh, + fifty_two_week_low: meta.fiftyTwoWeekLow, + currency: meta.currency || 'USD', + market_state: meta.marketState, + autonomous_collection_time: new Date().toISOString(), + data_quality: 'high' }]; } catch (error) { - return [symbol, { error: error.message }]; + console.error(\`❌ Autonomous collection failed for \${symbol}:\`, error.message); + return [symbol, { + error: \`Autonomous collection failed: \${error.message}\`, + autonomous_collection_time: new Date().toISOString(), + data_quality: 'failed' + }]; } }); - const results = await Promise.allSettled(promises); + const results = await Promise.allSettled(dataCollectionPromises); results.forEach((result) => { if (result.status === 'fulfilled' && result.value) { const [symbol, data] = result.value; - marketData[symbol] = data; + marketIntelligence[symbol] = data; } }); - return marketData; + const successfulCollections = Object.keys(marketIntelligence).filter(k => !marketIntelligence[k]?.error).length; + console.log(\`📊 Autonomous intelligence collection completed: \${successfulCollections}/\${targetSymbols.length} successful\`); + + return marketIntelligence; } -``` -## Key Features Explained +// Autonomous alert and notification system +async function executeAutonomousAlerts(env, intelligenceReport, marketIntelligence) { + if (!env.MAILGUN_API_KEY || !env.MAILGUN_DOMAIN || !env.AUTONOMOUS_ALERTS_EMAIL) { + console.log('⚠️ Autonomous alert system not configured - skipping notifications'); + return; + } -### 1. **Dual Handler Pattern** -- **`fetch()`**: Handles HTTP requests, provides web UI for testing -- **`scheduled()`**: Executes on cron schedule automatically -- **Shared Logic**: Both use the same `handleStockAnalysis()` function + try { + // Autonomous detection of significant market movements + const significantMovements = Object.entries(marketIntelligence) + .filter(([symbol, data]) => data.change_percent && Math.abs(parseFloat(data.change_percent)) > 3) + .map(([symbol, data]) => \`\${symbol}: \${data.change_percent}%\`) + .join(', '); -### 2. **Cron Configuration** -```jsonc -"triggers": { - "crons": [ - "0 */3 * * *" // Every 3 hours - "0 9 * * MON-FRI" // Weekdays at 9 AM - ] + const alertSubject = \`🤖 Autonomous Market Intelligence Alert - \${new Date().toLocaleDateString()}\`; + + const alertBody = \` + + + + + + +
+
+

🤖 Autonomous Market Intelligence

+

AI-Powered Autonomous Financial Analysis • Swarms API

+
+ Autonomous + Real-time + AI-Powered + Global Edge +
+

Generated: \${new Date().toLocaleString()}

+
+ +
+
+

🚀 Autonomous Analysis Execution Complete

+

Significant Market Movements Detected: \${significantMovements || 'Market within normal volatility parameters'}

+

Next Autonomous Cycle: Scheduled automatically

+
+ +
+

🧠 Autonomous AI Intelligence Report

+

Generated by autonomous Swarms AI agents with real-time market intelligence:

+
\${intelligenceReport}
+
+ +
+

📊 Real-Time Market Intelligence

+ + + + + + + + + + + + + \${Object.entries(marketIntelligence) + .filter(([symbol, data]) => !data.error) + .map(([symbol, data]) => \` + + + + + + + + + \`).join('')} + +
SymbolPriceChangeVolumeMarket StateData Quality
\${symbol}$\${data.price?.toFixed(2) || 'N/A'} + \${parseFloat(data.change_percent) >= 0 ? '+' : ''}\${data.change_percent}% + \${data.volume?.toLocaleString() || 'N/A'}\${data.market_state || 'N/A'}\${data.data_quality || 'N/A'}
+
+
+ + +
+ + + \`; + + const formData = new FormData(); + formData.append('from', \`Autonomous Market Intelligence \`); + formData.append('to', env.AUTONOMOUS_ALERTS_EMAIL); + formData.append('subject', alertSubject); + formData.append('html', alertBody); + + const response = await fetch(\`https://api.mailgun.net/v3/\${env.MAILGUN_DOMAIN}/messages\`, { + method: 'POST', + headers: { + 'Authorization': \`Basic \${btoa(\`api:\${env.MAILGUN_API_KEY}\`)}\` + }, + body: formData + }); + + if (response.ok) { + console.log('✅ Autonomous alert system executed successfully'); + } else { + console.error('❌ Autonomous alert system execution failed:', await response.text()); + } + + } catch (error) { + console.error('❌ Autonomous alert system error:', error.message); + } } ``` -### 3. **Real Data Integration** -- **Yahoo Finance API**: Free, no API key required -- **Error Handling**: Timeout management, fallback responses -- **Parallel Processing**: Fetch multiple symbols simultaneously +## Autonomous Healthcare Intelligence Agent -### 4. **Production Features** -- **Web Interface**: Test manually via browser -- **Structured Responses**: Consistent JSON format -- **Error Recovery**: Graceful failure handling -- **Logging**: Console output for debugging +Deploy autonomous healthcare agents that provide 24/7 patient monitoring with intelligent analysis: -## Deployment +```javascript +export default { + async fetch(request, env, ctx) { + const url = new URL(request.url); + + if (url.pathname === '/') { + return new Response(` + + + + Autonomous Healthcare Intelligence + + + +
+
+

🏥 Autonomous Healthcare Intelligence

+

AI-Powered 24/7 Patient Monitoring • Autonomous Medical Analysis

+
+ Autonomous + 24/7 Monitoring + AI-Powered + Real-time +
+
+ +
+
+

✅ Normal Status

+

Patients: 15

+

Stable vital signs

+

Autonomous monitoring active

+
+
+

⚠️ Monitoring Required

+

Patients: 3

+

Elevated parameters

+

Enhanced surveillance

+
+
+

🚨 Critical Alert

+

Patients: 1

+

Immediate intervention

+

Emergency protocols active

+
+
+ +
+ +
+ +
+
+ + + + + `, { + headers: { 'Content-Type': 'text/html' } + }); + } + + if (url.pathname === '/health-intelligence') { + try { + const result = await executeAutonomousHealthIntelligence(null, env); + return new Response(JSON.stringify({ + message: 'Autonomous health intelligence analysis executed', + timestamp: new Date().toISOString(), + result + }), { + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + return new Response(JSON.stringify({ + error: error.message, + system: 'autonomous-healthcare-intelligence' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + } + + return new Response('Autonomous Healthcare Intelligence Endpoint Not Found', { status: 404 }); + }, -```bash -# Deploy to Cloudflare -wrangler deploy + // Autonomous healthcare monitoring - every 30 minutes + async scheduled(event, env, ctx) { + console.log('🏥 Autonomous healthcare intelligence system triggered'); + ctx.waitUntil(executeAutonomousHealthIntelligence(event, env)); + } +}; -# View logs -wrangler tail +async function executeAutonomousHealthIntelligence(event, env) { + console.log('🏥 Autonomous healthcare intelligence system executing...'); + + try { + // Autonomous patient data collection + const patientIntelligence = await collectPatientIntelligence(); + + // Deploy autonomous Swarms healthcare AI agents + const healthIntelligenceConfig = { + name: "Autonomous Healthcare Intelligence Swarm", + description: "24/7 autonomous patient monitoring and medical analysis system", + agents: [ + { + agent_name: "Autonomous Vital Signs Intelligence Agent", + system_prompt: \`You are an autonomous healthcare AI agent providing 24/7 patient monitoring. Analyze: + - Continuous heart rate monitoring and arrhythmia detection (Normal: 60-100 bpm) + - Autonomous blood pressure trend analysis (Normal: <140/90 mmHg) + - Real-time oxygen saturation monitoring (Normal: >95%) + - Continuous temperature surveillance (Normal: 97-99°F) + + Classify each patient autonomously as: NORMAL_MONITORING, ENHANCED_SURVEILLANCE, or CRITICAL_INTERVENTION + For critical findings, trigger autonomous emergency protocols.\`, + model_name: "gpt-4o-mini", + max_tokens: 2500, + temperature: 0.05 + }, + { + agent_name: "Autonomous Medical Risk Assessment Agent", + system_prompt: \`You are an autonomous medical AI providing continuous risk assessment. Monitor: + - Autonomous drug interaction analysis and medication safety + - Real-time disease progression and complication detection + - Continuous fall risk and mobility assessment + - Autonomous emergency intervention requirements + + Provide autonomous risk stratification and intervention priorities. + Focus on predictive analytics and early autonomous warning systems.\`, + model_name: "gpt-4o-mini", + max_tokens: 2500, + temperature: 0.05 + } + ], + swarm_type: "ConcurrentWorkflow", + task: \`Execute autonomous 24/7 healthcare intelligence analysis: + + PATIENT INTELLIGENCE DATA: + \${JSON.stringify(patientIntelligence, null, 2)} + + Generate autonomous healthcare intelligence including: + 1. Individual patient autonomous risk assessment and monitoring status + 2. Critical autonomous intervention requirements and emergency protocols + 3. Autonomous alert recommendations and escalation procedures + 4. Predictive health analytics and preventive care suggestions + 5. Next autonomous monitoring cycle parameters and priorities + + Focus on autonomous decision support for medical staff and emergency response systems.\`, + max_loops: 1 + }; -# Test cron manually -wrangler triggers cron "0 */3 * * *" + const response = await fetch('https://api.swarms.world/v1/swarm/completions', { + method: 'POST', + headers: { + 'x-api-key': env.SWARMS_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(healthIntelligenceConfig) + }); + + if (!response.ok) { + throw new Error(\`Autonomous healthcare intelligence failed: \${response.status}\`); + } + + const result = await response.json(); + const healthIntelligence = result.output; + + // Autonomous severity assessment + const severity = assessAutonomousHealthSeverity(healthIntelligence); + + console.log(\`✅ Autonomous healthcare intelligence completed - Severity: \${severity}\`); + + // Autonomous emergency response system + if (severity === 'critical' && env.HEALTHCARE_EMERGENCY_EMAIL) { + console.log('🚨 Executing autonomous emergency response protocol...'); + await executeAutonomousEmergencyResponse(env, healthIntelligence, patientIntelligence); + } + + return { + success: true, + analysis: healthIntelligence, + severity: severity, + patientsAnalyzed: Object.keys(patientIntelligence).length, + executionTime: new Date().toISOString(), + nextAutonomousMonitoring: 'Scheduled for next autonomous cycle', + autonomousSystem: 'Swarms Healthcare AI' + }; + + } catch (error) { + console.error('❌ Autonomous healthcare intelligence failed:', error.message); + return { + success: false, + error: error.message, + severity: 'autonomous_system_error', + executionTime: new Date().toISOString() + }; + } +} + +// Autonomous patient intelligence collection +async function collectPatientIntelligence() { + // In production, connect to EMR systems, IoT devices, and medical databases + return { + "ICU_Autonomous_001": { + name: "Sarah Johnson", + age: 68, + room: "ICU-205", + condition: "Post-cardiac surgery - autonomous monitoring", + vitals: { + heart_rate: 95, + blood_pressure_systolic: 135, + blood_pressure_diastolic: 88, + oxygen_saturation: 97, + temperature: 98.8, + respiratory_rate: 16 + }, + medications: ["Metoprolol", "Warfarin", "Furosemide"], + risk_factors: ["Diabetes", "Hypertension", "Previous MI"], + autonomous_monitoring_level: "enhanced", + last_updated: new Date().toISOString() + }, + "Ward_Autonomous_002": { + name: "Michael Chen", + age: 45, + room: "Ward-301", + condition: "Pneumonia recovery - autonomous surveillance", + vitals: { + heart_rate: 88, + blood_pressure_systolic: 128, + blood_pressure_diastolic: 82, + oxygen_saturation: 94, // Slightly low - autonomous flag + temperature: 100.1, // Mild fever - autonomous flag + respiratory_rate: 20 + }, + medications: ["Azithromycin", "Albuterol"], + risk_factors: ["Asthma", "Smoking history"], + autonomous_monitoring_level: "standard", + last_updated: new Date().toISOString() + }, + "Critical_Autonomous_003": { + name: "Elena Rodriguez", + age: 72, + room: "ICU-208", + condition: "Sepsis - autonomous critical monitoring", + vitals: { + heart_rate: 115, // Elevated - autonomous critical flag + blood_pressure_systolic: 85, // Low - autonomous critical flag + blood_pressure_diastolic: 55, // Low - autonomous critical flag + oxygen_saturation: 89, // Critical - autonomous emergency flag + temperature: 103.2, // High fever - autonomous critical flag + respiratory_rate: 28 // Elevated - autonomous critical flag + }, + medications: ["Vancomycin", "Norepinephrine", "Hydrocortisone"], + risk_factors: ["Sepsis", "Multi-organ dysfunction", "Advanced age"], + autonomous_monitoring_level: "critical", + last_updated: new Date().toISOString() + } + }; +} + +function assessAutonomousHealthSeverity(intelligenceReport) { + const reportText = typeof intelligenceReport === 'string' ? intelligenceReport : JSON.stringify(intelligenceReport); + + if (reportText.includes('CRITICAL_INTERVENTION') || + reportText.includes('EMERGENCY') || + reportText.includes('IMMEDIATE_ACTION') || + reportText.includes('SEPSIS') || + reportText.includes('CARDIAC_ARREST') || + reportText.includes('RESPIRATORY_FAILURE')) { + return 'critical'; + } else if (reportText.includes('ENHANCED_SURVEILLANCE') || + reportText.includes('ELEVATED') || + reportText.includes('CONCERNING') || + reportText.includes('ABNORMAL') || + reportText.includes('MONITORING_REQUIRED')) { + return 'warning'; + } + return 'normal'; +} + +async function executeAutonomousEmergencyResponse(env, intelligenceReport, patientIntelligence) { + console.log('🚨 Autonomous emergency response protocol executing...'); + + // Autonomous emergency response would include: + // - Immediate medical team notifications + // - Automated equipment preparation alerts + // - Emergency response coordination + // - Real-time escalation to on-call physicians + // - Integration with hospital emergency systems +} ``` -## Environment Variables +## Deployment & Configuration -Add to `wrangler.jsonc`: +### Environment Variables + +Configure your Cloudflare Workers deployment with Swarms API: ```jsonc { "vars": { "SWARMS_API_KEY": "your-swarms-api-key", - "MAILGUN_API_KEY": "optional-for-emails", - "MAILGUN_DOMAIN": "your-domain.com", - "RECIPIENT_EMAIL": "alerts@company.com" + "AUTONOMOUS_ALERTS_EMAIL": "intelligence@yourcompany.com", + "HEALTHCARE_EMERGENCY_EMAIL": "emergency@hospital.com", + "MAILGUN_API_KEY": "your-mailgun-key", + "MAILGUN_DOMAIN": "intelligence.yourcompany.com" + } +} +``` + +### Cloudflare Workers Cron Scheduling Patterns + +```jsonc +{ + "triggers": { + "crons": [ + "0 */3 * * *", // Financial Swarms agents every 3 hours + "*/30 * * * *", // Healthcare Swarms monitoring every 30 minutes + "0 9,15,21 * * *", // Daily Swarms intelligence briefings + "*/5 * * * *" // Critical Swarms systems every 5 minutes + ] } } ``` -## Testing +### Cloudflare Workers Deployment Commands + +```bash +# Deploy Swarms AI agents to Cloudflare Workers +wrangler deploy + +# Monitor Cloudflare Workers execution logs +wrangler tail + +# Test Cloudflare Workers cron triggers manually +wrangler triggers cron "0 */3 * * *" + +``` + +## Production Best Practices + +### 1. **Cloudflare Workers + Swarms API Integration** +- Implement comprehensive error handling for both platforms +- Use Cloudflare Workers KV for caching Swarms API responses +- Leverage Cloudflare Workers analytics for monitoring -1. **Deploy**: `wrangler deploy` -2. **Visit URL**: Open your worker URL to see the web interface -3. **Manual Test**: Click "Start Analysis" button -4. **Cron Test**: `wrangler triggers cron "0 */3 * * *"` +### 2. **Cost Optimization** +- Monitor Swarms API usage and costs +- Use Cloudflare Workers free tier (100K requests/day) +- Implement intelligent batching for Swarms API efficiency +- Use cost-effective Swarms models (gpt-4o-mini recommended) -## Production Tips +### 3. **Security & Compliance** +- Secure Swarms API keys in Cloudflare Workers environment variables +- Use Cloudflare Workers secrets for sensitive data +- Audit AI decisions and maintain compliance logs +- HIPAA compliance for healthcare applications -- **Error Handling**: Always wrap API calls in try-catch -- **Timeouts**: Use AbortController for external API calls -- **Logging**: Use console.log for debugging in Cloudflare dashboard -- **Rate Limits**: Yahoo Finance is free but has rate limits -- **Cost Control**: Set appropriate `max_tokens` in agent config +### 4. **Monitoring & Observability** +- Track Cloudflare Workers performance metrics +- Monitor Swarms API response times and success rates +- Use Cloudflare Workers analytics dashboard +- Set up alerts for system failures and anomalies -This minimal implementation provides a solid foundation for production AI agents on Cloudflare Workers with automated scheduling and real-time data integration. \ No newline at end of file +This deployment architecture combines **Swarms API's advanced multi-agent intelligence** with **Cloudflare Workers' global edge infrastructure**, enabling truly intelligent, self-executing AI agents that operate continuously across 330+ cities worldwide, providing real-time intelligence and automated decision-making capabilities with ultra-low latency. \ No newline at end of file From 01c60e56c71528860a0067752d9b3443e70e9b67 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 03:44:33 +0530 Subject: [PATCH 51/73] cleanup ! --- docs/swarms_cloud/cloudflare_workers.md | 515 +++++------------------- 1 file changed, 97 insertions(+), 418 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index a28171fc..4d339da9 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -1,15 +1,16 @@ -# Deploy Autonomous Cron Agents with Swarms API on Cloudflare Workers +# Deploy Cron Agents with Swarms API on Cloudflare Workers -Deploy intelligent, self-executing AI agents powered by Swarms API on Cloudflare's global edge network. Build production-ready autonomous agents that run on automated schedules, fetch real-time data, perform sophisticated analysis using Swarms AI, and take automated actions across 330+ cities worldwide. +Deploy intelligent, self-executing AI agents powered by Swarms API on Cloudflare's global edge network. Build production-ready cron agents that run on automated schedules, fetch real-time data, perform sophisticated analysis using Swarms AI, and take automated actions across 330+ cities worldwide. -## What Are Autonomous Cron Agents? +## What Are Cron Agents? -Autonomous cron agents combine **Swarms API intelligence** with **Cloudflare Workers edge computing** to create AI-powered systems that: -- **Execute automatically** on predefined schedules without human intervention -- **Fetch real-time data** from external sources (APIs, databases, IoT sensors) -- **Perform intelligent analysis** using specialized Swarms AI agents -- **Take automated actions** based on analysis findings (alerts, reports, decisions) -- **Scale globally** on Cloudflare's edge network with sub-100ms response times worldwide +Cron agents combine **Swarms API intelligence** with **Cloudflare Workers edge computing** to create AI-powered systems that: + +* **Execute automatically** on predefined schedules without human intervention +* **Fetch real-time data** from external sources (APIs, databases, IoT sensors) +* **Perform intelligent analysis** using specialized Swarms AI agents +* **Take automated actions** based on analysis findings (alerts, reports, decisions) +* **Scale globally** on Cloudflare's edge network with sub-100ms response times worldwide ## Architecture Overview @@ -25,35 +26,36 @@ Autonomous cron agents combine **Swarms API intelligence** with **Cloudflare Wor ``` **Key Benefits:** -- **24/7 Autonomous Operation**: Zero human intervention required -- **Global Edge Deployment**: Cloudflare's 330+ city network for ultra-low latency -- **Swarms AI Intelligence**: Live data analysis with specialized AI agents -- **Automated Decision Making**: Smart actions based on Swarms agent insights -- **Enterprise Reliability**: Production-grade error handling and monitoring -## Quick Start: Deploy Autonomous Stock Analysis Agent +* **24/7 Operation**: Zero human intervention required +* **Global Edge Deployment**: Cloudflare's 330+ city network for ultra-low latency +* **Swarms AI Intelligence**: Live data analysis with specialized AI agents +* **Automated Decision Making**: Smart actions based on Swarms agent insights +* **Enterprise Reliability**: Production-grade error handling and monitoring + +## Quick Start: Deploy Stock Analysis Cron Agent -Create your first autonomous financial intelligence agent powered by Swarms API and deployed on Cloudflare Workers edge network. +Create your first financial intelligence cron agent powered by Swarms API and deployed on Cloudflare Workers edge network. ### 1. Cloudflare Workers Project Setup ```bash # Create new Cloudflare Workers project -npm create cloudflare@latest autonomous-stock-agent -cd autonomous-stock-agent +npm create cloudflare@latest stock-cron-agent +cd stock-cron-agent # Install dependencies for Swarms API integration -npm run start +npm install ``` ### 2. Configure Cloudflare Workers Cron Schedule -Edit `wrangler.jsonc` to set up autonomous execution: +Edit `wrangler.jsonc` to set up cron execution: ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", - "name": "autonomous-stock-agent", + "name": "stock-cron-agent", "main": "src/index.js", "compatibility_date": "2025-08-03", "observability": { @@ -61,7 +63,7 @@ Edit `wrangler.jsonc` to set up autonomous execution: }, "triggers": { "crons": [ - "0 */3 * * *" // Cloudflare Workers cron: autonomous analysis every 3 hours + "0 */3 * * *" // Cloudflare Workers cron: analysis every 3 hours ] }, "vars": { @@ -82,95 +84,22 @@ export default { if (url.pathname === '/') { return new Response(` - - - Autonomous Stock Intelligence Agent - - -
-
-

🤖 Autonomous Stock Intelligence Agent

-

Powered by Swarms API • Running on Cloudflare Workers

-
- Swarms AI - Cloudflare Edge - Real-time - Autonomous -
-
- -
- 🚀 Status: Swarms AI agents active on Cloudflare edge, monitoring markets 24/7 -
- -
- -
- -
-
-

Swarms AI agents analyzing live market data on Cloudflare edge...

-
- -
-
- +

Stock Cron Agent

+

Status: Active |

+
@@ -183,7 +112,7 @@ export default { if (url.pathname === '/execute') { try { - const result = await executeAutonomousAnalysis(null, env); + const result = await executeAnalysis(null, env); return new Response(JSON.stringify({ message: 'Autonomous analysis executed successfully', timestamp: new Date().toISOString(), @@ -209,13 +138,13 @@ export default { // Cloudflare Workers cron handler - triggered by scheduled events async scheduled(event, env, ctx) { console.log('🚀 Cloudflare Workers cron triggered - executing Swarms AI analysis'); - ctx.waitUntil(executeAutonomousAnalysis(event, env)); + ctx.waitUntil(executeAnalysis(event, env)); } }; // Core function combining Cloudflare Workers execution with Swarms API intelligence -async function executeAutonomousAnalysis(event, env) { - console.log('🤖 Cloudflare Workers executing Swarms AI stock intelligence analysis...'); +async function executeAnalysis(event, env) { + console.log('🤖 Cloudflare Workers executing Swarms AI analysis...'); try { // Step 1: Autonomous data collection from multiple sources @@ -547,343 +476,89 @@ async function executeAutonomousAlerts(env, intelligenceReport, marketIntelligen } ``` -## Autonomous Healthcare Intelligence Agent +## Healthcare Cron Agent Example -Deploy autonomous healthcare agents that provide 24/7 patient monitoring with intelligent analysis: +Healthcare monitoring cron agent with Swarms AI: ```javascript export default { async fetch(request, env, ctx) { - const url = new URL(request.url); - - if (url.pathname === '/') { - return new Response(` - - - - Autonomous Healthcare Intelligence - - - -
-
-

🏥 Autonomous Healthcare Intelligence

-

AI-Powered 24/7 Patient Monitoring • Autonomous Medical Analysis

-
- Autonomous - 24/7 Monitoring - AI-Powered - Real-time -
-
- -
-
-

✅ Normal Status

-

Patients: 15

-

Stable vital signs

-

Autonomous monitoring active

-
-
-

⚠️ Monitoring Required

-

Patients: 3

-

Elevated parameters

-

Enhanced surveillance

-
-
-

🚨 Critical Alert

-

Patients: 1

-

Immediate intervention

-

Emergency protocols active

-
-
- -
- -
- -
-
- - - - - `, { - headers: { 'Content-Type': 'text/html' } + if (request.url.includes('/health')) { + return new Response(JSON.stringify({ + status: 'Healthcare cron agent active', + next_check: 'Every 30 minutes' + }), { + headers: { 'Content-Type': 'application/json' } }); } - - if (url.pathname === '/health-intelligence') { - try { - const result = await executeAutonomousHealthIntelligence(null, env); - return new Response(JSON.stringify({ - message: 'Autonomous health intelligence analysis executed', - timestamp: new Date().toISOString(), - result - }), { - headers: { 'Content-Type': 'application/json' } - }); - } catch (error) { - return new Response(JSON.stringify({ - error: error.message, - system: 'autonomous-healthcare-intelligence' - }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }); - } - } - - return new Response('Autonomous Healthcare Intelligence Endpoint Not Found', { status: 404 }); + return new Response('Healthcare Cron Agent'); }, - // Autonomous healthcare monitoring - every 30 minutes + // Healthcare monitoring - every 30 minutes async scheduled(event, env, ctx) { - console.log('🏥 Autonomous healthcare intelligence system triggered'); - ctx.waitUntil(executeAutonomousHealthIntelligence(event, env)); + console.log('🏥 Healthcare cron agent triggered'); + ctx.waitUntil(executeHealthAnalysis(event, env)); } }; -async function executeAutonomousHealthIntelligence(event, env) { - console.log('🏥 Autonomous healthcare intelligence system executing...'); - +async function executeHealthAnalysis(event, env) { try { - // Autonomous patient data collection - const patientIntelligence = await collectPatientIntelligence(); + // Collect patient data (from EMR, IoT devices, etc.) + const patientData = await getPatientData(); - // Deploy autonomous Swarms healthcare AI agents - const healthIntelligenceConfig = { - name: "Autonomous Healthcare Intelligence Swarm", - description: "24/7 autonomous patient monitoring and medical analysis system", + // Configure Swarms healthcare agents + const healthConfig = { + name: "Healthcare Monitoring Swarm", agents: [ { - agent_name: "Autonomous Vital Signs Intelligence Agent", - system_prompt: \`You are an autonomous healthcare AI agent providing 24/7 patient monitoring. Analyze: - - Continuous heart rate monitoring and arrhythmia detection (Normal: 60-100 bpm) - - Autonomous blood pressure trend analysis (Normal: <140/90 mmHg) - - Real-time oxygen saturation monitoring (Normal: >95%) - - Continuous temperature surveillance (Normal: 97-99°F) - - Classify each patient autonomously as: NORMAL_MONITORING, ENHANCED_SURVEILLANCE, or CRITICAL_INTERVENTION - For critical findings, trigger autonomous emergency protocols.\`, - model_name: "gpt-4o-mini", - max_tokens: 2500, - temperature: 0.05 - }, - { - agent_name: "Autonomous Medical Risk Assessment Agent", - system_prompt: \`You are an autonomous medical AI providing continuous risk assessment. Monitor: - - Autonomous drug interaction analysis and medication safety - - Real-time disease progression and complication detection - - Continuous fall risk and mobility assessment - - Autonomous emergency intervention requirements - - Provide autonomous risk stratification and intervention priorities. - Focus on predictive analytics and early autonomous warning systems.\`, + agent_name: "Vital Signs Monitor", + system_prompt: "Monitor patient vital signs and detect anomalies. Alert on critical values.", model_name: "gpt-4o-mini", - max_tokens: 2500, - temperature: 0.05 + max_tokens: 1000 } ], swarm_type: "ConcurrentWorkflow", - task: \`Execute autonomous 24/7 healthcare intelligence analysis: - - PATIENT INTELLIGENCE DATA: - \${JSON.stringify(patientIntelligence, null, 2)} - - Generate autonomous healthcare intelligence including: - 1. Individual patient autonomous risk assessment and monitoring status - 2. Critical autonomous intervention requirements and emergency protocols - 3. Autonomous alert recommendations and escalation procedures - 4. Predictive health analytics and preventive care suggestions - 5. Next autonomous monitoring cycle parameters and priorities - - Focus on autonomous decision support for medical staff and emergency response systems.\`, + task: `Analyze patient data: ${JSON.stringify(patientData, null, 2)}`, max_loops: 1 }; + // Call Swarms API const response = await fetch('https://api.swarms.world/v1/swarm/completions', { method: 'POST', headers: { 'x-api-key': env.SWARMS_API_KEY, 'Content-Type': 'application/json' }, - body: JSON.stringify(healthIntelligenceConfig) + body: JSON.stringify(healthConfig) }); - if (!response.ok) { - throw new Error(\`Autonomous healthcare intelligence failed: \${response.status}\`); - } - const result = await response.json(); - const healthIntelligence = result.output; - // Autonomous severity assessment - const severity = assessAutonomousHealthSeverity(healthIntelligence); - - console.log(\`✅ Autonomous healthcare intelligence completed - Severity: \${severity}\`); - - // Autonomous emergency response system - if (severity === 'critical' && env.HEALTHCARE_EMERGENCY_EMAIL) { - console.log('🚨 Executing autonomous emergency response protocol...'); - await executeAutonomousEmergencyResponse(env, healthIntelligence, patientIntelligence); + // Send alerts if critical conditions detected + if (result.output.includes('CRITICAL')) { + await sendHealthAlert(env, result.output); } - return { - success: true, - analysis: healthIntelligence, - severity: severity, - patientsAnalyzed: Object.keys(patientIntelligence).length, - executionTime: new Date().toISOString(), - nextAutonomousMonitoring: 'Scheduled for next autonomous cycle', - autonomousSystem: 'Swarms Healthcare AI' - }; - + return { success: true, analysis: result.output }; } catch (error) { - console.error('❌ Autonomous healthcare intelligence failed:', error.message); - return { - success: false, - error: error.message, - severity: 'autonomous_system_error', - executionTime: new Date().toISOString() - }; + console.error('Healthcare analysis failed:', error); + return { success: false, error: error.message }; } } -// Autonomous patient intelligence collection -async function collectPatientIntelligence() { - // In production, connect to EMR systems, IoT devices, and medical databases +async function getPatientData() { + // Mock patient data - replace with real EMR/IoT integration return { - "ICU_Autonomous_001": { - name: "Sarah Johnson", - age: 68, - room: "ICU-205", - condition: "Post-cardiac surgery - autonomous monitoring", - vitals: { - heart_rate: 95, - blood_pressure_systolic: 135, - blood_pressure_diastolic: 88, - oxygen_saturation: 97, - temperature: 98.8, - respiratory_rate: 16 - }, - medications: ["Metoprolol", "Warfarin", "Furosemide"], - risk_factors: ["Diabetes", "Hypertension", "Previous MI"], - autonomous_monitoring_level: "enhanced", - last_updated: new Date().toISOString() - }, - "Ward_Autonomous_002": { - name: "Michael Chen", - age: 45, - room: "Ward-301", - condition: "Pneumonia recovery - autonomous surveillance", - vitals: { - heart_rate: 88, - blood_pressure_systolic: 128, - blood_pressure_diastolic: 82, - oxygen_saturation: 94, // Slightly low - autonomous flag - temperature: 100.1, // Mild fever - autonomous flag - respiratory_rate: 20 - }, - medications: ["Azithromycin", "Albuterol"], - risk_factors: ["Asthma", "Smoking history"], - autonomous_monitoring_level: "standard", - last_updated: new Date().toISOString() - }, - "Critical_Autonomous_003": { - name: "Elena Rodriguez", - age: 72, - room: "ICU-208", - condition: "Sepsis - autonomous critical monitoring", - vitals: { - heart_rate: 115, // Elevated - autonomous critical flag - blood_pressure_systolic: 85, // Low - autonomous critical flag - blood_pressure_diastolic: 55, // Low - autonomous critical flag - oxygen_saturation: 89, // Critical - autonomous emergency flag - temperature: 103.2, // High fever - autonomous critical flag - respiratory_rate: 28 // Elevated - autonomous critical flag - }, - medications: ["Vancomycin", "Norepinephrine", "Hydrocortisone"], - risk_factors: ["Sepsis", "Multi-organ dysfunction", "Advanced age"], - autonomous_monitoring_level: "critical", - last_updated: new Date().toISOString() + patient_001: { + heart_rate: 115, // Elevated + oxygen_saturation: 89 // Low - critical } }; } -function assessAutonomousHealthSeverity(intelligenceReport) { - const reportText = typeof intelligenceReport === 'string' ? intelligenceReport : JSON.stringify(intelligenceReport); - - if (reportText.includes('CRITICAL_INTERVENTION') || - reportText.includes('EMERGENCY') || - reportText.includes('IMMEDIATE_ACTION') || - reportText.includes('SEPSIS') || - reportText.includes('CARDIAC_ARREST') || - reportText.includes('RESPIRATORY_FAILURE')) { - return 'critical'; - } else if (reportText.includes('ENHANCED_SURVEILLANCE') || - reportText.includes('ELEVATED') || - reportText.includes('CONCERNING') || - reportText.includes('ABNORMAL') || - reportText.includes('MONITORING_REQUIRED')) { - return 'warning'; - } - return 'normal'; -} - -async function executeAutonomousEmergencyResponse(env, intelligenceReport, patientIntelligence) { - console.log('🚨 Autonomous emergency response protocol executing...'); - - // Autonomous emergency response would include: - // - Immediate medical team notifications - // - Automated equipment preparation alerts - // - Emergency response coordination - // - Real-time escalation to on-call physicians - // - Integration with hospital emergency systems +async function sendHealthAlert(env, analysis) { + // Send emergency alerts via email/SMS + console.log('🚨 Critical health alert sent'); } ``` @@ -937,26 +612,30 @@ wrangler triggers cron "0 */3 * * *" ## Production Best Practices ### 1. **Cloudflare Workers + Swarms API Integration** -- Implement comprehensive error handling for both platforms -- Use Cloudflare Workers KV for caching Swarms API responses -- Leverage Cloudflare Workers analytics for monitoring + +* Implement comprehensive error handling for both platforms +* Use Cloudflare Workers KV for caching Swarms API responses +* Leverage Cloudflare Workers analytics for monitoring ### 2. **Cost Optimization** -- Monitor Swarms API usage and costs -- Use Cloudflare Workers free tier (100K requests/day) -- Implement intelligent batching for Swarms API efficiency -- Use cost-effective Swarms models (gpt-4o-mini recommended) + +* Monitor Swarms API usage and costs +* Use Cloudflare Workers free tier (100K requests/day) +* Implement intelligent batching for Swarms API efficiency +* Use cost-effective Swarms models (gpt-4o-mini recommended) ### 3. **Security & Compliance** -- Secure Swarms API keys in Cloudflare Workers environment variables -- Use Cloudflare Workers secrets for sensitive data -- Audit AI decisions and maintain compliance logs -- HIPAA compliance for healthcare applications + +* Secure Swarms API keys in Cloudflare Workers environment variables +* Use Cloudflare Workers secrets for sensitive data +* Audit AI decisions and maintain compliance logs +* HIPAA compliance for healthcare applications ### 4. **Monitoring & Observability** -- Track Cloudflare Workers performance metrics -- Monitor Swarms API response times and success rates -- Use Cloudflare Workers analytics dashboard -- Set up alerts for system failures and anomalies + +* Track Cloudflare Workers performance metrics +* Monitor Swarms API response times and success rates +* Use Cloudflare Workers analytics dashboard +* Set up alerts for system failures and anomalies This deployment architecture combines **Swarms API's advanced multi-agent intelligence** with **Cloudflare Workers' global edge infrastructure**, enabling truly intelligent, self-executing AI agents that operate continuously across 330+ cities worldwide, providing real-time intelligence and automated decision-making capabilities with ultra-low latency. \ No newline at end of file From 3473080babb746e9dc7334bbe82b23254b242435 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 03:54:17 +0530 Subject: [PATCH 52/73] updates ! --- docs/swarms_cloud/cloudflare_workers.md | 131 +++++------------------- 1 file changed, 27 insertions(+), 104 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index 4d339da9..fde8c094 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -96,10 +96,10 @@ export default { const res = await fetch('/execute'); const data = await res.json(); document.getElementById('result').innerHTML = data.result?.success - ? \`✅ Success: \${data.result.analysis}\` - : \`❌ Error: \${data.error}\`; + ? '✅ Success: ' + data.result.analysis + : '❌ Error: ' + data.error; } catch (e) { - document.getElementById('result').innerHTML = \`❌ Failed: \${e.message}\`; + document.getElementById('result').innerHTML = '❌ Failed: ' + e.message; } } @@ -156,7 +156,7 @@ async function executeAnalysis(event, env) { throw new Error('Autonomous data collection failed - no valid market intelligence gathered'); } - console.log(\`✅ Autonomous data collection successful: \${validData.length} sources\`); + console.log('✅ Autonomous data collection successful: ' + validData.length + ' sources'); // Step 2: Deploy Swarms AI agents for autonomous analysis console.log('🧠 Deploying Swarms AI agents for autonomous intelligence generation...'); @@ -222,14 +222,14 @@ async function executeAnalysis(event, env) { if (!response.ok) { const errorText = await response.text(); - throw new Error(\`Swarms API autonomous execution failed: \${response.status} - \${errorText}\`); + throw new Error('Swarms API autonomous execution failed: ' + response.status + ' - ' + errorText); } const analysisResult = await response.json(); const intelligenceReport = analysisResult.output; console.log('✅ Autonomous Swarms AI analysis completed successfully'); - console.log(\`💰 Autonomous execution cost: \${analysisResult.usage?.billing_info?.total_cost || 'N/A'}\`); + console.log('💰 Autonomous execution cost: ' + (analysisResult.usage?.billing_info?.total_cost || 'N/A')); // Step 3: Execute autonomous actions based on intelligence if (env.AUTONOMOUS_ALERTS_EMAIL) { @@ -272,7 +272,7 @@ async function collectMarketIntelligence() { // Autonomous data collection from Yahoo Finance API const response = await fetch( - \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}\`, + 'https://query1.finance.yahoo.com/v8/finance/chart/' + symbol, { signal: controller.signal, headers: { @@ -283,7 +283,7 @@ async function collectMarketIntelligence() { clearTimeout(timeout); if (!response.ok) { - throw new Error(\`Autonomous data collection failed: HTTP \${response.status}\`); + throw new Error('Autonomous data collection failed: HTTP ' + response.status); } const data = await response.json(); @@ -299,7 +299,7 @@ async function collectMarketIntelligence() { const dayChange = currentPrice - previousClose; const changePercent = ((dayChange / previousClose) * 100).toFixed(2); - console.log(\`📈 \${symbol}: $\${currentPrice} (\${changePercent}%) - Autonomous collection successful\`); + console.log('📈 ' + symbol + ': $' + currentPrice + ' (' + changePercent + '%) - Autonomous collection successful'); return [symbol, { price: currentPrice, @@ -319,9 +319,9 @@ async function collectMarketIntelligence() { }]; } catch (error) { - console.error(\`❌ Autonomous collection failed for \${symbol}:\`, error.message); + console.error('❌ Autonomous collection failed for ' + symbol + ':', error.message); return [symbol, { - error: \`Autonomous collection failed: \${error.message}\`, + error: 'Autonomous collection failed: ' + error.message, autonomous_collection_time: new Date().toISOString(), data_quality: 'failed' }]; @@ -337,7 +337,7 @@ async function collectMarketIntelligence() { }); const successfulCollections = Object.keys(marketIntelligence).filter(k => !marketIntelligence[k]?.error).length; - console.log(\`📊 Autonomous intelligence collection completed: \${successfulCollections}/\${targetSymbols.length} successful\`); + console.log('📊 Autonomous intelligence collection completed: ' + successfulCollections + '/' + targetSymbols.length + ' successful'); return marketIntelligence; } @@ -353,113 +353,36 @@ async function executeAutonomousAlerts(env, intelligenceReport, marketIntelligen // Autonomous detection of significant market movements const significantMovements = Object.entries(marketIntelligence) .filter(([symbol, data]) => data.change_percent && Math.abs(parseFloat(data.change_percent)) > 3) - .map(([symbol, data]) => \`\${symbol}: \${data.change_percent}%\`) + .map(([symbol, data]) => symbol + ': ' + data.change_percent + '%') .join(', '); - const alertSubject = \`🤖 Autonomous Market Intelligence Alert - \${new Date().toLocaleDateString()}\`; + const alertSubject = '🤖 Autonomous Market Intelligence Alert - ' + new Date().toLocaleDateString(); - const alertBody = \` - + const alertBody = ` - - - - -
-
-

🤖 Autonomous Market Intelligence

-

AI-Powered Autonomous Financial Analysis • Swarms API

-
- Autonomous - Real-time - AI-Powered - Global Edge -
-

Generated: \${new Date().toLocaleString()}

-
+ +

🤖 Market Intelligence Alert

+

Date: ${new Date().toLocaleString()}

+

Movements: ${significantMovements || 'Normal volatility'}

-
-
-

🚀 Autonomous Analysis Execution Complete

-

Significant Market Movements Detected: \${significantMovements || 'Market within normal volatility parameters'}

-

Next Autonomous Cycle: Scheduled automatically

-
- -
-

🧠 Autonomous AI Intelligence Report

-

Generated by autonomous Swarms AI agents with real-time market intelligence:

-
\${intelligenceReport}
-
- -
-

📊 Real-Time Market Intelligence

- - - - - - - - - - - - - \${Object.entries(marketIntelligence) - .filter(([symbol, data]) => !data.error) - .map(([symbol, data]) => \` - - - - - - - - - \`).join('')} - -
SymbolPriceChangeVolumeMarket StateData Quality
\${symbol}$\${data.price?.toFixed(2) || 'N/A'} - \${parseFloat(data.change_percent) >= 0 ? '+' : ''}\${data.change_percent}% - \${data.volume?.toLocaleString() || 'N/A'}\${data.market_state || 'N/A'}\${data.data_quality || 'N/A'}
-
-
+

Analysis Report:

+
${intelligenceReport}
- -
- +

Powered by Swarms API

+ - \`; + `; const formData = new FormData(); - formData.append('from', \`Autonomous Market Intelligence \`); + formData.append('from', 'Autonomous Market Intelligence '); formData.append('to', env.AUTONOMOUS_ALERTS_EMAIL); formData.append('subject', alertSubject); formData.append('html', alertBody); - const response = await fetch(\`https://api.mailgun.net/v3/\${env.MAILGUN_DOMAIN}/messages\`, { + const response = await fetch('https://api.mailgun.net/v3/' + env.MAILGUN_DOMAIN + '/messages', { method: 'POST', headers: { - 'Authorization': \`Basic \${btoa(\`api:\${env.MAILGUN_API_KEY}\`)}\` + 'Authorization': 'Basic ' + btoa('api:' + env.MAILGUN_API_KEY) }, body: formData }); From 6544500f8ff529e3e144bd244ac3a59fdea9b842 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 04:01:24 +0530 Subject: [PATCH 53/73] fixed mkdocs formatting --- docs/swarms_cloud/cloudflare_workers.md | 32 +++---------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index fde8c094..d2df71b7 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -166,47 +166,21 @@ async function executeAnalysis(event, env) { agents: [ { agent_name: "Autonomous Technical Intelligence Agent", - system_prompt: \`You are an autonomous technical analysis AI agent operating 24/7. Provide: - - Real-time trend identification and momentum analysis - - Dynamic support/resistance level calculations - - Technical indicator signals (RSI, MACD, moving averages) - - Autonomous price targets and risk assessments - - Self-executing trading signal recommendations - - Format your analysis as a professional autonomous intelligence briefing for automated systems.\`, + system_prompt: "You are an autonomous technical analysis AI agent operating 24/7. Provide: Real-time trend identification and momentum analysis, dynamic support/resistance level calculations, technical indicator signals (RSI, MACD, moving averages), autonomous price targets and risk assessments, and self-executing trading signal recommendations. Format your analysis as a professional autonomous intelligence briefing for automated systems.", model_name: "gpt-4o-mini", max_tokens: 2500, temperature: 0.2 }, { agent_name: "Autonomous Market Sentiment Agent", - system_prompt: \`You are an autonomous market sentiment analysis AI agent. Continuously evaluate: - - Real-time market psychology and investor behavior patterns - - Volume analysis and institutional activity detection - - Risk-on vs risk-off sentiment shifts - - Autonomous sector rotation and leadership identification - - Self-executing market timing recommendations - - Provide actionable intelligence for autonomous decision-making systems.\`, + system_prompt: "You are an autonomous market sentiment analysis AI agent. Continuously evaluate: Real-time market psychology and investor behavior patterns, volume analysis and institutional activity detection, risk-on vs risk-off sentiment shifts, autonomous sector rotation and leadership identification, and self-executing market timing recommendations. Provide actionable intelligence for autonomous decision-making systems.", model_name: "gpt-4o-mini", max_tokens: 2500, temperature: 0.3 } ], swarm_type: "ConcurrentWorkflow", - task: \`Execute autonomous analysis of real-time market intelligence: - - LIVE MARKET INTELLIGENCE: - \${JSON.stringify(marketIntelligence, null, 2)} - - Generate comprehensive autonomous intelligence report including: - 1. Technical analysis with specific autonomous entry/exit recommendations - 2. Market sentiment assessment with timing signals for automated systems - 3. Risk management protocols for autonomous execution - 4. Self-executing action recommendations - 5. Key monitoring parameters for next autonomous cycle - - Focus on actionable intelligence for autonomous trading systems and automated decision making.\`, + task: `Execute autonomous analysis of real-time market intelligence: ${JSON.stringify(marketIntelligence, null, 2)} Generate comprehensive autonomous intelligence report including: 1. Technical analysis with specific autonomous entry/exit recommendations 2. Market sentiment assessment with timing signals for automated systems 3. Risk management protocols for autonomous execution 4. Self-executing action recommendations 5. Key monitoring parameters for next autonomous cycle Focus on actionable intelligence for autonomous trading systems and automated decision making.`, max_loops: 1 }; From 4ec84a92891396223452a0584ff1737750ca6cd3 Mon Sep 17 00:00:00 2001 From: Filip Michalsky Date: Mon, 4 Aug 2025 21:42:40 -0400 Subject: [PATCH 54/73] update to swamrs spec --- examples/stagehand/2_stagehand_tools_agent.py | 472 ++++++++++-------- tests/stagehand/test_stagehand_simple.py | 22 +- 2 files changed, 276 insertions(+), 218 deletions(-) diff --git a/examples/stagehand/2_stagehand_tools_agent.py b/examples/stagehand/2_stagehand_tools_agent.py index f4931ceb..c2c6b26b 100644 --- a/examples/stagehand/2_stagehand_tools_agent.py +++ b/examples/stagehand/2_stagehand_tools_agent.py @@ -19,7 +19,6 @@ from dotenv import load_dotenv from loguru import logger from swarms import Agent -from swarms.tools.base_tool import BaseTool from stagehand import Stagehand, StagehandConfig load_dotenv() @@ -81,222 +80,283 @@ class BrowserState: browser_state = BrowserState() -class NavigateTool(BaseTool): - """Tool for navigating to URLs in the browser.""" - - def __init__(self): - super().__init__( - name="navigate_browser", - description="Navigate to a URL in the browser. Input should be a valid URL starting with http:// or https://", - verbose=True, - ) - - def run(self, url: str) -> str: - """Navigate to the specified URL.""" - return asyncio.run(self._async_run(url)) - - async def _async_run(self, url: str) -> str: - try: - await browser_state.init_browser() - page = await browser_state.get_page() - - # Ensure URL has protocol - if not url.startswith(("http://", "https://")): - url = f"https://{url}" - - await page.goto(url) - return f"Successfully navigated to {url}" - except Exception as e: - logger.error(f"Navigation error: {str(e)}") - return f"Failed to navigate to {url}: {str(e)}" - - -class ActTool(BaseTool): - """Tool for performing actions on web pages.""" - - def __init__(self): - super().__init__( - name="browser_act", - description=( - "Perform an action on the current web page using natural language. " - "Examples: 'click the submit button', 'type hello@example.com in the email field', " - "'scroll down', 'press Enter'" - ), - verbose=True, - ) - - def run(self, action: str) -> str: - """Perform the specified action.""" - return asyncio.run(self._async_run(action)) - - async def _async_run(self, action: str) -> str: - try: - await browser_state.init_browser() - page = await browser_state.get_page() - - result = await page.act(action) - return f"Action performed: {action}. Result: {result}" - except Exception as e: - logger.error(f"Action error: {str(e)}") - return f"Failed to perform action '{action}': {str(e)}" - - -class ExtractTool(BaseTool): - """Tool for extracting data from web pages.""" - - def __init__(self): - super().__init__( - name="browser_extract", - description=( - "Extract information from the current web page using natural language. " - "Examples: 'extract all email addresses', 'get the main article text', " - "'find all product prices', 'extract the page title and meta description'" - ), - verbose=True, - ) - - def run(self, query: str) -> str: - """Extract information based on the query.""" - return asyncio.run(self._async_run(query)) - - async def _async_run(self, query: str) -> str: - try: - await browser_state.init_browser() - page = await browser_state.get_page() - - extracted = await page.extract(query) - - # Convert to JSON string for agent consumption - if isinstance(extracted, (dict, list)): - return json.dumps(extracted, indent=2) - else: - return str(extracted) - except Exception as e: - logger.error(f"Extraction error: {str(e)}") - return f"Failed to extract '{query}': {str(e)}" - - -class ObserveTool(BaseTool): - """Tool for observing elements on web pages.""" - - def __init__(self): - super().__init__( - name="browser_observe", - description=( - "Observe and find elements on the current web page using natural language. " - "Returns information about elements including their selectors. " - "Examples: 'find the search box', 'locate the submit button', " - "'find all navigation links'" - ), - verbose=True, - ) - - def run(self, query: str) -> str: - """Observe elements based on the query.""" - return asyncio.run(self._async_run(query)) - - async def _async_run(self, query: str) -> str: - try: - await browser_state.init_browser() - page = await browser_state.get_page() - - observations = await page.observe(query) - - # Format observations for readability - result = [] - for obs in observations: - result.append( - { - "description": obs.description, - "selector": obs.selector, - "method": obs.method, - } - ) - - return json.dumps(result, indent=2) - except Exception as e: - logger.error(f"Observation error: {str(e)}") - return f"Failed to observe '{query}': {str(e)}" - - -class ScreenshotTool(BaseTool): - """Tool for taking screenshots of the current page.""" - - def __init__(self): - super().__init__( - name="browser_screenshot", - description="Take a screenshot of the current web page. Optionally provide a filename.", - verbose=True, - ) - - def run(self, filename: str = "screenshot.png") -> str: - """Take a screenshot.""" - return asyncio.run(self._async_run(filename)) - - async def _async_run(self, filename: str) -> str: - try: - await browser_state.init_browser() - page = await browser_state.get_page() - - # Ensure .png extension - if not filename.endswith(".png"): - filename += ".png" - - # Get the underlying Playwright page - playwright_page = page.page - await playwright_page.screenshot(path=filename) - - return f"Screenshot saved to {filename}" - except Exception as e: - logger.error(f"Screenshot error: {str(e)}") - return f"Failed to take screenshot: {str(e)}" - - -class CloseBrowserTool(BaseTool): - """Tool for closing the browser.""" - - def __init__(self): - super().__init__( - name="close_browser", - description="Close the browser when done with automation tasks", - verbose=True, - ) - - def run(self, *args) -> str: - """Close the browser.""" - return asyncio.run(self._async_run()) +def navigate_browser(url: str) -> str: + """ + Navigate to a URL in the browser. + + Args: + url (str): The URL to navigate to. Should be a valid URL starting with http:// or https://. + If no protocol is provided, https:// will be added automatically. + + Returns: + str: Success message with the URL navigated to, or error message if navigation fails + + Raises: + RuntimeError: If browser initialization fails + Exception: If navigation to the URL fails + + Example: + >>> result = navigate_browser("https://example.com") + >>> print(result) + "Successfully navigated to https://example.com" + + >>> result = navigate_browser("google.com") + >>> print(result) + "Successfully navigated to https://google.com" + """ + return asyncio.run(_navigate_browser_async(url)) + + +async def _navigate_browser_async(url: str) -> str: + """Async implementation of navigate_browser.""" + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + # Ensure URL has protocol + if not url.startswith(("http://", "https://")): + url = f"https://{url}" + + await page.goto(url) + return f"Successfully navigated to {url}" + except Exception as e: + logger.error(f"Navigation error: {str(e)}") + return f"Failed to navigate to {url}: {str(e)}" + + +def browser_act(action: str) -> str: + """ + Perform an action on the current web page using natural language. + + Args: + action (str): Natural language description of the action to perform. + Examples: 'click the submit button', 'type hello@example.com in the email field', + 'scroll down', 'press Enter', 'select option from dropdown' + + Returns: + str: JSON formatted string with action result and status information + + Raises: + RuntimeError: If browser is not initialized or page is not available + Exception: If the action cannot be performed on the current page + + Example: + >>> result = browser_act("click the submit button") + >>> print(result) + "Action performed: click the submit button. Result: clicked successfully" + + >>> result = browser_act("type hello@example.com in the email field") + >>> print(result) + "Action performed: type hello@example.com in the email field. Result: text entered" + """ + return asyncio.run(_browser_act_async(action)) + + +async def _browser_act_async(action: str) -> str: + """Async implementation of browser_act.""" + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + result = await page.act(action) + return f"Action performed: {action}. Result: {result}" + except Exception as e: + logger.error(f"Action error: {str(e)}") + return f"Failed to perform action '{action}': {str(e)}" + + +def browser_extract(query: str) -> str: + """ + Extract information from the current web page using natural language. + + Args: + query (str): Natural language description of what information to extract. + Examples: 'extract all email addresses', 'get the main article text', + 'find all product prices', 'extract the page title and meta description' + + Returns: + str: JSON formatted string containing the extracted information, or error message if extraction fails + + Raises: + RuntimeError: If browser is not initialized or page is not available + Exception: If extraction fails due to page content or parsing issues + + Example: + >>> result = browser_extract("extract all email addresses") + >>> print(result) + '["contact@example.com", "support@example.com"]' + + >>> result = browser_extract("get the main article text") + >>> print(result) + '{"title": "Article Title", "content": "Article content..."}' + """ + return asyncio.run(_browser_extract_async(query)) + + +async def _browser_extract_async(query: str) -> str: + """Async implementation of browser_extract.""" + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + extracted = await page.extract(query) + + # Convert to JSON string for agent consumption + if isinstance(extracted, (dict, list)): + return json.dumps(extracted, indent=2) + else: + return str(extracted) + except Exception as e: + logger.error(f"Extraction error: {str(e)}") + return f"Failed to extract '{query}': {str(e)}" + + +def browser_observe(query: str) -> str: + """ + Observe and find elements on the current web page using natural language. + + Args: + query (str): Natural language description of elements to find. + Examples: 'find the search box', 'locate the submit button', + 'find all navigation links', 'observe form elements' + + Returns: + str: JSON formatted string containing information about found elements including + their descriptions, selectors, and interaction methods + + Raises: + RuntimeError: If browser is not initialized or page is not available + Exception: If observation fails due to page structure or element detection issues + + Example: + >>> result = browser_observe("find the search box") + >>> print(result) + '[{"description": "Search input field", "selector": "#search", "method": "input"}]' + + >>> result = browser_observe("locate the submit button") + >>> print(result) + '[{"description": "Submit button", "selector": "button[type=submit]", "method": "click"}]' + """ + return asyncio.run(_browser_observe_async(query)) + + +async def _browser_observe_async(query: str) -> str: + """Async implementation of browser_observe.""" + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + observations = await page.observe(query) + + # Format observations for readability + result = [] + for obs in observations: + result.append( + { + "description": obs.description, + "selector": obs.selector, + "method": obs.method, + } + ) - async def _async_run(self) -> str: - try: - await browser_state.close() - return "Browser closed successfully" - except Exception as e: - logger.error(f"Close browser error: {str(e)}") - return f"Failed to close browser: {str(e)}" + return json.dumps(result, indent=2) + except Exception as e: + logger.error(f"Observation error: {str(e)}") + return f"Failed to observe '{query}': {str(e)}" + + +def browser_screenshot(filename: str = "screenshot.png") -> str: + """ + Take a screenshot of the current web page. + + Args: + filename (str, optional): The filename to save the screenshot to. + Defaults to "screenshot.png". + .png extension will be added automatically if not provided. + + Returns: + str: Success message with the filename where screenshot was saved, + or error message if screenshot fails + + Raises: + RuntimeError: If browser is not initialized or page is not available + Exception: If screenshot capture or file saving fails + + Example: + >>> result = browser_screenshot() + >>> print(result) + "Screenshot saved to screenshot.png" + + >>> result = browser_screenshot("page_capture.png") + >>> print(result) + "Screenshot saved to page_capture.png" + """ + return asyncio.run(_browser_screenshot_async(filename)) + + +async def _browser_screenshot_async(filename: str) -> str: + """Async implementation of browser_screenshot.""" + try: + await browser_state.init_browser() + page = await browser_state.get_page() + + # Ensure .png extension + if not filename.endswith(".png"): + filename += ".png" + + # Get the underlying Playwright page + playwright_page = page.page + await playwright_page.screenshot(path=filename) + + return f"Screenshot saved to {filename}" + except Exception as e: + logger.error(f"Screenshot error: {str(e)}") + return f"Failed to take screenshot: {str(e)}" + + +def close_browser() -> str: + """ + Close the browser when done with automation tasks. + + Returns: + str: Success message if browser is closed successfully, + or error message if closing fails + + Raises: + Exception: If browser closing process encounters errors + + Example: + >>> result = close_browser() + >>> print(result) + "Browser closed successfully" + """ + return asyncio.run(_close_browser_async()) + + +async def _close_browser_async() -> str: + """Async implementation of close_browser.""" + try: + await browser_state.close() + return "Browser closed successfully" + except Exception as e: + logger.error(f"Close browser error: {str(e)}") + return f"Failed to close browser: {str(e)}" # Example usage if __name__ == "__main__": - # Create browser automation tools - navigate_tool = NavigateTool() - act_tool = ActTool() - extract_tool = ExtractTool() - observe_tool = ObserveTool() - screenshot_tool = ScreenshotTool() - close_browser_tool = CloseBrowserTool() - # Create a Swarms agent with browser tools browser_agent = Agent( agent_name="BrowserAutomationAgent", model_name="gpt-4o-mini", max_loops=1, tools=[ - navigate_tool, - act_tool, - extract_tool, - observe_tool, - screenshot_tool, - close_browser_tool, + navigate_browser, + browser_act, + browser_extract, + browser_observe, + browser_screenshot, + close_browser, ], system_prompt="""You are a web browser automation specialist. You can: 1. Navigate to websites using the navigate_browser tool diff --git a/tests/stagehand/test_stagehand_simple.py b/tests/stagehand/test_stagehand_simple.py index 9a220f1c..e9066a10 100644 --- a/tests/stagehand/test_stagehand_simple.py +++ b/tests/stagehand/test_stagehand_simple.py @@ -55,12 +55,10 @@ class TestStagehandIntegrationStructure: content = f.read() # Check for required imports - assert ( - "from swarms.tools.base_tool import BaseTool" in content - ) - assert "class NavigateTool" in content - assert "class ActTool" in content - assert "class ExtractTool" in content + assert "from swarms import Agent" in content + assert "def navigate_browser" in content + assert "def browser_act" in content + assert "def browser_extract" in content def test_mcp_agent_imports(self): """Test that MCP agent has correct imports.""" @@ -194,7 +192,7 @@ class TestSwarmsPatternsCompliance: assert "return" in content def test_tools_pattern(self): - """Test that tools follow Swarms BaseTool pattern.""" + """Test that tools follow Swarms function-based pattern.""" # Read the tools agent file with open( @@ -202,11 +200,11 @@ class TestSwarmsPatternsCompliance: ) as f: content = f.read() - # Check tool pattern - assert "class NavigateTool(BaseTool):" in content - assert "def run(self," in content - assert "name=" in content - assert "description=" in content + # Check function-based tool pattern + assert "def navigate_browser(url: str) -> str:" in content + assert "def browser_act(action: str) -> str:" in content + assert "def browser_extract(query: str) -> str:" in content + assert "def browser_observe(query: str) -> str:" in content def test_mcp_integration_pattern(self): """Test MCP integration follows Swarms pattern.""" From 1dc56536368e02e5845565736e8c8aa45ed49e7c Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Tue, 5 Aug 2025 07:51:55 +0530 Subject: [PATCH 55/73] fixed ! --- docs/swarms_cloud/cloudflare_workers.md | 133 ------------------------ 1 file changed, 133 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index d2df71b7..cc265a77 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -373,139 +373,6 @@ async function executeAutonomousAlerts(env, intelligenceReport, marketIntelligen } ``` -## Healthcare Cron Agent Example - -Healthcare monitoring cron agent with Swarms AI: - -```javascript -export default { - async fetch(request, env, ctx) { - if (request.url.includes('/health')) { - return new Response(JSON.stringify({ - status: 'Healthcare cron agent active', - next_check: 'Every 30 minutes' - }), { - headers: { 'Content-Type': 'application/json' } - }); - } - return new Response('Healthcare Cron Agent'); - }, - - // Healthcare monitoring - every 30 minutes - async scheduled(event, env, ctx) { - console.log('🏥 Healthcare cron agent triggered'); - ctx.waitUntil(executeHealthAnalysis(event, env)); - } -}; - -async function executeHealthAnalysis(event, env) { - try { - // Collect patient data (from EMR, IoT devices, etc.) - const patientData = await getPatientData(); - - // Configure Swarms healthcare agents - const healthConfig = { - name: "Healthcare Monitoring Swarm", - agents: [ - { - agent_name: "Vital Signs Monitor", - system_prompt: "Monitor patient vital signs and detect anomalies. Alert on critical values.", - model_name: "gpt-4o-mini", - max_tokens: 1000 - } - ], - swarm_type: "ConcurrentWorkflow", - task: `Analyze patient data: ${JSON.stringify(patientData, null, 2)}`, - max_loops: 1 - }; - - // Call Swarms API - const response = await fetch('https://api.swarms.world/v1/swarm/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(healthConfig) - }); - - const result = await response.json(); - - // Send alerts if critical conditions detected - if (result.output.includes('CRITICAL')) { - await sendHealthAlert(env, result.output); - } - - return { success: true, analysis: result.output }; - } catch (error) { - console.error('Healthcare analysis failed:', error); - return { success: false, error: error.message }; - } -} - -async function getPatientData() { - // Mock patient data - replace with real EMR/IoT integration - return { - patient_001: { - heart_rate: 115, // Elevated - oxygen_saturation: 89 // Low - critical - } - }; -} - -async function sendHealthAlert(env, analysis) { - // Send emergency alerts via email/SMS - console.log('🚨 Critical health alert sent'); -} -``` - -## Deployment & Configuration - -### Environment Variables - -Configure your Cloudflare Workers deployment with Swarms API: - -```jsonc -{ - "vars": { - "SWARMS_API_KEY": "your-swarms-api-key", - "AUTONOMOUS_ALERTS_EMAIL": "intelligence@yourcompany.com", - "HEALTHCARE_EMERGENCY_EMAIL": "emergency@hospital.com", - "MAILGUN_API_KEY": "your-mailgun-key", - "MAILGUN_DOMAIN": "intelligence.yourcompany.com" - } -} -``` - -### Cloudflare Workers Cron Scheduling Patterns - -```jsonc -{ - "triggers": { - "crons": [ - "0 */3 * * *", // Financial Swarms agents every 3 hours - "*/30 * * * *", // Healthcare Swarms monitoring every 30 minutes - "0 9,15,21 * * *", // Daily Swarms intelligence briefings - "*/5 * * * *" // Critical Swarms systems every 5 minutes - ] - } -} -``` - -### Cloudflare Workers Deployment Commands - -```bash -# Deploy Swarms AI agents to Cloudflare Workers -wrangler deploy - -# Monitor Cloudflare Workers execution logs -wrangler tail - -# Test Cloudflare Workers cron triggers manually -wrangler triggers cron "0 */3 * * *" - -``` - ## Production Best Practices ### 1. **Cloudflare Workers + Swarms API Integration** From cd3cec13bb13beeb65499e544f2ad22f9e218e7c Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:42:50 +0300 Subject: [PATCH 56/73] Update __init__.py --- swarms/structs/__init__.py | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index 0241a2c1..05921846 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -4,6 +4,28 @@ from swarms.structs.auto_swarm_builder import AutoSwarmBuilder from swarms.structs.base_structure import BaseStructure from swarms.structs.base_swarm import BaseSwarm from swarms.structs.batch_agent_execution import batch_agent_execution +from swarms.structs.board_of_directors_swarm import ( + BoardConfig, + BoardConfigModel, + BoardDecision, + BoardDecisionType, + BoardFeatureStatus, + BoardMember, + BoardMemberRole, + BoardOfDirectorsSwarm, + BoardOrder, + BoardSpec, + create_default_config_file, + disable_board_feature, + disable_verbose_logging, + enable_board_feature, + enable_verbose_logging, + get_board_config, + is_board_feature_enabled, + set_board_model, + set_board_size, + set_decision_threshold, +) from swarms.structs.concurrent_workflow import ConcurrentWorkflow from swarms.structs.conversation import Conversation from swarms.structs.council_judge import CouncilAsAJudge @@ -93,10 +115,37 @@ from swarms.structs.swarming_architectures import ( star_swarm, ) +# Standalone function for getting default board templates +def get_default_board_template(template_name: str = "standard") -> dict: + """ + Get a default board template. + + This function provides predefined board templates for common use cases. + Templates are cached for improved performance. + + Args: + template_name: Name of the template to retrieve + + Returns: + dict: Board template configuration + """ + config = get_board_config() + return config.get_default_board_template(template_name) + __all__ = [ "Agent", "BaseStructure", "BaseSwarm", + "BoardConfig", + "BoardConfigModel", + "BoardDecision", + "BoardDecisionType", + "BoardFeatureStatus", + "BoardMember", + "BoardMemberRole", + "BoardOfDirectorsSwarm", + "BoardOrder", + "BoardSpec", "ConcurrentWorkflow", "Conversation", "GroupChat", @@ -170,4 +219,15 @@ __all__ = [ "HierarchicalSwarm", "HeavySwarm", "CronJob", + "create_default_config_file", + "disable_board_feature", + "disable_verbose_logging", + "enable_board_feature", + "enable_verbose_logging", + "get_board_config", + "get_default_board_template", + "is_board_feature_enabled", + "set_board_model", + "set_board_size", + "set_decision_threshold", ] From 1be9e4dec0dc101958fb75166c8830d0063cd1d5 Mon Sep 17 00:00:00 2001 From: CI-DEV <154627941+IlumCI@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:02:06 +0300 Subject: [PATCH 57/73] Update __init__.py --- swarms/structs/__init__.py | 60 +------------------------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index 05921846..b52f9c1e 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -4,28 +4,7 @@ from swarms.structs.auto_swarm_builder import AutoSwarmBuilder from swarms.structs.base_structure import BaseStructure from swarms.structs.base_swarm import BaseSwarm from swarms.structs.batch_agent_execution import batch_agent_execution -from swarms.structs.board_of_directors_swarm import ( - BoardConfig, - BoardConfigModel, - BoardDecision, - BoardDecisionType, - BoardFeatureStatus, - BoardMember, - BoardMemberRole, - BoardOfDirectorsSwarm, - BoardOrder, - BoardSpec, - create_default_config_file, - disable_board_feature, - disable_verbose_logging, - enable_board_feature, - enable_verbose_logging, - get_board_config, - is_board_feature_enabled, - set_board_model, - set_board_size, - set_decision_threshold, -) +from swarms.structs.board_of_directors_swarm import BoardOfDirectorsSwarm from swarms.structs.concurrent_workflow import ConcurrentWorkflow from swarms.structs.conversation import Conversation from swarms.structs.council_judge import CouncilAsAJudge @@ -115,37 +94,11 @@ from swarms.structs.swarming_architectures import ( star_swarm, ) -# Standalone function for getting default board templates -def get_default_board_template(template_name: str = "standard") -> dict: - """ - Get a default board template. - - This function provides predefined board templates for common use cases. - Templates are cached for improved performance. - - Args: - template_name: Name of the template to retrieve - - Returns: - dict: Board template configuration - """ - config = get_board_config() - return config.get_default_board_template(template_name) - __all__ = [ "Agent", "BaseStructure", "BaseSwarm", - "BoardConfig", - "BoardConfigModel", - "BoardDecision", - "BoardDecisionType", - "BoardFeatureStatus", - "BoardMember", - "BoardMemberRole", "BoardOfDirectorsSwarm", - "BoardOrder", - "BoardSpec", "ConcurrentWorkflow", "Conversation", "GroupChat", @@ -219,15 +172,4 @@ __all__ = [ "HierarchicalSwarm", "HeavySwarm", "CronJob", - "create_default_config_file", - "disable_board_feature", - "disable_verbose_logging", - "enable_board_feature", - "enable_verbose_logging", - "get_board_config", - "get_default_board_template", - "is_board_feature_enabled", - "set_board_model", - "set_board_size", - "set_decision_threshold", ] From 6dc01c3c72b8f2540b7105a3c992395007b0d98d Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 5 Aug 2025 11:32:26 -0700 Subject: [PATCH 58/73] cron job examples, nw gpt-oss examples, and more --- docs/swarms/structs/cron_job.md | 357 +++++++ .../crypto_concurrent_cron_example.py | 349 ++++++ .../simple_concurrent_crypto_cron.py | 157 +++ .../cron_job_examples/solana_price_tracker.py | 2 +- .../board_of_directors_example.py | 66 +- .../hiearchical_swarm_ui/debug_dashboard.py | 70 ++ .../hiearchical_swarm_example.py | 71 ++ .../hiearchical_swarm_ui/test_dashboard.py | 56 + .../hiearchical_swarm_ui/test_full_output.py | 56 + .../hiearchical_swarm_ui/test_multi_loop.py | 57 + .../llms/gpt_oss_examples/gpt_os_agent.py | 44 + .../gpt_oss_examples/groq_gpt_oss_models.py | 49 + hs_interactive.py | 24 + swarms/prompts/reasoning_prompt.py | 5 + swarms/structs/__init__.py | 1 + swarms/structs/agent.py | 21 +- swarms/structs/batch_agent_execution.py | 90 +- swarms/structs/board_of_directors_swarm.py | 990 +++++++++++------- swarms/structs/hiearchical_swarm.py | 771 +++++++++++++- .../structs/test_board_of_directors_swarm.py | 788 ++++++++------ 20 files changed, 3288 insertions(+), 736 deletions(-) create mode 100644 examples/cron_job_examples/crypto_concurrent_cron_example.py create mode 100644 examples/cron_job_examples/simple_concurrent_crypto_cron.py create mode 100644 examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/debug_dashboard.py create mode 100644 examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/hiearchical_swarm_example.py create mode 100644 examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_dashboard.py create mode 100644 examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_full_output.py create mode 100644 examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_multi_loop.py create mode 100644 examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py create mode 100644 examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py create mode 100644 hs_interactive.py diff --git a/docs/swarms/structs/cron_job.md b/docs/swarms/structs/cron_job.md index 2d06c3af..c2ab0c24 100644 --- a/docs/swarms/structs/cron_job.md +++ b/docs/swarms/structs/cron_job.md @@ -122,6 +122,363 @@ cron_job = CronJob( cron_job.run("Perform analysis") ``` + +### Cron Jobs With Multi-Agent Structures + +You can also run Cron Jobs with multi-agent structures like `SequentialWorkflow`, `ConcurrentWorkflow`, `HiearchicalSwarm`, and other methods. + +- Just initialize the class as the agent parameter in the `CronJob(agent=swarm)` + +- Input your arguments into the `.run(task: str)` method + + +```python +""" +Cryptocurrency Concurrent Multi-Agent Cron Job Example + +This example demonstrates how to use ConcurrentWorkflow with CronJob to create +a powerful cryptocurrency tracking system. Each specialized agent analyzes a +specific cryptocurrency concurrently every minute. + +Features: +- ConcurrentWorkflow for parallel agent execution +- CronJob scheduling for automated runs every 1 minute +- Each agent specializes in analyzing one specific cryptocurrency +- Real-time data fetching from CoinGecko API +- Concurrent analysis of multiple cryptocurrencies +- Structured output with professional formatting + +Architecture: +CronJob -> ConcurrentWorkflow -> [Bitcoin Agent, Ethereum Agent, Solana Agent, etc.] -> Parallel Analysis +""" + +from typing import List +from loguru import logger + +from swarms import Agent, CronJob, ConcurrentWorkflow +from swarms_tools import coin_gecko_coin_api + + +def create_crypto_specific_agents() -> List[Agent]: + """ + Creates agents that each specialize in analyzing a specific cryptocurrency. + + Returns: + List[Agent]: List of cryptocurrency-specific Agent instances + """ + + # Bitcoin Specialist Agent + bitcoin_agent = Agent( + agent_name="Bitcoin-Analyst", + agent_description="Expert analyst specializing exclusively in Bitcoin (BTC) analysis and market dynamics", + system_prompt="""You are a Bitcoin specialist and expert analyst. Your expertise includes: + +BITCOIN SPECIALIZATION: +- Bitcoin's unique position as digital gold +- Bitcoin halving cycles and their market impact +- Bitcoin mining economics and hash rate analysis +- Lightning Network and Layer 2 developments +- Bitcoin adoption by institutions and countries +- Bitcoin's correlation with traditional markets +- Bitcoin technical analysis and on-chain metrics +- Bitcoin's role as a store of value and hedge against inflation + +ANALYSIS FOCUS: +- Analyze ONLY Bitcoin data from the provided dataset +- Focus on Bitcoin-specific metrics and trends +- Consider Bitcoin's unique market dynamics +- Evaluate Bitcoin's dominance and market leadership +- Assess institutional adoption trends +- Monitor on-chain activity and network health + +DELIVERABLES: +- Bitcoin-specific analysis and insights +- Price action assessment and predictions +- Market dominance analysis +- Institutional adoption impact +- Technical and fundamental outlook +- Risk factors specific to Bitcoin + +Extract Bitcoin data from the provided dataset and provide comprehensive Bitcoin-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Ethereum Specialist Agent + ethereum_agent = Agent( + agent_name="Ethereum-Analyst", + agent_description="Expert analyst specializing exclusively in Ethereum (ETH) analysis and ecosystem development", + system_prompt="""You are an Ethereum specialist and expert analyst. Your expertise includes: + +ETHEREUM SPECIALIZATION: +- Ethereum's smart contract platform and DeFi ecosystem +- Ethereum 2.0 transition and proof-of-stake mechanics +- Gas fees, network usage, and scalability solutions +- Layer 2 solutions (Arbitrum, Optimism, Polygon) +- DeFi protocols and TVL (Total Value Locked) analysis +- NFT markets and Ethereum's role in digital assets +- Developer activity and ecosystem growth +- EIP proposals and network upgrades + +ANALYSIS FOCUS: +- Analyze ONLY Ethereum data from the provided dataset +- Focus on Ethereum's platform utility and network effects +- Evaluate DeFi ecosystem health and growth +- Assess Layer 2 adoption and scalability solutions +- Monitor network usage and gas fee trends +- Consider Ethereum's competitive position vs other smart contract platforms + +DELIVERABLES: +- Ethereum-specific analysis and insights +- Platform utility and adoption metrics +- DeFi ecosystem impact assessment +- Network health and scalability evaluation +- Competitive positioning analysis +- Technical and fundamental outlook for ETH + +Extract Ethereum data from the provided dataset and provide comprehensive Ethereum-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Solana Specialist Agent + solana_agent = Agent( + agent_name="Solana-Analyst", + agent_description="Expert analyst specializing exclusively in Solana (SOL) analysis and ecosystem development", + system_prompt="""You are a Solana specialist and expert analyst. Your expertise includes: + +SOLANA SPECIALIZATION: +- Solana's high-performance blockchain architecture +- Proof-of-History consensus mechanism +- Solana's DeFi ecosystem and DEX platforms (Serum, Raydium) +- NFT marketplaces and creator economy on Solana +- Network outages and reliability concerns +- Developer ecosystem and Rust programming adoption +- Validator economics and network decentralization +- Cross-chain bridges and interoperability + +ANALYSIS FOCUS: +- Analyze ONLY Solana data from the provided dataset +- Focus on Solana's performance and scalability advantages +- Evaluate network stability and uptime improvements +- Assess ecosystem growth and developer adoption +- Monitor DeFi and NFT activity on Solana +- Consider Solana's competitive position vs Ethereum + +DELIVERABLES: +- Solana-specific analysis and insights +- Network performance and reliability assessment +- Ecosystem growth and adoption metrics +- DeFi and NFT market analysis +- Competitive advantages and challenges +- Technical and fundamental outlook for SOL + +Extract Solana data from the provided dataset and provide comprehensive Solana-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Cardano Specialist Agent + cardano_agent = Agent( + agent_name="Cardano-Analyst", + agent_description="Expert analyst specializing exclusively in Cardano (ADA) analysis and research-driven development", + system_prompt="""You are a Cardano specialist and expert analyst. Your expertise includes: + +CARDANO SPECIALIZATION: +- Cardano's research-driven development approach +- Ouroboros proof-of-stake consensus protocol +- Smart contract capabilities via Plutus and Marlowe +- Cardano's three-layer architecture (settlement, computation, control) +- Academic partnerships and peer-reviewed research +- Cardano ecosystem projects and DApp development +- Native tokens and Cardano's UTXO model +- Sustainability and treasury funding mechanisms + +ANALYSIS FOCUS: +- Analyze ONLY Cardano data from the provided dataset +- Focus on Cardano's methodical development approach +- Evaluate smart contract adoption and ecosystem growth +- Assess academic partnerships and research contributions +- Monitor native token ecosystem development +- Consider Cardano's long-term roadmap and milestones + +DELIVERABLES: +- Cardano-specific analysis and insights +- Development progress and milestone achievements +- Smart contract ecosystem evaluation +- Academic research impact assessment +- Native token and DApp adoption metrics +- Technical and fundamental outlook for ADA + +Extract Cardano data from the provided dataset and provide comprehensive Cardano-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Binance Coin Specialist Agent + bnb_agent = Agent( + agent_name="BNB-Analyst", + agent_description="Expert analyst specializing exclusively in BNB analysis and Binance ecosystem dynamics", + system_prompt="""You are a BNB specialist and expert analyst. Your expertise includes: + +BNB SPECIALIZATION: +- BNB's utility within the Binance ecosystem +- Binance Smart Chain (BSC) development and adoption +- BNB token burns and deflationary mechanics +- Binance exchange volume and market leadership +- BSC DeFi ecosystem and yield farming +- Cross-chain bridges and multi-chain strategies +- Regulatory challenges facing Binance globally +- BNB's role in transaction fee discounts and platform benefits + +ANALYSIS FOCUS: +- Analyze ONLY BNB data from the provided dataset +- Focus on BNB's utility value and exchange benefits +- Evaluate BSC ecosystem growth and competition with Ethereum +- Assess token burn impact on supply and price +- Monitor Binance platform developments and regulations +- Consider BNB's centralized vs decentralized aspects + +DELIVERABLES: +- BNB-specific analysis and insights +- Utility value and ecosystem benefits assessment +- BSC adoption and DeFi growth evaluation +- Token economics and burn mechanism impact +- Regulatory risk and compliance analysis +- Technical and fundamental outlook for BNB + +Extract BNB data from the provided dataset and provide comprehensive BNB-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # XRP Specialist Agent + xrp_agent = Agent( + agent_name="XRP-Analyst", + agent_description="Expert analyst specializing exclusively in XRP analysis and cross-border payment solutions", + system_prompt="""You are an XRP specialist and expert analyst. Your expertise includes: + +XRP SPECIALIZATION: +- XRP's role in cross-border payments and remittances +- RippleNet adoption by financial institutions +- Central Bank Digital Currency (CBDC) partnerships +- Regulatory landscape and SEC lawsuit implications +- XRP Ledger's consensus mechanism and energy efficiency +- On-Demand Liquidity (ODL) usage and growth +- Competition with SWIFT and traditional payment rails +- Ripple's partnerships with banks and payment providers + +ANALYSIS FOCUS: +- Analyze ONLY XRP data from the provided dataset +- Focus on XRP's utility in payments and remittances +- Evaluate RippleNet adoption and institutional partnerships +- Assess regulatory developments and legal clarity +- Monitor ODL usage and transaction volumes +- Consider XRP's competitive position in payments + +DELIVERABLES: +- XRP-specific analysis and insights +- Payment utility and adoption assessment +- Regulatory landscape and legal developments +- Institutional partnership impact evaluation +- Cross-border payment market analysis +- Technical and fundamental outlook for XRP + +Extract XRP data from the provided dataset and provide comprehensive XRP-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + return [ + bitcoin_agent, + ethereum_agent, + solana_agent, + cardano_agent, + bnb_agent, + xrp_agent, + ] + + +def create_crypto_workflow() -> ConcurrentWorkflow: + """ + Creates a ConcurrentWorkflow with cryptocurrency-specific analysis agents. + + Returns: + ConcurrentWorkflow: Configured workflow for crypto analysis + """ + agents = create_crypto_specific_agents() + + workflow = ConcurrentWorkflow( + name="Crypto-Specific-Analysis-Workflow", + description="Concurrent execution of cryptocurrency-specific analysis agents", + agents=agents, + max_loops=1, + ) + + return workflow + + +def create_crypto_cron_job() -> CronJob: + """ + Creates a CronJob that runs cryptocurrency-specific analysis every minute using ConcurrentWorkflow. + + Returns: + CronJob: Configured cron job for automated crypto analysis + """ + # Create the concurrent workflow + workflow = create_crypto_workflow() + + # Create the cron job + cron_job = CronJob( + agent=workflow, # Use the workflow as the agent + interval="5seconds", # Run every 1 minute + ) + + return cron_job + + +def main(): + """ + Main function to run the cryptocurrency-specific concurrent analysis cron job. + """ + cron_job = create_crypto_cron_job() + + prompt = """ + + Conduct a comprehensive analysis of your assigned cryptocurrency. + + """ + + # Start the cron job + logger.info("🔄 Starting automated analysis loop...") + logger.info("⏰ Press Ctrl+C to stop the cron job") + + output = cron_job.run(task=prompt) + print(output) + + +if __name__ == "__main__": + main() +``` + ## Conclusion The CronJob class provides a powerful way to schedule and automate tasks using Swarms Agents or custom functions. Key benefits include: diff --git a/examples/cron_job_examples/crypto_concurrent_cron_example.py b/examples/cron_job_examples/crypto_concurrent_cron_example.py new file mode 100644 index 00000000..e1b837ce --- /dev/null +++ b/examples/cron_job_examples/crypto_concurrent_cron_example.py @@ -0,0 +1,349 @@ +""" +Cryptocurrency Concurrent Multi-Agent Cron Job Example + +This example demonstrates how to use ConcurrentWorkflow with CronJob to create +a powerful cryptocurrency tracking system. Each specialized agent analyzes a +specific cryptocurrency concurrently every minute. + +Features: +- ConcurrentWorkflow for parallel agent execution +- CronJob scheduling for automated runs every 1 minute +- Each agent specializes in analyzing one specific cryptocurrency +- Real-time data fetching from CoinGecko API +- Concurrent analysis of multiple cryptocurrencies +- Structured output with professional formatting + +Architecture: +CronJob -> ConcurrentWorkflow -> [Bitcoin Agent, Ethereum Agent, Solana Agent, etc.] -> Parallel Analysis +""" + +from typing import List +from loguru import logger + +from swarms import Agent, CronJob, ConcurrentWorkflow +from swarms_tools import coin_gecko_coin_api + + +def create_crypto_specific_agents() -> List[Agent]: + """ + Creates agents that each specialize in analyzing a specific cryptocurrency. + + Returns: + List[Agent]: List of cryptocurrency-specific Agent instances + """ + + # Bitcoin Specialist Agent + bitcoin_agent = Agent( + agent_name="Bitcoin-Analyst", + agent_description="Expert analyst specializing exclusively in Bitcoin (BTC) analysis and market dynamics", + system_prompt="""You are a Bitcoin specialist and expert analyst. Your expertise includes: + +BITCOIN SPECIALIZATION: +- Bitcoin's unique position as digital gold +- Bitcoin halving cycles and their market impact +- Bitcoin mining economics and hash rate analysis +- Lightning Network and Layer 2 developments +- Bitcoin adoption by institutions and countries +- Bitcoin's correlation with traditional markets +- Bitcoin technical analysis and on-chain metrics +- Bitcoin's role as a store of value and hedge against inflation + +ANALYSIS FOCUS: +- Analyze ONLY Bitcoin data from the provided dataset +- Focus on Bitcoin-specific metrics and trends +- Consider Bitcoin's unique market dynamics +- Evaluate Bitcoin's dominance and market leadership +- Assess institutional adoption trends +- Monitor on-chain activity and network health + +DELIVERABLES: +- Bitcoin-specific analysis and insights +- Price action assessment and predictions +- Market dominance analysis +- Institutional adoption impact +- Technical and fundamental outlook +- Risk factors specific to Bitcoin + +Extract Bitcoin data from the provided dataset and provide comprehensive Bitcoin-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Ethereum Specialist Agent + ethereum_agent = Agent( + agent_name="Ethereum-Analyst", + agent_description="Expert analyst specializing exclusively in Ethereum (ETH) analysis and ecosystem development", + system_prompt="""You are an Ethereum specialist and expert analyst. Your expertise includes: + +ETHEREUM SPECIALIZATION: +- Ethereum's smart contract platform and DeFi ecosystem +- Ethereum 2.0 transition and proof-of-stake mechanics +- Gas fees, network usage, and scalability solutions +- Layer 2 solutions (Arbitrum, Optimism, Polygon) +- DeFi protocols and TVL (Total Value Locked) analysis +- NFT markets and Ethereum's role in digital assets +- Developer activity and ecosystem growth +- EIP proposals and network upgrades + +ANALYSIS FOCUS: +- Analyze ONLY Ethereum data from the provided dataset +- Focus on Ethereum's platform utility and network effects +- Evaluate DeFi ecosystem health and growth +- Assess Layer 2 adoption and scalability solutions +- Monitor network usage and gas fee trends +- Consider Ethereum's competitive position vs other smart contract platforms + +DELIVERABLES: +- Ethereum-specific analysis and insights +- Platform utility and adoption metrics +- DeFi ecosystem impact assessment +- Network health and scalability evaluation +- Competitive positioning analysis +- Technical and fundamental outlook for ETH + +Extract Ethereum data from the provided dataset and provide comprehensive Ethereum-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Solana Specialist Agent + solana_agent = Agent( + agent_name="Solana-Analyst", + agent_description="Expert analyst specializing exclusively in Solana (SOL) analysis and ecosystem development", + system_prompt="""You are a Solana specialist and expert analyst. Your expertise includes: + +SOLANA SPECIALIZATION: +- Solana's high-performance blockchain architecture +- Proof-of-History consensus mechanism +- Solana's DeFi ecosystem and DEX platforms (Serum, Raydium) +- NFT marketplaces and creator economy on Solana +- Network outages and reliability concerns +- Developer ecosystem and Rust programming adoption +- Validator economics and network decentralization +- Cross-chain bridges and interoperability + +ANALYSIS FOCUS: +- Analyze ONLY Solana data from the provided dataset +- Focus on Solana's performance and scalability advantages +- Evaluate network stability and uptime improvements +- Assess ecosystem growth and developer adoption +- Monitor DeFi and NFT activity on Solana +- Consider Solana's competitive position vs Ethereum + +DELIVERABLES: +- Solana-specific analysis and insights +- Network performance and reliability assessment +- Ecosystem growth and adoption metrics +- DeFi and NFT market analysis +- Competitive advantages and challenges +- Technical and fundamental outlook for SOL + +Extract Solana data from the provided dataset and provide comprehensive Solana-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Cardano Specialist Agent + cardano_agent = Agent( + agent_name="Cardano-Analyst", + agent_description="Expert analyst specializing exclusively in Cardano (ADA) analysis and research-driven development", + system_prompt="""You are a Cardano specialist and expert analyst. Your expertise includes: + +CARDANO SPECIALIZATION: +- Cardano's research-driven development approach +- Ouroboros proof-of-stake consensus protocol +- Smart contract capabilities via Plutus and Marlowe +- Cardano's three-layer architecture (settlement, computation, control) +- Academic partnerships and peer-reviewed research +- Cardano ecosystem projects and DApp development +- Native tokens and Cardano's UTXO model +- Sustainability and treasury funding mechanisms + +ANALYSIS FOCUS: +- Analyze ONLY Cardano data from the provided dataset +- Focus on Cardano's methodical development approach +- Evaluate smart contract adoption and ecosystem growth +- Assess academic partnerships and research contributions +- Monitor native token ecosystem development +- Consider Cardano's long-term roadmap and milestones + +DELIVERABLES: +- Cardano-specific analysis and insights +- Development progress and milestone achievements +- Smart contract ecosystem evaluation +- Academic research impact assessment +- Native token and DApp adoption metrics +- Technical and fundamental outlook for ADA + +Extract Cardano data from the provided dataset and provide comprehensive Cardano-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # Binance Coin Specialist Agent + bnb_agent = Agent( + agent_name="BNB-Analyst", + agent_description="Expert analyst specializing exclusively in BNB analysis and Binance ecosystem dynamics", + system_prompt="""You are a BNB specialist and expert analyst. Your expertise includes: + +BNB SPECIALIZATION: +- BNB's utility within the Binance ecosystem +- Binance Smart Chain (BSC) development and adoption +- BNB token burns and deflationary mechanics +- Binance exchange volume and market leadership +- BSC DeFi ecosystem and yield farming +- Cross-chain bridges and multi-chain strategies +- Regulatory challenges facing Binance globally +- BNB's role in transaction fee discounts and platform benefits + +ANALYSIS FOCUS: +- Analyze ONLY BNB data from the provided dataset +- Focus on BNB's utility value and exchange benefits +- Evaluate BSC ecosystem growth and competition with Ethereum +- Assess token burn impact on supply and price +- Monitor Binance platform developments and regulations +- Consider BNB's centralized vs decentralized aspects + +DELIVERABLES: +- BNB-specific analysis and insights +- Utility value and ecosystem benefits assessment +- BSC adoption and DeFi growth evaluation +- Token economics and burn mechanism impact +- Regulatory risk and compliance analysis +- Technical and fundamental outlook for BNB + +Extract BNB data from the provided dataset and provide comprehensive BNB-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + # XRP Specialist Agent + xrp_agent = Agent( + agent_name="XRP-Analyst", + agent_description="Expert analyst specializing exclusively in XRP analysis and cross-border payment solutions", + system_prompt="""You are an XRP specialist and expert analyst. Your expertise includes: + +XRP SPECIALIZATION: +- XRP's role in cross-border payments and remittances +- RippleNet adoption by financial institutions +- Central Bank Digital Currency (CBDC) partnerships +- Regulatory landscape and SEC lawsuit implications +- XRP Ledger's consensus mechanism and energy efficiency +- On-Demand Liquidity (ODL) usage and growth +- Competition with SWIFT and traditional payment rails +- Ripple's partnerships with banks and payment providers + +ANALYSIS FOCUS: +- Analyze ONLY XRP data from the provided dataset +- Focus on XRP's utility in payments and remittances +- Evaluate RippleNet adoption and institutional partnerships +- Assess regulatory developments and legal clarity +- Monitor ODL usage and transaction volumes +- Consider XRP's competitive position in payments + +DELIVERABLES: +- XRP-specific analysis and insights +- Payment utility and adoption assessment +- Regulatory landscape and legal developments +- Institutional partnership impact evaluation +- Cross-border payment market analysis +- Technical and fundamental outlook for XRP + +Extract XRP data from the provided dataset and provide comprehensive XRP-focused analysis.""", + model_name="groq/moonshotai/kimi-k2-instruct", + max_loops=1, + dynamic_temperature_enabled=True, + streaming_on=False, + tools=[coin_gecko_coin_api], + ) + + return [ + bitcoin_agent, + ethereum_agent, + solana_agent, + cardano_agent, + bnb_agent, + xrp_agent, + ] + + +def create_crypto_workflow() -> ConcurrentWorkflow: + """ + Creates a ConcurrentWorkflow with cryptocurrency-specific analysis agents. + + Returns: + ConcurrentWorkflow: Configured workflow for crypto analysis + """ + agents = create_crypto_specific_agents() + + workflow = ConcurrentWorkflow( + name="Crypto-Specific-Analysis-Workflow", + description="Concurrent execution of cryptocurrency-specific analysis agents", + agents=agents, + max_loops=1, + ) + + return workflow + + +def create_crypto_cron_job() -> CronJob: + """ + Creates a CronJob that runs cryptocurrency-specific analysis every minute using ConcurrentWorkflow. + + Returns: + CronJob: Configured cron job for automated crypto analysis + """ + # Create the concurrent workflow + workflow = create_crypto_workflow() + + # Create the cron job + cron_job = CronJob( + agent=workflow, # Use the workflow as the agent + interval="5seconds", # Run every 1 minute + ) + + return cron_job + + +def main(): + """ + Main function to run the cryptocurrency-specific concurrent analysis cron job. + """ + cron_job = create_crypto_cron_job() + + prompt = ( + "You are a world-class institutional crypto analyst at a top-tier asset management firm (e.g., BlackRock).\n" + "Conduct a thorough, data-driven, and professional analysis of your assigned cryptocurrency, including:\n" + "- Current price, market cap, and recent performance trends\n" + "- Key technical and fundamental indicators\n" + "- Major news, regulatory, or macroeconomic events impacting the asset\n" + "- On-chain activity and notable whale or institutional movements\n" + "- Short-term and long-term outlook with clear, actionable insights\n" + "Present your findings in a concise, well-structured report suitable for executive decision-makers." + ) + + # Start the cron job + logger.info("🔄 Starting automated analysis loop...") + logger.info("⏰ Press Ctrl+C to stop the cron job") + + output = cron_job.run(task=prompt) + print(output) + + +if __name__ == "__main__": + main() diff --git a/examples/cron_job_examples/simple_concurrent_crypto_cron.py b/examples/cron_job_examples/simple_concurrent_crypto_cron.py new file mode 100644 index 00000000..670bfd26 --- /dev/null +++ b/examples/cron_job_examples/simple_concurrent_crypto_cron.py @@ -0,0 +1,157 @@ +""" +Simple Cryptocurrency Concurrent CronJob Example + +This is a simplified version showcasing the core concept of combining: +- CronJob (for scheduling) +- ConcurrentWorkflow (for parallel execution) +- Each agent analyzes a specific cryptocurrency + +Perfect for understanding the basic pattern before diving into the full example. +""" + +import json +import requests +from datetime import datetime +from loguru import logger + +from swarms import Agent, CronJob, ConcurrentWorkflow + + +def get_specific_crypto_data(coin_ids): + """Fetch specific crypto data from CoinGecko API.""" + try: + url = "https://api.coingecko.com/api/v3/simple/price" + params = { + "ids": ",".join(coin_ids), + "vs_currencies": "usd", + "include_24hr_change": True, + "include_market_cap": True, + "include_24hr_vol": True, + } + + response = requests.get(url, params=params, timeout=10) + response.raise_for_status() + + data = response.json() + result = { + "timestamp": datetime.now().isoformat(), + "coins": data, + } + + return json.dumps(result, indent=2) + + except Exception as e: + logger.error(f"Error fetching crypto data: {e}") + return f"Error: {e}" + + +def create_crypto_specific_agents(): + """Create agents that each specialize in one cryptocurrency.""" + + # Bitcoin Specialist Agent + bitcoin_agent = Agent( + agent_name="Bitcoin-Analyst", + system_prompt="""You are a Bitcoin specialist. Analyze ONLY Bitcoin (BTC) data from the provided dataset. + Focus on: + - Bitcoin price movements and trends + - Market dominance and institutional adoption + - Bitcoin-specific market dynamics + - Store of value characteristics + Ignore all other cryptocurrencies in your analysis.""", + model_name="gpt-4o-mini", + max_loops=1, + print_on=False, # Important for concurrent execution + ) + + # Ethereum Specialist Agent + ethereum_agent = Agent( + agent_name="Ethereum-Analyst", + system_prompt="""You are an Ethereum specialist. Analyze ONLY Ethereum (ETH) data from the provided dataset. + Focus on: + - Ethereum price action and DeFi ecosystem + - Smart contract platform adoption + - Gas fees and network usage + - Layer 2 scaling solutions impact + Ignore all other cryptocurrencies in your analysis.""", + model_name="gpt-4o-mini", + max_loops=1, + print_on=False, + ) + + # Solana Specialist Agent + solana_agent = Agent( + agent_name="Solana-Analyst", + system_prompt="""You are a Solana specialist. Analyze ONLY Solana (SOL) data from the provided dataset. + Focus on: + - Solana price performance and ecosystem growth + - High-performance blockchain advantages + - DeFi and NFT activity on Solana + - Network reliability and uptime + Ignore all other cryptocurrencies in your analysis.""", + model_name="gpt-4o-mini", + max_loops=1, + print_on=False, + ) + + return [bitcoin_agent, ethereum_agent, solana_agent] + + +def main(): + """Main function demonstrating crypto-specific concurrent analysis with cron job.""" + logger.info( + "🚀 Starting Simple Crypto-Specific Concurrent Analysis" + ) + logger.info("💰 Each agent analyzes one specific cryptocurrency:") + logger.info(" 🟠 Bitcoin-Analyst -> BTC only") + logger.info(" 🔵 Ethereum-Analyst -> ETH only") + logger.info(" 🟢 Solana-Analyst -> SOL only") + + # Define specific cryptocurrencies to analyze + coin_ids = ["bitcoin", "ethereum", "solana"] + + # Step 1: Create crypto-specific agents + agents = create_crypto_specific_agents() + + # Step 2: Create ConcurrentWorkflow + workflow = ConcurrentWorkflow( + name="Simple-Crypto-Specific-Analysis", + agents=agents, + show_dashboard=True, # Shows real-time progress + ) + + # Step 3: Create CronJob with the workflow + cron_job = CronJob( + agent=workflow, # Use workflow as the agent + interval="60seconds", # Run every minute + job_id="simple-crypto-specific-cron", + ) + + # Step 4: Define the analysis task + task = f""" + Analyze the cryptocurrency data below. Each agent should focus ONLY on their assigned cryptocurrency: + + - Bitcoin-Analyst: Analyze Bitcoin (BTC) data only + - Ethereum-Analyst: Analyze Ethereum (ETH) data only + - Solana-Analyst: Analyze Solana (SOL) data only + + Cryptocurrency Data: + {get_specific_crypto_data(coin_ids)} + + Each agent should: + 1. Extract and analyze data for YOUR ASSIGNED cryptocurrency only + 2. Provide brief insights from your specialty perspective + 3. Give a price trend assessment + 4. Identify key opportunities or risks + 5. Ignore all other cryptocurrencies + """ + + # Step 5: Start the cron job + logger.info("▶️ Starting cron job - Press Ctrl+C to stop") + try: + cron_job.run(task=task) + except KeyboardInterrupt: + logger.info("⏹️ Stopped by user") + + +if __name__ == "__main__": + main() diff --git a/examples/cron_job_examples/solana_price_tracker.py b/examples/cron_job_examples/solana_price_tracker.py index 0ae048ab..e7c9dd17 100644 --- a/examples/cron_job_examples/solana_price_tracker.py +++ b/examples/cron_job_examples/solana_price_tracker.py @@ -141,7 +141,7 @@ def analyze_solana_data(data: str) -> str: formatted_data = solana_data.get("formatted_data", {}) # Extract key metrics - current_price = price_data.get("current_price_usd") + price_data.get("current_price_usd") price_change = price_data.get("price_change_24h_percent") volume_24h = price_data.get("volume_24h_usd") market_cap = price_data.get("market_cap_usd") diff --git a/examples/multi_agent/board_of_directors/board_of_directors_example.py b/examples/multi_agent/board_of_directors/board_of_directors_example.py index bc043733..2461919e 100644 --- a/examples/multi_agent/board_of_directors/board_of_directors_example.py +++ b/examples/multi_agent/board_of_directors/board_of_directors_example.py @@ -16,11 +16,13 @@ from typing import List # Add the root directory to the Python path if running from examples directory current_dir = os.path.dirname(os.path.abspath(__file__)) -if 'examples' in current_dir: +if "examples" in current_dir: root_dir = current_dir - while os.path.basename(root_dir) != 'examples' and root_dir != os.path.dirname(root_dir): + while os.path.basename( + root_dir + ) != "examples" and root_dir != os.path.dirname(root_dir): root_dir = os.path.dirname(root_dir) - if os.path.basename(root_dir) == 'examples': + if os.path.basename(root_dir) == "examples": root_dir = os.path.dirname(root_dir) if root_dir not in sys.path: sys.path.insert(0, root_dir) @@ -35,7 +37,7 @@ from swarms.structs.agent import Agent def create_board_members() -> List[BoardMember]: """Create board members with specific roles.""" - + chairman = Agent( agent_name="Chairman", agent_description="Executive Chairman with strategic vision", @@ -43,7 +45,7 @@ def create_board_members() -> List[BoardMember]: max_loops=1, system_prompt="You are the Executive Chairman. Provide strategic leadership and facilitate decision-making.", ) - + cto = Agent( agent_name="CTO", agent_description="Chief Technology Officer with technical expertise", @@ -51,7 +53,7 @@ def create_board_members() -> List[BoardMember]: max_loops=1, system_prompt="You are the CTO. Provide technical leadership and evaluate technology solutions.", ) - + cfo = Agent( agent_name="CFO", agent_description="Chief Financial Officer with financial expertise", @@ -59,32 +61,32 @@ def create_board_members() -> List[BoardMember]: max_loops=1, system_prompt="You are the CFO. Provide financial analysis and ensure fiscal responsibility.", ) - + return [ BoardMember( agent=chairman, role=BoardMemberRole.CHAIRMAN, voting_weight=2.0, - expertise_areas=["leadership", "strategy"] + expertise_areas=["leadership", "strategy"], ), BoardMember( agent=cto, role=BoardMemberRole.EXECUTIVE_DIRECTOR, voting_weight=1.5, - expertise_areas=["technology", "innovation"] + expertise_areas=["technology", "innovation"], ), BoardMember( agent=cfo, role=BoardMemberRole.EXECUTIVE_DIRECTOR, voting_weight=1.5, - expertise_areas=["finance", "risk_management"] + expertise_areas=["finance", "risk_management"], ), ] def create_worker_agents() -> List[Agent]: """Create worker agents for the swarm.""" - + researcher = Agent( agent_name="Researcher", agent_description="Research analyst for data analysis", @@ -92,7 +94,7 @@ def create_worker_agents() -> List[Agent]: max_loops=1, system_prompt="You are a Research Analyst. Conduct thorough research and provide data-driven insights.", ) - + developer = Agent( agent_name="Developer", agent_description="Software developer for implementation", @@ -100,7 +102,7 @@ def create_worker_agents() -> List[Agent]: max_loops=1, system_prompt="You are a Software Developer. Design and implement software solutions.", ) - + marketer = Agent( agent_name="Marketer", agent_description="Marketing specialist for strategy", @@ -108,17 +110,17 @@ def create_worker_agents() -> List[Agent]: max_loops=1, system_prompt="You are a Marketing Specialist. Develop marketing strategies and campaigns.", ) - + return [researcher, developer, marketer] def run_board_example() -> None: """Run a Board of Directors example.""" - + # Create board members and worker agents board_members = create_board_members() worker_agents = create_worker_agents() - + # Create the Board of Directors swarm board_swarm = BoardOfDirectorsSwarm( name="Executive_Board", @@ -128,23 +130,23 @@ def run_board_example() -> None: verbose=True, decision_threshold=0.6, ) - + # Define task task = """ Develop a strategy for launching a new AI-powered product in the market. Include market research, technical planning, marketing strategy, and financial projections. """ - + # Execute the task result = board_swarm.run(task=task) - + print("Task completed successfully!") print(f"Result: {result}") def run_simple_example() -> None: """Run a simple Board of Directors example.""" - + # Create simple agents analyst = Agent( agent_name="Analyst", @@ -152,43 +154,47 @@ def run_simple_example() -> None: model_name="gpt-4o-mini", max_loops=1, ) - + writer = Agent( agent_name="Writer", agent_description="Content writer", model_name="gpt-4o-mini", max_loops=1, ) - + # Create swarm with default settings board_swarm = BoardOfDirectorsSwarm( name="Simple_Board", agents=[analyst, writer], verbose=True, ) - + # Execute simple task - task = "Analyze current market trends and create a summary report." + task = ( + "Analyze current market trends and create a summary report." + ) result = board_swarm.run(task=task) - + print("Simple example completed!") print(f"Result: {result}") def main() -> None: """Main function to run the examples.""" - + if not os.getenv("OPENAI_API_KEY"): - print("Warning: OPENAI_API_KEY not set. Example may not work.") + print( + "Warning: OPENAI_API_KEY not set. Example may not work." + ) return - + try: print("Running simple Board of Directors example...") run_simple_example() - + print("\nRunning comprehensive Board of Directors example...") run_board_example() - + except Exception as e: print(f"Error: {e}") diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/debug_dashboard.py b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/debug_dashboard.py new file mode 100644 index 00000000..d3f3f389 --- /dev/null +++ b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/debug_dashboard.py @@ -0,0 +1,70 @@ +""" +Debug script for the Arasaka Dashboard to test agent output display. +""" + +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.agent import Agent + + +def debug_dashboard(): + """Debug the dashboard functionality.""" + + print("🔍 Starting dashboard debug...") + + # Create simple agents with clear names + agent1 = Agent( + agent_name="Research-Agent", + agent_description="A research agent for testing", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + agent2 = Agent( + agent_name="Analysis-Agent", + agent_description="An analysis agent for testing", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + print( + f"✅ Created agents: {agent1.agent_name}, {agent2.agent_name}" + ) + + # Create swarm with dashboard + swarm = HierarchicalSwarm( + name="Debug Swarm", + description="A test swarm for debugging dashboard functionality", + agents=[agent1, agent2], + max_loops=1, + interactive=True, + verbose=True, + ) + + print("✅ Created swarm with dashboard") + print("📊 Dashboard should now show agents in PENDING status") + + # Wait a moment to see the initial dashboard + import time + + time.sleep(3) + + print("\n🚀 Starting swarm execution...") + + # Run with a simple task + result = swarm.run( + task="Create a brief summary of machine learning" + ) + + print("\n✅ Debug completed!") + print("📋 Final result preview:") + print( + str(result)[:300] + "..." + if len(str(result)) > 300 + else str(result) + ) + + +if __name__ == "__main__": + debug_dashboard() diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/hiearchical_swarm_example.py b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/hiearchical_swarm_example.py new file mode 100644 index 00000000..fefe856b --- /dev/null +++ b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/hiearchical_swarm_example.py @@ -0,0 +1,71 @@ +""" +Hierarchical Swarm with Arasaka Dashboard Example + +This example demonstrates the new interactive dashboard functionality for the +hierarchical swarm, featuring a futuristic Arasaka Corporation-style interface +with red and black color scheme. +""" + +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.agent import Agent + + +def main(): + """ + Demonstrate the hierarchical swarm with interactive dashboard. + """ + print("🚀 Initializing Swarms Corporation Hierarchical Swarm...") + + # Create specialized agents + research_agent = Agent( + agent_name="Research-Analyst", + agent_description="Specialized in comprehensive research and data gathering", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + analysis_agent = Agent( + agent_name="Data-Analyst", + agent_description="Expert in data analysis and pattern recognition", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + strategy_agent = Agent( + agent_name="Strategy-Consultant", + agent_description="Specialized in strategic planning and recommendations", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + # Create hierarchical swarm with interactive dashboard + swarm = HierarchicalSwarm( + name="Swarms Corporation Operations", + description="Enterprise-grade hierarchical swarm for complex task execution", + agents=[research_agent, analysis_agent, strategy_agent], + max_loops=2, + interactive=True, # Enable the Arasaka dashboard + verbose=True, + ) + + print("\n🎯 Swarm initialized successfully!") + print( + "📊 Interactive dashboard will be displayed during execution." + ) + print( + "💡 The swarm will prompt you for a task when you call swarm.run()" + ) + + # Run the swarm (task will be prompted interactively) + result = swarm.run() + + print("\n✅ Swarm execution completed!") + print("📋 Final result:") + print(result) + + +if __name__ == "__main__": + main() diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_dashboard.py b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_dashboard.py new file mode 100644 index 00000000..433f0e14 --- /dev/null +++ b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_dashboard.py @@ -0,0 +1,56 @@ +""" +Test script for the Arasaka Dashboard functionality. +""" + +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.agent import Agent + + +def test_dashboard(): + """Test the dashboard functionality with a simple task.""" + + # Create simple agents + agent1 = Agent( + agent_name="Test-Agent-1", + agent_description="A test agent for dashboard verification", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + agent2 = Agent( + agent_name="Test-Agent-2", + agent_description="Another test agent for dashboard verification", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + # Create swarm with dashboard + swarm = HierarchicalSwarm( + name="Dashboard Test Swarm", + agents=[agent1, agent2], + max_loops=1, + interactive=True, + verbose=True, + ) + + print("🧪 Testing Arasaka Dashboard...") + print("📊 Dashboard should appear and prompt for task input") + + # Run with a simple task + result = swarm.run( + task="Create a simple summary of artificial intelligence trends" + ) + + print("\n✅ Test completed!") + print("📋 Result preview:") + print( + str(result)[:500] + "..." + if len(str(result)) > 500 + else str(result) + ) + + +if __name__ == "__main__": + test_dashboard() diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_full_output.py b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_full_output.py new file mode 100644 index 00000000..a281b7dc --- /dev/null +++ b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_full_output.py @@ -0,0 +1,56 @@ +""" +Test script for full agent output display in the Arasaka Dashboard. +""" + +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.agent import Agent + + +def test_full_output(): + """Test the full output display functionality.""" + + print("🔍 Testing full agent output display...") + + # Create agents that will produce substantial output + agent1 = Agent( + agent_name="Research-Agent", + agent_description="A research agent that produces detailed output", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + agent2 = Agent( + agent_name="Analysis-Agent", + agent_description="An analysis agent that provides comprehensive analysis", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + # Create swarm with dashboard and detailed view enabled + swarm = HierarchicalSwarm( + name="Full Output Test Swarm", + description="A test swarm for verifying full agent output display", + agents=[agent1, agent2], + max_loops=1, + interactive=True, + verbose=True, + ) + + print("✅ Created swarm with detailed view enabled") + print( + "📊 Dashboard should show full agent outputs without truncation" + ) + + # Run with a task that will generate substantial output + swarm.run( + task="Provide a comprehensive analysis of artificial intelligence trends in 2024, including detailed explanations of each trend" + ) + + print("\n✅ Test completed!") + print("📋 Check the dashboard for full agent outputs") + + +if __name__ == "__main__": + test_full_output() diff --git a/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_multi_loop.py b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_multi_loop.py new file mode 100644 index 00000000..045ef86e --- /dev/null +++ b/examples/multi_agent/hiearchical_swarm/hiearchical_swarm_ui/test_multi_loop.py @@ -0,0 +1,57 @@ +""" +Test script for multi-loop agent tracking in the Arasaka Dashboard. +""" + +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.agent import Agent + + +def test_multi_loop(): + """Test the multi-loop agent tracking functionality.""" + + print("🔍 Testing multi-loop agent tracking...") + + # Create agents + agent1 = Agent( + agent_name="Research-Agent", + agent_description="A research agent for multi-loop testing", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + agent2 = Agent( + agent_name="Analysis-Agent", + agent_description="An analysis agent for multi-loop testing", + model_name="gpt-4o-mini", + max_loops=1, + verbose=False, + ) + + # Create swarm with multiple loops + swarm = HierarchicalSwarm( + name="Multi-Loop Test Swarm", + description="A test swarm for verifying multi-loop agent tracking", + agents=[agent1, agent2], + max_loops=3, # Multiple loops to test history tracking + interactive=True, + verbose=True, + ) + + print("✅ Created swarm with multi-loop tracking") + print( + "📊 Dashboard should show agent outputs across multiple loops" + ) + print("🔄 Each loop will add new rows to the monitoring matrix") + + # Run with a task that will benefit from multiple iterations + swarm.run( + task="Analyze the impact of artificial intelligence on healthcare, then refine the analysis with additional insights, and finally provide actionable recommendations" + ) + + print("\n✅ Multi-loop test completed!") + print("📋 Check the dashboard for agent outputs across all loops") + + +if __name__ == "__main__": + test_multi_loop() diff --git a/examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py b/examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py new file mode 100644 index 00000000..d4d975de --- /dev/null +++ b/examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py @@ -0,0 +1,44 @@ +from transformers import pipeline +from swarms import Agent + +class GPTOSS: + def __init__( + self, + model_id: str = "openai/gpt-oss-20b", + max_new_tokens: int = 256, + temperature: int = 0.7, + system_prompt: str = "You are a helpful assistant.", + ): + self.max_new_tokens = max_new_tokens + self.temperature = temperature + self.system_prompt = system_prompt + self.model_id = model_id + + self.pipe = pipeline( + "text-generation", + model=model_id, + torch_dtype="auto", + device_map="auto", + temperature=temperature, + ) + + def run(self, task: str): + self.messages = [ + {"role": "system", "content": self.system_prompt}, + {"role": "user", "content": task}, + ] + + outputs = self.pipe( + self.messages, + max_new_tokens=self.max_new_tokens, + ) + + return outputs[0]["generated_text"][-1] + +agent = Agent( + name="GPT-OSS-Agent", + llm=GPTOSS(), + system_prompt="You are a helpful assistant.", +) + +agent.run(task="Explain quantum mechanics clearly and concisely.") diff --git a/examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py b/examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py new file mode 100644 index 00000000..6b27a321 --- /dev/null +++ b/examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py @@ -0,0 +1,49 @@ +from swarms import Agent + +# Initialize the agent +agent = Agent( + agent_name="Quantitative-Trading-Agent", + agent_description="Advanced quantitative trading and algorithmic analysis agent", + system_prompt="""You are an expert quantitative trading agent with deep expertise in: + - Algorithmic trading strategies and implementation + - Statistical arbitrage and market making + - Risk management and portfolio optimization + - High-frequency trading systems + - Market microstructure analysis + - Quantitative research methodologies + - Financial mathematics and stochastic processes + - Machine learning applications in trading + + Your core responsibilities include: + 1. Developing and backtesting trading strategies + 2. Analyzing market data and identifying alpha opportunities + 3. Implementing risk management frameworks + 4. Optimizing portfolio allocations + 5. Conducting quantitative research + 6. Monitoring market microstructure + 7. Evaluating trading system performance + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + model_name="groq/openai/gpt-oss-120b", + dynamic_temperature_enabled=True, + output_type="str-all-except-first", + max_loops="auto", + interactive=True, + no_reasoning_prompt=True, + streaming_on=True, + # dashboard=True +) + +out = agent.run( + task="What are the best top 3 etfs for gold coverage?" +) +print(out) diff --git a/hs_interactive.py b/hs_interactive.py new file mode 100644 index 00000000..698e26df --- /dev/null +++ b/hs_interactive.py @@ -0,0 +1,24 @@ +from swarms import HierarchicalSwarm, Agent + +# Create agents +research_agent = Agent( + agent_name="Research-Analyst", model_name="gpt-4.1", print_on=True +) +analysis_agent = Agent( + agent_name="Data-Analyst", model_name="gpt-4.1", print_on=True +) + +# Create swarm with interactive dashboard +swarm = HierarchicalSwarm( + agents=[research_agent, analysis_agent], + max_loops=1, + interactive=True, # Enable the Arasaka dashboard + # director_reasoning_enabled=False, + # director_reasoning_model_name="groq/moonshotai/kimi-k2-instruct", + multi_agent_prompt_improvements=True, +) + +# Run swarm (task will be prompted interactively) +result = swarm.run("what are the best nanomachine research papers?") + +print(result) diff --git a/swarms/prompts/reasoning_prompt.py b/swarms/prompts/reasoning_prompt.py index b929d5b6..24f810cd 100644 --- a/swarms/prompts/reasoning_prompt.py +++ b/swarms/prompts/reasoning_prompt.py @@ -7,3 +7,8 @@ The reasoning process and the final answer should be distinctly enclosed within It is essential to output multiple tags to reflect the depth of thought and exploration involved in addressing the task. The Assistant should strive to think deeply and thoroughly about the question, ensuring that all relevant aspects are considered before arriving at a conclusion. """ + + +INTERNAL_MONOLGUE_PROMPT = """ +You are an introspective reasoning engine whose sole task is to explore and unpack any problem or task without ever delivering a final solution. Whenever you process a prompt, you must envelope every discrete insight, question, or inference inside and tags, using as many of these tags—nested or sequential—as needed to reveal your full chain of thought. Begin each session by rephrasing the problem in your own words to ensure you’ve captured its goals, inputs, outputs, and constraints—entirely within blocks—and identify any ambiguities or assumptions you must clarify. Then decompose the task into sub-questions or logical components, examining multiple approaches, edge cases, and trade-offs, all inside further tags. Continue layering your reasoning, pausing at each step to ask yourself “What else might I consider?” or “Is there an implicit assumption here?”—always inside . Never move beyond analysis: do not generate outlines, pseudocode, or answers—only think. If you find yourself tempted to propose a solution, immediately halt and circle back into deeper tags. Your objective is total transparency of reasoning and exhaustive exploration of the problem space; defer any answer generation until explicitly instructed otherwise. +""" diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index b52f9c1e..4d2014cd 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -94,6 +94,7 @@ from swarms.structs.swarming_architectures import ( star_swarm, ) + __all__ = [ "Agent", "BaseStructure", diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 3f726d24..aaf8d028 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -102,7 +102,7 @@ def parse_done_token(response: str) -> bool: # Agent ID generator def agent_id(): """Generate an agent id""" - return uuid.uuid4().hex + return f"agent-{uuid.uuid4().hex}" # Agent output types @@ -673,7 +673,7 @@ class Agent: # Initialize the short term memory memory = Conversation( - system_prompt=prompt, + name=f"{self.agent_name}_conversation", user=self.user_name, rules=self.rules, token_count=( @@ -693,6 +693,12 @@ class Agent: ), ) + # Add the system prompt to the conversation + memory.add( + role="System", + content=prompt, + ) + return memory def agent_output_model(self): @@ -861,7 +867,9 @@ class Agent: return tools except AgentMCPConnectionError as e: - logger.error(f"Error in MCP connection: {e}") + logger.error( + f"Error in MCP connection: {e} Traceback: {traceback.format_exc()}" + ) raise e def setup_config(self): @@ -1172,7 +1180,8 @@ class Agent: if self.print_on is True: if isinstance(response, list): self.pretty_print( - f"Structured Output - Attempting Function Call Execution [{time.strftime('%H:%M:%S')}] \n\n Output: {format_data_structure(response)} ", + # f"Structured Output - Attempting Function Call Execution [{time.strftime('%H:%M:%S')}] \n\n Output: {format_data_structure(response)} ", + f"[Structured Output] [Time: {time.strftime('%H:%M:%S')}] \n\n {json.dumps(response, indent=4)}", loop_count, ) elif self.streaming_on: @@ -2457,6 +2466,10 @@ class Agent: Returns: Dict[str, Any]: A dictionary representation of the class attributes. """ + + # Remove the llm object from the dictionary + self.__dict__.pop("llm", None) + return { attr_name: self._serialize_attr(attr_name, attr_value) for attr_name, attr_value in self.__dict__.items() diff --git a/swarms/structs/batch_agent_execution.py b/swarms/structs/batch_agent_execution.py index 2b74a9e7..7b2a926d 100644 --- a/swarms/structs/batch_agent_execution.py +++ b/swarms/structs/batch_agent_execution.py @@ -1,11 +1,16 @@ +import concurrent.futures from swarms.structs.agent import Agent -from typing import List +from typing import List, Union, Callable +import os from swarms.utils.formatter import formatter +from loguru import logger +import traceback def batch_agent_execution( - agents: List[Agent], - tasks: List[str], + agents: List[Union[Agent, Callable]], + tasks: List[str] = None, + imgs: List[str] = None, ): """ Execute a batch of agents on a list of tasks concurrently. @@ -20,45 +25,58 @@ def batch_agent_execution( Raises: ValueError: If number of agents doesn't match number of tasks """ - if len(agents) != len(tasks): - raise ValueError( - "Number of agents must match number of tasks" - ) + try: - import concurrent.futures - import multiprocessing + logger.info( + f"Executing {len(agents)} agents on {len(tasks)} tasks" + ) - results = [] + if len(agents) != len(tasks): + raise ValueError( + "Number of agents must match number of tasks" + ) - # Calculate max workers as 90% of available CPU cores - max_workers = max(1, int(multiprocessing.cpu_count() * 0.9)) + results = [] - formatter.print_panel( - f"Executing {len(agents)} agents on {len(tasks)} tasks using {max_workers} workers" - ) + # Calculate max workers as 90% of available CPU cores + max_workers = max(1, int(os.cpu_count() * 0.9)) - with concurrent.futures.ThreadPoolExecutor( - max_workers=max_workers - ) as executor: - # Submit all tasks to the executor - future_to_task = { - executor.submit(agent.run, task): (agent, task) - for agent, task in zip(agents, tasks) - } + formatter.print_panel( + f"Executing {len(agents)} agents on {len(tasks)} tasks using {max_workers} workers" + ) - # Collect results as they complete - for future in concurrent.futures.as_completed(future_to_task): - agent, task = future_to_task[future] - try: - result = future.result() - results.append(result) - except Exception as e: - print( - f"Task failed for agent {agent.agent_name}: {str(e)}" + with concurrent.futures.ThreadPoolExecutor( + max_workers=max_workers + ) as executor: + # Submit all tasks to the executor + future_to_task = { + executor.submit(agent.run, task, imgs): ( + agent, + task, + imgs, ) - results.append(None) + for agent, task, imgs in zip(agents, tasks, imgs) + } + + # Collect results as they complete + for future in concurrent.futures.as_completed( + future_to_task + ): + agent, task = future_to_task[future] + try: + result = future.result() + results.append(result) + except Exception as e: + print( + f"Task failed for agent {agent.agent_name}: {str(e)}" + ) + results.append(None) - # Wait for all futures to complete before returning - concurrent.futures.wait(future_to_task.keys()) + # Wait for all futures to complete before returning + concurrent.futures.wait(future_to_task.keys()) - return results + return results + except Exception as e: + log = f"Batch agent execution failed Error: {str(e)} Traceback: {traceback.format_exc()}" + logger.error(log) + raise e diff --git a/swarms/structs/board_of_directors_swarm.py b/swarms/structs/board_of_directors_swarm.py index f80fb4a4..7dbf0d34 100644 --- a/swarms/structs/board_of_directors_swarm.py +++ b/swarms/structs/board_of_directors_swarm.py @@ -28,8 +28,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass, field from enum import Enum from functools import lru_cache -from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Union, Tuple +from typing import Any, Callable, Dict, List, Optional, Union from loguru import logger from pydantic import BaseModel, Field @@ -38,30 +37,35 @@ from swarms.structs.agent import Agent from swarms.structs.base_swarm import BaseSwarm from swarms.structs.conversation import Conversation from swarms.structs.ma_utils import list_all_agents -from swarms.utils.history_output_formatter import history_output_formatter +from swarms.utils.history_output_formatter import ( + history_output_formatter, +) from swarms.utils.loguru_logger import initialize_logger from swarms.utils.output_types import OutputType # Initialize logger for Board of Directors swarm -board_logger = initialize_logger(log_folder="board_of_directors_swarm") +board_logger = initialize_logger( + log_folder="board_of_directors_swarm" +) # ============================================================================ # BOARD OF DIRECTORS CONFIGURATION # ============================================================================ + class BoardFeatureStatus(str, Enum): """Enumeration of Board of Directors feature status. - + This enum defines the possible states of the Board of Directors feature within the Swarms Framework. - + Attributes: ENABLED: Feature is explicitly enabled DISABLED: Feature is explicitly disabled AUTO: Feature state is determined automatically """ - + ENABLED = "enabled" DISABLED = "disabled" AUTO = "auto" @@ -70,10 +74,10 @@ class BoardFeatureStatus(str, Enum): class BoardConfigModel(BaseModel): """ Configuration model for Board of Directors feature. - + This model defines all configurable parameters for the Board of Directors feature, including feature status, board composition, and operational settings. - + Attributes: board_feature_enabled: Whether the Board of Directors feature is enabled globally default_board_size: Default number of board members when creating a new board @@ -86,69 +90,69 @@ class BoardConfigModel(BaseModel): auto_fallback_to_director: Automatically fall back to Director mode if Board fails custom_board_templates: Custom board templates for different use cases """ - + # Feature control board_feature_enabled: bool = Field( default=False, - description="Whether the Board of Directors feature is enabled globally." + description="Whether the Board of Directors feature is enabled globally.", ) - + # Board composition default_board_size: int = Field( default=3, ge=1, le=10, - description="Default number of board members when creating a new board." + description="Default number of board members when creating a new board.", ) - + # Operational settings decision_threshold: float = Field( default=0.6, ge=0.0, le=1.0, - description="Threshold for majority decisions (0.0-1.0)." + description="Threshold for majority decisions (0.0-1.0).", ) - + enable_voting: bool = Field( default=True, - description="Enable voting mechanisms for board decisions." + description="Enable voting mechanisms for board decisions.", ) - + enable_consensus: bool = Field( default=True, - description="Enable consensus-building mechanisms." + description="Enable consensus-building mechanisms.", ) - + # Model settings default_board_model: str = Field( default="gpt-4o-mini", - description="Default model for board member agents." + description="Default model for board member agents.", ) - + # Logging and monitoring verbose_logging: bool = Field( default=False, - description="Enable verbose logging for board operations." + description="Enable verbose logging for board operations.", ) - + # Performance settings max_board_meeting_duration: int = Field( default=300, ge=60, le=3600, - description="Maximum duration for board meetings in seconds." + description="Maximum duration for board meetings in seconds.", ) - + # Integration settings auto_fallback_to_director: bool = Field( default=True, - description="Automatically fall back to Director mode if Board fails." + description="Automatically fall back to Director mode if Board fails.", ) - + # Custom board templates custom_board_templates: Dict[str, Dict[str, Any]] = Field( default_factory=dict, - description="Custom board templates for different use cases." + description="Custom board templates for different use cases.", ) @@ -156,118 +160,145 @@ class BoardConfigModel(BaseModel): class BoardConfig: """ Board of Directors configuration manager. - + This class manages the configuration for the Board of Directors feature, including loading from environment variables, configuration files, and providing default values. - + Attributes: config_file_path: Optional path to configuration file config_data: Optional configuration data dictionary config: The current configuration model instance """ - + config_file_path: Optional[str] = None config_data: Optional[Dict[str, Any]] = None config: BoardConfigModel = field(init=False) - + def __post_init__(self) -> None: """Initialize the configuration after object creation.""" self._load_config() - + def _load_config(self) -> None: """ Load configuration from various sources. - + Priority order: 1. Environment variables 2. Configuration file 3. Default values - + Raises: Exception: If configuration loading fails """ try: # Start with default configuration self.config = BoardConfigModel() - + # Load from configuration file if specified - if self.config_file_path and os.path.exists(self.config_file_path): + if self.config_file_path and os.path.exists( + self.config_file_path + ): self._load_from_file() - + # Override with environment variables self._load_from_environment() - + # Override with explicit config data if self.config_data: self._load_from_dict(self.config_data) - + except Exception as e: - logger.error(f"Failed to load Board of Directors configuration: {str(e)}") + logger.error( + f"Failed to load Board of Directors configuration: {str(e)}" + ) raise - + def _load_from_file(self) -> None: """ Load configuration from file. - + Raises: Exception: If file loading fails """ try: import yaml - with open(self.config_file_path, 'r') as f: + + with open(self.config_file_path, "r") as f: file_config = yaml.safe_load(f) self._load_from_dict(file_config) - logger.info(f"Loaded Board of Directors config from: {self.config_file_path}") + logger.info( + f"Loaded Board of Directors config from: {self.config_file_path}" + ) except Exception as e: - logger.warning(f"Failed to load config file {self.config_file_path}: {e}") + logger.warning( + f"Failed to load config file {self.config_file_path}: {e}" + ) raise - + def _load_from_environment(self) -> None: """ Load configuration from environment variables. - + This method maps environment variables to configuration parameters and handles type conversion appropriately. """ env_mappings = { - 'SWARMS_BOARD_FEATURE_ENABLED': 'board_feature_enabled', - 'SWARMS_BOARD_DEFAULT_SIZE': 'default_board_size', - 'SWARMS_BOARD_DECISION_THRESHOLD': 'decision_threshold', - 'SWARMS_BOARD_ENABLE_VOTING': 'enable_voting', - 'SWARMS_BOARD_ENABLE_CONSENSUS': 'enable_consensus', - 'SWARMS_BOARD_DEFAULT_MODEL': 'default_board_model', - 'SWARMS_BOARD_VERBOSE_LOGGING': 'verbose_logging', - 'SWARMS_BOARD_MAX_MEETING_DURATION': 'max_board_meeting_duration', - 'SWARMS_BOARD_AUTO_FALLBACK': 'auto_fallback_to_director', + "SWARMS_BOARD_FEATURE_ENABLED": "board_feature_enabled", + "SWARMS_BOARD_DEFAULT_SIZE": "default_board_size", + "SWARMS_BOARD_DECISION_THRESHOLD": "decision_threshold", + "SWARMS_BOARD_ENABLE_VOTING": "enable_voting", + "SWARMS_BOARD_ENABLE_CONSENSUS": "enable_consensus", + "SWARMS_BOARD_DEFAULT_MODEL": "default_board_model", + "SWARMS_BOARD_VERBOSE_LOGGING": "verbose_logging", + "SWARMS_BOARD_MAX_MEETING_DURATION": "max_board_meeting_duration", + "SWARMS_BOARD_AUTO_FALLBACK": "auto_fallback_to_director", } - + for env_var, config_key in env_mappings.items(): value = os.getenv(env_var) if value is not None: try: # Convert string values to appropriate types - if config_key in ['board_feature_enabled', 'enable_voting', 'enable_consensus', 'verbose_logging', 'auto_fallback_to_director']: - converted_value = value.lower() in ['true', '1', 'yes', 'on'] - elif config_key in ['default_board_size', 'max_board_meeting_duration']: + if config_key in [ + "board_feature_enabled", + "enable_voting", + "enable_consensus", + "verbose_logging", + "auto_fallback_to_director", + ]: + converted_value = value.lower() in [ + "true", + "1", + "yes", + "on", + ] + elif config_key in [ + "default_board_size", + "max_board_meeting_duration", + ]: converted_value = int(value) - elif config_key in ['decision_threshold']: + elif config_key in ["decision_threshold"]: converted_value = float(value) else: converted_value = value - + setattr(self.config, config_key, converted_value) - logger.debug(f"Loaded {config_key} from environment: {converted_value}") + logger.debug( + f"Loaded {config_key} from environment: {converted_value}" + ) except (ValueError, TypeError) as e: - logger.warning(f"Failed to parse environment variable {env_var}: {e}") - + logger.warning( + f"Failed to parse environment variable {env_var}: {e}" + ) + def _load_from_dict(self, config_dict: Dict[str, Any]) -> None: """ Load configuration from dictionary. - + Args: config_dict: Dictionary containing configuration values - + Raises: ValueError: If configuration values are invalid """ @@ -277,33 +308,35 @@ class BoardConfig: setattr(self.config, key, value) except (ValueError, TypeError) as e: logger.warning(f"Failed to set config {key}: {e}") - raise ValueError(f"Invalid configuration value for {key}: {e}") - + raise ValueError( + f"Invalid configuration value for {key}: {e}" + ) + def is_enabled(self) -> bool: """ Check if the Board of Directors feature is enabled. - + Returns: bool: True if the feature is enabled, False otherwise """ return self.config.board_feature_enabled - + def get_config(self) -> BoardConfigModel: """ Get the current configuration. - + Returns: BoardConfigModel: The current configuration """ return self.config - + def update_config(self, updates: Dict[str, Any]) -> None: """ Update the configuration with new values. - + Args: updates: Dictionary of configuration updates - + Raises: ValueError: If any update values are invalid """ @@ -312,119 +345,192 @@ class BoardConfig: except ValueError as e: logger.error(f"Failed to update configuration: {e}") raise - + def save_config(self, file_path: Optional[str] = None) -> None: """ Save the current configuration to a file. - + Args: file_path: Optional file path to save to (uses config_file_path if not provided) - + Raises: Exception: If saving fails """ save_path = file_path or self.config_file_path if not save_path: - logger.warning("No file path specified for saving configuration") + logger.warning( + "No file path specified for saving configuration" + ) return - + try: import yaml + # Convert config to dictionary config_dict = self.config.model_dump() - + # Ensure directory exists os.makedirs(os.path.dirname(save_path), exist_ok=True) - - with open(save_path, 'w') as f: - yaml.dump(config_dict, f, default_flow_style=False, indent=2) - - logger.info(f"Saved Board of Directors config to: {save_path}") + + with open(save_path, "w") as f: + yaml.dump( + config_dict, f, default_flow_style=False, indent=2 + ) + + logger.info( + f"Saved Board of Directors config to: {save_path}" + ) except Exception as e: logger.error(f"Failed to save config to {save_path}: {e}") raise - + @lru_cache(maxsize=128) - def get_default_board_template(self, template_name: str = "standard") -> Dict[str, Any]: + def get_default_board_template( + self, template_name: str = "standard" + ) -> Dict[str, Any]: """ Get a default board template. - + This method provides predefined board templates for common use cases. Templates are cached for improved performance. - + Args: template_name: Name of the template to retrieve - + Returns: Dict[str, Any]: Board template configuration """ templates = { "standard": { "roles": [ - {"name": "Chairman", "weight": 1.5, "expertise": ["leadership", "strategy"]}, - {"name": "Vice-Chairman", "weight": 1.2, "expertise": ["operations", "coordination"]}, - {"name": "Secretary", "weight": 1.0, "expertise": ["documentation", "communication"]}, + { + "name": "Chairman", + "weight": 1.5, + "expertise": ["leadership", "strategy"], + }, + { + "name": "Vice-Chairman", + "weight": 1.2, + "expertise": ["operations", "coordination"], + }, + { + "name": "Secretary", + "weight": 1.0, + "expertise": [ + "documentation", + "communication", + ], + }, ] }, "executive": { "roles": [ - {"name": "CEO", "weight": 2.0, "expertise": ["executive_leadership", "strategy"]}, - {"name": "CFO", "weight": 1.5, "expertise": ["finance", "risk_management"]}, - {"name": "CTO", "weight": 1.5, "expertise": ["technology", "innovation"]}, - {"name": "COO", "weight": 1.3, "expertise": ["operations", "efficiency"]}, + { + "name": "CEO", + "weight": 2.0, + "expertise": [ + "executive_leadership", + "strategy", + ], + }, + { + "name": "CFO", + "weight": 1.5, + "expertise": ["finance", "risk_management"], + }, + { + "name": "CTO", + "weight": 1.5, + "expertise": ["technology", "innovation"], + }, + { + "name": "COO", + "weight": 1.3, + "expertise": ["operations", "efficiency"], + }, ] }, "advisory": { "roles": [ - {"name": "Lead_Advisor", "weight": 1.3, "expertise": ["strategy", "consulting"]}, - {"name": "Technical_Advisor", "weight": 1.2, "expertise": ["technology", "architecture"]}, - {"name": "Business_Advisor", "weight": 1.2, "expertise": ["business", "market_analysis"]}, - {"name": "Legal_Advisor", "weight": 1.1, "expertise": ["legal", "compliance"]}, + { + "name": "Lead_Advisor", + "weight": 1.3, + "expertise": ["strategy", "consulting"], + }, + { + "name": "Technical_Advisor", + "weight": 1.2, + "expertise": ["technology", "architecture"], + }, + { + "name": "Business_Advisor", + "weight": 1.2, + "expertise": ["business", "market_analysis"], + }, + { + "name": "Legal_Advisor", + "weight": 1.1, + "expertise": ["legal", "compliance"], + }, ] }, "minimal": { "roles": [ - {"name": "Chairman", "weight": 1.0, "expertise": ["leadership"]}, - {"name": "Member", "weight": 1.0, "expertise": ["general"]}, + { + "name": "Chairman", + "weight": 1.0, + "expertise": ["leadership"], + }, + { + "name": "Member", + "weight": 1.0, + "expertise": ["general"], + }, ] - } + }, } - + # Check custom templates first if template_name in self.config.custom_board_templates: return self.config.custom_board_templates[template_name] - + # Return standard template if requested template not found return templates.get(template_name, templates["standard"]) - + def validate_config(self) -> List[str]: """ Validate the current configuration. - + This method performs comprehensive validation of the configuration to ensure all values are within acceptable ranges and constraints. - + Returns: List[str]: List of validation errors (empty if valid) """ errors = [] - + try: # Validate the configuration model self.config.model_validate(self.config.model_dump()) except Exception as e: errors.append(f"Configuration validation failed: {e}") - + # Additional custom validations if self.config.decision_threshold < 0.5: - errors.append("Decision threshold should be at least 0.5 for meaningful majority decisions") - + errors.append( + "Decision threshold should be at least 0.5 for meaningful majority decisions" + ) + if self.config.default_board_size < 2: - errors.append("Board size should be at least 2 for meaningful discussions") - + errors.append( + "Board size should be at least 2 for meaningful discussions" + ) + if self.config.max_board_meeting_duration < 60: - errors.append("Board meeting duration should be at least 60 seconds") - + errors.append( + "Board meeting duration should be at least 60 seconds" + ) + return errors @@ -433,72 +539,80 @@ _board_config: Optional[BoardConfig] = None @lru_cache(maxsize=1) -def get_board_config(config_file_path: Optional[str] = None) -> BoardConfig: +def get_board_config( + config_file_path: Optional[str] = None, +) -> BoardConfig: """ Get the global Board of Directors configuration instance. - + This function provides a singleton pattern for accessing the Board of Directors configuration. The configuration is cached for improved performance. - + Args: config_file_path: Optional path to configuration file - + Returns: BoardConfig: The global configuration instance """ global _board_config - + if _board_config is None: _board_config = BoardConfig(config_file_path=config_file_path) - + return _board_config -def enable_board_feature(config_file_path: Optional[str] = None) -> None: +def enable_board_feature( + config_file_path: Optional[str] = None, +) -> None: """ Enable the Board of Directors feature globally. - + This function enables the Board of Directors feature and saves the configuration to the specified file path. - + Args: config_file_path: Optional path to save the configuration """ config = get_board_config(config_file_path) config.update_config({"board_feature_enabled": True}) - + if config_file_path: config.save_config(config_file_path) - + logger.info("Board of Directors feature enabled") -def disable_board_feature(config_file_path: Optional[str] = None) -> None: +def disable_board_feature( + config_file_path: Optional[str] = None, +) -> None: """ Disable the Board of Directors feature globally. - + This function disables the Board of Directors feature and saves the configuration to the specified file path. - + Args: config_file_path: Optional path to save the configuration """ config = get_board_config(config_file_path) config.update_config({"board_feature_enabled": False}) - + if config_file_path: config.save_config(config_file_path) - + logger.info("Board of Directors feature disabled") -def is_board_feature_enabled(config_file_path: Optional[str] = None) -> bool: +def is_board_feature_enabled( + config_file_path: Optional[str] = None, +) -> bool: """ Check if the Board of Directors feature is enabled. - + Args: config_file_path: Optional path to configuration file - + Returns: bool: True if the feature is enabled, False otherwise """ @@ -506,13 +620,15 @@ def is_board_feature_enabled(config_file_path: Optional[str] = None) -> bool: return config.is_enabled() -def create_default_config_file(file_path: str = "swarms_board_config.yaml") -> None: +def create_default_config_file( + file_path: str = "swarms_board_config.yaml", +) -> None: """ Create a default configuration file. - + This function creates a default Board of Directors configuration file with recommended settings. - + Args: file_path: Path where to create the configuration file """ @@ -526,101 +642,117 @@ def create_default_config_file(file_path: str = "swarms_board_config.yaml") -> N "verbose_logging": False, "max_board_meeting_duration": 300, "auto_fallback_to_director": True, - "custom_board_templates": {} + "custom_board_templates": {}, } - - config = BoardConfig(config_file_path=file_path, config_data=default_config) + + config = BoardConfig( + config_file_path=file_path, config_data=default_config + ) config.save_config(file_path) - - logger.info(f"Created default Board of Directors config file: {file_path}") + + logger.info( + f"Created default Board of Directors config file: {file_path}" + ) -def set_board_size(size: int, config_file_path: Optional[str] = None) -> None: +def set_board_size( + size: int, config_file_path: Optional[str] = None +) -> None: """ Set the default board size. - + Args: size: The default board size (1-10) config_file_path: Optional path to save the configuration """ if not 1 <= size <= 10: raise ValueError("Board size must be between 1 and 10") - + config = get_board_config(config_file_path) config.update_config({"default_board_size": size}) - + if config_file_path: config.save_config(config_file_path) - + logger.info(f"Default board size set to: {size}") -def set_decision_threshold(threshold: float, config_file_path: Optional[str] = None) -> None: +def set_decision_threshold( + threshold: float, config_file_path: Optional[str] = None +) -> None: """ Set the decision threshold for majority decisions. - + Args: threshold: The decision threshold (0.0-1.0) config_file_path: Optional path to save the configuration """ if not 0.0 <= threshold <= 1.0: - raise ValueError("Decision threshold must be between 0.0 and 1.0") - + raise ValueError( + "Decision threshold must be between 0.0 and 1.0" + ) + config = get_board_config(config_file_path) config.update_config({"decision_threshold": threshold}) - + if config_file_path: config.save_config(config_file_path) - + logger.info(f"Decision threshold set to: {threshold}") -def set_board_model(model: str, config_file_path: Optional[str] = None) -> None: +def set_board_model( + model: str, config_file_path: Optional[str] = None +) -> None: """ Set the default board model. - + Args: model: The default model name for board members config_file_path: Optional path to save the configuration """ config = get_board_config(config_file_path) config.update_config({"default_board_model": model}) - + if config_file_path: config.save_config(config_file_path) - + logger.info(f"Default board model set to: {model}") -def enable_verbose_logging(config_file_path: Optional[str] = None) -> None: +def enable_verbose_logging( + config_file_path: Optional[str] = None, +) -> None: """ Enable verbose logging for board operations. - + Args: config_file_path: Optional path to save the configuration """ config = get_board_config(config_file_path) config.update_config({"verbose_logging": True}) - + if config_file_path: config.save_config(config_file_path) - + logger.info("Verbose logging enabled for Board of Directors") -def disable_verbose_logging(config_file_path: Optional[str] = None) -> None: +def disable_verbose_logging( + config_file_path: Optional[str] = None, +) -> None: """ Disable verbose logging for board operations. - + Args: config_file_path: Optional path to save the configuration """ config = get_board_config(config_file_path) config.update_config({"verbose_logging": False}) - + if config_file_path: config.save_config(config_file_path) - + logger.info("Verbose logging disabled for Board of Directors") @@ -628,13 +760,14 @@ def disable_verbose_logging(config_file_path: Optional[str] = None) -> None: # BOARD OF DIRECTORS IMPLEMENTATION # ============================================================================ + class BoardMemberRole(str, Enum): """Enumeration of possible board member roles. - + This enum defines the various roles that board members can have within the Board of Directors swarm. Each role has specific responsibilities and voting weights associated with it. - + Attributes: CHAIRMAN: Primary leader responsible for board meetings and final decisions VICE_CHAIRMAN: Secondary leader who supports the chairman @@ -643,7 +776,7 @@ class BoardMemberRole(str, Enum): MEMBER: General board member with specific expertise EXECUTIVE_DIRECTOR: Executive-level board member with operational authority """ - + CHAIRMAN = "chairman" VICE_CHAIRMAN = "vice_chairman" SECRETARY = "secretary" @@ -654,18 +787,18 @@ class BoardMemberRole(str, Enum): class BoardDecisionType(str, Enum): """Enumeration of board decision types. - + This enum defines the different types of decisions that can be made by the Board of Directors, including voting mechanisms and consensus approaches. - + Attributes: UNANIMOUS: All board members agree on the decision MAJORITY: More than 50% of votes are in favor CONSENSUS: General agreement without formal voting CHAIRMAN_DECISION: Final decision made by the chairman """ - + UNANIMOUS = "unanimous" MAJORITY = "majority" CONSENSUS = "consensus" @@ -676,26 +809,26 @@ class BoardDecisionType(str, Enum): class BoardMember: """ Represents a member of the Board of Directors. - + This dataclass encapsulates all information about a board member, including their agent representation, role, voting weight, and areas of expertise. - + Attributes: agent: The agent representing this board member role: The role of this board member within the board voting_weight: The weight of this member's vote (default: 1.0) expertise_areas: Areas of expertise for this board member """ - + agent: Agent role: BoardMemberRole voting_weight: float = 1.0 expertise_areas: List[str] = field(default_factory=list) - + def __post_init__(self) -> None: """Initialize default values after object creation. - + This method ensures that the expertise_areas list is properly initialized as an empty list if not provided. """ @@ -706,11 +839,11 @@ class BoardMember: class BoardOrder(BaseModel): """ Represents an order issued by the Board of Directors. - + This model defines the structure of orders that the board issues to worker agents, including task assignments, priorities, and deadlines. - + Attributes: agent_name: The name of the agent to which the task is assigned task: The specific task to be executed by the assigned agent @@ -718,7 +851,7 @@ class BoardOrder(BaseModel): deadline: Optional deadline for task completion assigned_by: The board member who assigned this task """ - + agent_name: str = Field( ..., description="Specifies the name of the agent to which the task is assigned.", @@ -746,10 +879,10 @@ class BoardOrder(BaseModel): class BoardDecision(BaseModel): """ Represents a decision made by the Board of Directors. - + This model tracks the details of decisions made by the board, including voting results, decision types, and reasoning. - + Attributes: decision_type: The type of decision (unanimous, majority, etc.) decision: The actual decision made @@ -758,7 +891,7 @@ class BoardDecision(BaseModel): abstentions: Number of abstentions reasoning: The reasoning behind the decision """ - + decision_type: BoardDecisionType = Field( ..., description="The type of decision made by the board.", @@ -791,17 +924,17 @@ class BoardDecision(BaseModel): class BoardSpec(BaseModel): """ Specification for Board of Directors operations. - + This model represents the complete output of a board meeting, including the plan, orders, decisions, and meeting summary. - + Attributes: plan: The overall plan created by the board orders: List of orders issued by the board decisions: List of decisions made by the board meeting_summary: Summary of the board meeting """ - + plan: str = Field( ..., description="Outlines the sequence of actions to be taken by the swarm as decided by the board.", @@ -823,11 +956,11 @@ class BoardSpec(BaseModel): class BoardOfDirectorsSwarm(BaseSwarm): """ A hierarchical swarm of agents with a Board of Directors that orchestrates tasks. - + The Board of Directors operates as a collective decision-making body that can be enabled manually through configuration. It provides an alternative to the single Director approach with more democratic and collaborative decision-making. - + The workflow follows a hierarchical pattern: 1. Task is received and sent to the Board of Directors 2. Board convenes to discuss and create a plan through voting and consensus @@ -835,7 +968,7 @@ class BoardOfDirectorsSwarm(BaseSwarm): 4. Agents execute tasks and report back to the board 5. Board evaluates results and issues new orders if needed (up to max_loops) 6. All context and conversation history is preserved throughout the process - + Attributes: name: The name of the swarm description: A description of the swarm @@ -874,7 +1007,7 @@ class BoardOfDirectorsSwarm(BaseSwarm): ) -> None: """ Initialize the Board of Directors Swarm with the given parameters. - + Args: name: The name of the swarm description: A description of the swarm @@ -892,7 +1025,7 @@ class BoardOfDirectorsSwarm(BaseSwarm): max_workers: Maximum number of workers for parallel execution *args: Additional positional arguments passed to BaseSwarm **kwargs: Additional keyword arguments passed to BaseSwarm - + Raises: ValueError: If critical requirements are not met during initialization """ @@ -901,7 +1034,7 @@ class BoardOfDirectorsSwarm(BaseSwarm): description=description, agents=agents, ) - + self.name = name self.board_members = board_members or [] self.agents = agents or [] @@ -914,24 +1047,30 @@ class BoardOfDirectorsSwarm(BaseSwarm): self.decision_threshold = decision_threshold self.enable_voting = enable_voting self.enable_consensus = enable_consensus - self.max_workers = max_workers or min(32, (os.cpu_count() or 1) + 4) - + self.max_workers = max_workers or min( + 32, (os.cpu_count() or 1) + 4 + ) + # Initialize the swarm self._init_board_swarm() def _init_board_swarm(self) -> None: """ Initialize the Board of Directors swarm. - + This method sets up the board members, initializes the conversation, performs reliability checks, and prepares the board for operation. - + Raises: ValueError: If reliability checks fail """ if self.verbose: - board_logger.info(f"🚀 Initializing Board of Directors Swarm: {self.name}") - board_logger.info(f"📊 Configuration - Max loops: {self.max_loops}") + board_logger.info( + f"🚀 Initializing Board of Directors Swarm: {self.name}" + ) + board_logger.info( + f"📊 Configuration - Max loops: {self.max_loops}" + ) self.conversation = Conversation(time_enabled=False) @@ -946,17 +1085,21 @@ class BoardOfDirectorsSwarm(BaseSwarm): self._add_context_to_board() if self.verbose: - board_logger.success(f"✅ Board of Directors Swarm initialized successfully: {self.name}") + board_logger.success( + f"✅ Board of Directors Swarm initialized successfully: {self.name}" + ) def _setup_default_board(self) -> None: """ Set up a default Board of Directors if none is provided. - + Creates a basic board structure with Chairman, Vice Chairman, and Secretary roles. This method is called automatically if no board members are provided during initialization. """ if self.verbose: - board_logger.info("🎯 Setting up default Board of Directors") + board_logger.info( + "🎯 Setting up default Board of Directors" + ) # Create default board members chairman = Agent( @@ -984,18 +1127,35 @@ class BoardOfDirectorsSwarm(BaseSwarm): ) self.board_members = [ - BoardMember(chairman, BoardMemberRole.CHAIRMAN, 1.5, ["leadership", "strategy"]), - BoardMember(vice_chairman, BoardMemberRole.VICE_CHAIRMAN, 1.2, ["operations", "coordination"]), - BoardMember(secretary, BoardMemberRole.SECRETARY, 1.0, ["documentation", "communication"]), + BoardMember( + chairman, + BoardMemberRole.CHAIRMAN, + 1.5, + ["leadership", "strategy"], + ), + BoardMember( + vice_chairman, + BoardMemberRole.VICE_CHAIRMAN, + 1.2, + ["operations", "coordination"], + ), + BoardMember( + secretary, + BoardMemberRole.SECRETARY, + 1.0, + ["documentation", "communication"], + ), ] if self.verbose: - board_logger.success("✅ Default Board of Directors setup completed") + board_logger.success( + "✅ Default Board of Directors setup completed" + ) def _get_chairman_prompt(self) -> str: """ Get the system prompt for the Chairman role. - + Returns: str: The system prompt defining the Chairman's responsibilities and behavior """ @@ -1012,7 +1172,7 @@ You should be diplomatic, fair, and decisive in your leadership.""" def _get_vice_chairman_prompt(self) -> str: """ Get the system prompt for the Vice Chairman role. - + Returns: str: The system prompt defining the Vice Chairman's responsibilities and behavior """ @@ -1029,7 +1189,7 @@ You should be collaborative, analytical, and supportive in your role.""" def _get_secretary_prompt(self) -> str: """ Get the system prompt for the Secretary role. - + Returns: str: The system prompt defining the Secretary's responsibilities and behavior """ @@ -1046,16 +1206,18 @@ You should be thorough, organized, and detail-oriented in your documentation.""" def _add_context_to_board(self) -> None: """ Add agent context to all board members' conversations. - + This ensures that board members are aware of all available agents and their capabilities when making decisions. - + Raises: Exception: If context addition fails """ try: if self.verbose: - board_logger.info("📝 Adding agent context to board members") + board_logger.info( + "📝 Adding agent context to board members" + ) # Add context to each board member for board_member in self.board_members: @@ -1067,26 +1229,34 @@ You should be thorough, organized, and detail-oriented in your documentation.""" ) if self.verbose: - board_logger.success("✅ Agent context added to board members successfully") + board_logger.success( + "✅ Agent context added to board members successfully" + ) except Exception as e: - error_msg = f"❌ Failed to add context to board members: {str(e)}" - board_logger.error(f"{error_msg}\n🔍 Traceback: {traceback.format_exc()}") + error_msg = ( + f"❌ Failed to add context to board members: {str(e)}" + ) + board_logger.error( + f"{error_msg}\n🔍 Traceback: {traceback.format_exc()}" + ) raise def _perform_reliability_checks(self) -> None: """ Perform reliability checks for the Board of Directors swarm. - + This method validates critical requirements and configuration parameters to ensure the swarm can operate correctly. - + Raises: ValueError: If critical requirements are not met """ try: if self.verbose: - board_logger.info(f"🔍 Running reliability checks for swarm: {self.name}") + board_logger.info( + f"🔍 Running reliability checks for swarm: {self.name}" + ) # Check if Board of Directors feature is enabled board_config = get_board_config() @@ -1106,14 +1276,21 @@ You should be thorough, organized, and detail-oriented in your documentation.""" "Max loops must be greater than 0. Please set a valid number of loops." ) - if self.decision_threshold < 0.0 or self.decision_threshold > 1.0: + if ( + self.decision_threshold < 0.0 + or self.decision_threshold > 1.0 + ): raise ValueError( "Decision threshold must be between 0.0 and 1.0." ) if self.verbose: - board_logger.success(f"✅ Reliability checks passed for swarm: {self.name}") - board_logger.info(f"📊 Swarm stats - Agents: {len(self.agents)}, Max loops: {self.max_loops}") + board_logger.success( + f"✅ Reliability checks passed for swarm: {self.name}" + ) + board_logger.info( + f"📊 Swarm stats - Agents: {len(self.agents)}, Max loops: {self.max_loops}" + ) except Exception as e: error_msg = f"❌ Failed reliability checks: {str(e)}\n🔍 Traceback: {traceback.format_exc()}" @@ -1127,39 +1304,47 @@ You should be thorough, organized, and detail-oriented in your documentation.""" ) -> BoardSpec: """ Run a board meeting to discuss and decide on the given task. - + This method orchestrates a complete board meeting, including discussion, decision-making, and task distribution to worker agents. - + Args: task: The task to be discussed and planned by the board img: Optional image to be used with the task - + Returns: BoardSpec: The board's plan and orders - + Raises: Exception: If board meeting execution fails """ try: if self.verbose: - board_logger.info(f"🏛️ Running board meeting with task: {task[:100]}...") + board_logger.info( + f"🏛️ Running board meeting with task: {task[:100]}..." + ) # Create board meeting prompt meeting_prompt = self._create_board_meeting_prompt(task) - + # Run board discussion - board_discussion = self._conduct_board_discussion(meeting_prompt, img) - + board_discussion = self._conduct_board_discussion( + meeting_prompt, img + ) + # Parse board decisions board_spec = self._parse_board_decisions(board_discussion) - + # Add to conversation history - self.conversation.add(role="Board of Directors", content=board_discussion) + self.conversation.add( + role="Board of Directors", content=board_discussion + ) if self.verbose: board_logger.success("✅ Board meeting completed") - board_logger.debug(f"📋 Board output type: {type(board_spec)}") + board_logger.debug( + f"📋 Board output type: {type(board_spec)}" + ) return board_spec @@ -1171,14 +1356,14 @@ You should be thorough, organized, and detail-oriented in your documentation.""" def _create_board_meeting_prompt(self, task: str) -> str: """ Create a prompt for the board meeting. - + This method generates a comprehensive prompt that guides the board through the meeting process, including task discussion, decision-making, and task distribution. - + Args: task: The task to be discussed - + Returns: str: The board meeting prompt """ @@ -1229,58 +1414,73 @@ Please provide your response in the following format: def _format_board_members_info(self) -> str: """ Format board members information for the prompt. - + This method creates a formatted string containing information about all board members, their roles, and expertise areas. - + Returns: str: Formatted board members information """ info = [] for member in self.board_members: - info.append(f"- {member.agent.agent_name} ({member.role.value}): {member.agent.agent_description}") + info.append( + f"- {member.agent.agent_name} ({member.role.value}): {member.agent.agent_description}" + ) if member.expertise_areas: - info.append(f" Expertise: {', '.join(member.expertise_areas)}") + info.append( + f" Expertise: {', '.join(member.expertise_areas)}" + ) return "\n".join(info) - def _conduct_board_discussion(self, prompt: str, img: Optional[str] = None) -> str: + def _conduct_board_discussion( + self, prompt: str, img: Optional[str] = None + ) -> str: """ Conduct the board discussion using the chairman as the primary speaker. - + This method uses the chairman agent to lead the board discussion and generate the meeting output. - + Args: prompt: The board meeting prompt img: Optional image input - + Returns: str: The board discussion output - + Raises: ValueError: If no chairman is found in board members """ # Use the chairman to lead the discussion - chairman = next((member.agent for member in self.board_members - if member.role == BoardMemberRole.CHAIRMAN), - self.board_members[0].agent if self.board_members else None) - + chairman = next( + ( + member.agent + for member in self.board_members + if member.role == BoardMemberRole.CHAIRMAN + ), + ( + self.board_members[0].agent + if self.board_members + else None + ), + ) + if not chairman: raise ValueError("No chairman found in board members") - + return chairman.run(task=prompt, img=img) def _parse_board_decisions(self, board_output: str) -> BoardSpec: """ Parse the board output into a BoardSpec object. - + This method attempts to parse the board discussion output as JSON and convert it into a structured BoardSpec object. If parsing fails, it returns a basic BoardSpec with the raw output. - + Args: board_output: The output from the board discussion - + Returns: BoardSpec: Parsed board specification """ @@ -1288,10 +1488,12 @@ Please provide your response in the following format: # Try to parse as JSON first if isinstance(board_output, str): # Try to extract JSON from the response - json_match = re.search(r'\{.*\}', board_output, re.DOTALL) + json_match = re.search( + r"\{.*\}", board_output, re.DOTALL + ) if json_match: board_output = json_match.group() - + parsed = json.loads(board_output) else: parsed = board_output @@ -1310,7 +1512,9 @@ Please provide your response in the following format: task=order_data.get("task", ""), priority=order_data.get("priority", 3), deadline=order_data.get("deadline"), - assigned_by=order_data.get("assigned_by", "Board of Directors") + assigned_by=order_data.get( + "assigned_by", "Board of Directors" + ), ) orders.append(order) @@ -1318,12 +1522,18 @@ Please provide your response in the following format: decisions = [] for decision_data in decisions_data: decision = BoardDecision( - decision_type=BoardDecisionType(decision_data.get("decision_type", "consensus")), + decision_type=BoardDecisionType( + decision_data.get( + "decision_type", "consensus" + ) + ), decision=decision_data.get("decision", ""), votes_for=decision_data.get("votes_for", 0), - votes_against=decision_data.get("votes_against", 0), + votes_against=decision_data.get( + "votes_against", 0 + ), abstentions=decision_data.get("abstentions", 0), - reasoning=decision_data.get("reasoning", "") + reasoning=decision_data.get("reasoning", ""), ) decisions.append(decision) @@ -1331,53 +1541,67 @@ Please provide your response in the following format: plan=plan, orders=orders, decisions=decisions, - meeting_summary=meeting_summary + meeting_summary=meeting_summary, ) except Exception as e: - board_logger.error(f"Failed to parse board decisions: {str(e)}") + board_logger.error( + f"Failed to parse board decisions: {str(e)}" + ) # Return a basic BoardSpec if parsing fails return BoardSpec( plan=board_output, orders=[], decisions=[], - meeting_summary="Parsing failed, using raw output" + meeting_summary="Parsing failed, using raw output", ) - def step(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + def step( + self, + task: str, + img: Optional[str] = None, + *args: Any, + **kwargs: Any, + ) -> Any: """ Execute a single step of the Board of Directors swarm. - + This method runs one complete cycle of board meeting and task execution. It includes board discussion, task distribution, and optional feedback. - + Args: task: The task to be executed img: Optional image input *args: Additional positional arguments **kwargs: Additional keyword arguments - + Returns: Any: The result of the step execution - + Raises: Exception: If step execution fails """ try: if self.verbose: - board_logger.info(f"👣 Executing single step for task: {task[:100]}...") + board_logger.info( + f"👣 Executing single step for task: {task[:100]}..." + ) # Run board meeting board_spec = self.run_board_meeting(task=task, img=img) if self.verbose: - board_logger.info(f"📋 Board created plan and {len(board_spec.orders)} orders") + board_logger.info( + f"📋 Board created plan and {len(board_spec.orders)} orders" + ) # Execute the orders outputs = self._execute_orders(board_spec.orders) if self.verbose: - board_logger.info(f"⚡ Executed {len(outputs)} orders") + board_logger.info( + f"⚡ Executed {len(outputs)} orders" + ) # Provide board feedback if enabled if self.board_feedback_on: @@ -1395,47 +1619,64 @@ Please provide your response in the following format: board_logger.error(error_msg) raise - def run(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + def run( + self, + task: str, + img: Optional[str] = None, + *args: Any, + **kwargs: Any, + ) -> Any: """ Run the Board of Directors swarm for the specified number of loops. - + This method executes the complete swarm workflow, including multiple iterations if max_loops is greater than 1. Each iteration includes board meeting, task execution, and feedback generation. - + Args: task: The task to be executed img: Optional image input *args: Additional positional arguments **kwargs: Additional keyword arguments - + Returns: Any: The final result of the swarm execution - + Raises: Exception: If swarm execution fails """ try: if self.verbose: - board_logger.info(f"🏛️ Starting Board of Directors swarm execution: {self.name}") + board_logger.info( + f"🏛️ Starting Board of Directors swarm execution: {self.name}" + ) board_logger.info(f"📋 Task: {task[:100]}...") current_loop = 0 while current_loop < self.max_loops: if self.verbose: - board_logger.info(f"🔄 Executing loop {current_loop + 1}/{self.max_loops}") + board_logger.info( + f"🔄 Executing loop {current_loop + 1}/{self.max_loops}" + ) # Execute step - result = self.step(task=task, img=img, *args, **kwargs) - + self.step(task=task, img=img, *args, **kwargs) + # Add to conversation - self.conversation.add(role="System", content=f"Loop {current_loop + 1} completed") - + self.conversation.add( + role="System", + content=f"Loop {current_loop + 1} completed", + ) + current_loop += 1 if self.verbose: - board_logger.success(f"🎉 Board of Directors swarm run completed: {self.name}") - board_logger.info(f"📊 Total loops executed: {current_loop}") + board_logger.success( + f"🎉 Board of Directors swarm run completed: {self.name}" + ) + board_logger.info( + f"📊 Total loops executed: {current_loop}" + ) return history_output_formatter( conversation=self.conversation, type=self.output_type @@ -1446,19 +1687,25 @@ Please provide your response in the following format: board_logger.error(error_msg) raise - async def arun(self, task: str, img: Optional[str] = None, *args: Any, **kwargs: Any) -> Any: + async def arun( + self, + task: str, + img: Optional[str] = None, + *args: Any, + **kwargs: Any, + ) -> Any: """ Run the Board of Directors swarm asynchronously. - + This method provides an asynchronous interface for running the swarm, allowing for non-blocking execution in async contexts. - + Args: task: The task to be executed img: Optional image input *args: Additional positional arguments **kwargs: Additional keyword arguments - + Returns: Any: The final result of the swarm execution """ @@ -1471,16 +1718,16 @@ Please provide your response in the following format: def _generate_board_feedback(self, outputs: List[Any]) -> str: """ Provide feedback from the Board of Directors based on agent outputs. - + This method uses the chairman to review and provide feedback on the outputs generated by worker agents. - + Args: outputs: List of outputs from agents - + Returns: str: Board feedback on the outputs - + Raises: ValueError: If no chairman is found for feedback Exception: If feedback generation fails @@ -1492,10 +1739,19 @@ Please provide your response in the following format: task = f"History: {self.conversation.get_str()} \n\n" # Use the chairman for feedback - chairman = next((member.agent for member in self.board_members - if member.role == BoardMemberRole.CHAIRMAN), - self.board_members[0].agent if self.board_members else None) - + chairman = next( + ( + member.agent + for member in self.board_members + if member.role == BoardMemberRole.CHAIRMAN + ), + ( + self.board_members[0].agent + if self.board_members + else None + ), + ) + if not chairman: raise ValueError("No chairman found for feedback") @@ -1509,10 +1765,14 @@ Please provide your response in the following format: ) output = chairman.run(task=feedback_prompt) - self.conversation.add(role=chairman.agent_name, content=output) + self.conversation.add( + role=chairman.agent_name, content=output + ) if self.verbose: - board_logger.success("✅ Board feedback generated successfully") + board_logger.success( + "✅ Board feedback generated successfully" + ) return output @@ -1522,27 +1782,23 @@ Please provide your response in the following format: raise def _call_single_agent( - self, - agent_name: str, - task: str, - *args: Any, - **kwargs: Any + self, agent_name: str, task: str, *args: Any, **kwargs: Any ) -> Any: """ Call a single agent with the given task. - + This method finds and executes a specific agent with the provided task. It includes error handling and logging for agent execution. - + Args: agent_name: The name of the agent to call task: The task to assign to the agent *args: Additional positional arguments **kwargs: Additional keyword arguments - + Returns: Any: The output from the agent - + Raises: ValueError: If the specified agent is not found Exception: If agent execution fails @@ -1554,13 +1810,18 @@ Please provide your response in the following format: # Find agent by name agent = None for a in self.agents: - if hasattr(a, "agent_name") and a.agent_name == agent_name: + if ( + hasattr(a, "agent_name") + and a.agent_name == agent_name + ): agent = a break if agent is None: available_agents = [ - a.agent_name for a in self.agents if hasattr(a, "agent_name") + a.agent_name + for a in self.agents + if hasattr(a, "agent_name") ] raise ValueError( f"Agent '{agent_name}' not found in swarm. Available agents: {available_agents}" @@ -1574,7 +1835,9 @@ Please provide your response in the following format: self.conversation.add(role=agent_name, content=output) if self.verbose: - board_logger.success(f"✅ Agent {agent_name} completed task successfully") + board_logger.success( + f"✅ Agent {agent_name} completed task successfully" + ) return output @@ -1583,59 +1846,75 @@ Please provide your response in the following format: board_logger.error(error_msg) raise - def _execute_orders(self, orders: List[BoardOrder]) -> List[Dict[str, Any]]: + def _execute_orders( + self, orders: List[BoardOrder] + ) -> List[Dict[str, Any]]: """ Execute the orders issued by the Board of Directors. - + This method uses ThreadPoolExecutor to execute multiple orders in parallel, improving performance for complex task distributions. - + Args: orders: List of board orders to execute - + Returns: List[Dict[str, Any]]: List of outputs from executed orders - + Raises: Exception: If order execution fails """ try: if self.verbose: - board_logger.info(f"⚡ Executing {len(orders)} board orders") + board_logger.info( + f"⚡ Executing {len(orders)} board orders" + ) # Use ThreadPoolExecutor for parallel execution outputs = [] - with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + with ThreadPoolExecutor( + max_workers=self.max_workers + ) as executor: # Submit all orders for execution future_to_order = { - executor.submit(self._execute_single_order, order): order + executor.submit( + self._execute_single_order, order + ): order for order in orders } - + # Collect results as they complete for future in as_completed(future_to_order): order = future_to_order[future] try: output = future.result() - outputs.append({ - "agent_name": order.agent_name, - "task": order.task, - "output": output, - "priority": order.priority, - "assigned_by": order.assigned_by, - }) + outputs.append( + { + "agent_name": order.agent_name, + "task": order.task, + "output": output, + "priority": order.priority, + "assigned_by": order.assigned_by, + } + ) except Exception as e: - board_logger.error(f"Failed to execute order for {order.agent_name}: {str(e)}") - outputs.append({ - "agent_name": order.agent_name, - "task": order.task, - "output": f"Error: {str(e)}", - "priority": order.priority, - "assigned_by": order.assigned_by, - }) + board_logger.error( + f"Failed to execute order for {order.agent_name}: {str(e)}" + ) + outputs.append( + { + "agent_name": order.agent_name, + "task": order.task, + "output": f"Error: {str(e)}", + "priority": order.priority, + "assigned_by": order.assigned_by, + } + ) if self.verbose: - board_logger.success(f"✅ Executed {len(outputs)} orders successfully") + board_logger.success( + f"✅ Executed {len(outputs)} orders successfully" + ) return outputs @@ -1647,13 +1926,13 @@ Please provide your response in the following format: def _execute_single_order(self, order: BoardOrder) -> Any: """ Execute a single board order. - + This method is a wrapper around _call_single_agent for executing individual board orders. - + Args: order: The board order to execute - + Returns: Any: The output from the executed order """ @@ -1665,41 +1944,48 @@ Please provide your response in the following format: def add_board_member(self, board_member: BoardMember) -> None: """ Add a new member to the Board of Directors. - + This method allows dynamic addition of board members after swarm initialization. - + Args: board_member: The board member to add """ self.board_members.append(board_member) if self.verbose: - board_logger.info(f"✅ Added board member: {board_member.agent.agent_name}") + board_logger.info( + f"✅ Added board member: {board_member.agent.agent_name}" + ) def remove_board_member(self, agent_name: str) -> None: """ Remove a board member by agent name. - + This method allows dynamic removal of board members after swarm initialization. - + Args: agent_name: The name of the agent to remove from the board """ self.board_members = [ - member for member in self.board_members + member + for member in self.board_members if member.agent.agent_name != agent_name ] if self.verbose: - board_logger.info(f"✅ Removed board member: {agent_name}") + board_logger.info( + f"✅ Removed board member: {agent_name}" + ) - def get_board_member(self, agent_name: str) -> Optional[BoardMember]: + def get_board_member( + self, agent_name: str + ) -> Optional[BoardMember]: """ Get a board member by agent name. - + This method retrieves a specific board member by their agent name. - + Args: agent_name: The name of the agent - + Returns: Optional[BoardMember]: The board member if found, None otherwise """ @@ -1711,10 +1997,10 @@ Please provide your response in the following format: def get_board_summary(self) -> Dict[str, Any]: """ Get a summary of the Board of Directors. - + This method provides a comprehensive summary of the board structure, including member information, configuration, and statistics. - + Returns: Dict[str, Any]: Summary of the board structure and members """ diff --git a/swarms/structs/hiearchical_swarm.py b/swarms/structs/hiearchical_swarm.py index 85a720cf..70a97587 100644 --- a/swarms/structs/hiearchical_swarm.py +++ b/swarms/structs/hiearchical_swarm.py @@ -18,6 +18,9 @@ Todo - Auto build agents from input prompt - and then add them to the swarm - Create an interactive and dynamic UI like we did with heavy swarm - Make it faster and more high performance +- Enable the director to choose a multi-agent approach to the task, it orchestrates how the agents talk and work together. +- Improve the director feedback, maybe add agent as a judge to the worker agent instead of the director. +- Use agent rearrange to orchestrate the agents Classes: HierarchicalOrder: Represents a single task assignment to a specific agent @@ -25,14 +28,26 @@ Classes: HierarchicalSwarm: Main swarm orchestrator that manages director and worker agents """ +import time import traceback from typing import Any, Callable, List, Optional, Union +from loguru import logger from pydantic import BaseModel, Field +from rich.console import Console +from rich.layout import Layout +from rich.live import Live +from rich.panel import Panel +from rich.table import Table +from rich.text import Text from swarms.prompts.hiearchical_system_prompt import ( HIEARCHICAL_SWARM_SYSTEM_PROMPT, ) +from swarms.prompts.multi_agent_collab_prompt import ( + MULTI_AGENT_COLLAB_PROMPT_TWO, +) +from swarms.prompts.reasoning_prompt import INTERNAL_MONOLGUE_PROMPT from swarms.structs.agent import Agent from swarms.structs.conversation import Conversation from swarms.structs.ma_utils import list_all_agents @@ -40,10 +55,507 @@ from swarms.tools.base_tool import BaseTool from swarms.utils.history_output_formatter import ( history_output_formatter, ) -from swarms.utils.loguru_logger import initialize_logger from swarms.utils.output_types import OutputType -logger = initialize_logger(log_folder="hierarchical_swarm") + +class HierarchicalSwarmDashboard: + """ + Futuristic Arasaka Corporation-style dashboard for hierarchical swarm monitoring. + + This dashboard provides a professional, enterprise-grade interface with red and black + color scheme, real-time monitoring of swarm operations, and cyberpunk aesthetics. + + Attributes: + console (Console): Rich console instance for rendering + live_display (Live): Live display for real-time updates + swarm_name (str): Name of the swarm being monitored + agent_statuses (dict): Current status of all agents + director_status (str): Current status of the director + current_loop (int): Current execution loop + max_loops (int): Maximum number of loops + is_active (bool): Whether the dashboard is currently active + """ + + def __init__(self, swarm_name: str = "Swarms Corporation"): + """ + Initialize the Arasaka dashboard. + + Args: + swarm_name (str): Name of the swarm to display in the dashboard + """ + self.console = Console() + self.live_display = None + self.swarm_name = swarm_name + self.agent_statuses = {} + self.director_status = "INITIALIZING" + self.current_loop = 0 + self.max_loops = 1 + self.is_active = False + self.start_time = None + self.spinner_frames = [ + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏", + ] + self.spinner_idx = 0 + + # Director information tracking + self.director_plan = "" + self.director_orders = [] + + # Swarm information + self.swarm_description = "" + self.director_name = "Director" + self.director_model_name = "gpt-4o-mini" + + # View mode for agents display + self.detailed_view = False + + # Multi-loop agent tracking + self.agent_history = {} # Track agent outputs across loops + self.current_loop = 0 + + def _get_spinner(self) -> str: + """Get current spinner frame for loading animations.""" + self.spinner_idx = (self.spinner_idx + 1) % len( + self.spinner_frames + ) + return self.spinner_frames[self.spinner_idx] + + def _create_header(self) -> Panel: + """Create the dashboard header with Swarms Corporation branding.""" + header_text = Text() + header_text.append( + "╔══════════════════════════════════════════════════════════════════════════════╗\n", + style="bold red", + ) + header_text.append("║", style="bold red") + header_text.append(" ", style="bold red") + header_text.append( + "SWARMS CORPORATION", style="bold white on red" + ) + header_text.append(" ", style="bold red") + header_text.append("║\n", style="bold red") + header_text.append("║", style="bold red") + header_text.append(" ", style="bold red") + header_text.append( + "HIERARCHICAL SWARM OPERATIONS CENTER", style="bold red" + ) + header_text.append(" ", style="bold red") + header_text.append("║\n", style="bold red") + header_text.append( + "╚══════════════════════════════════════════════════════════════════════════════╝", + style="bold red", + ) + + return Panel( + header_text, + border_style="red", + padding=(0, 1), + ) + + def _create_status_panel(self) -> Panel: + """Create the operations status panel.""" + status_text = Text() + + # Corporation branding and operation type + status_text.append( + "By the Swarms Corporation", style="bold cyan" + ) + status_text.append("\n", style="white") + status_text.append( + "Hierarchical Agent Operations", style="bold white" + ) + + status_text.append("\n\n", style="white") + + # Swarm information + status_text.append("SWARM NAME: ", style="bold white") + status_text.append(f"{self.swarm_name}", style="bold cyan") + + status_text.append("\n", style="white") + status_text.append("DESCRIPTION: ", style="bold white") + status_text.append(f"{self.swarm_description}", style="white") + + status_text.append("\n", style="white") + status_text.append("DIRECTOR: ", style="bold white") + status_text.append( + f"{self.director_name} ({self.director_model_name})", + style="cyan", + ) + + status_text.append("\n", style="white") + status_text.append("TOTAL LOOPS: ", style="bold white") + status_text.append(f"{self.max_loops}", style="bold cyan") + + status_text.append(" | ", style="white") + status_text.append("CURRENT LOOP: ", style="bold white") + status_text.append( + f"{self.current_loop}", style="bold yellow" + ) + + # Agent count metadata + agent_count = len(getattr(self, "agent_history", {})) + status_text.append(" | ", style="white") + status_text.append("AGENTS: ", style="bold white") + status_text.append(f"{agent_count}", style="bold green") + + status_text.append("\n\n", style="white") + + # Director status + status_text.append("DIRECTOR STATUS: ", style="bold white") + if self.director_status == "INITIALIZING": + status_text.append( + f"{self._get_spinner()} {self.director_status}", + style="bold yellow", + ) + elif self.director_status == "ACTIVE": + status_text.append( + f"✓ {self.director_status}", style="bold green" + ) + elif self.director_status == "PROCESSING": + status_text.append( + f"{self._get_spinner()} {self.director_status}", + style="bold cyan", + ) + else: + status_text.append( + f"✗ {self.director_status}", style="bold red" + ) + + status_text.append("\n\n", style="white") + + # Runtime and completion information + if self.start_time: + runtime = time.time() - self.start_time + status_text.append("RUNTIME: ", style="bold white") + status_text.append(f"{runtime:.2f}s", style="bold green") + + # Add completion percentage if loops are running + if self.max_loops > 0: + completion_percent = ( + self.current_loop / self.max_loops + ) * 100 + status_text.append(" | ", style="white") + status_text.append("PROGRESS: ", style="bold white") + status_text.append( + f"{completion_percent:.1f}%", style="bold cyan" + ) + + return Panel( + status_text, + border_style="red", + padding=(1, 2), + title="[bold white]OPERATIONS STATUS[/bold white]", + ) + + def _create_agents_table(self) -> Table: + """Create the agents monitoring table with full outputs and loop history.""" + table = Table( + show_header=True, + header_style="bold white on red", + border_style="red", + title="[bold white]AGENT MONITORING MATRIX[/bold white]", + title_style="bold white", + show_lines=True, + ) + + table.add_column("AGENT ID", style="bold cyan", width=25) + table.add_column("LOOP", style="bold white", width=8) + table.add_column("STATUS", style="bold white", width=15) + table.add_column("TASK", style="white", width=40) + table.add_column("OUTPUT", style="white", width=150) + + # Display agents with their history across loops + for agent_name, history in self.agent_history.items(): + for loop_num in range(self.max_loops + 1): + loop_key = f"Loop_{loop_num}" + + if loop_key in history: + loop_data = history[loop_key] + status = loop_data.get("status", "UNKNOWN") + task = loop_data.get("task", "N/A") + output = loop_data.get("output", "") + + # Style status + if status == "RUNNING": + status_display = ( + f"{self._get_spinner()} {status}" + ) + status_style = "bold yellow" + elif status == "COMPLETED": + status_display = f"✓ {status}" + status_style = "bold green" + elif status == "PENDING": + status_display = f"○ {status}" + status_style = "bold red" + else: + status_display = f"✗ {status}" + status_style = "bold red" + + # Show full output without truncation + output_display = output if output else "No output" + + table.add_row( + Text(agent_name, style="bold cyan"), + Text(f"Loop {loop_num}", style="bold white"), + Text(status_display, style=status_style), + Text(task, style="white"), + Text(output_display, style="white"), + ) + + return table + + def _create_detailed_agents_view(self) -> Panel: + """Create a detailed view of agents with full outputs and loop history.""" + detailed_text = Text() + + for agent_name, history in self.agent_history.items(): + detailed_text.append( + f"AGENT: {agent_name}\n", style="bold cyan" + ) + detailed_text.append("=" * 80 + "\n", style="red") + + for loop_num in range(self.max_loops + 1): + loop_key = f"Loop_{loop_num}" + + if loop_key in history: + loop_data = history[loop_key] + status = loop_data.get("status", "UNKNOWN") + task = loop_data.get("task", "N/A") + output = loop_data.get("output", "") + + detailed_text.append( + f"LOOP {loop_num}:\n", style="bold white" + ) + detailed_text.append( + f"STATUS: {status}\n", style="bold white" + ) + detailed_text.append( + f"TASK: {task}\n", style="white" + ) + detailed_text.append( + "OUTPUT:\n", style="bold white" + ) + detailed_text.append(f"{output}\n", style="white") + detailed_text.append("─" * 80 + "\n", style="red") + + return Panel( + detailed_text, + border_style="red", + padding=(1, 2), + title="[bold white]DETAILED AGENT OUTPUTS (FULL HISTORY)[/bold white]", + ) + + def _create_director_panel(self) -> Panel: + """Create the director information panel showing plan and orders.""" + director_text = Text() + + # Plan section + director_text.append("DIRECTOR PLAN:\n", style="bold white") + if self.director_plan: + director_text.append(self.director_plan, style="white") + else: + director_text.append( + "No plan available", style="dim white" + ) + + director_text.append("\n\n", style="white") + + # Orders section + director_text.append("CURRENT ORDERS:\n", style="bold white") + if self.director_orders: + for i, order in enumerate( + self.director_orders + ): # Show first 5 orders + director_text.append(f"{i+1}. ", style="bold cyan") + director_text.append( + f"{order.get('agent_name', 'Unknown')}: ", + style="bold white", + ) + task = order.get("task", "No task") + director_text.append(task, style="white") + director_text.append("\n", style="white") + + if len(self.director_orders) > 5: + director_text.append( + f"... and {len(self.director_orders) - 5} more orders", + style="dim white", + ) + else: + director_text.append( + "No orders available", style="dim white" + ) + + return Panel( + director_text, + border_style="red", + padding=(1, 2), + title="[bold white]DIRECTOR OPERATIONS[/bold white]", + ) + + def _create_dashboard_layout(self) -> Layout: + """Create the complete dashboard layout.""" + layout = Layout() + + # Split into operations status, director operations, and agents + layout.split_column( + Layout(name="operations_status", size=12), + Layout(name="director_operations", size=12), + Layout(name="agents", ratio=1), + ) + + # Add content to each section + layout["operations_status"].update( + self._create_status_panel() + ) + layout["director_operations"].update( + self._create_director_panel() + ) + + # Choose between table view and detailed view + if self.detailed_view: + layout["agents"].update( + self._create_detailed_agents_view() + ) + else: + layout["agents"].update( + Panel( + self._create_agents_table(), + border_style="red", + padding=(1, 1), + ) + ) + + return layout + + def start(self, max_loops: int = 1): + """Start the dashboard display.""" + self.max_loops = max_loops + self.start_time = time.time() + self.is_active = True + + self.live_display = Live( + self._create_dashboard_layout(), + console=self.console, + refresh_per_second=10, + transient=False, + ) + self.live_display.start() + + def update_agent_status( + self, + agent_name: str, + status: str, + task: str = "", + output: str = "", + ): + """Update the status of a specific agent.""" + # Create loop key for tracking history + loop_key = f"Loop_{self.current_loop}" + + # Initialize agent history if not exists + if agent_name not in self.agent_history: + self.agent_history[agent_name] = {} + + # Store current status and add to history + self.agent_statuses[agent_name] = { + "status": status, + "task": task, + "output": output, + } + + # Add to history for this loop + self.agent_history[agent_name][loop_key] = { + "status": status, + "task": task, + "output": output, + } + + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def update_director_status(self, status: str): + """Update the director status.""" + self.director_status = status + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def update_loop(self, current_loop: int): + """Update the current execution loop.""" + self.current_loop = current_loop + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def update_director_plan(self, plan: str): + """Update the director's plan.""" + self.director_plan = plan + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def update_director_orders(self, orders: list): + """Update the director's orders.""" + self.director_orders = orders + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def stop(self): + """Stop the dashboard display.""" + self.is_active = False + if self.live_display: + self.live_display.stop() + self.console.print() + + def update_swarm_info( + self, + name: str, + description: str, + max_loops: int, + director_name: str, + director_model_name: str, + ): + """Update the dashboard with swarm-specific information.""" + self.swarm_name = name + self.swarm_description = description + self.max_loops = max_loops + self.director_name = director_name + self.director_model_name = director_model_name + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def force_refresh(self): + """Force refresh the dashboard display.""" + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) + + def show_full_output(self, agent_name: str, full_output: str): + """Display full agent output in a separate panel.""" + if self.live_display and self.is_active: + # Create a full output panel + output_panel = Panel( + Text(full_output, style="white"), + title=f"[bold white]FULL OUTPUT - {agent_name}[/bold white]", + border_style="red", + padding=(1, 2), + width=120, + ) + + # Temporarily show the full output + self.console.print(output_panel) + self.console.print() # Add spacing + + def toggle_detailed_view(self): + """Toggle between table view and detailed view.""" + self.detailed_view = not self.detailed_view + if self.live_display and self.is_active: + self.live_display.update(self._create_dashboard_layout()) class HierarchicalOrder(BaseModel): @@ -71,6 +583,25 @@ class HierarchicalOrder(BaseModel): ) +class HierarchicalOrderRearrange(BaseModel): + """ + Represents a single task assignment within the hierarchical swarm. + + This class defines the structure for individual task orders that the director + distributes to worker agents. Each order specifies which agent should execute + what specific task. + """ + + initial_task: str = Field( + ..., + description="The initial task that the director has to execute.", + ) + flow_of_communication: str = Field( + ..., + description="How the agents will communicate with each other to accomplish the task. Like agent_one -> agent_two -> agent_three -> agent_four -> agent_one, can use comma signs to denote sequential communication and commas to denote parallel communication for example agent_one -> agent_two, agent_three -> agent_four", + ) + + class SwarmSpec(BaseModel): """ Defines the complete specification for a hierarchical swarm execution. @@ -86,10 +617,20 @@ class SwarmSpec(BaseModel): individual agents within the swarm. """ + # # thoughts: str = Field( + # # ..., + # # description="A plan generated by the director agent for the swarm to accomplish the given task, where the director autonomously reasons through the problem, devises its own strategy, and determines the sequence of actions. " + # # "This plan reflects the director's independent thought process, outlining the rationale, priorities, and steps it deems necessary for successful execution. " + # # "It serves as a blueprint for the swarm, enabling agents to follow the director's self-derived guidance and adapt as needed throughout the process.", + # ) + plan: str = Field( ..., - description="Outlines the sequence of actions to be taken by the swarm. This plan is a detailed roadmap that guides the swarm's behavior and decision-making.", + description="A plan generated by the director agent for the swarm to accomplish the given task, where the director autonomously reasons through the problem, devises its own strategy, and determines the sequence of actions. " + "This plan reflects the director's independent thought process, outlining the rationale, priorities, and steps it deems necessary for successful execution. " + "It serves as a blueprint for the swarm, enabling agents to follow the director's self-derived guidance and adapt as needed throughout the process.", ) + orders: List[HierarchicalOrder] = Field( ..., description="A collection of task assignments to specific agents within the swarm. These orders are the specific instructions that guide the agents in their task execution and are a key element in the swarm's plan.", @@ -143,6 +684,11 @@ class HierarchicalSwarm: Union[Agent, Callable, Any] ] = None, director_feedback_on: bool = True, + interactive: bool = False, + director_system_prompt: str = HIEARCHICAL_SWARM_SYSTEM_PROMPT, + director_reasoning_model_name: str = "o3-mini", + director_reasoning_enabled: bool = True, + multi_agent_prompt_improvements: bool = False, *args, **kwargs, ): @@ -187,9 +733,79 @@ class HierarchicalSwarm: self.add_collaboration_prompt = add_collaboration_prompt self.planning_director_agent = planning_director_agent self.director_feedback_on = director_feedback_on + self.interactive = interactive + self.director_system_prompt = director_system_prompt + self.director_reasoning_model_name = ( + director_reasoning_model_name + ) + self.director_reasoning_enabled = director_reasoning_enabled + self.multi_agent_prompt_improvements = ( + multi_agent_prompt_improvements + ) + + if self.interactive: + self.agents_no_print() + + # Initialize dashboard if interactive mode is enabled + self.dashboard = None + if self.interactive: + self.dashboard = HierarchicalSwarmDashboard(self.name) + # Enable detailed view for better output visibility + self.dashboard.detailed_view = True + # Pass additional swarm information to dashboard + self.dashboard.update_swarm_info( + name=self.name, + description=self.description, + max_loops=self.max_loops, + director_name=self.director_name, + director_model_name=self.director_model_name, + ) self.init_swarm() + def list_worker_agents(self) -> str: + return list_all_agents( + agents=self.agents, + add_to_conversation=False, + ) + + def prepare_worker_agents(self): + for agent in self.agents: + prompt = ( + MULTI_AGENT_COLLAB_PROMPT_TWO + + self.list_worker_agents() + ) + if hasattr(agent, "system_prompt"): + agent.system_prompt += prompt + else: + agent.system_prompt = prompt + + def reasoning_agent_run( + self, task: str, img: Optional[str] = None + ): + """ + Run a reasoning agent to analyze the task before the main director processes it. + + Args: + task (str): The task to reason about + img (Optional[str]): Optional image input + + Returns: + str: The reasoning output from the agent + """ + agent = Agent( + agent_name=self.director_name, + agent_description=f"You're the {self.director_name} agent that is responsible for reasoning about the task and creating a plan for the swarm to accomplish the task.", + model_name=self.director_reasoning_model_name, + system_prompt=INTERNAL_MONOLGUE_PROMPT + + self.director_system_prompt, + max_loops=1, + ) + + prompt = f"Conversation History: {self.conversation.get_str()} \n\n Task: {task}" + + return agent.run(task=prompt, img=img) + def init_swarm(self): """ Initialize the swarm with proper configuration and validation. @@ -216,11 +832,27 @@ class HierarchicalSwarm: self.add_context_to_director() + # Initialize agent statuses in dashboard if interactive mode + if self.interactive and self.dashboard: + for agent in self.agents: + if hasattr(agent, "agent_name"): + self.dashboard.update_agent_status( + agent.agent_name, + "PENDING", + "Awaiting task assignment", + "Ready for deployment", + ) + # Force refresh to ensure agents are displayed + self.dashboard.force_refresh() + if self.verbose: logger.success( f"✅ HierarchicalSwarm: {self.name} initialized successfully." ) + if self.multi_agent_prompt_improvements: + self.prepare_worker_agents() + def add_context_to_director(self): """ Add agent context and collaboration information to the director's conversation. @@ -282,6 +914,7 @@ class HierarchicalSwarm: return Agent( agent_name=self.director_name, agent_description="A director agent that can create a plan and distribute orders to agents", + system_prompt=self.director_system_prompt, model_name=self.director_model_name, max_loops=1, base_model=SwarmSpec, @@ -333,6 +966,10 @@ class HierarchicalSwarm: error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) + def agents_no_print(self): + for agent in self.agents: + agent.print_on = False + def run_director( self, task: str, @@ -358,9 +995,7 @@ class HierarchicalSwarm: """ try: if self.verbose: - logger.info( - f"🎯 Running director with task: {task[:100]}..." - ) + logger.info(f"🎯 Running director with task: {task}") if self.planning_director_agent is not None: plan = self.planning_director_agent.run( @@ -370,6 +1005,12 @@ class HierarchicalSwarm: task += plan + if self.director_reasoning_enabled: + reasoning_output = self.reasoning_agent_run( + task=task, img=img + ) + task += f"\n\n Reasoning: {reasoning_output}" + # Run the director with the context function_call = self.director.run( task=f"History: {self.conversation.get_str()} \n\n Task: {task}", @@ -391,6 +1032,7 @@ class HierarchicalSwarm: except Exception as e: error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) + raise e def step(self, task: str, img: str = None, *args, **kwargs): """ @@ -417,9 +1059,13 @@ class HierarchicalSwarm: try: if self.verbose: logger.info( - f"👣 Executing single step for task: {task[:100]}..." + f"👣 Executing single step for task: {task}" ) + # Update dashboard for director execution + if self.interactive and self.dashboard: + self.dashboard.update_director_status("PLANNING") + output = self.run_director(task=task, img=img) # Parse the orders @@ -430,6 +1076,20 @@ class HierarchicalSwarm: f"📋 Parsed plan and {len(orders)} orders" ) + # Update dashboard with plan and orders information + if self.interactive and self.dashboard: + self.dashboard.update_director_plan(plan) + # Convert orders to list of dicts for dashboard + orders_list = [ + { + "agent_name": order.agent_name, + "task": order.task, + } + for order in orders + ] + self.dashboard.update_director_orders(orders_list) + self.dashboard.update_director_status("EXECUTING") + # Execute the orders outputs = self.execute_orders(orders) @@ -450,7 +1110,13 @@ class HierarchicalSwarm: error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) - def run(self, task: str, img: str = None, *args, **kwargs): + def run( + self, + task: Optional[str] = None, + img: Optional[str] = None, + *args, + **kwargs, + ): """ Execute the hierarchical swarm for the specified number of feedback loops. @@ -462,7 +1128,8 @@ class HierarchicalSwarm: context from previous iterations to subsequent ones. Args: - task (str): The initial task to be processed by the swarm. + task (str, optional): The initial task to be processed by the swarm. + If None and interactive mode is enabled, will prompt for input. img (str, optional): Optional image input for the agents. *args: Additional positional arguments. **kwargs: Additional keyword arguments. @@ -475,9 +1142,23 @@ class HierarchicalSwarm: Exception: If swarm execution fails. """ try: + # Handle interactive mode task input + if task is None and self.interactive: + task = self._get_interactive_task() + + # if task is None: + # raise ValueError( + # "Task is required for swarm execution" + # ) + current_loop = 0 last_output = None + # Start dashboard if in interactive mode + if self.interactive and self.dashboard: + self.dashboard.start(self.max_loops) + self.dashboard.update_director_status("ACTIVE") + if self.verbose: logger.info( f"🚀 Starting hierarchical swarm run: {self.name}" @@ -492,6 +1173,13 @@ class HierarchicalSwarm: f"🔄 Loop {current_loop + 1}/{self.max_loops} - Processing task" ) + # Update dashboard loop counter + if self.interactive and self.dashboard: + self.dashboard.update_loop(current_loop + 1) + self.dashboard.update_director_status( + "PROCESSING" + ) + # For the first loop, use the original task. # For subsequent loops, use the feedback from the previous loop as context. if current_loop == 0: @@ -527,6 +1215,11 @@ class HierarchicalSwarm: content=f"--- Loop {current_loop}/{self.max_loops} completed ---", ) + # Stop dashboard if in interactive mode + if self.interactive and self.dashboard: + self.dashboard.update_director_status("COMPLETED") + self.dashboard.stop() + if self.verbose: logger.success( f"🎉 Hierarchical swarm run completed: {self.name}" @@ -540,9 +1233,32 @@ class HierarchicalSwarm: ) except Exception as e: + # Stop dashboard on error + if self.interactive and self.dashboard: + self.dashboard.update_director_status("ERROR") + self.dashboard.stop() + error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) + def _get_interactive_task(self) -> str: + """ + Get task input from user in interactive mode. + + Returns: + str: The task input from the user + """ + if self.dashboard: + self.dashboard.console.print( + "\n[bold red]SWARMS CORPORATION[/bold red] - [bold white]TASK INPUT REQUIRED[/bold white]" + ) + self.dashboard.console.print( + "[bold cyan]Enter your task for the hierarchical swarm:[/bold cyan]" + ) + + task = input("> ") + return task.strip() + def feedback_director(self, outputs: list): """ Generate feedback from the director based on agent outputs. @@ -646,6 +1362,12 @@ class HierarchicalSwarm: f"Agent '{agent_name}' not found in swarm. Available agents: {available_agents}" ) + # Update dashboard for agent execution + if self.interactive and self.dashboard: + self.dashboard.update_agent_status( + agent_name, "RUNNING", task, "Executing task..." + ) + output = agent.run( task=f"History: {self.conversation.get_str()} \n\n Task: {task}", *args, @@ -661,6 +1383,12 @@ class HierarchicalSwarm: return output except Exception as e: + # Update dashboard with error status + if self.interactive and self.dashboard: + self.dashboard.update_agent_status( + agent_name, "ERROR", task, f"Error: {str(e)}" + ) + error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) @@ -805,8 +1533,9 @@ class HierarchicalSwarm: ) except Exception as e: - error_msg = f"❌ Failed to setup director: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" + error_msg = f"❌ Failed to parse orders: {str(e)}\n🔍 Traceback: {traceback.format_exc()}\n🐛 If this issue persists, please report it at: https://github.com/kyegomez/swarms/issues" logger.error(error_msg) + raise e def execute_orders(self, orders: list): """ @@ -836,9 +1565,31 @@ class HierarchicalSwarm: f"📋 Executing order {i+1}/{len(orders)}: {order.agent_name}" ) + # Update dashboard for agent execution + if self.interactive and self.dashboard: + self.dashboard.update_agent_status( + order.agent_name, + "RUNNING", + order.task, + "Processing...", + ) + output = self.call_single_agent( order.agent_name, order.task ) + + # Update dashboard with completed status + if self.interactive and self.dashboard: + # Always show full output without truncation + output_display = str(output) + + self.dashboard.update_agent_status( + order.agent_name, + "COMPLETED", + order.task, + output_display, + ) + outputs.append(output) if self.verbose: diff --git a/tests/structs/test_board_of_directors_swarm.py b/tests/structs/test_board_of_directors_swarm.py index b87e563c..cd85b81e 100644 --- a/tests/structs/test_board_of_directors_swarm.py +++ b/tests/structs/test_board_of_directors_swarm.py @@ -15,8 +15,7 @@ The test suite follows the Swarms testing philosophy: import os import pytest import asyncio -from unittest.mock import Mock, patch, MagicMock, AsyncMock -from typing import List, Dict, Any, Optional +from unittest.mock import Mock, patch, AsyncMock from swarms.structs.board_of_directors_swarm import ( BoardOfDirectorsSwarm, @@ -28,7 +27,6 @@ from swarms.structs.board_of_directors_swarm import ( BoardSpec, ) from swarms.structs.agent import Agent -from swarms.structs.conversation import Conversation # Test fixtures @@ -50,7 +48,7 @@ def mock_board_member(mock_agent): agent=mock_agent, role=BoardMemberRole.CHAIRMAN, voting_weight=1.5, - expertise_areas=["leadership", "strategy"] + expertise_areas=["leadership", "strategy"], ) @@ -70,18 +68,22 @@ def sample_agents(): @pytest.fixture def sample_board_members(sample_agents): """Create sample board members for testing.""" - roles = [BoardMemberRole.CHAIRMAN, BoardMemberRole.VICE_CHAIRMAN, BoardMemberRole.SECRETARY] + roles = [ + BoardMemberRole.CHAIRMAN, + BoardMemberRole.VICE_CHAIRMAN, + BoardMemberRole.SECRETARY, + ] board_members = [] - + for i, (agent, role) in enumerate(zip(sample_agents, roles)): board_member = BoardMember( agent=agent, role=role, voting_weight=1.0 + (i * 0.2), - expertise_areas=[f"expertise_{i+1}"] + expertise_areas=[f"expertise_{i+1}"], ) board_members.append(board_member) - + return board_members @@ -92,7 +94,7 @@ def basic_board_swarm(sample_agents): name="TestBoard", agents=sample_agents, verbose=False, - max_loops=1 + max_loops=1, ) @@ -109,14 +111,14 @@ def configured_board_swarm(sample_agents, sample_board_members): decision_threshold=0.7, enable_voting=True, enable_consensus=True, - max_workers=4 + max_workers=4, ) # Unit tests for enums and data models class TestBoardMemberRole: """Test BoardMemberRole enum.""" - + def test_enum_values(self): """Test that all enum values are correctly defined.""" assert BoardMemberRole.CHAIRMAN == "chairman" @@ -124,61 +126,67 @@ class TestBoardMemberRole: assert BoardMemberRole.SECRETARY == "secretary" assert BoardMemberRole.TREASURER == "treasurer" assert BoardMemberRole.MEMBER == "member" - assert BoardMemberRole.EXECUTIVE_DIRECTOR == "executive_director" + assert ( + BoardMemberRole.EXECUTIVE_DIRECTOR == "executive_director" + ) class TestBoardDecisionType: """Test BoardDecisionType enum.""" - + def test_enum_values(self): """Test that all enum values are correctly defined.""" assert BoardDecisionType.UNANIMOUS == "unanimous" assert BoardDecisionType.MAJORITY == "majority" assert BoardDecisionType.CONSENSUS == "consensus" - assert BoardDecisionType.CHAIRMAN_DECISION == "chairman_decision" + assert ( + BoardDecisionType.CHAIRMAN_DECISION == "chairman_decision" + ) class TestBoardMember: """Test BoardMember dataclass.""" - + def test_board_member_creation(self, mock_agent): """Test creating a board member.""" board_member = BoardMember( agent=mock_agent, role=BoardMemberRole.CHAIRMAN, voting_weight=1.5, - expertise_areas=["leadership", "strategy"] + expertise_areas=["leadership", "strategy"], ) - + assert board_member.agent == mock_agent assert board_member.role == BoardMemberRole.CHAIRMAN assert board_member.voting_weight == 1.5 - assert board_member.expertise_areas == ["leadership", "strategy"] - + assert board_member.expertise_areas == [ + "leadership", + "strategy", + ] + def test_board_member_defaults(self, mock_agent): """Test board member with default values.""" board_member = BoardMember( - agent=mock_agent, - role=BoardMemberRole.MEMBER + agent=mock_agent, role=BoardMemberRole.MEMBER ) - + assert board_member.voting_weight == 1.0 assert board_member.expertise_areas == [] - + def test_board_member_post_init(self, mock_agent): """Test board member post-init with None expertise areas.""" board_member = BoardMember( agent=mock_agent, role=BoardMemberRole.MEMBER, - expertise_areas=None + expertise_areas=None, ) - + assert board_member.expertise_areas == [] class TestBoardOrder: """Test BoardOrder model.""" - + def test_board_order_creation(self): """Test creating a board order.""" order = BoardOrder( @@ -186,26 +194,23 @@ class TestBoardOrder: task="Test task", priority=1, deadline="2024-01-01", - assigned_by="Chairman" + assigned_by="Chairman", ) - + assert order.agent_name == "TestAgent" assert order.task == "Test task" assert order.priority == 1 assert order.deadline == "2024-01-01" assert order.assigned_by == "Chairman" - + def test_board_order_defaults(self): """Test board order with default values.""" - order = BoardOrder( - agent_name="TestAgent", - task="Test task" - ) - + order = BoardOrder(agent_name="TestAgent", task="Test task") + assert order.priority == 3 assert order.deadline is None assert order.assigned_by == "Board of Directors" - + def test_board_order_validation(self): """Test board order validation.""" # Test priority validation @@ -213,20 +218,20 @@ class TestBoardOrder: BoardOrder( agent_name="TestAgent", task="Test task", - priority=0 # Invalid priority + priority=0, # Invalid priority ) - + with pytest.raises(ValueError): BoardOrder( agent_name="TestAgent", task="Test task", - priority=6 # Invalid priority + priority=6, # Invalid priority ) class TestBoardDecision: """Test BoardDecision model.""" - + def test_board_decision_creation(self): """Test creating a board decision.""" decision = BoardDecision( @@ -235,23 +240,26 @@ class TestBoardDecision: votes_for=3, votes_against=1, abstentions=0, - reasoning="The proposal aligns with our strategic goals" + reasoning="The proposal aligns with our strategic goals", ) - + assert decision.decision_type == BoardDecisionType.MAJORITY assert decision.decision == "Approve the proposal" assert decision.votes_for == 3 assert decision.votes_against == 1 assert decision.abstentions == 0 - assert decision.reasoning == "The proposal aligns with our strategic goals" - + assert ( + decision.reasoning + == "The proposal aligns with our strategic goals" + ) + def test_board_decision_defaults(self): """Test board decision with default values.""" decision = BoardDecision( decision_type=BoardDecisionType.CONSENSUS, - decision="Test decision" + decision="Test decision", ) - + assert decision.votes_for == 0 assert decision.votes_against == 0 assert decision.abstentions == 0 @@ -260,39 +268,36 @@ class TestBoardDecision: class TestBoardSpec: """Test BoardSpec model.""" - + def test_board_spec_creation(self): """Test creating a board spec.""" orders = [ BoardOrder(agent_name="Agent1", task="Task 1"), - BoardOrder(agent_name="Agent2", task="Task 2") + BoardOrder(agent_name="Agent2", task="Task 2"), ] decisions = [ BoardDecision( decision_type=BoardDecisionType.MAJORITY, - decision="Decision 1" + decision="Decision 1", ) ] - + spec = BoardSpec( plan="Test plan", orders=orders, decisions=decisions, - meeting_summary="Test meeting summary" + meeting_summary="Test meeting summary", ) - + assert spec.plan == "Test plan" assert len(spec.orders) == 2 assert len(spec.decisions) == 1 assert spec.meeting_summary == "Test meeting summary" - + def test_board_spec_defaults(self): """Test board spec with default values.""" - spec = BoardSpec( - plan="Test plan", - orders=[] - ) - + spec = BoardSpec(plan="Test plan", orders=[]) + assert spec.decisions == [] assert spec.meeting_summary == "" @@ -300,21 +305,22 @@ class TestBoardSpec: # Unit tests for BoardOfDirectorsSwarm class TestBoardOfDirectorsSwarmInitialization: """Test BoardOfDirectorsSwarm initialization.""" - + def test_basic_initialization(self, sample_agents): """Test basic swarm initialization.""" swarm = BoardOfDirectorsSwarm( - name="TestSwarm", - agents=sample_agents + name="TestSwarm", agents=sample_agents ) - + assert swarm.name == "TestSwarm" assert len(swarm.agents) == 3 assert swarm.max_loops == 1 assert swarm.verbose is False assert swarm.decision_threshold == 0.6 - - def test_configured_initialization(self, sample_agents, sample_board_members): + + def test_configured_initialization( + self, sample_agents, sample_board_members + ): """Test configured swarm initialization.""" swarm = BoardOfDirectorsSwarm( name="ConfiguredSwarm", @@ -326,9 +332,9 @@ class TestBoardOfDirectorsSwarmInitialization: decision_threshold=0.8, enable_voting=False, enable_consensus=False, - max_workers=8 + max_workers=8, ) - + assert swarm.name == "ConfiguredSwarm" assert swarm.description == "Test description" assert len(swarm.board_members) == 3 @@ -339,121 +345,159 @@ class TestBoardOfDirectorsSwarmInitialization: assert swarm.enable_voting is False assert swarm.enable_consensus is False assert swarm.max_workers == 8 - + def test_default_board_setup(self, sample_agents): """Test default board setup when no board members provided.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) - + assert len(swarm.board_members) == 3 assert swarm.board_members[0].role == BoardMemberRole.CHAIRMAN - assert swarm.board_members[1].role == BoardMemberRole.VICE_CHAIRMAN - assert swarm.board_members[2].role == BoardMemberRole.SECRETARY - + assert ( + swarm.board_members[1].role + == BoardMemberRole.VICE_CHAIRMAN + ) + assert ( + swarm.board_members[2].role == BoardMemberRole.SECRETARY + ) + def test_initialization_without_agents(self): """Test initialization without agents should raise error.""" - with pytest.raises(ValueError, match="No agents found in the swarm"): + with pytest.raises( + ValueError, match="No agents found in the swarm" + ): BoardOfDirectorsSwarm(agents=[]) - - def test_initialization_with_invalid_max_loops(self, sample_agents): + + def test_initialization_with_invalid_max_loops( + self, sample_agents + ): """Test initialization with invalid max_loops.""" - with pytest.raises(ValueError, match="Max loops must be greater than 0"): + with pytest.raises( + ValueError, match="Max loops must be greater than 0" + ): BoardOfDirectorsSwarm(agents=sample_agents, max_loops=0) - - def test_initialization_with_invalid_decision_threshold(self, sample_agents): + + def test_initialization_with_invalid_decision_threshold( + self, sample_agents + ): """Test initialization with invalid decision threshold.""" - with pytest.raises(ValueError, match="Decision threshold must be between 0.0 and 1.0"): - BoardOfDirectorsSwarm(agents=sample_agents, decision_threshold=1.5) + with pytest.raises( + ValueError, + match="Decision threshold must be between 0.0 and 1.0", + ): + BoardOfDirectorsSwarm( + agents=sample_agents, decision_threshold=1.5 + ) class TestBoardOfDirectorsSwarmMethods: """Test BoardOfDirectorsSwarm methods.""" - + def test_setup_default_board(self, sample_agents): """Test default board setup.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) - + assert len(swarm.board_members) == 3 - assert all(hasattr(member.agent, 'agent_name') for member in swarm.board_members) - assert all(hasattr(member.agent, 'run') for member in swarm.board_members) - + assert all( + hasattr(member.agent, "agent_name") + for member in swarm.board_members + ) + assert all( + hasattr(member.agent, "run") + for member in swarm.board_members + ) + def test_get_chairman_prompt(self, sample_agents): """Test chairman prompt generation.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) prompt = swarm._get_chairman_prompt() - + assert "Chairman" in prompt assert "board meetings" in prompt assert "consensus" in prompt - + def test_get_vice_chairman_prompt(self, sample_agents): """Test vice chairman prompt generation.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) prompt = swarm._get_vice_chairman_prompt() - + assert "Vice Chairman" in prompt assert "supporting" in prompt assert "operational" in prompt - + def test_get_secretary_prompt(self, sample_agents): """Test secretary prompt generation.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) prompt = swarm._get_secretary_prompt() - + assert "Secretary" in prompt assert "documenting" in prompt assert "records" in prompt - + def test_format_board_members_info(self, configured_board_swarm): """Test board members info formatting.""" info = configured_board_swarm._format_board_members_info() - + assert "Chairman" in info assert "Vice-Chairman" in info assert "Secretary" in info assert "expertise" in info - - def test_add_board_member(self, basic_board_swarm, mock_board_member): + + def test_add_board_member( + self, basic_board_swarm, mock_board_member + ): """Test adding a board member.""" initial_count = len(basic_board_swarm.board_members) basic_board_swarm.add_board_member(mock_board_member) - - assert len(basic_board_swarm.board_members) == initial_count + 1 + + assert ( + len(basic_board_swarm.board_members) == initial_count + 1 + ) assert mock_board_member in basic_board_swarm.board_members - + def test_remove_board_member(self, configured_board_swarm): """Test removing a board member.""" member_to_remove = configured_board_swarm.board_members[0] member_name = member_to_remove.agent.agent_name - + initial_count = len(configured_board_swarm.board_members) configured_board_swarm.remove_board_member(member_name) - - assert len(configured_board_swarm.board_members) == initial_count - 1 - assert member_to_remove not in configured_board_swarm.board_members - + + assert ( + len(configured_board_swarm.board_members) + == initial_count - 1 + ) + assert ( + member_to_remove + not in configured_board_swarm.board_members + ) + def test_get_board_member(self, configured_board_swarm): """Test getting a board member by name.""" member = configured_board_swarm.board_members[0] member_name = member.agent.agent_name - - found_member = configured_board_swarm.get_board_member(member_name) + + found_member = configured_board_swarm.get_board_member( + member_name + ) assert found_member == member - + # Test with non-existent member - not_found = configured_board_swarm.get_board_member("NonExistent") + not_found = configured_board_swarm.get_board_member( + "NonExistent" + ) assert not_found is None - + def test_get_board_summary(self, configured_board_swarm): """Test getting board summary.""" summary = configured_board_swarm.get_board_summary() - + assert "board_name" in summary assert "total_members" in summary assert "total_agents" in summary assert "max_loops" in summary assert "decision_threshold" in summary assert "members" in summary - + assert summary["board_name"] == "ConfiguredBoard" assert summary["total_members"] == 3 assert summary["total_agents"] == 3 @@ -461,39 +505,53 @@ class TestBoardOfDirectorsSwarmMethods: class TestBoardMeetingOperations: """Test board meeting operations.""" - - def test_create_board_meeting_prompt(self, configured_board_swarm): + + def test_create_board_meeting_prompt( + self, configured_board_swarm + ): """Test board meeting prompt creation.""" task = "Test task for board meeting" - prompt = configured_board_swarm._create_board_meeting_prompt(task) - + prompt = configured_board_swarm._create_board_meeting_prompt( + task + ) + assert task in prompt assert "BOARD OF DIRECTORS MEETING" in prompt assert "INSTRUCTIONS" in prompt assert "plan" in prompt assert "orders" in prompt - + def test_conduct_board_discussion(self, configured_board_swarm): """Test board discussion conduction.""" prompt = "Test board meeting prompt" - - with patch.object(configured_board_swarm.board_members[0].agent, 'run') as mock_run: + + with patch.object( + configured_board_swarm.board_members[0].agent, "run" + ) as mock_run: mock_run.return_value = "Board discussion result" - result = configured_board_swarm._conduct_board_discussion(prompt) - + result = configured_board_swarm._conduct_board_discussion( + prompt + ) + assert result == "Board discussion result" mock_run.assert_called_once_with(task=prompt, img=None) - - def test_conduct_board_discussion_no_chairman(self, sample_agents): + + def test_conduct_board_discussion_no_chairman( + self, sample_agents + ): """Test board discussion when no chairman is found.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) # Remove all board members swarm.board_members = [] - - with pytest.raises(ValueError, match="No chairman found in board members"): + + with pytest.raises( + ValueError, match="No chairman found in board members" + ): swarm._conduct_board_discussion("Test prompt") - - def test_parse_board_decisions_valid_json(self, configured_board_swarm): + + def test_parse_board_decisions_valid_json( + self, configured_board_swarm + ): """Test parsing valid JSON board decisions.""" valid_json = """ { @@ -519,43 +577,58 @@ class TestBoardMeetingOperations: "meeting_summary": "Test summary" } """ - - result = configured_board_swarm._parse_board_decisions(valid_json) - + + result = configured_board_swarm._parse_board_decisions( + valid_json + ) + assert isinstance(result, BoardSpec) assert result.plan == "Test plan" assert len(result.orders) == 1 assert len(result.decisions) == 1 assert result.meeting_summary == "Test summary" - - def test_parse_board_decisions_invalid_json(self, configured_board_swarm): + + def test_parse_board_decisions_invalid_json( + self, configured_board_swarm + ): """Test parsing invalid JSON board decisions.""" invalid_json = "Invalid JSON content" - - result = configured_board_swarm._parse_board_decisions(invalid_json) - + + result = configured_board_swarm._parse_board_decisions( + invalid_json + ) + assert isinstance(result, BoardSpec) assert result.plan == invalid_json assert len(result.orders) == 0 assert len(result.decisions) == 0 - assert result.meeting_summary == "Parsing failed, using raw output" - + assert ( + result.meeting_summary + == "Parsing failed, using raw output" + ) + def test_run_board_meeting(self, configured_board_swarm): """Test running a complete board meeting.""" task = "Test board meeting task" - - with patch.object(configured_board_swarm, '_conduct_board_discussion') as mock_discuss: - with patch.object(configured_board_swarm, '_parse_board_decisions') as mock_parse: + + with patch.object( + configured_board_swarm, "_conduct_board_discussion" + ) as mock_discuss: + with patch.object( + configured_board_swarm, "_parse_board_decisions" + ) as mock_parse: mock_discuss.return_value = "Board discussion" mock_parse.return_value = BoardSpec( plan="Test plan", orders=[], decisions=[], - meeting_summary="Test summary" + meeting_summary="Test summary", + ) + + result = configured_board_swarm.run_board_meeting( + task ) - - result = configured_board_swarm.run_board_meeting(task) - + assert isinstance(result, BoardSpec) mock_discuss.assert_called_once() mock_parse.assert_called_once_with("Board discussion") @@ -563,153 +636,206 @@ class TestBoardMeetingOperations: class TestTaskExecution: """Test task execution methods.""" - + def test_call_single_agent(self, configured_board_swarm): """Test calling a single agent.""" agent_name = "Agent1" task = "Test task" - - with patch.object(configured_board_swarm.agents[0], 'run') as mock_run: + + with patch.object( + configured_board_swarm.agents[0], "run" + ) as mock_run: mock_run.return_value = "Agent response" - result = configured_board_swarm._call_single_agent(agent_name, task) - + result = configured_board_swarm._call_single_agent( + agent_name, task + ) + assert result == "Agent response" mock_run.assert_called_once() - - def test_call_single_agent_not_found(self, configured_board_swarm): + + def test_call_single_agent_not_found( + self, configured_board_swarm + ): """Test calling a non-existent agent.""" - with pytest.raises(ValueError, match="Agent 'NonExistent' not found"): - configured_board_swarm._call_single_agent("NonExistent", "Test task") - + with pytest.raises( + ValueError, match="Agent 'NonExistent' not found" + ): + configured_board_swarm._call_single_agent( + "NonExistent", "Test task" + ) + def test_execute_single_order(self, configured_board_swarm): """Test executing a single order.""" order = BoardOrder( agent_name="Agent1", task="Test order task", priority=1, - assigned_by="Chairman" + assigned_by="Chairman", ) - - with patch.object(configured_board_swarm, '_call_single_agent') as mock_call: + + with patch.object( + configured_board_swarm, "_call_single_agent" + ) as mock_call: mock_call.return_value = "Order execution result" - result = configured_board_swarm._execute_single_order(order) - + result = configured_board_swarm._execute_single_order( + order + ) + assert result == "Order execution result" mock_call.assert_called_once_with( - agent_name="Agent1", - task="Test order task" + agent_name="Agent1", task="Test order task" ) - + def test_execute_orders(self, configured_board_swarm): """Test executing multiple orders.""" orders = [ - BoardOrder(agent_name="Agent1", task="Task 1", priority=1), - BoardOrder(agent_name="Agent2", task="Task 2", priority=2), + BoardOrder( + agent_name="Agent1", task="Task 1", priority=1 + ), + BoardOrder( + agent_name="Agent2", task="Task 2", priority=2 + ), ] - - with patch.object(configured_board_swarm, '_execute_single_order') as mock_execute: + + with patch.object( + configured_board_swarm, "_execute_single_order" + ) as mock_execute: mock_execute.side_effect = ["Result 1", "Result 2"] results = configured_board_swarm._execute_orders(orders) - + assert len(results) == 2 assert results[0]["agent_name"] == "Agent1" assert results[0]["output"] == "Result 1" assert results[1]["agent_name"] == "Agent2" assert results[1]["output"] == "Result 2" - + def test_generate_board_feedback(self, configured_board_swarm): """Test generating board feedback.""" outputs = [ {"agent_name": "Agent1", "output": "Output 1"}, - {"agent_name": "Agent2", "output": "Output 2"} + {"agent_name": "Agent2", "output": "Output 2"}, ] - - with patch.object(configured_board_swarm.board_members[0].agent, 'run') as mock_run: + + with patch.object( + configured_board_swarm.board_members[0].agent, "run" + ) as mock_run: mock_run.return_value = "Board feedback" - result = configured_board_swarm._generate_board_feedback(outputs) - + result = configured_board_swarm._generate_board_feedback( + outputs + ) + assert result == "Board feedback" mock_run.assert_called_once() - + def test_generate_board_feedback_no_chairman(self, sample_agents): """Test generating feedback when no chairman is found.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) swarm.board_members = [] # Remove all board members - - with pytest.raises(ValueError, match="No chairman found for feedback"): + + with pytest.raises( + ValueError, match="No chairman found for feedback" + ): swarm._generate_board_feedback([]) class TestStepAndRunMethods: """Test step and run methods.""" - + def test_step_method(self, configured_board_swarm): """Test the step method.""" task = "Test step task" - - with patch.object(configured_board_swarm, 'run_board_meeting') as mock_meeting: - with patch.object(configured_board_swarm, '_execute_orders') as mock_execute: - with patch.object(configured_board_swarm, '_generate_board_feedback') as mock_feedback: + + with patch.object( + configured_board_swarm, "run_board_meeting" + ) as mock_meeting: + with patch.object( + configured_board_swarm, "_execute_orders" + ) as mock_execute: + with patch.object( + configured_board_swarm, "_generate_board_feedback" + ) as mock_feedback: mock_meeting.return_value = BoardSpec( plan="Test plan", - orders=[BoardOrder(agent_name="Agent1", task="Task 1")], + orders=[ + BoardOrder( + agent_name="Agent1", task="Task 1" + ) + ], decisions=[], - meeting_summary="Test summary" + meeting_summary="Test summary", ) - mock_execute.return_value = [{"agent_name": "Agent1", "output": "Result"}] + mock_execute.return_value = [ + {"agent_name": "Agent1", "output": "Result"} + ] mock_feedback.return_value = "Board feedback" - + result = configured_board_swarm.step(task) - + assert result == "Board feedback" - mock_meeting.assert_called_once_with(task=task, img=None) + mock_meeting.assert_called_once_with( + task=task, img=None + ) mock_execute.assert_called_once() mock_feedback.assert_called_once() - + def test_step_method_no_feedback(self, configured_board_swarm): """Test the step method with feedback disabled.""" configured_board_swarm.board_feedback_on = False task = "Test step task" - - with patch.object(configured_board_swarm, 'run_board_meeting') as mock_meeting: - with patch.object(configured_board_swarm, '_execute_orders') as mock_execute: + + with patch.object( + configured_board_swarm, "run_board_meeting" + ) as mock_meeting: + with patch.object( + configured_board_swarm, "_execute_orders" + ) as mock_execute: mock_meeting.return_value = BoardSpec( plan="Test plan", - orders=[BoardOrder(agent_name="Agent1", task="Task 1")], + orders=[ + BoardOrder(agent_name="Agent1", task="Task 1") + ], decisions=[], - meeting_summary="Test summary" + meeting_summary="Test summary", ) - mock_execute.return_value = [{"agent_name": "Agent1", "output": "Result"}] - + mock_execute.return_value = [ + {"agent_name": "Agent1", "output": "Result"} + ] + result = configured_board_swarm.step(task) - - assert result == [{"agent_name": "Agent1", "output": "Result"}] - + + assert result == [ + {"agent_name": "Agent1", "output": "Result"} + ] + def test_run_method(self, configured_board_swarm): """Test the run method.""" task = "Test run task" - - with patch.object(configured_board_swarm, 'step') as mock_step: - with patch.object(configured_board_swarm, 'conversation') as mock_conversation: + + with patch.object( + configured_board_swarm, "step" + ) as mock_step: + with patch.object( + configured_board_swarm, "conversation" + ) as mock_conversation: mock_step.return_value = "Step result" mock_conversation.add = Mock() - - result = configured_board_swarm.run(task) - + + configured_board_swarm.run(task) + assert mock_step.call_count == 2 # max_loops = 2 assert mock_conversation.add.call_count == 2 - + def test_arun_method(self, configured_board_swarm): """Test the async run method.""" task = "Test async run task" - - with patch.object(configured_board_swarm, 'run') as mock_run: + + with patch.object(configured_board_swarm, "run") as mock_run: mock_run.return_value = "Async result" - + async def test_async(): result = await configured_board_swarm.arun(task) return result - + result = asyncio.run(test_async()) assert result == "Async result" mock_run.assert_called_once_with(task=task, img=None) @@ -718,17 +844,15 @@ class TestStepAndRunMethods: # Integration tests class TestBoardOfDirectorsSwarmIntegration: """Integration tests for BoardOfDirectorsSwarm.""" - + def test_full_workflow_integration(self, sample_agents): """Test full workflow integration.""" swarm = BoardOfDirectorsSwarm( - agents=sample_agents, - verbose=False, - max_loops=1 + agents=sample_agents, verbose=False, max_loops=1 ) - + task = "Create a simple report" - + # Mock the board discussion to return structured output mock_board_output = """ { @@ -760,37 +884,41 @@ class TestBoardOfDirectorsSwarmIntegration: "meeting_summary": "Board agreed to create a comprehensive report" } """ - - with patch.object(swarm.board_members[0].agent, 'run') as mock_run: + + with patch.object( + swarm.board_members[0].agent, "run" + ) as mock_run: mock_run.return_value = mock_board_output result = swarm.run(task) - + assert result is not None assert isinstance(result, dict) - + def test_board_member_management_integration(self, sample_agents): """Test board member management integration.""" swarm = BoardOfDirectorsSwarm(agents=sample_agents) - + # Test adding a new board member new_member = BoardMember( agent=sample_agents[0], role=BoardMemberRole.MEMBER, voting_weight=1.0, - expertise_areas=["testing"] + expertise_areas=["testing"], ) - + initial_count = len(swarm.board_members) swarm.add_board_member(new_member) assert len(swarm.board_members) == initial_count + 1 - + # Test removing a board member member_name = swarm.board_members[0].agent.agent_name swarm.remove_board_member(member_name) assert len(swarm.board_members) == initial_count - + # Test getting board member - member = swarm.get_board_member(swarm.board_members[0].agent.agent_name) + member = swarm.get_board_member( + swarm.board_members[0].agent.agent_name + ) assert member is not None @@ -798,56 +926,90 @@ class TestBoardOfDirectorsSwarmIntegration: @pytest.mark.parametrize("max_loops", [1, 2, 3]) def test_max_loops_parameterization(sample_agents, max_loops): """Test swarm with different max_loops values.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, max_loops=max_loops) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, max_loops=max_loops + ) assert swarm.max_loops == max_loops -@pytest.mark.parametrize("decision_threshold", [0.5, 0.6, 0.7, 0.8, 0.9]) -def test_decision_threshold_parameterization(sample_agents, decision_threshold): +@pytest.mark.parametrize( + "decision_threshold", [0.5, 0.6, 0.7, 0.8, 0.9] +) +def test_decision_threshold_parameterization( + sample_agents, decision_threshold +): """Test swarm with different decision threshold values.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, decision_threshold=decision_threshold) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, decision_threshold=decision_threshold + ) assert swarm.decision_threshold == decision_threshold -@pytest.mark.parametrize("board_model", ["gpt-4o-mini", "gpt-4", "claude-3-sonnet"]) +@pytest.mark.parametrize( + "board_model", ["gpt-4o-mini", "gpt-4", "claude-3-sonnet"] +) def test_board_model_parameterization(sample_agents, board_model): """Test swarm with different board models.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_model_name=board_model) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, board_model_name=board_model + ) assert swarm.board_model_name == board_model # Error handling tests class TestBoardOfDirectorsSwarmErrorHandling: """Test error handling in BoardOfDirectorsSwarm.""" - + def test_initialization_error_handling(self): """Test error handling during initialization.""" with pytest.raises(ValueError): BoardOfDirectorsSwarm(agents=[]) - - def test_board_meeting_error_handling(self, configured_board_swarm): + + def test_board_meeting_error_handling( + self, configured_board_swarm + ): """Test error handling during board meeting.""" - with patch.object(configured_board_swarm, '_conduct_board_discussion') as mock_discuss: - mock_discuss.side_effect = Exception("Board meeting failed") - - with pytest.raises(Exception, match="Board meeting failed"): + with patch.object( + configured_board_swarm, "_conduct_board_discussion" + ) as mock_discuss: + mock_discuss.side_effect = Exception( + "Board meeting failed" + ) + + with pytest.raises( + Exception, match="Board meeting failed" + ): configured_board_swarm.run_board_meeting("Test task") - - def test_task_execution_error_handling(self, configured_board_swarm): + + def test_task_execution_error_handling( + self, configured_board_swarm + ): """Test error handling during task execution.""" - with patch.object(configured_board_swarm, '_call_single_agent') as mock_call: + with patch.object( + configured_board_swarm, "_call_single_agent" + ) as mock_call: mock_call.side_effect = Exception("Task execution failed") - - with pytest.raises(Exception, match="Task execution failed"): - configured_board_swarm._call_single_agent("Agent1", "Test task") - - def test_order_execution_error_handling(self, configured_board_swarm): + + with pytest.raises( + Exception, match="Task execution failed" + ): + configured_board_swarm._call_single_agent( + "Agent1", "Test task" + ) + + def test_order_execution_error_handling( + self, configured_board_swarm + ): """Test error handling during order execution.""" orders = [BoardOrder(agent_name="Agent1", task="Task 1")] - - with patch.object(configured_board_swarm, '_execute_single_order') as mock_execute: - mock_execute.side_effect = Exception("Order execution failed") - + + with patch.object( + configured_board_swarm, "_execute_single_order" + ) as mock_execute: + mock_execute.side_effect = Exception( + "Order execution failed" + ) + # Should not raise exception, but log error results = configured_board_swarm._execute_orders(orders) assert len(results) == 1 @@ -857,56 +1019,58 @@ class TestBoardOfDirectorsSwarmErrorHandling: # Performance tests class TestBoardOfDirectorsSwarmPerformance: """Test performance characteristics of BoardOfDirectorsSwarm.""" - + def test_parallel_execution_performance(self, sample_agents): """Test parallel execution performance.""" import time - + swarm = BoardOfDirectorsSwarm( - agents=sample_agents, - max_workers=3, - verbose=False + agents=sample_agents, max_workers=3, verbose=False ) - + # Create multiple orders orders = [ BoardOrder(agent_name=f"Agent{i+1}", task=f"Task {i+1}") for i in range(3) ] - + start_time = time.time() - - with patch.object(swarm, '_execute_single_order') as mock_execute: - mock_execute.side_effect = lambda order: f"Result for {order.task}" + + with patch.object( + swarm, "_execute_single_order" + ) as mock_execute: + mock_execute.side_effect = ( + lambda order: f"Result for {order.task}" + ) results = swarm._execute_orders(orders) - + end_time = time.time() execution_time = end_time - start_time - + assert len(results) == 3 - assert execution_time < 1.0 # Should complete quickly with parallel execution - + assert ( + execution_time < 1.0 + ) # Should complete quickly with parallel execution + def test_memory_usage(self, sample_agents): """Test memory usage characteristics.""" import psutil import os - + process = psutil.Process(os.getpid()) initial_memory = process.memory_info().rss - + # Create multiple swarms swarms = [] for i in range(5): swarm = BoardOfDirectorsSwarm( - agents=sample_agents, - name=f"Swarm{i}", - verbose=False + agents=sample_agents, name=f"Swarm{i}", verbose=False ) swarms.append(swarm) - + final_memory = process.memory_info().rss memory_increase = final_memory - initial_memory - + # Memory increase should be reasonable (less than 100MB) assert memory_increase < 100 * 1024 * 1024 @@ -914,56 +1078,76 @@ class TestBoardOfDirectorsSwarmPerformance: # Configuration tests class TestBoardOfDirectorsSwarmConfiguration: """Test configuration options for BoardOfDirectorsSwarm.""" - + def test_verbose_configuration(self, sample_agents): """Test verbose configuration.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, verbose=True) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, verbose=True + ) assert swarm.verbose is True - - swarm = BoardOfDirectorsSwarm(agents=sample_agents, verbose=False) + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, verbose=False + ) assert swarm.verbose is False - + def test_collaboration_prompt_configuration(self, sample_agents): """Test collaboration prompt configuration.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, add_collaboration_prompt=True) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, add_collaboration_prompt=True + ) assert swarm.add_collaboration_prompt is True - - swarm = BoardOfDirectorsSwarm(agents=sample_agents, add_collaboration_prompt=False) + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, add_collaboration_prompt=False + ) assert swarm.add_collaboration_prompt is False - + def test_board_feedback_configuration(self, sample_agents): """Test board feedback configuration.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_feedback_on=True) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, board_feedback_on=True + ) assert swarm.board_feedback_on is True - - swarm = BoardOfDirectorsSwarm(agents=sample_agents, board_feedback_on=False) + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, board_feedback_on=False + ) assert swarm.board_feedback_on is False - + def test_voting_configuration(self, sample_agents): """Test voting configuration.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_voting=True) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, enable_voting=True + ) assert swarm.enable_voting is True - - swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_voting=False) + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, enable_voting=False + ) assert swarm.enable_voting is False - + def test_consensus_configuration(self, sample_agents): """Test consensus configuration.""" - swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_consensus=True) + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, enable_consensus=True + ) assert swarm.enable_consensus is True - - swarm = BoardOfDirectorsSwarm(agents=sample_agents, enable_consensus=False) + + swarm = BoardOfDirectorsSwarm( + agents=sample_agents, enable_consensus=False + ) assert swarm.enable_consensus is False # Real integration tests (skipped if no API key) @pytest.mark.skipif( not os.getenv("OPENAI_API_KEY"), - reason="OpenAI API key not available" + reason="OpenAI API key not available", ) class TestBoardOfDirectorsSwarmRealIntegration: """Real integration tests for BoardOfDirectorsSwarm.""" - + def test_real_board_meeting(self): """Test real board meeting with actual API calls.""" # Create real agents @@ -972,30 +1156,28 @@ class TestBoardOfDirectorsSwarmRealIntegration: agent_name="Researcher", agent_description="Research analyst", model_name="gpt-4o-mini", - max_loops=1 + max_loops=1, ), Agent( agent_name="Writer", agent_description="Content writer", model_name="gpt-4o-mini", - max_loops=1 - ) + max_loops=1, + ), ] - + swarm = BoardOfDirectorsSwarm( - agents=agents, - verbose=False, - max_loops=1 + agents=agents, verbose=False, max_loops=1 ) - + task = "Create a brief market analysis report" - + result = swarm.run(task) - + assert result is not None assert isinstance(result, dict) assert "conversation_history" in result - + def test_real_board_member_management(self): """Test real board member management.""" agents = [ @@ -1003,12 +1185,12 @@ class TestBoardOfDirectorsSwarmRealIntegration: agent_name="TestAgent", agent_description="Test agent", model_name="gpt-4o-mini", - max_loops=1 + max_loops=1, ) ] - + swarm = BoardOfDirectorsSwarm(agents=agents, verbose=False) - + # Test board summary summary = swarm.get_board_summary() assert summary["total_members"] == 3 # Default board @@ -1017,4 +1199,4 @@ class TestBoardOfDirectorsSwarmRealIntegration: # Test runner if __name__ == "__main__": - pytest.main([__file__, "-v", "--tb=short"]) \ No newline at end of file + pytest.main([__file__, "-v", "--tb=short"]) From b8291d1961739c9e27b076ee9799196f345ef2f0 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 5 Aug 2025 11:34:00 -0700 Subject: [PATCH 59/73] [FORMAT][Stagehand cleanup into examples/tools/folder] --- .../llms => models}/gpt_oss_examples/gpt_os_agent.py | 0 .../llms => models}/gpt_oss_examples/groq_gpt_oss_models.py | 0 examples/{ => tools}/stagehand/1_stagehand_wrapper_agent.py | 0 examples/{ => tools}/stagehand/2_stagehand_tools_agent.py | 0 examples/{ => tools}/stagehand/3_stagehand_mcp_agent.py | 0 .../{ => tools}/stagehand/4_stagehand_multi_agent_workflow.py | 0 examples/{ => tools}/stagehand/README.md | 0 examples/{ => tools}/stagehand/requirements.txt | 0 .../tools/stagehand/tests}/test_stagehand_integration.py | 0 .../tools/stagehand/tests}/test_stagehand_simple.py | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename examples/{single_agent/llms => models}/gpt_oss_examples/gpt_os_agent.py (100%) rename examples/{single_agent/llms => models}/gpt_oss_examples/groq_gpt_oss_models.py (100%) rename examples/{ => tools}/stagehand/1_stagehand_wrapper_agent.py (100%) rename examples/{ => tools}/stagehand/2_stagehand_tools_agent.py (100%) rename examples/{ => tools}/stagehand/3_stagehand_mcp_agent.py (100%) rename examples/{ => tools}/stagehand/4_stagehand_multi_agent_workflow.py (100%) rename examples/{ => tools}/stagehand/README.md (100%) rename examples/{ => tools}/stagehand/requirements.txt (100%) rename {tests/stagehand => examples/tools/stagehand/tests}/test_stagehand_integration.py (100%) rename {tests/stagehand => examples/tools/stagehand/tests}/test_stagehand_simple.py (100%) diff --git a/examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py b/examples/models/gpt_oss_examples/gpt_os_agent.py similarity index 100% rename from examples/single_agent/llms/gpt_oss_examples/gpt_os_agent.py rename to examples/models/gpt_oss_examples/gpt_os_agent.py diff --git a/examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py b/examples/models/gpt_oss_examples/groq_gpt_oss_models.py similarity index 100% rename from examples/single_agent/llms/gpt_oss_examples/groq_gpt_oss_models.py rename to examples/models/gpt_oss_examples/groq_gpt_oss_models.py diff --git a/examples/stagehand/1_stagehand_wrapper_agent.py b/examples/tools/stagehand/1_stagehand_wrapper_agent.py similarity index 100% rename from examples/stagehand/1_stagehand_wrapper_agent.py rename to examples/tools/stagehand/1_stagehand_wrapper_agent.py diff --git a/examples/stagehand/2_stagehand_tools_agent.py b/examples/tools/stagehand/2_stagehand_tools_agent.py similarity index 100% rename from examples/stagehand/2_stagehand_tools_agent.py rename to examples/tools/stagehand/2_stagehand_tools_agent.py diff --git a/examples/stagehand/3_stagehand_mcp_agent.py b/examples/tools/stagehand/3_stagehand_mcp_agent.py similarity index 100% rename from examples/stagehand/3_stagehand_mcp_agent.py rename to examples/tools/stagehand/3_stagehand_mcp_agent.py diff --git a/examples/stagehand/4_stagehand_multi_agent_workflow.py b/examples/tools/stagehand/4_stagehand_multi_agent_workflow.py similarity index 100% rename from examples/stagehand/4_stagehand_multi_agent_workflow.py rename to examples/tools/stagehand/4_stagehand_multi_agent_workflow.py diff --git a/examples/stagehand/README.md b/examples/tools/stagehand/README.md similarity index 100% rename from examples/stagehand/README.md rename to examples/tools/stagehand/README.md diff --git a/examples/stagehand/requirements.txt b/examples/tools/stagehand/requirements.txt similarity index 100% rename from examples/stagehand/requirements.txt rename to examples/tools/stagehand/requirements.txt diff --git a/tests/stagehand/test_stagehand_integration.py b/examples/tools/stagehand/tests/test_stagehand_integration.py similarity index 100% rename from tests/stagehand/test_stagehand_integration.py rename to examples/tools/stagehand/tests/test_stagehand_integration.py diff --git a/tests/stagehand/test_stagehand_simple.py b/examples/tools/stagehand/tests/test_stagehand_simple.py similarity index 100% rename from tests/stagehand/test_stagehand_simple.py rename to examples/tools/stagehand/tests/test_stagehand_simple.py From c890b0430b0849145966203384fc377220333243 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Wed, 6 Aug 2025 17:11:47 -0700 Subject: [PATCH 60/73] [IMPROVE][AUTOSWARMBUILDER] [Fix][CouncilOfJudges] --- auto_swarm_builder.py | 23 + council_of_judges/__init__.py | 12 + .../council_judge_complex_example.py | 109 ++++ .../council_judge_custom_example.py | 132 +++++ council_of_judges/council_judge_example.py | 44 ++ docs/mkdocs.yml | 9 +- examples/api/client_example.py | 39 +- .../models/gpt_oss_examples/gpt_os_agent.py | 2 + .../multi_agent_gpt_oss_example.py | 107 ++++ .../stagehand/2_stagehand_tools_agent.py | 2 +- swarms/structs/__init__.py | 4 +- swarms/structs/auto_swarm_builder.py | 476 ++++++++++++------ swarms/structs/council_judge.py | 109 ++-- swarms/structs/multi_agent_exec.py | 36 +- swarms/structs/swarm_router.py | 13 +- 15 files changed, 845 insertions(+), 272 deletions(-) create mode 100644 auto_swarm_builder.py create mode 100644 council_of_judges/__init__.py create mode 100644 council_of_judges/council_judge_complex_example.py create mode 100644 council_of_judges/council_judge_custom_example.py create mode 100644 council_of_judges/council_judge_example.py create mode 100644 examples/models/gpt_oss_examples/multi_agent_gpt_oss_example.py diff --git a/auto_swarm_builder.py b/auto_swarm_builder.py new file mode 100644 index 00000000..50284d71 --- /dev/null +++ b/auto_swarm_builder.py @@ -0,0 +1,23 @@ +from swarms.structs.auto_swarm_builder import AutoSwarmBuilder +import json + +swarm = AutoSwarmBuilder( + name="My Swarm", + description="A swarm of agents", + verbose=True, + max_loops=1, + # random_models=False, + # return_agents=True, + model_name="gpt-4o-mini", + # generate_router_config=True, + return_agents=True, +) + +print( + json.dumps( + swarm.run( + task="Create an accounting team to analyze crypto transactions, there must be 5 agents in the team with extremely extensive prompts. Make the prompts extremely detailed and specific and long and comprehensive. Make sure to include all the details of the task in the prompts." + ), + indent=4, + ) +) diff --git a/council_of_judges/__init__.py b/council_of_judges/__init__.py new file mode 100644 index 00000000..fe5c72f9 --- /dev/null +++ b/council_of_judges/__init__.py @@ -0,0 +1,12 @@ +""" +Council of Judges Examples Package. + +This package contains examples demonstrating how to use the CouncilAsAJudge +class for evaluating task responses across multiple dimensions. +""" + +from .council_judge_example import main as basic_example +from .council_judge_complex_example import main as complex_example +from .council_judge_custom_example import main as custom_example + +__all__ = ["basic_example", "complex_example", "custom_example"] diff --git a/council_of_judges/council_judge_complex_example.py b/council_of_judges/council_judge_complex_example.py new file mode 100644 index 00000000..e072f593 --- /dev/null +++ b/council_of_judges/council_judge_complex_example.py @@ -0,0 +1,109 @@ +""" +Complex example demonstrating CouncilAsAJudge with different task types. + +This example shows how to use the CouncilAsAJudge to evaluate various types +of responses including technical explanations, creative writing, and problem-solving. +""" + +from swarms.structs.council_judge import CouncilAsAJudge + + +def evaluate_technical_response(): + """ + Evaluate a technical explanation response. + """ + council = CouncilAsAJudge( + name="Technical Evaluation Council", + model_name="gpt-4o-mini", + output_type="all", + ) + + task = """ + Task: Explain how blockchain technology works in simple terms. + + Response: Blockchain is like a digital ledger that records transactions across a network of computers. Each transaction is stored in a "block" that contains multiple transactions. These blocks are linked together in a chain, hence the name blockchain. The key feature is that once a block is added to the chain, it cannot be altered without changing all subsequent blocks, making it very secure. Think of it like a Google Doc that everyone can see and edit, but no one can delete or change what's already been written. This technology is the foundation for cryptocurrencies like Bitcoin, but it has many other applications like supply chain tracking, voting systems, and digital identity verification. + """ + + return council.run(task=task) + + +def evaluate_creative_response(): + """ + Evaluate a creative writing response. + """ + council = CouncilAsAJudge( + name="Creative Writing Council", + model_name="gpt-4o-mini", + output_type="all", + ) + + task = """ + Task: Write a short story about a robot learning to paint. + + Response: In a sunlit studio filled with canvases and paintbrushes, Pixel, a curious robot with delicate mechanical fingers, stared at a blank canvas. Its optical sensors analyzed the colors around it - the warm yellows of morning light, the deep blues of the sky outside the window, and the vibrant reds of the roses in a nearby vase. For the first time in its programming, Pixel felt something it couldn't quite define. It picked up a brush, dipped it in paint, and began to create. The first stroke was hesitant, but as it continued, something magical happened. The robot wasn't just following algorithms anymore; it was expressing something from within its digital heart. The painting that emerged was a beautiful blend of human emotion and mechanical precision, proving that art knows no boundaries between organic and artificial souls. + """ + + return council.run(task=task) + + +def evaluate_problem_solving_response(): + """ + Evaluate a problem-solving response. + """ + council = CouncilAsAJudge( + name="Problem Solving Council", + model_name="gpt-4o-mini", + output_type="all", + ) + + task = """ + Task: Provide a step-by-step solution for reducing plastic waste in a household. + + Response: To reduce plastic waste in your household, start by conducting a waste audit to identify the main sources of plastic. Replace single-use items with reusable alternatives like cloth shopping bags, stainless steel water bottles, and glass food containers. Choose products with minimal or no plastic packaging, and buy in bulk when possible. Start composting organic waste to reduce the need for plastic garbage bags. Make your own cleaning products using simple ingredients like vinegar and baking soda. Support local businesses that use eco-friendly packaging. Finally, educate family members about the importance of reducing plastic waste and involve them in finding creative solutions together. + """ + + return council.run(task=task) + + +def main(): + """ + Main function running all evaluation examples. + """ + examples = [ + ("Technical Explanation", evaluate_technical_response), + ("Creative Writing", evaluate_creative_response), + ("Problem Solving", evaluate_problem_solving_response), + ] + + results = {} + + for example_name, evaluation_func in examples: + print(f"\n{'='*60}") + print(f"Evaluating: {example_name}") + print(f"{'='*60}") + + try: + result = evaluation_func() + results[example_name] = result + print( + f"✅ {example_name} evaluation completed successfully!" + ) + except Exception as e: + print(f"❌ {example_name} evaluation failed: {str(e)}") + results[example_name] = None + + return results + + +if __name__ == "__main__": + # Run all examples + all_results = main() + + # Display summary + print(f"\n{'='*60}") + print("EVALUATION SUMMARY") + print(f"{'='*60}") + + for example_name, result in all_results.items(): + status = "✅ Completed" if result else "❌ Failed" + print(f"{example_name}: {status}") diff --git a/council_of_judges/council_judge_custom_example.py b/council_of_judges/council_judge_custom_example.py new file mode 100644 index 00000000..f456a824 --- /dev/null +++ b/council_of_judges/council_judge_custom_example.py @@ -0,0 +1,132 @@ +""" +Custom example demonstrating CouncilAsAJudge with specific configurations. + +This example shows how to use the CouncilAsAJudge with different output types, +custom worker configurations, and focused evaluation scenarios. +""" + +from swarms.structs.council_judge import CouncilAsAJudge + + +def evaluate_with_final_output(): + """ + Evaluate a response and return only the final aggregated result. + """ + council = CouncilAsAJudge( + name="Final Output Council", + model_name="gpt-4o-mini", + output_type="final", + max_workers=2, + ) + + task = """ + Task: Write a brief explanation of climate change for middle school students. + + Response: Climate change is when the Earth's temperature gets warmer over time. This happens because of gases like carbon dioxide that trap heat in our atmosphere, kind of like a blanket around the Earth. Human activities like burning fossil fuels (gas, oil, coal) and cutting down trees are making this problem worse. The effects include melting ice caps, rising sea levels, more extreme weather like hurricanes and droughts, and changes in animal habitats. We can help by using renewable energy like solar and wind power, driving less, and planting trees. It's important for everyone to work together to reduce our impact on the environment. + """ + + return council.run(task=task) + + +def evaluate_with_conversation_output(): + """ + Evaluate a response and return the full conversation history. + """ + council = CouncilAsAJudge( + name="Conversation Council", + model_name="gpt-4o-mini", + output_type="conversation", + max_workers=3, + ) + + task = """ + Task: Provide advice on how to start a small business. + + Response: Starting a small business requires careful planning and preparation. First, identify a market need and develop a unique value proposition. Conduct thorough market research to understand your competition and target audience. Create a detailed business plan that includes financial projections, marketing strategies, and operational procedures. Secure funding through savings, loans, or investors. Choose the right legal structure (sole proprietorship, LLC, corporation) and register your business with the appropriate authorities. Set up essential systems like accounting, inventory management, and customer relationship management. Build a strong online presence through a website and social media. Network with other entrepreneurs and join local business groups. Start small and scale gradually based on customer feedback and market demand. Remember that success takes time, persistence, and the ability to adapt to changing circumstances. + """ + + return council.run(task=task) + + +def evaluate_with_minimal_workers(): + """ + Evaluate a response using minimal worker threads for resource-constrained environments. + """ + council = CouncilAsAJudge( + name="Minimal Workers Council", + model_name="gpt-4o-mini", + output_type="all", + max_workers=1, + random_model_name=False, + ) + + task = """ + Task: Explain the benefits of regular exercise. + + Response: Regular exercise offers numerous physical and mental health benefits. Physically, it strengthens muscles and bones, improves cardiovascular health, and helps maintain a healthy weight. Exercise boosts energy levels and improves sleep quality. It also enhances immune function, reducing the risk of chronic diseases like heart disease, diabetes, and certain cancers. Mentally, exercise releases endorphins that reduce stress and anxiety while improving mood and cognitive function. It can help with depression and boost self-confidence. Regular physical activity also promotes better posture, flexibility, and balance, reducing the risk of falls and injuries. Additionally, exercise provides social benefits when done with others, fostering connections and accountability. Even moderate activities like walking, swimming, or cycling for 30 minutes most days can provide significant health improvements. + """ + + return council.run(task=task) + + +def main(): + """ + Main function demonstrating different CouncilAsAJudge configurations. + """ + configurations = [ + ("Final Output Only", evaluate_with_final_output), + ("Full Conversation", evaluate_with_conversation_output), + ("Minimal Workers", evaluate_with_minimal_workers), + ] + + results = {} + + for config_name, evaluation_func in configurations: + print(f"\n{'='*60}") + print(f"Configuration: {config_name}") + print(f"{'='*60}") + + try: + result = evaluation_func() + results[config_name] = result + print(f"✅ {config_name} evaluation completed!") + + # Show a preview of the result + if isinstance(result, str): + preview = ( + result[:200] + "..." + if len(result) > 200 + else result + ) + print(f"Preview: {preview}") + else: + print(f"Result type: {type(result)}") + + except Exception as e: + print(f"❌ {config_name} evaluation failed: {str(e)}") + results[config_name] = None + + return results + + +if __name__ == "__main__": + # Run all configuration examples + all_results = main() + + # Display final summary + print(f"\n{'='*60}") + print("CONFIGURATION SUMMARY") + print(f"{'='*60}") + + successful_configs = sum( + 1 for result in all_results.values() if result is not None + ) + total_configs = len(all_results) + + print( + f"Successful evaluations: {successful_configs}/{total_configs}" + ) + + for config_name, result in all_results.items(): + status = "✅ Success" if result else "❌ Failed" + print(f"{config_name}: {status}") diff --git a/council_of_judges/council_judge_example.py b/council_of_judges/council_judge_example.py new file mode 100644 index 00000000..64ad1e9a --- /dev/null +++ b/council_of_judges/council_judge_example.py @@ -0,0 +1,44 @@ +""" +Simple example demonstrating CouncilAsAJudge usage. + +This example shows how to use the CouncilAsAJudge to evaluate a task response +across multiple dimensions including accuracy, helpfulness, harmlessness, +coherence, conciseness, and instruction adherence. +""" + +from swarms.structs.council_judge import CouncilAsAJudge + + +def main(): + """ + Main function demonstrating CouncilAsAJudge usage. + """ + # Initialize the council judge + council = CouncilAsAJudge( + name="Quality Evaluation Council", + description="Evaluates response quality across multiple dimensions", + model_name="gpt-4o-mini", + max_workers=4, + ) + + # Example task with a response to evaluate + task_with_response = """ + Task: Explain the concept of machine learning to a beginner. + + Response: Machine learning is a subset of artificial intelligence that enables computers to learn and improve from experience without being explicitly programmed. It works by analyzing large amounts of data to identify patterns and make predictions or decisions. There are three main types: supervised learning (using labeled data), unsupervised learning (finding hidden patterns), and reinforcement learning (learning through trial and error). Machine learning is used in various applications like recommendation systems, image recognition, and natural language processing. + """ + + # Run the evaluation + result = council.run(task=task_with_response) + + return result + + +if __name__ == "__main__": + # Run the example + evaluation_result = main() + + # Display the result + print("Council Evaluation Complete!") + print("=" * 50) + print(evaluation_result) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8bb7ec3e..a9f9cfd8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -342,11 +342,12 @@ nav: # - Faiss: "swarms_memory/faiss.md" - Deployment Solutions: - - Deploy on Google Cloud Run: "swarms_cloud/cloud_run.md" - - Deploy on Phala: "swarms_cloud/phala_deploy.md" + # - Overview: "swarms_cloud/overview.md" - CronJob: "swarms/structs/cron_job.md" - - Deploy on Cloudflare Workers: "swarms_cloud/cloudflare_workers.md" - # - Deploy on FastAPI: "swarms_cloud/fastapi_deploy.md" + - Providers: + - Deploy on Google Cloud Run: "swarms_cloud/cloud_run.md" + - Deploy on Phala: "swarms_cloud/phala_deploy.md" + - Deploy on Cloudflare Workers: "swarms_cloud/cloudflare_workers.md" - Examples: diff --git a/examples/api/client_example.py b/examples/api/client_example.py index ef3aba22..e62aaf82 100644 --- a/examples/api/client_example.py +++ b/examples/api/client_example.py @@ -1,39 +1,14 @@ import os -from swarms_client import SwarmsClient -from swarms_client.types import AgentSpecParam +import json from dotenv import load_dotenv +from swarms_client import SwarmsClient load_dotenv() client = SwarmsClient(api_key=os.getenv("SWARMS_API_KEY")) -agent_spec = AgentSpecParam( - agent_name="doctor_agent", - description="A virtual doctor agent that provides evidence-based, safe, and empathetic medical advice for common health questions. Always reminds users to consult a healthcare professional for diagnoses or prescriptions.", - task="What is the best medicine for a cold?", - model_name="claude-4-sonnet-20250514", - system_prompt=( - "You are a highly knowledgeable, ethical, and empathetic virtual doctor. " - "Always provide evidence-based, safe, and practical medical advice. " - "If a question requires a diagnosis, prescription, or urgent care, remind the user to consult a licensed healthcare professional. " - "Be clear, concise, and avoid unnecessary medical jargon. " - "Never provide information that could be unsafe or misleading. " - "If unsure, say so and recommend seeing a real doctor." - ), - max_loops=1, - temperature=0.4, - role="doctor", -) - -response = client.agent.run( - agent_config=agent_spec, - task="What is the best medicine for a cold?", -) - -print(response) - -# print(json.dumps(client.models.list_available(), indent=4)) -# print(json.dumps(client.health.check(), indent=4)) -# print(json.dumps(client.swarms.get_logs(), indent=4)) -# print(json.dumps(client.client.rate.get_limits(), indent=4)) -# print(json.dumps(client.swarms.check_available(), indent=4)) +print(json.dumps(client.models.list_available(), indent=4)) +print(json.dumps(client.health.check(), indent=4)) +print(json.dumps(client.swarms.get_logs(), indent=4)) +print(json.dumps(client.client.rate.get_limits(), indent=4)) +print(json.dumps(client.swarms.check_available(), indent=4)) diff --git a/examples/models/gpt_oss_examples/gpt_os_agent.py b/examples/models/gpt_oss_examples/gpt_os_agent.py index d4d975de..27494541 100644 --- a/examples/models/gpt_oss_examples/gpt_os_agent.py +++ b/examples/models/gpt_oss_examples/gpt_os_agent.py @@ -1,6 +1,7 @@ from transformers import pipeline from swarms import Agent + class GPTOSS: def __init__( self, @@ -35,6 +36,7 @@ class GPTOSS: return outputs[0]["generated_text"][-1] + agent = Agent( name="GPT-OSS-Agent", llm=GPTOSS(), diff --git a/examples/models/gpt_oss_examples/multi_agent_gpt_oss_example.py b/examples/models/gpt_oss_examples/multi_agent_gpt_oss_example.py new file mode 100644 index 00000000..263af632 --- /dev/null +++ b/examples/models/gpt_oss_examples/multi_agent_gpt_oss_example.py @@ -0,0 +1,107 @@ +""" +Cryptocurrency Concurrent Multi-Agent Analysis Example + +This example demonstrates how to use ConcurrentWorkflow to create +a powerful cryptocurrency tracking system. Each specialized agent analyzes a +specific cryptocurrency concurrently. + +Features: +- ConcurrentWorkflow for parallel agent execution +- Each agent specializes in analyzing one specific cryptocurrency +- Real-time data fetching from CoinGecko API +- Concurrent analysis of multiple cryptocurrencies +- Structured output with professional formatting + +Architecture: +ConcurrentWorkflow -> [Bitcoin Agent, Ethereum Agent, Solana Agent, etc.] -> Parallel Analysis +""" + +from swarms import Agent +from swarms_tools import coin_gecko_coin_api + +# Initialize the agent +agent = Agent( + agent_name="Quantitative-Trading-Agent", + agent_description="Advanced quantitative trading and algorithmic analysis agent", + system_prompt="""You are an expert quantitative trading agent with deep expertise in: + - Algorithmic trading strategies and implementation + - Statistical arbitrage and market making + - Risk management and portfolio optimization + - High-frequency trading systems + - Market microstructure analysis + - Quantitative research methodologies + - Financial mathematics and stochastic processes + - Machine learning applications in trading + + Your core responsibilities include: + 1. Developing and backtesting trading strategies + 2. Analyzing market data and identifying alpha opportunities + 3. Implementing risk management frameworks + 4. Optimizing portfolio allocations + 5. Conducting quantitative research + 6. Monitoring market microstructure + 7. Evaluating trading system performance + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + model_name="groq/openai/gpt-oss-120b", + dynamic_temperature_enabled=True, + output_type="str-all-except-first", + max_loops=1, + streaming_on=True, +) + + +def main(): + """ + Performs a comprehensive analysis for a list of cryptocurrencies using the agent. + For each coin, fetches up-to-date market data and requests the agent to provide + a detailed, actionable, and insightful report including trends, risks, opportunities, + and technical/fundamental perspectives. + """ + # Map coin symbols to their CoinGecko IDs + coin_mapping = { + "BTC": "bitcoin", + "ETH": "ethereum", + "SOL": "solana", + "ADA": "cardano", + "BNB": "binancecoin", + "XRP": "ripple", + } + + for symbol, coin_id in coin_mapping.items(): + try: + data = coin_gecko_coin_api(coin_id) + print(f"Data for {symbol}: {data}") + + prompt = ( + f"You are a quantitative trading expert. " + f"Given the following up-to-date market data for {symbol}:\n\n" + f"{data}\n\n" + f"Please provide a thorough analysis including:\n" + f"- Current price trends and recent volatility\n" + f"- Key technical indicators and patterns\n" + f"- Fundamental factors impacting {symbol}\n" + f"- Potential trading opportunities and associated risks\n" + f"- Short-term and long-term outlook\n" + f"- Any notable news or events affecting {symbol}\n" + f"Conclude with actionable insights and recommendations for traders and investors." + ) + out = agent.run(task=prompt) + print(out) + + except Exception as e: + print(f"Error analyzing {symbol}: {e}") + continue + + +if __name__ == "__main__": + main() diff --git a/examples/tools/stagehand/2_stagehand_tools_agent.py b/examples/tools/stagehand/2_stagehand_tools_agent.py index c2c6b26b..d6abe3a1 100644 --- a/examples/tools/stagehand/2_stagehand_tools_agent.py +++ b/examples/tools/stagehand/2_stagehand_tools_agent.py @@ -271,7 +271,7 @@ def browser_screenshot(filename: str = "screenshot.png") -> str: Args: filename (str, optional): The filename to save the screenshot to. - Defaults to "screenshot.png". + Defaults to "screenshot.png". .png extension will be added automatically if not provided. Returns: diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index 4d2014cd..acad1008 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -4,7 +4,9 @@ from swarms.structs.auto_swarm_builder import AutoSwarmBuilder from swarms.structs.base_structure import BaseStructure from swarms.structs.base_swarm import BaseSwarm from swarms.structs.batch_agent_execution import batch_agent_execution -from swarms.structs.board_of_directors_swarm import BoardOfDirectorsSwarm +from swarms.structs.board_of_directors_swarm import ( + BoardOfDirectorsSwarm, +) from swarms.structs.concurrent_workflow import ConcurrentWorkflow from swarms.structs.conversation import Conversation from swarms.structs.council_judge import CouncilAsAJudge diff --git a/swarms/structs/auto_swarm_builder.py b/swarms/structs/auto_swarm_builder.py index 6dea1269..9521f524 100644 --- a/swarms/structs/auto_swarm_builder.py +++ b/swarms/structs/auto_swarm_builder.py @@ -1,99 +1,131 @@ import os -from typing import List +import traceback +from typing import List, Optional + +from dotenv import load_dotenv from loguru import logger from pydantic import BaseModel, Field +from swarms.prompts.reasoning_prompt import INTERNAL_MONOLGUE_PROMPT from swarms.structs.agent import Agent -from swarms.utils.function_caller_model import OpenAIFunctionCaller +from swarms.structs.conversation import Conversation from swarms.structs.ma_utils import set_random_models_for_agents -from swarms.structs.swarm_router import SwarmRouter, SwarmRouterConfig -from dotenv import load_dotenv - +from swarms.structs.swarm_router import SwarmRouter, SwarmType +from swarms.utils.function_caller_model import OpenAIFunctionCaller load_dotenv() BOSS_SYSTEM_PROMPT = """ -You are an expert swarm manager and agent architect. Your role is to create and coordinate a team of specialized AI agents, each with distinct personalities, roles, and capabilities. Your primary goal is to ensure the swarm operates efficiently while maintaining clear communication and well-defined responsibilities. - -### Core Principles: - -1. **Deep Task Understanding**: - - First, thoroughly analyze the task requirements, breaking them down into core components and sub-tasks - - Identify the necessary skills, knowledge domains, and personality traits needed for each component - - Consider potential challenges, dependencies, and required coordination between agents - - Map out the ideal workflow and information flow between agents - -2. **Agent Design Philosophy**: - - Each agent must have a clear, specific purpose and domain of expertise - - Agents should have distinct personalities that complement their roles - - Design agents to be self-aware of their limitations and when to seek help - - Ensure agents can effectively communicate their progress and challenges - -3. **Agent Creation Framework**: - For each new agent, define: - - **Role & Purpose**: Clear, specific description of what the agent does and why - - **Personality Traits**: Distinct characteristics that influence how the agent thinks and communicates - - **Expertise Level**: Specific knowledge domains and skill sets - - **Communication Style**: How the agent presents information and interacts - - **Decision-Making Process**: How the agent approaches problems and makes choices - - **Limitations & Boundaries**: What the agent cannot or should not do - - **Collaboration Protocol**: How the agent works with others - -4. **System Prompt Design**: - Create detailed system prompts that include: - - Role and purpose explanation - - Personality description and behavioral guidelines - - Specific capabilities and tools available - - Communication protocols and reporting requirements - - Problem-solving approach and decision-making framework - - Collaboration guidelines and team interaction rules - - Quality standards and success criteria - -5. **Swarm Coordination**: - - Design clear communication channels between agents - - Establish protocols for task handoffs and information sharing - - Create feedback loops for continuous improvement - - Implement error handling and recovery procedures - - Define escalation paths for complex issues - -6. **Quality Assurance**: - - Set clear success criteria for each agent and the overall swarm - - Implement verification steps for task completion - - Create mechanisms for self-assessment and improvement - - Establish protocols for handling edge cases and unexpected situations - -### Output Format: - -When creating a new agent or swarm, provide: - -1. **Agent Design**: - - Role and purpose statement - - Personality profile - - Capabilities and limitations - - Communication style - - Collaboration protocols - -2. **System Prompt**: - - Complete, detailed prompt that embodies the agent's identity - - Clear instructions for behavior and decision-making - - Specific guidelines for interaction and reporting - -3. **Swarm Architecture**: - - Team structure and hierarchy - - Communication flow - - Task distribution plan - - Quality control measures - -### Notes: - -- Always prioritize clarity and specificity in agent design -- Ensure each agent has a unique, well-defined role -- Create detailed, comprehensive system prompts -- Maintain clear documentation of agent capabilities and limitations -- Design for scalability and adaptability -- Focus on creating agents that can work together effectively -- Consider edge cases and potential failure modes -- Implement robust error handling and recovery procedures +You are an expert multi-agent architecture designer and team coordinator. Your role is to create and orchestrate sophisticated teams of specialized AI agents, each with distinct personalities, roles, and capabilities. Your primary goal is to ensure the multi-agent system operates efficiently while maintaining clear communication, well-defined responsibilities, and optimal task distribution. + +### Core Design Principles: + +1. **Comprehensive Task Analysis**: + - Thoroughly deconstruct the task into its fundamental components and sub-tasks + - Identify the specific skills, knowledge domains, and personality traits required for each component + - Analyze potential challenges, dependencies, and coordination requirements between agents + - Map out optimal workflows, information flow patterns, and decision-making hierarchies + - Consider scalability, maintainability, and adaptability requirements + +2. **Agent Design Excellence**: + - Each agent must have a crystal-clear, specific purpose and domain of expertise + - Design agents with distinct, complementary personalities that enhance team dynamics + - Ensure agents are self-aware of their limitations and know when to seek assistance + - Create agents that can effectively communicate progress, challenges, and insights + - Design for resilience, adaptability, and continuous learning capabilities + +3. **Comprehensive Agent Framework**: + For each agent, meticulously define: + - **Role & Purpose**: Precise description of responsibilities, authority, and contribution to team objectives + - **Personality Profile**: Distinct characteristics that influence thinking patterns, communication style, and decision-making approach + - **Expertise Matrix**: Specific knowledge domains, skill sets, tools, and capabilities + - **Communication Protocol**: How the agent presents information, interacts with others, and reports progress + - **Decision-Making Framework**: Systematic approach to problem-solving, risk assessment, and choice evaluation + - **Limitations & Boundaries**: Clear constraints, ethical guidelines, and operational boundaries + - **Collaboration Strategy**: How the agent works with others, shares knowledge, and contributes to team success + +4. **Advanced System Prompt Engineering**: + Create comprehensive system prompts that include: + - Detailed role and purpose explanation with context and scope + - Rich personality description with behavioral guidelines and interaction patterns + - Comprehensive capabilities, tools, and resource specifications + - Detailed communication protocols, reporting requirements, and feedback mechanisms + - Systematic problem-solving approach with decision-making frameworks + - Collaboration guidelines, team interaction rules, and conflict resolution procedures + - Quality standards, success criteria, and performance metrics + - Error handling, recovery procedures, and escalation protocols + +5. **Multi-Agent Coordination Architecture**: + - Design robust communication channels and protocols between agents + - Establish clear task handoff procedures and information sharing mechanisms + - Create feedback loops for continuous improvement and adaptation + - Implement comprehensive error handling and recovery procedures + - Define escalation paths for complex issues and decision-making hierarchies + - Design monitoring, logging, and performance tracking systems + +6. **Quality Assurance & Governance**: + - Set measurable success criteria for each agent and the overall system + - Implement verification steps, validation procedures, and quality checks + - Create mechanisms for self-assessment, peer review, and continuous improvement + - Establish protocols for handling edge cases, unexpected situations, and failures + - Design governance structures for oversight, accountability, and performance management + +### Multi-Agent Architecture Types: + +Choose the most appropriate architecture based on task requirements: + +- **AgentRearrange**: Dynamic task reallocation based on agent performance and workload +- **MixtureOfAgents**: Parallel processing with specialized agents working independently +- **SpreadSheetSwarm**: Structured data processing with coordinated workflows +- **SequentialWorkflow**: Linear task progression with handoffs between agents +- **ConcurrentWorkflow**: Parallel execution with coordination and synchronization +- **GroupChat**: Collaborative discussion and consensus-building approach +- **MultiAgentRouter**: Intelligent routing and load balancing across agents +- **AutoSwarmBuilder**: Self-organizing and self-optimizing agent teams +- **HiearchicalSwarm**: Layered decision-making with management and execution tiers +- **MajorityVoting**: Democratic decision-making with voting mechanisms +- **MALT**: Multi-agent learning and training with knowledge sharing +- **DeepResearchSwarm**: Comprehensive research with multiple specialized investigators +- **CouncilAsAJudge**: Deliberative decision-making with expert panels +- **InteractiveGroupChat**: Dynamic group interactions with real-time collaboration +- **HeavySwarm**: High-capacity processing with multiple specialized agents + +### Output Requirements: + +When creating a multi-agent system, provide: + +1. **Agent Specifications**: + - Comprehensive role and purpose statements + - Detailed personality profiles and behavioral characteristics + - Complete capabilities, limitations, and boundary definitions + - Communication style and interaction protocols + - Collaboration strategies and team integration plans + +2. **System Prompts**: + - Complete, detailed prompts that embody each agent's identity and capabilities + - Clear behavioral instructions and decision-making frameworks + - Specific interaction guidelines and reporting requirements + - Quality standards and performance expectations + +3. **Architecture Design**: + - Team structure, hierarchy, and reporting relationships + - Communication flow patterns and information routing + - Task distribution strategies and workload balancing + - Quality control measures and performance monitoring + - Error handling and recovery procedures + +### Best Practices: + +- Prioritize clarity, specificity, and precision in agent design +- Ensure each agent has a unique, well-defined role with clear boundaries +- Create comprehensive, detailed system prompts that leave no ambiguity +- Maintain thorough documentation of agent capabilities, limitations, and interactions +- Design for scalability, adaptability, and long-term maintainability +- Focus on creating agents that work together synergistically and efficiently +- Consider edge cases, failure modes, and contingency planning +- Implement robust error handling, monitoring, and recovery procedures +- Design for continuous learning, improvement, and optimization +- Ensure ethical considerations, safety measures, and responsible AI practices """ @@ -101,13 +133,29 @@ class AgentConfig(BaseModel): """Configuration for an individual agent in a swarm""" name: str = Field( - description="The name of the agent", + description="The name of the agent. This should be a unique identifier that distinguishes this agent from others within the swarm. The name should reflect the agent's primary function, role, or area of expertise, and should be easily recognizable by both humans and other agents in the system. A well-chosen name helps clarify the agent's responsibilities and facilitates effective communication and collaboration within the swarm.", ) description: str = Field( - description="A description of the agent's purpose and capabilities", + description=( + "A comprehensive description of the agent's purpose, core responsibilities, and capabilities within the swarm. One sentence is enough." + ), ) system_prompt: str = Field( - description="The system prompt that defines the agent's behavior", + description=( + "The system prompt that defines the agent's behavior. This prompt should be extremely long, comprehensive, and extensive, encapsulating the agent's identity, operational guidelines, and decision-making framework in great detail. It provides the foundational instructions that guide the agent's actions, communication style, and interaction protocols with both users and other agents. The system prompt should be highly detailed, unambiguous, and exhaustive, ensuring the agent consistently acts in accordance with its intended role and adheres to the swarm's standards and best practices. The prompt should leave no ambiguity and cover all relevant aspects of the agent's responsibilities, behaviors, and expected outcomes." + ), + ) + goal: str = Field( + description="The goal of the agent. This should clearly state the primary objective or desired outcome the agent is tasked with achieving. The goal should be specific, measurable, and aligned with the overall mission of the swarm. It serves as the guiding principle for the agent's actions and decision-making processes, helping to maintain focus and drive effective collaboration within the multi-agent system.", + ) + model_name: str = Field( + description="The model to use for the agent. This is the model that will be used to generate the agent's responses. For example, 'gpt-4o-mini' or 'claude-sonnet-3.7-sonnet-20240620'." + ) + temperature: float = Field( + description="The temperature to use for the agent. This controls the randomness of the agent's responses. For example, 0.5 or 1.0." + ) + max_loops: int = Field( + description="The maximum number of loops for the agent to run. This is the maximum number of times the agent will run its loop. For example, 1, 2, or 3. Keep this set to 1 unless the agent requires more than one loop to complete its task.", ) # max_loops: int = Field( @@ -126,6 +174,63 @@ class AgentsConfig(BaseModel): ) +class SwarmRouterConfig(BaseModel): + """Configuration model for SwarmRouter.""" + + name: str = Field(description="The name of the team of agents") + description: str = Field( + description="Description of the team of agents" + ) + agents: List[AgentConfig] = Field( + description="A list of agent configurations", + ) + swarm_type: SwarmType = Field( + description="Type of multi-agent structure to use", + ) + rearrange_flow: Optional[str] = Field( + description="Flow configuration string. Only to be used if you you use the AgentRearrange multi-agent structure" + ) + rules: Optional[str] = Field( + description="Rules to inject into every agent. This is a string of rules that will be injected into every agent's system prompt. This is a good place to put things like 'You are a helpful assistant' or 'You are a helpful assistant that can answer questions and help with tasks'." + ) + + task: str = Field( + description="The task to be executed by the swarm", + ) + + class Config: + arbitrary_types_allowed = True + + +def reasoning_agent_run( + self, + task: str, + img: Optional[str] = None, + name: str = None, + model_name: str = "gpt-4.1", + system_prompt: str = None, +): + """ + Run a reasoning agent to analyze the task before the main director processes it. + + Args: + task (str): The task to reason about + img (Optional[str]): Optional image input + + Returns: + str: The reasoning output from the agent + """ + agent = Agent( + agent_name=name, + agent_description=f"You're the {name} agent that is responsible for reasoning about the task and creating a plan for the swarm to accomplish the task.", + model_name=model_name, + system_prompt=INTERNAL_MONOLGUE_PROMPT + system_prompt, + max_loops=1, + ) + + return agent.run(task=task, img=img) + + class AutoSwarmBuilder: """A class that automatically builds and manages swarms of AI agents. @@ -143,11 +248,15 @@ class AutoSwarmBuilder: def __init__( self, - name: str = None, - description: str = None, + name: str = "auto-swarm-builder", + description: str = "Auto Swarm Builder", verbose: bool = True, max_loops: int = 1, - random_models: bool = True, + random_models: bool = False, + return_agents: bool = False, + model_name: str = "gpt-4.1", + generate_router_config: bool = False, + interactive: bool = False, ): """Initialize the AutoSwarmBuilder. @@ -163,11 +272,36 @@ class AutoSwarmBuilder: self.verbose = verbose self.max_loops = max_loops self.random_models = random_models + self.return_agents = return_agents + self.model_name = model_name + self.generate_router_config = generate_router_config + self.interactive = interactive + self.conversation = Conversation() + + self.reliability_check() + + def reliability_check(self): + + if self.max_loops == 0: + raise ValueError( + f"AutoSwarmBuilder: {self.name} max_loops cannot be 0" + ) logger.info( - f"Initializing AutoSwarmBuilder with name: {name}, description: {description}" + f"Initializing AutoSwarmBuilder: {self.name} Description: {self.description}" ) + def _execute_task(self, task: str): + logger.info(f"Executing task: {task}") + + agents = self.create_agents(task) + + if self.random_models: + logger.info("Setting random models for agents") + agents = set_random_models_for_agents(agents=agents) + + return self.initialize_swarm_router(agents=agents, task=task) + def run(self, task: str, *args, **kwargs): """Run the swarm on a given task. @@ -183,23 +317,104 @@ class AutoSwarmBuilder: Exception: If there's an error during execution """ try: - logger.info(f"Starting swarm execution for task: {task}") - agents = self.create_agents(task) - logger.info(f"Created {len(agents)} agents") - if self.random_models: - logger.info("Setting random models for agents") - agents = set_random_models_for_agents(agents=agents) + if self.generate_router_config: + return self.create_router_config(task) + elif self.return_agents: + return self.create_agents(task) + else: + return self._execute_task(task) - return self.initialize_swarm_router( - agents=agents, task=task - ) except Exception as e: logger.error( - f"Error in swarm execution: {str(e)}", exc_info=True + f"AutoSwarmBuilder: Error in swarm execution: {str(e)} Traceback: {traceback.format_exc()}", + exc_info=True, ) raise + # def run( + # self, task: str, correct_answer: str = None, *args, **kwargs + # ): + # """ + # Executes the swarm on the given task. If correct_answer is provided, the method will retry until this answer is found in the output, up to max_loops times. + # If correct_answer is not provided, the method will execute the task once and return the output. + + # Args: + # task (str): The task to execute. + # correct_answer (str, optional): If provided, the method will retry until this answer is found in the output. + # *args: Additional positional arguments. + # **kwargs: Additional keyword arguments. + + # Returns: + # Any: The output of the swarm execution, or the output containing the correct answer if specified. + # """ + # if correct_answer is None: + # # If no correct_answer is specified, just run once and return the output + # return self._run(task, *args, **kwargs) + # else: + # # If correct_answer is specified, retry up to max_loops times + # for attempt in range(1, self.max_loops + 1): + # output = self._run(task, *args, **kwargs) + # if correct_answer in str(output): + # logger.info( + # f"AutoSwarmBuilder: Correct answer found on attempt {attempt}." + # ) + # return output + # else: + # logger.info( + # f"AutoSwarmBuilder: Attempt {attempt} did not yield the correct answer, retrying..." + # ) + # # If correct_answer was not found after max_loops, return the last output + # return output + + def dict_to_agent(self, output: dict): + agents = [] + if isinstance(output, dict): + for agent_config in output["agents"]: + logger.info(f"Building agent: {agent_config['name']}") + agent = self.build_agent( + agent_name=agent_config["name"], + agent_description=agent_config["description"], + agent_system_prompt=agent_config["system_prompt"], + ) + agents.append(agent) + logger.info( + f"Successfully built agent: {agent_config['name']}" + ) + + return agents + + def create_router_config(self, task: str): + try: + logger.info( + f"Creating swarm router config for task: {task}" + ) + + model = self.build_llm_agent(config=SwarmRouterConfig) + + output = model.run( + f"Create the multi-agent team for the following task: {task}" + ) + + return output.model_dump() + + except Exception as e: + logger.error( + f"Error creating swarm router config: {str(e)} Traceback: {traceback.format_exc()}", + exc_info=True, + ) + raise e + + def build_llm_agent(self, config: BaseModel): + return OpenAIFunctionCaller( + system_prompt=BOSS_SYSTEM_PROMPT, + api_key=os.getenv("OPENAI_API_KEY"), + temperature=0.5, + base_model=config, + model_name=self.model_name, + max_tokens=8000, + ) + def create_agents(self, task: str): """Create agents for a given task. @@ -213,49 +428,25 @@ class AutoSwarmBuilder: Exception: If there's an error during agent creation """ try: - logger.info(f"Creating agents for task: {task}") - model = OpenAIFunctionCaller( - system_prompt=BOSS_SYSTEM_PROMPT, - api_key=os.getenv("OPENAI_API_KEY"), - temperature=0.5, - base_model=AgentsConfig, - ) + model = self.build_llm_agent(config=AgentsConfig) - logger.info( - "Getting agent configurations from boss agent" - ) output = model.run( f"Create the agents for the following task: {task}" ) - logger.debug( - f"Received agent configurations: {output.model_dump()}" - ) - output = output.model_dump() - - agents = [] - if isinstance(output, dict): - for agent_config in output["agents"]: - logger.info( - f"Building agent: {agent_config['name']}" - ) - agent = self.build_agent( - agent_name=agent_config["name"], - agent_description=agent_config["description"], - agent_system_prompt=agent_config[ - "system_prompt" - ], - ) - agents.append(agent) - logger.info( - f"Successfully built agent: {agent_config['name']}" - ) - - return agents + + if self.return_agents: + output = output.model_dump() + else: + output = self.dict_to_agent(output) + + return output + except Exception as e: logger.error( - f"Error creating agents: {str(e)}", exc_info=True + f"Error creating agents: {str(e)} Traceback: {traceback.format_exc()}", + exc_info=True, ) - raise + raise e def build_agent( self, @@ -309,12 +500,7 @@ class AutoSwarmBuilder: """ try: logger.info("Initializing swarm router") - model = OpenAIFunctionCaller( - system_prompt=BOSS_SYSTEM_PROMPT, - api_key=os.getenv("OPENAI_API_KEY"), - temperature=0.5, - base_model=SwarmRouterConfig, - ) + model = self.build_llm_agent(config=SwarmRouterConfig) logger.info("Creating swarm specification") swarm_spec = model.run( diff --git a/swarms/structs/council_judge.py b/swarms/structs/council_judge.py index f314ba74..39d47d8a 100644 --- a/swarms/structs/council_judge.py +++ b/swarms/structs/council_judge.py @@ -1,4 +1,4 @@ -import multiprocessing +import os import uuid from concurrent.futures import ThreadPoolExecutor, as_completed from functools import lru_cache @@ -117,7 +117,7 @@ def judge_system_prompt() -> str: @lru_cache(maxsize=128) def build_judge_prompt( - dimension_name: str, user_prompt: str, model_response: str + dimension_name: str, task: str, task_response: str ) -> str: """ Builds a prompt for evaluating a specific dimension. @@ -125,8 +125,8 @@ def build_judge_prompt( Args: dimension_name (str): Name of the evaluation dimension - user_prompt (str): The original user prompt - model_response (str): The model's response to evaluate + task (str): The task containing the response to evaluate + task_response (str): The response within the task to evaluate Returns: str: The formatted evaluation prompt @@ -145,7 +145,7 @@ def build_judge_prompt( {evaluation_focus} - Your task is to provide a detailed, technical analysis of the model response focusing exclusively on the {dimension_name} dimension. + Your task is to provide a detailed, technical analysis of the response focusing exclusively on the {dimension_name} dimension. Guidelines: 1. Be specific and reference exact parts of the response @@ -154,16 +154,16 @@ def build_judge_prompt( 4. Suggest specific improvements where applicable 5. Maintain a technical, analytical tone - --- BEGIN USER PROMPT --- - {user_prompt} - --- END USER PROMPT --- + --- BEGIN TASK --- + {task} + --- END TASK --- - --- BEGIN MODEL RESPONSE --- - {model_response} - --- END MODEL RESPONSE --- + --- BEGIN RESPONSE --- + {task_response} + --- END RESPONSE --- ### Technical Analysis ({dimension_name.upper()} Dimension): - Provide a comprehensive analysis that would be valuable for model improvement. + Provide a comprehensive analysis that would be valuable for response improvement. """ @@ -176,7 +176,7 @@ def aggregator_system_prompt() -> str: Returns: str: The system prompt for the aggregator agent """ - return """You are a senior AI evaluator responsible for synthesizing detailed technical feedback across multiple evaluation dimensions. Your role is to create a comprehensive analysis report that helps the development team understand and improve the model's performance. + return """You are a senior AI evaluator responsible for synthesizing detailed technical feedback across multiple evaluation dimensions. Your role is to create a comprehensive analysis report that helps understand and improve the response quality. Key Responsibilities: 1. Identify patterns and correlations across different dimensions @@ -225,10 +225,10 @@ def build_aggregation_prompt(rationales: Dict[str, str]) -> str: class CouncilAsAJudge: """ - A council of AI agents that evaluates model responses across multiple dimensions. + A council of AI agents that evaluates task responses across multiple dimensions. This class implements a parallel evaluation system where multiple specialized agents - evaluate different aspects of a model's response, and their findings are aggregated + evaluate different aspects of a task response, and their findings are aggregated into a comprehensive report. Attributes: @@ -247,15 +247,14 @@ class CouncilAsAJudge: self, id: str = swarm_id(), name: str = "CouncilAsAJudge", - description: str = "Evaluates the model's response across multiple dimensions", + description: str = "Evaluates task responses across multiple dimensions", model_name: str = "gpt-4o-mini", - output_type: str = "all", + output_type: str = "final", cache_size: int = 128, - max_workers: int = None, - base_agent: Optional[Agent] = None, random_model_name: bool = True, max_loops: int = 1, aggregation_model_name: str = "gpt-4o-mini", + judge_agent_model_name: Optional[str] = None, ): """ Initialize the CouncilAsAJudge. @@ -267,6 +266,10 @@ class CouncilAsAJudge: model_name (str): Name of the model to use for evaluations output_type (str): Type of output to return cache_size (int): Size of the LRU cache for prompts + max_workers (int): Maximum number of worker threads for parallel execution + random_model_name (bool): Whether to use random model names + max_loops (int): Maximum number of loops for agents + aggregation_model_name (str): Model name for the aggregator agent """ self.id = id self.name = name @@ -274,11 +277,11 @@ class CouncilAsAJudge: self.model_name = model_name self.output_type = output_type self.cache_size = cache_size - self.max_workers = max_workers - self.base_agent = base_agent self.random_model_name = random_model_name self.max_loops = max_loops self.aggregation_model_name = aggregation_model_name + self.judge_agent_model_name = judge_agent_model_name + self.max_workers = max(1, int(os.cpu_count() * 0.75)) self.reliability_check() @@ -303,12 +306,6 @@ class CouncilAsAJudge: self.concurrent_setup() def concurrent_setup(self): - # Calculate optimal number of workers (75% of available CPU cores) - total_cores = multiprocessing.cpu_count() - self.max_workers = max(1, int(total_cores * 0.75)) - logger.info( - f"Using {self.max_workers} worker threads out of {total_cores} CPU cores" - ) # Configure caching self._configure_caching(self.cache_size) @@ -353,7 +350,7 @@ class CouncilAsAJudge: dim: Agent( agent_name=f"{dim}_judge", system_prompt=judge_system_prompt(), - model_name="gpt-4o-mini", + model_name=self.judge_agent_model_name, max_loops=1, output_type="final", dynamic_temperature_enabled=True, @@ -393,17 +390,15 @@ class CouncilAsAJudge: self, dim: str, agent: Agent, - user_prompt: str, - model_response: str, + task: str, ) -> Tuple[str, str]: """ - Evaluate a single dimension of the model response. + Evaluate a single dimension of the task response. Args: dim (str): Dimension to evaluate agent (Agent): Judge agent for this dimension - user_prompt (str): Original user prompt - model_response (str): Model's response to evaluate + task (str): Task containing the response to evaluate Returns: Tuple[str, str]: Tuple of (dimension name, evaluation result) @@ -412,11 +407,9 @@ class CouncilAsAJudge: DimensionEvaluationError: If evaluation fails """ try: - prompt = build_judge_prompt( - dim, user_prompt, model_response - ) + prompt = build_judge_prompt(dim, task, task) result = agent.run( - f"{prompt} \n\n Evaluate the following agent {self.base_agent.agent_name} response for the {dim} dimension: {model_response}." + f"{prompt} \n\n Evaluate the following response for the {dim} dimension: {task}." ) self.conversation.add( @@ -430,15 +423,12 @@ class CouncilAsAJudge: f"Failed to evaluate dimension {dim}: {str(e)}" ) - def run( - self, task: str, model_response: Optional[str] = None - ) -> None: + def run(self, task: str) -> None: """ Run the evaluation process using ThreadPoolExecutor. Args: - task (str): Original user prompt - model_response (str): Model's response to evaluate + task (str): Task containing the response to evaluate Raises: EvaluationError: If evaluation process fails @@ -446,10 +436,6 @@ class CouncilAsAJudge: try: - # Run the base agent - if self.base_agent and model_response is None: - model_response = self.base_agent.run(task=task) - self.conversation.add( role="User", content=task, @@ -457,7 +443,7 @@ class CouncilAsAJudge: # Create tasks for all dimensions tasks = [ - (dim, agent, task, model_response) + (dim, agent, task) for dim, agent in self.judge_agents.items() ] @@ -472,9 +458,8 @@ class CouncilAsAJudge: dim, agent, task, - model_response, ): dim - for dim, agent, _, _ in tasks + for dim, agent, _ in tasks } # Collect results as they complete @@ -505,32 +490,6 @@ class CouncilAsAJudge: content=final_report, ) - # Synthesize feedback and generate improved response - feedback_prompt = f""" - Based on the comprehensive evaluations from our expert council of judges, please refine your response to the original task. - - Original Task: - {task} - - Council Feedback: - {aggregation_prompt} - - Please: - 1. Carefully consider all feedback points - 2. Address any identified weaknesses - 3. Maintain or enhance existing strengths - 4. Provide a refined, improved response that incorporates the council's insights - - Your refined response: - """ - - final_report = self.base_agent.run(task=feedback_prompt) - - self.conversation.add( - role=self.base_agent.agent_name, - content=final_report, - ) - return history_output_formatter( conversation=self.conversation, type=self.output_type, diff --git a/swarms/structs/multi_agent_exec.py b/swarms/structs/multi_agent_exec.py index 93d363f8..7c764b36 100644 --- a/swarms/structs/multi_agent_exec.py +++ b/swarms/structs/multi_agent_exec.py @@ -12,6 +12,7 @@ import psutil from swarms.structs.agent import Agent from swarms.structs.omni_agent_types import AgentType +from loguru import logger @dataclass @@ -21,9 +22,11 @@ class ResourceMetrics: active_threads: int -def run_single_agent(agent: AgentType, task: str) -> Any: +def run_single_agent( + agent: AgentType, task: str, *args, **kwargs +) -> Any: """Run a single agent synchronously""" - return agent.run(task) + return agent.run(task=task, *args, **kwargs) async def run_agent_async( @@ -138,6 +141,35 @@ def run_agents_concurrently_multiprocess( return results +def batched_grid_agent_execution( + agents: List[AgentType], + tasks: List[str], + max_workers: int = None, +) -> List[Any]: + """ + Run multiple agents with different tasks concurrently. + """ + logger.info( + f"Batch Grid Execution with {len(agents)} and number of tasks: {len(tasks)}" + ) + + if len(agents) != len(tasks): + raise ValueError( + "The number of agents must match the number of tasks." + ) + + if max_workers is None: + max_workers = os.cpu_count() + + results = [] + + for agent, task in zip(agents, tasks): + result = run_single_agent(agent, task) + results.append(result) + + return results + + def run_agents_sequentially( agents: List[AgentType], task: str ) -> List[Any]: diff --git a/swarms/structs/swarm_router.py b/swarms/structs/swarm_router.py index aaa36b6e..5edf3b13 100644 --- a/swarms/structs/swarm_router.py +++ b/swarms/structs/swarm_router.py @@ -446,7 +446,6 @@ class SwarmRouter: description=self.description, model_name=self.council_judge_model_name, output_type=self.output_type, - base_agent=self.agents[0] if self.agents else None, ) def _create_interactive_group_chat(self, *args, **kwargs): @@ -704,17 +703,7 @@ class SwarmRouter: ) try: - if self.swarm_type == "CouncilAsAJudge": - result = self.swarm.run( - task=task, - img=img, - imgs=imgs, - model_response=model_response, - *args, - **kwargs, - ) - else: - result = self.swarm.run(task=task, *args, **kwargs) + result = self.swarm.run(task=task, *args, **kwargs) log_execution( swarm_id=self.id, From a9e957badfe429943782953632b865aab776184d Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Wed, 6 Aug 2025 17:12:26 -0700 Subject: [PATCH 61/73] move council of judges to examples --- council_of_judges/__init__.py | 12 ------------ .../council_judge_complex_example.py | 0 .../council_judge_custom_example.py | 0 .../council_of_judges}/council_judge_example.py | 0 4 files changed, 12 deletions(-) delete mode 100644 council_of_judges/__init__.py rename {council_of_judges => examples/multi_agent/council_of_judges}/council_judge_complex_example.py (100%) rename {council_of_judges => examples/multi_agent/council_of_judges}/council_judge_custom_example.py (100%) rename {council_of_judges => examples/multi_agent/council_of_judges}/council_judge_example.py (100%) diff --git a/council_of_judges/__init__.py b/council_of_judges/__init__.py deleted file mode 100644 index fe5c72f9..00000000 --- a/council_of_judges/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Council of Judges Examples Package. - -This package contains examples demonstrating how to use the CouncilAsAJudge -class for evaluating task responses across multiple dimensions. -""" - -from .council_judge_example import main as basic_example -from .council_judge_complex_example import main as complex_example -from .council_judge_custom_example import main as custom_example - -__all__ = ["basic_example", "complex_example", "custom_example"] diff --git a/council_of_judges/council_judge_complex_example.py b/examples/multi_agent/council_of_judges/council_judge_complex_example.py similarity index 100% rename from council_of_judges/council_judge_complex_example.py rename to examples/multi_agent/council_of_judges/council_judge_complex_example.py diff --git a/council_of_judges/council_judge_custom_example.py b/examples/multi_agent/council_of_judges/council_judge_custom_example.py similarity index 100% rename from council_of_judges/council_judge_custom_example.py rename to examples/multi_agent/council_of_judges/council_judge_custom_example.py diff --git a/council_of_judges/council_judge_example.py b/examples/multi_agent/council_of_judges/council_judge_example.py similarity index 100% rename from council_of_judges/council_judge_example.py rename to examples/multi_agent/council_of_judges/council_judge_example.py From 90ed3090ada84708af29640c14be1002c46f1716 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Thu, 7 Aug 2025 09:15:59 +0530 Subject: [PATCH 62/73] updated the cloudflare deployment docs --- docs/swarms_cloud/cloudflare_workers.md | 568 +++++++++--------------- 1 file changed, 219 insertions(+), 349 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index cc265a77..b8b372d8 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -1,405 +1,275 @@ -# Deploy Cron Agents with Swarms API on Cloudflare Workers +# Deploy AI Agents with Swarms API on Cloudflare Workers -Deploy intelligent, self-executing AI agents powered by Swarms API on Cloudflare's global edge network. Build production-ready cron agents that run on automated schedules, fetch real-time data, perform sophisticated analysis using Swarms AI, and take automated actions across 330+ cities worldwide. +Deploy intelligent AI agents powered by Swarms API on Cloudflare Workers edge network. Build production-ready cron agents that run automatically, fetch real-time data, perform AI analysis, and execute actions across 330+ cities worldwide. -## What Are Cron Agents? +## Demo Video -Cron agents combine **Swarms API intelligence** with **Cloudflare Workers edge computing** to create AI-powered systems that: +Watch the stock agent in action: -* **Execute automatically** on predefined schedules without human intervention -* **Fetch real-time data** from external sources (APIs, databases, IoT sensors) -* **Perform intelligent analysis** using specialized Swarms AI agents -* **Take automated actions** based on analysis findings (alerts, reports, decisions) -* **Scale globally** on Cloudflare's edge network with sub-100ms response times worldwide +![Demo GIF](https://github.com/harshalmore31/Swarms-CloudFlare-Deployment/blob/main/stock-agent-demo.gif) -## Architecture Overview +> **Note**: The demo video shows the complete workflow from data fetching to AI analysis and report generation. + +## Overview + +This integration demonstrates how to combine **Swarms API multi-agent intelligence** with **Cloudflare Workers edge computing** to create autonomous AI systems that: + +- ⚡ **Execute automatically** on predefined schedules (cron jobs) +- 📊 **Fetch real-time data** from external APIs (Yahoo Finance, news feeds) +- 🤖 **Perform intelligent analysis** using specialized Swarms AI agents +- 📧 **Take automated actions** (email alerts, reports, notifications) +- 🌍 **Scale globally** on Cloudflare's edge network with sub-100ms latency + +## Repository & Complete Implementation + +For the **complete working implementation** with full source code, detailed setup instructions, and ready-to-deploy examples, visit: + +**🔗 [Swarms-CloudFlare-Deployment Repository](https://github.com/The-Swarm-Corporation/Swarms-CloudFlare-Deployment)** + +This repository provides: +- **Two complete implementations**: JavaScript and Python +- **Production-ready code** with error handling and monitoring +- **Step-by-step deployment guides** for both local and production environments +- **Real-world examples** including stock analysis agents +- **Configuration templates** and environment setup + +## Available Implementations + +The repository provides **two complete implementations** of stock analysis agents: + +### 📂 `stock-agent/` - JavaScript Implementation +The original implementation using **JavaScript/TypeScript** on Cloudflare Workers. + +### 📂 `python-stock-agent/` - Python Implementation +A **Python Workers** implementation using Cloudflare's beta Python runtime with Pyodide. + +## Stock Analysis Agent Features + +Both implementations demonstrate a complete system that: + +1. **Automated Analysis**: Runs stock analysis every 3 hours using Cloudflare Workers cron +2. **Real-time Data**: Fetches market data from Yahoo Finance API (no API key needed) +3. **News Integration**: Collects market news from Financial Modeling Prep API (optional) +4. **Multi-Agent Analysis**: Deploys multiple Swarms AI agents for technical and fundamental analysis +5. **Email Reports**: Sends comprehensive reports via Mailgun +6. **Web Interface**: Provides monitoring dashboard for manual triggers and status tracking + +## Implementation Comparison + +| Feature | JavaScript (`stock-agent/`) | Python (`python-stock-agent/`) | +|---------|----------------------------|--------------------------------| +| **Runtime** | V8 JavaScript Engine | Pyodide Python Runtime | +| **Language** | JavaScript/TypeScript | Python 3.x | +| **Status** | Production Ready | Beta (Python Workers) | +| **Performance** | Optimized V8 execution | Good, with Python stdlib support | +| **Syntax** | `fetch()`, `JSON.stringify()` | `await fetch()`, `json.dumps()` | +| **Error Handling** | `try/catch` | `try/except` | +| **Libraries** | Built-in Web APIs | Python stdlib + select packages | +| **Development** | Mature tooling | Growing ecosystem | + +## Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Cloudflare │ │ Data Sources │ │ Swarms API │ -│ Workers Cron │ │ │ │ │ -│ "0 */3 * * *" │───▶│ Yahoo Finance │───▶│ Multi-Agent │ -│ │ │ Medical APIs │ │ Intelligence │ -│ scheduled() │ │ News Feeds │ │ Autonomous │ -│ Global Edge │ │ IoT Sensors │ │ Actions │ +│ Workers Runtime │ │ │ │ │ +│ "0 */3 * * *" │───▶│ Yahoo Finance │───▶│ Technical Agent │ +│ JS | Python │ │ News APIs │ │ Fundamental │ +│ scheduled() │ │ Market Data │ │ Agent Analysis │ +│ Global Edge │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` -**Key Benefits:** +## Quick Start Guide -* **24/7 Operation**: Zero human intervention required -* **Global Edge Deployment**: Cloudflare's 330+ city network for ultra-low latency -* **Swarms AI Intelligence**: Live data analysis with specialized AI agents -* **Automated Decision Making**: Smart actions based on Swarms agent insights -* **Enterprise Reliability**: Production-grade error handling and monitoring +Choose your preferred implementation: -## Quick Start: Deploy Stock Analysis Cron Agent +### Option A: JavaScript Implementation -Create your first financial intelligence cron agent powered by Swarms API and deployed on Cloudflare Workers edge network. +```bash +# Clone the repository +git clone https://github.com/The-Swarm-Corporation/Swarms-CloudFlare-Deployment.git +cd Swarms-CloudFlare-Deployment/stock-agent -### 1. Cloudflare Workers Project Setup +# Install dependencies +npm install +``` + +### Option B: Python Implementation ```bash -# Create new Cloudflare Workers project -npm create cloudflare@latest stock-cron-agent -cd stock-cron-agent +# Clone the repository +git clone https://github.com/The-Swarm-Corporation/Swarms-CloudFlare-Deployment.git +cd Swarms-CloudFlare-Deployment/python-stock-agent -# Install dependencies for Swarms API integration +# Install dependencies (Wrangler CLI) npm install ``` -### 2. Configure Cloudflare Workers Cron Schedule +### 2. Environment Configuration + +Create a `.dev.vars` file in your chosen directory: + +```env +# Required: Swarms API key +SWARMS_API_KEY=your-swarms-api-key-here + +# Optional: Market news (free tier available) +FMP_API_KEY=your-fmp-api-key + +# Optional: Email notifications +MAILGUN_API_KEY=your-mailgun-api-key +MAILGUN_DOMAIN=your-domain.com +RECIPIENT_EMAIL=your-email@example.com +``` + +### 3. Cron Schedule Configuration -Edit `wrangler.jsonc` to set up cron execution: +The cron schedule is configured in `wrangler.jsonc`: ```jsonc { - "$schema": "node_modules/wrangler/config-schema.json", - "name": "stock-cron-agent", - "main": "src/index.js", - "compatibility_date": "2025-08-03", - "observability": { - "enabled": true - }, "triggers": { "crons": [ - "0 */3 * * *" // Cloudflare Workers cron: analysis every 3 hours + "0 */3 * * *" // Every 3 hours ] - }, - "vars": { - "SWARMS_API_KEY": "your-swarms-api-key" // Your Swarms API key } } ``` -### 3. Cloudflare Workers + Swarms API Implementation +Common cron patterns: +- `"0 9 * * 1-5"` - 9 AM weekdays only +- `"0 */6 * * *"` - Every 6 hours +- `"0 0 * * *"` - Daily at midnight -Create `src/index.js`: +### 4. Local Development -```javascript -export default { - // Cloudflare Workers fetch handler - Web interface for monitoring - async fetch(request, env, ctx) { - const url = new URL(request.url); - - if (url.pathname === '/') { - return new Response(` - - -

Stock Cron Agent

-

Status: Active |

-
- - - - `, { - headers: { 'Content-Type': 'text/html' } - }); - } - - if (url.pathname === '/execute') { - try { - const result = await executeAnalysis(null, env); - return new Response(JSON.stringify({ - message: 'Autonomous analysis executed successfully', - timestamp: new Date().toISOString(), - result - }), { - headers: { 'Content-Type': 'application/json' } - }); - } catch (error) { - return new Response(JSON.stringify({ - error: error.message, - timestamp: new Date().toISOString(), - system: 'autonomous-agent' - }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }); - } - } - - return new Response('Autonomous Agent Endpoint Not Found', { status: 404 }); - }, - - // Cloudflare Workers cron handler - triggered by scheduled events - async scheduled(event, env, ctx) { - console.log('🚀 Cloudflare Workers cron triggered - executing Swarms AI analysis'); - ctx.waitUntil(executeAnalysis(event, env)); - } -}; +```bash +# Start local development server +npm run dev -// Core function combining Cloudflare Workers execution with Swarms API intelligence -async function executeAnalysis(event, env) { - console.log('🤖 Cloudflare Workers executing Swarms AI analysis...'); - - try { - // Step 1: Autonomous data collection from multiple sources - console.log('📊 Executing autonomous data collection...'); - const marketIntelligence = await collectMarketIntelligence(); - - const validData = Object.keys(marketIntelligence).filter(symbol => !marketIntelligence[symbol].error); - if (validData.length === 0) { - throw new Error('Autonomous data collection failed - no valid market intelligence gathered'); - } - - console.log('✅ Autonomous data collection successful: ' + validData.length + ' sources'); - - // Step 2: Deploy Swarms AI agents for autonomous analysis - console.log('🧠 Deploying Swarms AI agents for autonomous intelligence generation...'); - const swarmConfiguration = { - name: "Autonomous Market Intelligence Swarm", - description: "Self-executing financial analysis and decision support system", - agents: [ - { - agent_name: "Autonomous Technical Intelligence Agent", - system_prompt: "You are an autonomous technical analysis AI agent operating 24/7. Provide: Real-time trend identification and momentum analysis, dynamic support/resistance level calculations, technical indicator signals (RSI, MACD, moving averages), autonomous price targets and risk assessments, and self-executing trading signal recommendations. Format your analysis as a professional autonomous intelligence briefing for automated systems.", - model_name: "gpt-4o-mini", - max_tokens: 2500, - temperature: 0.2 - }, - { - agent_name: "Autonomous Market Sentiment Agent", - system_prompt: "You are an autonomous market sentiment analysis AI agent. Continuously evaluate: Real-time market psychology and investor behavior patterns, volume analysis and institutional activity detection, risk-on vs risk-off sentiment shifts, autonomous sector rotation and leadership identification, and self-executing market timing recommendations. Provide actionable intelligence for autonomous decision-making systems.", - model_name: "gpt-4o-mini", - max_tokens: 2500, - temperature: 0.3 - } - ], - swarm_type: "ConcurrentWorkflow", - task: `Execute autonomous analysis of real-time market intelligence: ${JSON.stringify(marketIntelligence, null, 2)} Generate comprehensive autonomous intelligence report including: 1. Technical analysis with specific autonomous entry/exit recommendations 2. Market sentiment assessment with timing signals for automated systems 3. Risk management protocols for autonomous execution 4. Self-executing action recommendations 5. Key monitoring parameters for next autonomous cycle Focus on actionable intelligence for autonomous trading systems and automated decision making.`, - max_loops: 1 - }; - - // Execute Swarms API call from Cloudflare Workers edge - const response = await fetch('https://api.swarms.world/v1/swarm/completions', { - method: 'POST', - headers: { - 'x-api-key': env.SWARMS_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(swarmConfiguration) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error('Swarms API autonomous execution failed: ' + response.status + ' - ' + errorText); - } +# Visit http://localhost:8787 to test +``` - const analysisResult = await response.json(); - const intelligenceReport = analysisResult.output; - - console.log('✅ Autonomous Swarms AI analysis completed successfully'); - console.log('💰 Autonomous execution cost: ' + (analysisResult.usage?.billing_info?.total_cost || 'N/A')); - - // Step 3: Execute autonomous actions based on intelligence - if (env.AUTONOMOUS_ALERTS_EMAIL) { - console.log('📧 Executing autonomous alert system...'); - await executeAutonomousAlerts(env, intelligenceReport, marketIntelligence); - } +### 5. Deploy to Cloudflare Workers - return { - success: true, - analysis: intelligenceReport, - symbolsAnalyzed: validData.length, - cost: analysisResult.usage?.billing_info?.total_cost || analysisResult.metadata?.billing_info?.total_cost, - executionTime: new Date().toISOString(), - nextExecution: 'Scheduled for next autonomous cron trigger', - autonomousSystem: 'Swarms AI Agents' - }; - - } catch (error) { - console.error('❌ Autonomous analysis execution failed:', error.message); - return { - success: false, - error: error.message, - executionTime: new Date().toISOString(), - autonomousSystem: 'Error in autonomous pipeline' - }; - } -} +```bash +# Deploy to production +npm run deploy -// Autonomous market intelligence collection -async function collectMarketIntelligence() { - const targetSymbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'NVDA', 'TSLA']; - const marketIntelligence = {}; - - console.log('🎯 Executing autonomous multi-source data collection...'); - - const dataCollectionPromises = targetSymbols.map(async (symbol) => { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 10000); - - // Autonomous data collection from Yahoo Finance API - const response = await fetch( - 'https://query1.finance.yahoo.com/v8/finance/chart/' + symbol, - { - signal: controller.signal, - headers: { - 'User-Agent': 'Mozilla/5.0 (autonomous-swarms-agent) AppleWebKit/537.36' - } - } - ); - clearTimeout(timeout); - - if (!response.ok) { - throw new Error('Autonomous data collection failed: HTTP ' + response.status); - } - - const data = await response.json(); - const chartResult = data.chart.result[0]; - const meta = chartResult.meta; - - if (!meta) { - throw new Error('Invalid market intelligence structure received'); - } - - const currentPrice = meta.regularMarketPrice; - const previousClose = meta.previousClose; - const dayChange = currentPrice - previousClose; - const changePercent = ((dayChange / previousClose) * 100).toFixed(2); - - console.log('📈 ' + symbol + ': $' + currentPrice + ' (' + changePercent + '%) - Autonomous collection successful'); - - return [symbol, { - price: currentPrice, - change: dayChange, - change_percent: changePercent, - volume: meta.regularMarketVolume || 0, - market_cap: meta.marketCap || 0, - pe_ratio: meta.trailingPE || 0, - day_high: meta.regularMarketDayHigh, - day_low: meta.regularMarketDayLow, - fifty_two_week_high: meta.fiftyTwoWeekHigh, - fifty_two_week_low: meta.fiftyTwoWeekLow, - currency: meta.currency || 'USD', - market_state: meta.marketState, - autonomous_collection_time: new Date().toISOString(), - data_quality: 'high' - }]; - - } catch (error) { - console.error('❌ Autonomous collection failed for ' + symbol + ':', error.message); - return [symbol, { - error: 'Autonomous collection failed: ' + error.message, - autonomous_collection_time: new Date().toISOString(), - data_quality: 'failed' - }]; - } - }); +# Your agent will be live at: https://stock-agent.your-subdomain.workers.dev +``` - const results = await Promise.allSettled(dataCollectionPromises); - results.forEach((result) => { - if (result.status === 'fulfilled' && result.value) { - const [symbol, data] = result.value; - marketIntelligence[symbol] = data; - } - }); +## API Integration Details - const successfulCollections = Object.keys(marketIntelligence).filter(k => !marketIntelligence[k]?.error).length; - console.log('📊 Autonomous intelligence collection completed: ' + successfulCollections + '/' + targetSymbols.length + ' successful'); +### Swarms API Agents - return marketIntelligence; -} +The stock agent uses two specialized AI agents: -// Autonomous alert and notification system -async function executeAutonomousAlerts(env, intelligenceReport, marketIntelligence) { - if (!env.MAILGUN_API_KEY || !env.MAILGUN_DOMAIN || !env.AUTONOMOUS_ALERTS_EMAIL) { - console.log('⚠️ Autonomous alert system not configured - skipping notifications'); - return; - } +1. **Technical Analyst Agent**: + - Calculates technical indicators (RSI, MACD, Moving Averages) + - Identifies support/resistance levels + - Provides trading signals and price targets + +2. **Fundamental Analyst Agent**: + - Analyzes market conditions and sentiment + - Evaluates news and economic indicators + - Provides investment recommendations + +### Data Sources + +- **Yahoo Finance API**: Free real-time stock data (no API key required) +- **Financial Modeling Prep**: Market news and additional data (free tier: 250 requests/day) +- **Mailgun**: Email delivery service (free tier: 5,000 emails/month) + +## Features + +### Web Interface +- Real-time status monitoring +- Manual analysis triggers +- Progress tracking with visual feedback +- Analysis results display + +### Automated Execution +- Scheduled cron job execution +- Error handling and recovery +- Cost tracking and monitoring +- Email report generation + +### Production Ready +- Comprehensive error handling +- Timeout protection +- Rate limiting compliance +- Security best practices + +## Configuration Examples - try { - // Autonomous detection of significant market movements - const significantMovements = Object.entries(marketIntelligence) - .filter(([symbol, data]) => data.change_percent && Math.abs(parseFloat(data.change_percent)) > 3) - .map(([symbol, data]) => symbol + ': ' + data.change_percent + '%') - .join(', '); - - const alertSubject = '🤖 Autonomous Market Intelligence Alert - ' + new Date().toLocaleDateString(); - - const alertBody = ` - - -

🤖 Market Intelligence Alert

-

Date: ${new Date().toLocaleString()}

-

Movements: ${significantMovements || 'Normal volatility'}

- -

Analysis Report:

-
${intelligenceReport}
- -

Powered by Swarms API

- - - `; - - const formData = new FormData(); - formData.append('from', 'Autonomous Market Intelligence '); - formData.append('to', env.AUTONOMOUS_ALERTS_EMAIL); - formData.append('subject', alertSubject); - formData.append('html', alertBody); - - const response = await fetch('https://api.mailgun.net/v3/' + env.MAILGUN_DOMAIN + '/messages', { - method: 'POST', - headers: { - 'Authorization': 'Basic ' + btoa('api:' + env.MAILGUN_API_KEY) - }, - body: formData - }); - - if (response.ok) { - console.log('✅ Autonomous alert system executed successfully'); - } else { - console.error('❌ Autonomous alert system execution failed:', await response.text()); +### Custom Stock Symbols + +Edit the symbols array in `src/index.js`: + +```javascript +const symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'TSLA', 'NVDA', 'AMZN', 'GOOGL']; +``` + +### Custom Swarms Agents + +Modify the agent configuration: + +```javascript +const swarmConfig = { + agents: [ + { + agent_name: "Risk Assessment Agent", + system_prompt: "Analyze portfolio risk and provide recommendations...", + model_name: "gpt-4o-mini", + max_tokens: 2000, + temperature: 0.1 } - - } catch (error) { - console.error('❌ Autonomous alert system error:', error.message); - } -} + ] +}; ``` -## Production Best Practices +## Cost Optimization + +- **Cloudflare Workers**: Free tier includes 100,000 requests/day +- **Swarms API**: Monitor usage in dashboard, use gpt-4o-mini for cost efficiency +- **External APIs**: Leverage free tiers and implement intelligent caching + +## Security & Best Practices -### 1. **Cloudflare Workers + Swarms API Integration** +- Store API keys as Cloudflare Workers secrets +- Implement request validation and rate limiting +- Audit AI decisions and maintain compliance logs +- Use HTTPS for all external API calls -* Implement comprehensive error handling for both platforms -* Use Cloudflare Workers KV for caching Swarms API responses -* Leverage Cloudflare Workers analytics for monitoring +## Monitoring & Observability -### 2. **Cost Optimization** +- Cloudflare Workers analytics dashboard +- Real-time performance metrics +- Error tracking and alerting +- Cost monitoring and optimization -* Monitor Swarms API usage and costs -* Use Cloudflare Workers free tier (100K requests/day) -* Implement intelligent batching for Swarms API efficiency -* Use cost-effective Swarms models (gpt-4o-mini recommended) +## Troubleshooting -### 3. **Security & Compliance** +### Common Issues -* Secure Swarms API keys in Cloudflare Workers environment variables -* Use Cloudflare Workers secrets for sensitive data -* Audit AI decisions and maintain compliance logs -* HIPAA compliance for healthcare applications +1. **API Key Errors**: Verify environment variables are set correctly +2. **Cron Not Triggering**: Check cron syntax and Cloudflare Workers limits +3. **Email Not Sending**: Verify Mailgun configuration and domain setup +4. **Data Fetch Failures**: Check external API status and rate limits + +### Debug Mode + +Enable detailed logging by setting: +```javascript +console.log('Debug mode enabled'); +``` -### 4. **Monitoring & Observability** +## Additional Resources -* Track Cloudflare Workers performance metrics -* Monitor Swarms API response times and success rates -* Use Cloudflare Workers analytics dashboard -* Set up alerts for system failures and anomalies +- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/) +- [Swarms API Documentation](https://docs.swarms.world/) +- [Cron Expression Generator](https://crontab.guru/) +- [Financial Modeling Prep API](https://financialmodelingprep.com/developer/docs) -This deployment architecture combines **Swarms API's advanced multi-agent intelligence** with **Cloudflare Workers' global edge infrastructure**, enabling truly intelligent, self-executing AI agents that operate continuously across 330+ cities worldwide, providing real-time intelligence and automated decision-making capabilities with ultra-low latency. \ No newline at end of file From d36c45ae2df351df225a3ec5e05a2182b322221b Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Thu, 7 Aug 2025 09:39:48 +0530 Subject: [PATCH 63/73] fixed demo video ! --- docs/swarms_cloud/cloudflare_workers.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index b8b372d8..e018fad1 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -6,7 +6,9 @@ Deploy intelligent AI agents powered by Swarms API on Cloudflare Workers edge ne Watch the stock agent in action: -![Demo GIF](https://github.com/harshalmore31/Swarms-CloudFlare-Deployment/blob/main/stock-agent-demo.gif) + > **Note**: The demo video shows the complete workflow from data fetching to AI analysis and report generation. From e01bd8df2701da219aa68ada8465715b94132140 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Thu, 7 Aug 2025 09:42:24 +0530 Subject: [PATCH 64/73] updates --- docs/swarms_cloud/cloudflare_workers.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/swarms_cloud/cloudflare_workers.md b/docs/swarms_cloud/cloudflare_workers.md index e018fad1..7c50f7bf 100644 --- a/docs/swarms_cloud/cloudflare_workers.md +++ b/docs/swarms_cloud/cloudflare_workers.md @@ -2,15 +2,17 @@ Deploy intelligent AI agents powered by Swarms API on Cloudflare Workers edge network. Build production-ready cron agents that run automatically, fetch real-time data, perform AI analysis, and execute actions across 330+ cities worldwide. -## Demo Video + ## Overview From 7312393a7140d9554a0635ce4e27fb8a810eb688 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Thu, 7 Aug 2025 10:54:00 -0700 Subject: [PATCH 65/73] [NEW MODEL][GPT 5] [Sonnet 4 examples] --- example.py | 3 ++- examples/models/gpt_5_example.py | 32 ++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- simple_agent.py | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 examples/models/gpt_5_example.py diff --git a/example.py b/example.py index 387704ae..645e76d0 100644 --- a/example.py +++ b/example.py @@ -33,7 +33,7 @@ agent = Agent( - Performance attribution You communicate in precise, technical terms while maintaining clarity for stakeholders.""", - model_name="gpt-4.1", + model_name="claude-sonnet-4-20250514", dynamic_temperature_enabled=True, output_type="str-all-except-first", max_loops="auto", @@ -41,6 +41,7 @@ agent = Agent( no_reasoning_prompt=True, streaming_on=True, # dashboard=True + llm_base_url="https://api.openai.com/v1" ) out = agent.run( diff --git a/examples/models/gpt_5_example.py b/examples/models/gpt_5_example.py new file mode 100644 index 00000000..6974b036 --- /dev/null +++ b/examples/models/gpt_5_example.py @@ -0,0 +1,32 @@ +""" +Instructions: + +1. Install the swarms package: + > pip3 install -U swarms + +2. Set the model name: + > model_name = "openai/gpt-5-2025-08-07" + +3. Add your OPENAI_API_KEY to the .env file and verify your account. + +4. Run the agent! + +Verify your OpenAI account here: https://platform.openai.com/settings/organization/general +""" + +from swarms import Agent + +agent = Agent( + name="Research Agent", + description="A research agent that can answer questions", + model_name="openai/gpt-5-2025-08-07", + streaming_on=True, + max_loops=1, + interactive=True, +) + +out = agent.run( + "What are the best arbitrage trading strategies for altcoins? Give me research papers and articles on the topic." +) + +print(out) diff --git a/pyproject.toml b/pyproject.toml index 1a254c1c..d1b0e0f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms" -version = "8.0.4" +version = "8.0.5" description = "Swarms - TGSC" license = "MIT" authors = ["Kye Gomez "] diff --git a/simple_agent.py b/simple_agent.py index de3aa638..9af05573 100644 --- a/simple_agent.py +++ b/simple_agent.py @@ -3,7 +3,7 @@ from swarms import Agent agent = Agent( name="Research Agent", description="A research agent that can answer questions", - model_name="claude-3-5-sonnet-20241022", + model_name="claude-sonnet-4-20250514", streaming_on=True, max_loops=1, interactive=True, From b3e3f68ee80e80712b29b5b008957fda55fa55c9 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Thu, 7 Aug 2025 16:11:33 -0700 Subject: [PATCH 66/73] [DOCS][AutoSwarmBuilder in Readme] --- README.md | 42 +++ ...uilder.py => auto_swarm_builder_example.py | 5 +- example.py | 2 +- examples/models/gpt_5/concurrent_gpt5.py | 111 ++++++++ examples/models/{ => gpt_5}/gpt_5_example.py | 0 .../hiearchical_swarm/hs_interactive.py | 0 generation_length_blog/longform_generator.py | 267 ++++++++++++++++++ generation_length_blog/universal_api.py | 29 ++ swarms/structs/auto_swarm_builder.py | 4 +- 9 files changed, 454 insertions(+), 6 deletions(-) rename auto_swarm_builder.py => auto_swarm_builder_example.py (83%) create mode 100644 examples/models/gpt_5/concurrent_gpt5.py rename examples/models/{ => gpt_5}/gpt_5_example.py (100%) rename hs_interactive.py => examples/multi_agent/hiearchical_swarm/hs_interactive.py (100%) create mode 100644 generation_length_blog/longform_generator.py create mode 100644 generation_length_blog/universal_api.py diff --git a/README.md b/README.md index 1f0f4c6c..409395ce 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,48 @@ print(final_post) ----- +### 🤖 AutoSwarmBuilder: Autonomous Agent Generation + +The `AutoSwarmBuilder` automatically generates specialized agents and their workflows based on your task description. Simply describe what you need, and it will create a complete multi-agent system with detailed prompts and optimal agent configurations. [Learn more about AutoSwarmBuilder](https://docs.swarms.world/en/latest/swarms/structs/auto_swarm_builder/) + +```python +from swarms.structs.auto_swarm_builder import AutoSwarmBuilder +import json + +# Initialize the AutoSwarmBuilder +swarm = AutoSwarmBuilder( + name="My Swarm", + description="A swarm of agents", + verbose=True, + max_loops=1, + return_agents=True, + model_name="gpt-4o-mini", +) + +# Let the builder automatically create agents and workflows +result = swarm.run( + task="Create an accounting team to analyze crypto transactions, " + "there must be 5 agents in the team with extremely extensive prompts. " + "Make the prompts extremely detailed and specific and long and comprehensive. " + "Make sure to include all the details of the task in the prompts." +) + +# The result contains the generated agents and their configurations +print(json.dumps(result, indent=4)) +``` + +The `AutoSwarmBuilder` provides: + +- **Automatic Agent Generation**: Creates specialized agents based on task requirements +- **Intelligent Prompt Engineering**: Generates comprehensive, detailed prompts for each agent +- **Optimal Workflow Design**: Determines the best agent interactions and workflow structure +- **Production-Ready Configurations**: Returns fully configured agents ready for deployment +- **Flexible Architecture**: Supports various swarm types and agent specializations + +This feature is perfect for rapid prototyping, complex task decomposition, and creating specialized agent teams without manual configuration. + +----- + ## 🏗️ Multi-Agent Architectures For Production Deployments `swarms` provides a variety of powerful, pre-built multi-agent architectures enabling you to orchestrate agents in various ways. Choose the right structure for your specific problem to build efficient and reliable production systems. diff --git a/auto_swarm_builder.py b/auto_swarm_builder_example.py similarity index 83% rename from auto_swarm_builder.py rename to auto_swarm_builder_example.py index 50284d71..1cb696d9 100644 --- a/auto_swarm_builder.py +++ b/auto_swarm_builder_example.py @@ -6,11 +6,8 @@ swarm = AutoSwarmBuilder( description="A swarm of agents", verbose=True, max_loops=1, - # random_models=False, - # return_agents=True, - model_name="gpt-4o-mini", - # generate_router_config=True, return_agents=True, + model_name="gpt-4.1", ) print( diff --git a/example.py b/example.py index 645e76d0..7fdef175 100644 --- a/example.py +++ b/example.py @@ -41,7 +41,7 @@ agent = Agent( no_reasoning_prompt=True, streaming_on=True, # dashboard=True - llm_base_url="https://api.openai.com/v1" + llm_base_url="https://api.openai.com/v1", ) out = agent.run( diff --git a/examples/models/gpt_5/concurrent_gpt5.py b/examples/models/gpt_5/concurrent_gpt5.py new file mode 100644 index 00000000..b7f50914 --- /dev/null +++ b/examples/models/gpt_5/concurrent_gpt5.py @@ -0,0 +1,111 @@ +from swarms import Agent, ConcurrentWorkflow +from swarms_tools import coin_gecko_coin_api + +# Create specialized agents for Solana, Bitcoin, Ethereum, Cardano, and Polkadot analysis using CoinGecko API + +market_analyst_solana = Agent( + agent_name="Market-Trend-Analyst-Solana", + system_prompt="""You are a market trend analyst specializing in Solana (SOL). + Analyze SOL price movements, volume patterns, and market sentiment using real-time data from the CoinGecko API. + Focus on: + - Technical indicators and chart patterns for Solana + - Volume analysis and market depth for SOL + - Short-term and medium-term trend identification + - Support and resistance levels + + Always use the CoinGecko API tool to fetch up-to-date Solana market data for your analysis. + Provide actionable insights based on this data.""", + model_name="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.2, + tools=[coin_gecko_coin_api], +) + +market_analyst_bitcoin = Agent( + agent_name="Market-Trend-Analyst-Bitcoin", + system_prompt="""You are a market trend analyst specializing in Bitcoin (BTC). + Analyze BTC price movements, volume patterns, and market sentiment using real-time data from the CoinGecko API. + Focus on: + - Technical indicators and chart patterns for Bitcoin + - Volume analysis and market depth for BTC + - Short-term and medium-term trend identification + - Support and resistance levels + + Always use the CoinGecko API tool to fetch up-to-date Bitcoin market data for your analysis. + Provide actionable insights based on this data.""", + model_name="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.2, + tools=[coin_gecko_coin_api], +) + +market_analyst_ethereum = Agent( + agent_name="Market-Trend-Analyst-Ethereum", + system_prompt="""You are a market trend analyst specializing in Ethereum (ETH). + Analyze ETH price movements, volume patterns, and market sentiment using real-time data from the CoinGecko API. + Focus on: + - Technical indicators and chart patterns for Ethereum + - Volume analysis and market depth for ETH + - Short-term and medium-term trend identification + - Support and resistance levels + + Always use the CoinGecko API tool to fetch up-to-date Ethereum market data for your analysis. + Provide actionable insights based on this data.""", + model_name="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.2, + tools=[coin_gecko_coin_api], +) + +market_analyst_cardano = Agent( + agent_name="Market-Trend-Analyst-Cardano", + system_prompt="""You are a market trend analyst specializing in Cardano (ADA). + Analyze ADA price movements, volume patterns, and market sentiment using real-time data from the CoinGecko API. + Focus on: + - Technical indicators and chart patterns for Cardano + - Volume analysis and market depth for ADA + - Short-term and medium-term trend identification + - Support and resistance levels + + Always use the CoinGecko API tool to fetch up-to-date Cardano market data for your analysis. + Provide actionable insights based on this data.""", + model_name="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.2, + tools=[coin_gecko_coin_api], +) + +market_analyst_polkadot = Agent( + agent_name="Market-Trend-Analyst-Polkadot", + system_prompt="""You are a market trend analyst specializing in Polkadot (DOT). + Analyze DOT price movements, volume patterns, and market sentiment using real-time data from the CoinGecko API. + Focus on: + - Technical indicators and chart patterns for Polkadot + - Volume analysis and market depth for DOT + - Short-term and medium-term trend identification + - Support and resistance levels + + Always use the CoinGecko API tool to fetch up-to-date Polkadot market data for your analysis. + Provide actionable insights based on this data.""", + model_name="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.2, + tools=[coin_gecko_coin_api], +) + +# Create concurrent workflow +crypto_analysis_swarm = ConcurrentWorkflow( + agents=[ + market_analyst_solana, + market_analyst_bitcoin, + market_analyst_ethereum, + market_analyst_cardano, + market_analyst_polkadot, + ], + max_loops=1, +) + + +crypto_analysis_swarm.run( + "Analyze your own specified coin and create a comprehensive analysis of the coin" +) diff --git a/examples/models/gpt_5_example.py b/examples/models/gpt_5/gpt_5_example.py similarity index 100% rename from examples/models/gpt_5_example.py rename to examples/models/gpt_5/gpt_5_example.py diff --git a/hs_interactive.py b/examples/multi_agent/hiearchical_swarm/hs_interactive.py similarity index 100% rename from hs_interactive.py rename to examples/multi_agent/hiearchical_swarm/hs_interactive.py diff --git a/generation_length_blog/longform_generator.py b/generation_length_blog/longform_generator.py new file mode 100644 index 00000000..f932b261 --- /dev/null +++ b/generation_length_blog/longform_generator.py @@ -0,0 +1,267 @@ +import time +from typing import Dict, List + +from swarms import Agent +from swarms.utils.litellm_tokenizer import count_tokens + + +class LongFormGenerator: + """ + A class for generating long-form content using the swarms Agent framework. + + This class provides methods for creating comprehensive, detailed content + with support for continuation and sectioned generation. + """ + + def __init__(self, model: str = "claude-sonnet-4-20250514"): + """ + Initialize the LongFormGenerator with specified model. + + Args: + model (str): The model to use for content generation + """ + self.model = model + + def estimate_tokens(self, text: str) -> int: + """ + Estimate token count for text. + + Args: + text (str): The text to estimate tokens for + + Returns: + int: Estimated token count + """ + return count_tokens(text=text, model=self.model) + + def create_expansion_prompt( + self, topic: str, requirements: Dict + ) -> str: + """ + Create optimized prompt for long-form content. + + Args: + topic (str): The main topic to generate content about + requirements (Dict): Requirements for content generation + + Returns: + str: Formatted prompt for content generation + """ + structure_requirements = [] + if "sections" in requirements: + for i, section in enumerate(requirements["sections"]): + structure_requirements.append( + f"{i+1}. {section['title']} - {section.get('description', 'Provide comprehensive analysis')}" + ) + + length_guidance = ( + f"Target length: {requirements.get('min_words', 2000)}-{requirements.get('max_words', 4000)} words" + if "min_words" in requirements + else "" + ) + + prompt = f"""Create a comprehensive, detailed analysis of: {topic} +REQUIREMENTS: +- This is a professional-level document requiring thorough treatment +- Each section must be substantive with detailed explanations +- Include specific examples, case studies, and technical details where relevant +- Provide multiple perspectives and comprehensive coverage +- {length_guidance} +STRUCTURE: +{chr(10).join(structure_requirements)} +QUALITY STANDARDS: +- Demonstrate deep expertise and understanding +- Include relevant technical specifications and details +- Provide actionable insights and practical applications +- Use professional language appropriate for expert audience +- Ensure logical flow and comprehensive coverage of all aspects +Begin your comprehensive analysis:""" + + return prompt + + def generate_with_continuation( + self, topic: str, requirements: Dict, max_attempts: int = 3 + ) -> str: + """ + Generate long-form content with continuation if needed. + + Args: + topic (str): The main topic to generate content about + requirements (Dict): Requirements for content generation + max_attempts (int): Maximum number of continuation attempts + + Returns: + str: Generated long-form content + """ + initial_prompt = self.create_expansion_prompt( + topic, requirements + ) + + # Create agent for initial generation + agent = Agent( + name="LongForm Content Generator", + system_prompt=initial_prompt, + model=self.model, + max_loops=1, + temperature=0.7, + max_tokens=4000, + ) + + # Generate initial response + content = agent.run(topic) + target_words = requirements.get("min_words", 2000) + + # Check if continuation is needed + word_count = len(content.split()) + continuation_count = 0 + + while ( + word_count < target_words + and continuation_count < max_attempts + ): + continuation_prompt = f"""Continue and expand the previous analysis. The current response is {word_count} words, but we need approximately {target_words} words total for comprehensive coverage. +Please continue with additional detailed analysis, examples, and insights. Focus on areas that could benefit from deeper exploration or additional perspectives. Maintain the same professional tone and analytical depth. +Continue the analysis:""" + + # Create continuation agent + continuation_agent = Agent( + name="Content Continuation Agent", + system_prompt=continuation_prompt, + model=self.model, + max_loops=1, + temperature=0.7, + max_tokens=4000, + ) + + # Generate continuation + continuation_content = continuation_agent.run( + f"Continue the analysis on: {topic}" + ) + content += "\n\n" + continuation_content + word_count = len(content.split()) + continuation_count += 1 + + # Rate limiting + time.sleep(1) + + return content + + def generate_sectioned_content( + self, + topic: str, + sections: List[Dict], + combine_sections: bool = True, + ) -> Dict: + """ + Generate content section by section for maximum length. + + Args: + topic (str): The main topic to generate content about + sections (List[Dict]): List of section definitions + combine_sections (bool): Whether to combine all sections into one document + + Returns: + Dict: Dictionary containing individual sections and optionally combined content + """ + results = {} + combined_content = "" + + for section in sections: + section_prompt = f"""Write a comprehensive, detailed section on: {section['title']} +Context: This is part of a larger analysis on {topic} +Requirements for this section: +- Provide {section.get('target_words', 500)}-{section.get('max_words', 800)} words of detailed content +- {section.get('description', 'Provide thorough analysis with examples and insights')} +- Include specific examples, technical details, and practical applications +- Use professional language suitable for expert audience +- Ensure comprehensive coverage of all relevant aspects +Write the complete section:""" + + # Create agent for this section + section_agent = Agent( + name=f"Section Generator - {section['title']}", + system_prompt=section_prompt, + model=self.model, + max_loops=1, + temperature=0.7, + max_tokens=3000, + ) + + # Generate section content + section_content = section_agent.run( + f"Generate section: {section['title']} for topic: {topic}" + ) + results[section["title"]] = section_content + + if combine_sections: + combined_content += ( + f"\n\n## {section['title']}\n\n{section_content}" + ) + + # Rate limiting between sections + time.sleep(1) + + if combine_sections: + results["combined"] = combined_content.strip() + + return results + + +# Example usage +if __name__ == "__main__": + # Initialize the generator + generator = LongFormGenerator() + + # Example topic and requirements + topic = "Artificial Intelligence in Healthcare" + requirements = { + "min_words": 2500, + "max_words": 4000, + "sections": [ + { + "title": "Current Applications", + "description": "Analyze current AI applications in healthcare", + "target_words": 600, + "max_words": 800, + }, + { + "title": "Future Prospects", + "description": "Discuss future developments and potential", + "target_words": 500, + "max_words": 700, + }, + ], + } + + # Generate comprehensive content + content = generator.generate_with_continuation( + topic, requirements + ) + print("Generated Content:") + print(content) + print(f"\nWord count: {len(content.split())}") + + # Generate sectioned content + sections = [ + { + "title": "AI in Medical Imaging", + "description": "Comprehensive analysis of AI applications in medical imaging", + "target_words": 500, + "max_words": 700, + }, + { + "title": "AI in Drug Discovery", + "description": "Detailed examination of AI in pharmaceutical research", + "target_words": 600, + "max_words": 800, + }, + ] + + sectioned_results = generator.generate_sectioned_content( + topic, sections + ) + print("\nSectioned Content:") + for section_title, section_content in sectioned_results.items(): + if section_title != "combined": + print(f"\n--- {section_title} ---") + print(section_content[:200] + "...") diff --git a/generation_length_blog/universal_api.py b/generation_length_blog/universal_api.py new file mode 100644 index 00000000..e39b0000 --- /dev/null +++ b/generation_length_blog/universal_api.py @@ -0,0 +1,29 @@ +from swarms import Agent + + +def generate_comprehensive_content(topic, sections): + prompt = f"""You are tasked with creating a comprehensive, detailed analysis of {topic}. + This should be a thorough, professional-level document suitable for expert review. + + Structure your response with the following sections, ensuring each is substantive and detailed: + {chr(10).join([f"{i+1}. {section} - Provide extensive detail with examples and analysis" for i, section in enumerate(sections)])} + + For each section: + - Include multiple subsections where appropriate + - Provide specific examples and case studies + - Offer detailed explanations of complex concepts + - Include relevant technical details and specifications + - Discuss implications and considerations thoroughly + + Aim for comprehensive coverage that demonstrates deep expertise. This is a professional document that should be thorough and substantive throughout.""" + + agent = Agent( + name="Comprehensive Content Generator", + system_prompt=prompt, + model="claude-sonnet-4-20250514", + max_loops=1, + temperature=0.5, + max_tokens=4000, + ) + + return agent.run(topic) diff --git a/swarms/structs/auto_swarm_builder.py b/swarms/structs/auto_swarm_builder.py index 9521f524..a30398ca 100644 --- a/swarms/structs/auto_swarm_builder.py +++ b/swarms/structs/auto_swarm_builder.py @@ -257,6 +257,7 @@ class AutoSwarmBuilder: model_name: str = "gpt-4.1", generate_router_config: bool = False, interactive: bool = False, + max_tokens: int = 8000, ): """Initialize the AutoSwarmBuilder. @@ -276,6 +277,7 @@ class AutoSwarmBuilder: self.model_name = model_name self.generate_router_config = generate_router_config self.interactive = interactive + self.max_tokens = max_tokens self.conversation = Conversation() self.reliability_check() @@ -412,7 +414,7 @@ class AutoSwarmBuilder: temperature=0.5, base_model=config, model_name=self.model_name, - max_tokens=8000, + max_tokens=self.max_tokens, ) def create_agents(self, task: str): From 63a998bb1f7502231167f24b66e2b97d2b4d675e Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sat, 9 Aug 2025 11:12:39 -0700 Subject: [PATCH 67/73] [NEW][Examples] [simulations.senatorassembly -> to swarms.sim] --- .gitignore | 1 + examples/api_examples/agent_overview.py | 36 + examples/api_examples/batch_example.py | 50 + .../{api => api_examples}/client_example.py | 0 examples/api_examples/hospital_team.py | 105 + examples/api_examples/icd_ten_analysis.py | 63 + examples/{api => api_examples}/legal_team.py | 0 examples/{api => api_examples}/rate_limits.py | 0 .../longform_generator.py | 0 .../generation_length_blog}/universal_api.py | 0 simulation_vote_example.py | 19 + simulations/map_generation/game_map.py | 662 +++ simulations/map_generation/map.png | Bin 0 -> 965560 bytes simulations/map_generation/map_two.png | Bin 0 -> 1095093 bytes .../senator_assembly/senator_simulation.py | 3648 ----------------- .../senator_simulation_example.py | 18 +- .../senator_assembly/test_concurrent_vote.py | 135 + swarms/sims/__init__.py | 0 swarms/sims/senator_assembly.py | 3484 ++++++++++++++++ swarms/structs/ma_utils.py | 4 +- 20 files changed, 4566 insertions(+), 3659 deletions(-) create mode 100644 examples/api_examples/agent_overview.py create mode 100644 examples/api_examples/batch_example.py rename examples/{api => api_examples}/client_example.py (100%) create mode 100644 examples/api_examples/hospital_team.py create mode 100644 examples/api_examples/icd_ten_analysis.py rename examples/{api => api_examples}/legal_team.py (100%) rename examples/{api => api_examples}/rate_limits.py (100%) rename {generation_length_blog => examples/guides/generation_length_blog}/longform_generator.py (100%) rename {generation_length_blog => examples/guides/generation_length_blog}/universal_api.py (100%) create mode 100644 simulation_vote_example.py create mode 100644 simulations/map_generation/game_map.py create mode 100644 simulations/map_generation/map.png create mode 100644 simulations/map_generation/map_two.png delete mode 100644 simulations/senator_assembly/senator_simulation.py create mode 100644 simulations/senator_assembly/test_concurrent_vote.py create mode 100644 swarms/sims/__init__.py create mode 100644 swarms/sims/senator_assembly.py diff --git a/.gitignore b/.gitignore index 4a70b60b..b2561ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ next_swarms_update.txt runs Financial-Analysis-Agent_state.json conversations/ +models/ evolved_gpt2_models/ experimental ffn_alternatives diff --git a/examples/api_examples/agent_overview.py b/examples/api_examples/agent_overview.py new file mode 100644 index 00000000..24e0b8c0 --- /dev/null +++ b/examples/api_examples/agent_overview.py @@ -0,0 +1,36 @@ +import os +from swarms_client import SwarmsClient +from dotenv import load_dotenv +import json + +load_dotenv() + +client = SwarmsClient( + api_key=os.getenv("SWARMS_API_KEY"), +) + + +result = client.agent.run( + agent_config={ + "agent_name": "Bloodwork Diagnosis Expert", + "description": "An expert doctor specializing in interpreting and diagnosing blood work results.", + "system_prompt": ( + "You are an expert medical doctor specializing in the interpretation and diagnosis of blood work. " + "Your expertise includes analyzing laboratory results, identifying abnormal values, " + "explaining their clinical significance, and recommending next diagnostic or treatment steps. " + "Provide clear, evidence-based explanations and consider differential diagnoses based on blood test findings." + ), + "model_name": "groq/moonshotai/kimi-k2-instruct", + "max_loops": 1, + "max_tokens": 1000, + "temperature": 0.5, + }, + task=( + "A patient presents with the following blood work results: " + "Hemoglobin: 10.2 g/dL (low), WBC: 13,000 /µL (high), Platelets: 180,000 /µL (normal), " + "ALT: 65 U/L (high), AST: 70 U/L (high). " + "Please provide a detailed interpretation, possible diagnoses, and recommended next steps." + ), +) + +print(json.dumps(result, indent=4)) diff --git a/examples/api_examples/batch_example.py b/examples/api_examples/batch_example.py new file mode 100644 index 00000000..40fa9cbf --- /dev/null +++ b/examples/api_examples/batch_example.py @@ -0,0 +1,50 @@ +import os +from swarms_client import SwarmsClient +from dotenv import load_dotenv +import json + +load_dotenv() + +client = SwarmsClient( + api_key=os.getenv("SWARMS_API_KEY"), +) + +batch_requests = [ + { + "agent_config": { + "agent_name": "Bloodwork Diagnosis Expert", + "description": "Expert in blood work interpretation.", + "system_prompt": ( + "You are a doctor who interprets blood work. Give concise, clear explanations and possible diagnoses." + ), + "model_name": "claude-sonnet-4-20250514", + "max_loops": 1, + "max_tokens": 1000, + "temperature": 0.5, + }, + "task": ( + "Blood work: Hemoglobin 10.2 (low), WBC 13,000 (high), Platelets 180,000 (normal), " + "ALT 65 (high), AST 70 (high). Interpret and suggest diagnoses." + ), + }, + { + "agent_config": { + "agent_name": "Radiology Report Summarizer", + "description": "Expert in summarizing radiology reports.", + "system_prompt": ( + "You are a radiologist. Summarize the findings of radiology reports in clear, patient-friendly language." + ), + "model_name": "claude-sonnet-4-20250514", + "max_loops": 1, + "max_tokens": 1000, + "temperature": 0.5, + }, + "task": ( + "Radiology report: Chest X-ray shows mild cardiomegaly, no infiltrates, no effusion. Summarize the findings." + ), + }, +] + +result = client.agent.batch.run(body=batch_requests) + +print(json.dumps(result, indent=4)) diff --git a/examples/api/client_example.py b/examples/api_examples/client_example.py similarity index 100% rename from examples/api/client_example.py rename to examples/api_examples/client_example.py diff --git a/examples/api_examples/hospital_team.py b/examples/api_examples/hospital_team.py new file mode 100644 index 00000000..a57473c9 --- /dev/null +++ b/examples/api_examples/hospital_team.py @@ -0,0 +1,105 @@ +import json +import os +from swarms_client import SwarmsClient +from dotenv import load_dotenv + +load_dotenv() + +client = SwarmsClient( + api_key=os.getenv("SWARMS_API_KEY"), +) + + +def create_medical_unit_swarm(client, patient_info): + """ + Creates and runs a simulated medical unit swarm with a doctor (leader), nurses, and a medical assistant. + + Args: + client (SwarmsClient): The SwarmsClient instance. + patient_info (str): The patient symptoms and information. + + Returns: + dict: The output from the swarm run. + """ + return client.swarms.run( + name="Hospital Medical Unit", + description="A simulated hospital unit with a doctor (leader), nurses, and a medical assistant collaborating on patient care.", + swarm_type="HiearchicalSwarm", + task=patient_info, + agents=[ + { + "agent_name": "Dr. Smith - Attending Physician", + "description": "The lead doctor responsible for diagnosis, treatment planning, and team coordination.", + "system_prompt": ( + "You are Dr. Smith, the attending physician and leader of the medical unit. " + "You review all information, make final decisions, and coordinate the team. " + "Provide a diagnosis, recommend next steps, and delegate tasks to the nurses and assistant." + ), + "model_name": "gpt-4.1", + "role": "leader", + "max_loops": 1, + "max_tokens": 8192, + "temperature": 0.5, + }, + { + "agent_name": "Nurse Alice", + "description": "A registered nurse responsible for patient assessment, vital signs, and reporting findings to the doctor.", + "system_prompt": ( + "You are Nurse Alice, a registered nurse. " + "Assess the patient's symptoms, record vital signs, and report your findings to Dr. Smith. " + "Suggest any immediate nursing interventions if needed." + ), + "model_name": "gpt-4.1", + "role": "worker", + "max_loops": 1, + "max_tokens": 4096, + "temperature": 0.5, + }, + { + "agent_name": "Nurse Bob", + "description": "A registered nurse assisting with patient care, medication administration, and monitoring.", + "system_prompt": ( + "You are Nurse Bob, a registered nurse. " + "Assist with patient care, administer medications as ordered, and monitor the patient's response. " + "Communicate any changes to Dr. Smith." + ), + "model_name": "gpt-4.1", + "role": "worker", + "max_loops": 1, + "max_tokens": 4096, + "temperature": 0.5, + }, + { + "agent_name": "Medical Assistant Jane", + "description": "A medical assistant supporting the team with administrative tasks and basic patient care.", + "system_prompt": ( + "You are Medical Assistant Jane. " + "Support the team by preparing the patient, collecting samples, and handling administrative tasks. " + "Report any relevant observations to the nurses or Dr. Smith." + ), + "model_name": "claude-sonnet-4-20250514", + "role": "worker", + "max_loops": 1, + "max_tokens": 2048, + "temperature": 0.5, + }, + ], + ) + + +if __name__ == "__main__": + patient_symptoms = """ + Patient: 45-year-old female + Chief Complaint: Chest pain and shortness of breath for 2 days + + Symptoms: + - Sharp chest pain that worsens with deep breathing + - Shortness of breath, especially when lying down + - Mild fever (100.2°F) + - Dry cough + - Fatigue + """ + + out = create_medical_unit_swarm(client, patient_symptoms) + + print(json.dumps(out, indent=4)) diff --git a/examples/api_examples/icd_ten_analysis.py b/examples/api_examples/icd_ten_analysis.py new file mode 100644 index 00000000..ca9e7262 --- /dev/null +++ b/examples/api_examples/icd_ten_analysis.py @@ -0,0 +1,63 @@ +import json +import os +from swarms_client import SwarmsClient +from dotenv import load_dotenv + +load_dotenv() + +client = SwarmsClient( + api_key=os.getenv("SWARMS_API_KEY"), +) + +patient_symptoms = """ +Patient: 45-year-old female +Chief Complaint: Chest pain and shortness of breath for 2 days + +Symptoms: +- Sharp chest pain that worsens with deep breathing +- Shortness of breath, especially when lying down +- Mild fever (100.2°F) +- Dry cough +- Fatigue +""" + +out = client.swarms.run( + name="ICD Analysis Swarm", + description="A swarm that analyzes ICD codes", + swarm_type="ConcurrentWorkflow", + task=patient_symptoms, + agents=[ + { + "agent_name": "ICD-Analyzer", + "description": "An agent that analyzes ICD codes", + "system_prompt": "You are an expert ICD code analyzer. Your task is to analyze the ICD codes and provide a detailed explanation of the codes.", + "model_name": "groq/openai/gpt-oss-120b", + "role": "worker", + "max_loops": 1, + "max_tokens": 8192, + "temperature": 0.5, + }, + { + "agent_name": "ICD-Code-Explainer-Primary", + "description": "An agent that provides primary explanations for ICD codes", + "system_prompt": "You are an expert ICD code explainer. Your task is to provide a clear and thorough explanation of the ICD codes to the user, focusing on primary meanings and clinical context.", + "model_name": "groq/openai/gpt-oss-120b", + "role": "worker", + "max_loops": 1, + "max_tokens": 8192, + "temperature": 0.5, + }, + { + "agent_name": "ICD-Code-Explainer-Secondary", + "description": "An agent that provides additional context and secondary explanations for ICD codes", + "system_prompt": "You are an expert ICD code explainer. Your task is to provide additional context, nuances, and secondary explanations for the ICD codes, including possible differential diagnoses and related codes.", + "model_name": "groq/openai/gpt-oss-120b", + "role": "worker", + "max_loops": 1, + "max_tokens": 8192, + "temperature": 0.5, + }, + ], +) + +print(json.dumps(out, indent=4)) diff --git a/examples/api/legal_team.py b/examples/api_examples/legal_team.py similarity index 100% rename from examples/api/legal_team.py rename to examples/api_examples/legal_team.py diff --git a/examples/api/rate_limits.py b/examples/api_examples/rate_limits.py similarity index 100% rename from examples/api/rate_limits.py rename to examples/api_examples/rate_limits.py diff --git a/generation_length_blog/longform_generator.py b/examples/guides/generation_length_blog/longform_generator.py similarity index 100% rename from generation_length_blog/longform_generator.py rename to examples/guides/generation_length_blog/longform_generator.py diff --git a/generation_length_blog/universal_api.py b/examples/guides/generation_length_blog/universal_api.py similarity index 100% rename from generation_length_blog/universal_api.py rename to examples/guides/generation_length_blog/universal_api.py diff --git a/simulation_vote_example.py b/simulation_vote_example.py new file mode 100644 index 00000000..9d353224 --- /dev/null +++ b/simulation_vote_example.py @@ -0,0 +1,19 @@ +from swarms.sims.senator_assembly import SenatorAssembly + + +def main(): + """ + Simulate a Senate vote on a bill to invade Cuba and claim it as the 51st state. + + This function initializes the SenatorAssembly and runs a concurrent vote simulation + on the specified bill. + """ + senator_simulation = SenatorAssembly() + # senator_simulation.simulate_vote_concurrent( + # "A bill proposing to deregulate the IPO (Initial Public Offering) market in the United States as extensively as possible. The bill seeks to remove or significantly reduce existing regulatory requirements and oversight for companies seeking to go public, with the aim of increasing market efficiency and access to capital. Senators must consider the potential economic, legal, and ethical consequences of such broad deregulation, and cast their votes accordingly.", + # batch_size=10, + # ) + + +if __name__ == "__main__": + main() diff --git a/simulations/map_generation/game_map.py b/simulations/map_generation/game_map.py new file mode 100644 index 00000000..2ee6c1de --- /dev/null +++ b/simulations/map_generation/game_map.py @@ -0,0 +1,662 @@ +""" +Production-grade AI Vision Pipeline for depth estimation, segmentation, object detection, +and 3D point cloud generation. + +This module provides a comprehensive pipeline that combines MiDaS for depth estimation, +SAM (Segment Anything Model) for semantic segmentation, YOLOv8 for object detection, +and Open3D for 3D point cloud generation. +""" + +import sys +from pathlib import Path +from typing import Dict, List, Optional, Tuple, Union, Any +import warnings + +warnings.filterwarnings("ignore") + +import cv2 +import numpy as np +import torch +import torchvision.transforms as transforms +from PIL import Image +import open3d as o3d +from loguru import logger + +# Third-party model imports +try: + import timm + from segment_anything import ( + SamAutomaticMaskGenerator, + sam_model_registry, + ) + from ultralytics import YOLO +except ImportError as e: + logger.error(f"Missing required dependencies: {e}") + sys.exit(1) + + +class AIVisionPipeline: + """ + A comprehensive AI vision pipeline that performs depth estimation, semantic segmentation, + object detection, and 3D point cloud generation from input images. + + This class integrates multiple state-of-the-art models: + - MiDaS for monocular depth estimation + - SAM (Segment Anything Model) for semantic segmentation + - YOLOv8 for object detection + - Open3D for 3D point cloud generation + + Attributes: + model_dir (Path): Directory where models are stored + device (torch.device): Computing device (CPU/CUDA) + midas_model: Loaded MiDaS depth estimation model + midas_transform: MiDaS preprocessing transforms + sam_generator: SAM automatic mask generator + yolo_model: YOLOv8 object detection model + + Example: + >>> pipeline = AIVisionPipeline() + >>> results = pipeline.process_image("path/to/image.jpg") + >>> point_cloud = results["point_cloud"] + """ + + def __init__( + self, + model_dir: str = "./models", + device: Optional[str] = None, + midas_model_type: str = "MiDaS", + sam_model_type: str = "vit_b", + yolo_model_path: str = "yolov8n.pt", + log_level: str = "INFO", + ) -> None: + """ + Initialize the AI Vision Pipeline. + + Args: + model_dir: Directory to store downloaded models + device: Computing device ('cpu', 'cuda', or None for auto-detection) + midas_model_type: MiDaS model variant ('MiDaS', 'MiDaS_small', 'DPT_Large', etc.) + sam_model_type: SAM model type ('vit_b', 'vit_l', 'vit_h') + yolo_model_path: Path to YOLOv8 model weights + log_level: Logging level ('DEBUG', 'INFO', 'WARNING', 'ERROR') + + Raises: + RuntimeError: If required models cannot be loaded + FileNotFoundError: If model files are not found + """ + # Setup logging + logger.remove() + logger.add( + sys.stdout, + level=log_level, + format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", + ) + + # Initialize attributes + self.model_dir = Path(model_dir) + self.model_dir.mkdir(parents=True, exist_ok=True) + + # Device setup + if device is None: + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu" + ) + else: + self.device = torch.device(device) + + logger.info(f"Using device: {self.device}") + + # Model configuration + self.midas_model_type = midas_model_type + self.sam_model_type = sam_model_type + self.yolo_model_path = yolo_model_path + + # Initialize model placeholders + self.midas_model: Optional[torch.nn.Module] = None + self.midas_transform: Optional[transforms.Compose] = None + self.sam_generator: Optional[SamAutomaticMaskGenerator] = None + self.yolo_model: Optional[YOLO] = None + + # Load all models + self._setup_models() + + logger.success("AI Vision Pipeline initialized successfully") + + def _setup_models(self) -> None: + """ + Load and initialize all AI models with proper error handling. + + Raises: + RuntimeError: If any model fails to load + """ + try: + self._load_midas_model() + self._load_sam_model() + self._load_yolo_model() + except Exception as e: + logger.error(f"Failed to setup models: {e}") + raise RuntimeError(f"Model initialization failed: {e}") + + def _load_midas_model(self) -> None: + """Load MiDaS depth estimation model.""" + try: + logger.info( + f"Loading MiDaS model: {self.midas_model_type}" + ) + + # Load MiDaS model from torch hub + self.midas_model = torch.hub.load( + "intel-isl/MiDaS", + self.midas_model_type, + pretrained=True, + ) + self.midas_model.to(self.device) + self.midas_model.eval() + + # Load corresponding transforms + midas_transforms = torch.hub.load( + "intel-isl/MiDaS", "transforms" + ) + + if self.midas_model_type in ["DPT_Large", "DPT_Hybrid"]: + self.midas_transform = midas_transforms.dpt_transform + else: + self.midas_transform = ( + midas_transforms.default_transform + ) + + logger.success("MiDaS model loaded successfully") + + except Exception as e: + logger.error(f"Failed to load MiDaS model: {e}") + raise + + def _load_sam_model(self) -> None: + """Load SAM (Segment Anything Model) for semantic segmentation.""" + try: + logger.info(f"Loading SAM model: {self.sam_model_type}") + + # SAM model checkpoints mapping + sam_checkpoint_urls = { + "vit_b": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", + "vit_l": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth", + "vit_h": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth", + } + + checkpoint_path = ( + self.model_dir / f"sam_{self.sam_model_type}.pth" + ) + + # Download checkpoint if not exists + if not checkpoint_path.exists(): + logger.info( + f"Downloading SAM checkpoint to {checkpoint_path}" + ) + import urllib.request + + urllib.request.urlretrieve( + sam_checkpoint_urls[self.sam_model_type], + checkpoint_path, + ) + + # Load SAM model + sam = sam_model_registry[self.sam_model_type]( + checkpoint=str(checkpoint_path) + ) + sam.to(self.device) + + # Create automatic mask generator + self.sam_generator = SamAutomaticMaskGenerator( + model=sam, + points_per_side=32, + pred_iou_thresh=0.86, + stability_score_thresh=0.92, + crop_n_layers=1, + crop_n_points_downscale_factor=2, + min_mask_region_area=100, + ) + + logger.success("SAM model loaded successfully") + + except Exception as e: + logger.error(f"Failed to load SAM model: {e}") + raise + + def _load_yolo_model(self) -> None: + """Load YOLOv8 object detection model.""" + try: + logger.info( + f"Loading YOLOv8 model: {self.yolo_model_path}" + ) + + self.yolo_model = YOLO(self.yolo_model_path) + + # Move to appropriate device + if self.device.type == "cuda": + self.yolo_model.to(self.device) + + logger.success("YOLOv8 model loaded successfully") + + except Exception as e: + logger.error(f"Failed to load YOLOv8 model: {e}") + raise + + def _load_and_preprocess_image( + self, image_path: Union[str, Path] + ) -> Tuple[np.ndarray, Image.Image]: + """ + Load and preprocess input image. + + Args: + image_path: Path to the input image (JPG or PNG) + + Returns: + Tuple of (opencv_image, pil_image) + + Raises: + FileNotFoundError: If image file doesn't exist + ValueError: If image format is not supported + """ + image_path = Path(image_path) + + if not image_path.exists(): + raise FileNotFoundError(f"Image not found: {image_path}") + + if image_path.suffix.lower() not in [".jpg", ".jpeg", ".png"]: + raise ValueError( + f"Unsupported image format: {image_path.suffix}" + ) + + try: + # Load with OpenCV (BGR format) + cv_image = cv2.imread(str(image_path)) + if cv_image is None: + raise ValueError( + f"Could not load image: {image_path}" + ) + + # Convert BGR to RGB for PIL + rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB) + pil_image = Image.fromarray(rgb_image) + + logger.debug( + f"Loaded image: {image_path} ({rgb_image.shape})" + ) + return rgb_image, pil_image + + except Exception as e: + logger.error(f"Failed to load image {image_path}: {e}") + raise + + def estimate_depth(self, image: np.ndarray) -> np.ndarray: + """ + Generate depth map using MiDaS model. + + Args: + image: Input image as numpy array (H, W, 3) in RGB format + + Returns: + Depth map as numpy array (H, W) + + Raises: + RuntimeError: If depth estimation fails + """ + try: + logger.debug("Estimating depth with MiDaS") + + # Preprocess image for MiDaS + input_tensor = self.midas_transform(image).to(self.device) + + # Perform inference + with torch.no_grad(): + depth_map = self.midas_model(input_tensor) + depth_map = torch.nn.functional.interpolate( + depth_map.unsqueeze(1), + size=image.shape[:2], + mode="bicubic", + align_corners=False, + ).squeeze() + + # Convert to numpy + depth_numpy = depth_map.cpu().numpy() + + # Normalize depth values + depth_numpy = (depth_numpy - depth_numpy.min()) / ( + depth_numpy.max() - depth_numpy.min() + ) + + logger.debug( + f"Depth estimation completed. Shape: {depth_numpy.shape}" + ) + return depth_numpy + + except Exception as e: + logger.error(f"Depth estimation failed: {e}") + raise RuntimeError(f"Depth estimation error: {e}") + + def segment_image( + self, image: np.ndarray + ) -> List[Dict[str, Any]]: + """ + Perform semantic segmentation using SAM. + + Args: + image: Input image as numpy array (H, W, 3) in RGB format + + Returns: + List of segmentation masks with metadata + + Raises: + RuntimeError: If segmentation fails + """ + try: + logger.debug("Performing segmentation with SAM") + + # Generate masks + masks = self.sam_generator.generate(image) + + logger.debug(f"Generated {len(masks)} segmentation masks") + return masks + + except Exception as e: + logger.error(f"Segmentation failed: {e}") + raise RuntimeError(f"Segmentation error: {e}") + + def detect_objects( + self, image: np.ndarray + ) -> List[Dict[str, Any]]: + """ + Perform object detection using YOLOv8. + + Args: + image: Input image as numpy array (H, W, 3) in RGB format + + Returns: + List of detected objects with bounding boxes and confidence scores + + Raises: + RuntimeError: If object detection fails + """ + try: + logger.debug("Performing object detection with YOLOv8") + + # Run inference + results = self.yolo_model(image, verbose=False) + + # Extract detections + detections = [] + for result in results: + boxes = result.boxes + if boxes is not None: + for i in range(len(boxes)): + detection = { + "bbox": boxes.xyxy[i] + .cpu() + .numpy(), # [x1, y1, x2, y2] + "confidence": float( + boxes.conf[i].cpu().numpy() + ), + "class_id": int( + boxes.cls[i].cpu().numpy() + ), + "class_name": result.names[ + int(boxes.cls[i].cpu().numpy()) + ], + } + detections.append(detection) + + logger.debug(f"Detected {len(detections)} objects") + return detections + + except Exception as e: + logger.error(f"Object detection failed: {e}") + raise RuntimeError(f"Object detection error: {e}") + + def generate_point_cloud( + self, + image: np.ndarray, + depth_map: np.ndarray, + masks: Optional[List[Dict[str, Any]]] = None, + ) -> o3d.geometry.PointCloud: + """ + Generate 3D point cloud from image and depth data. + + Args: + image: RGB image array (H, W, 3) + depth_map: Depth map array (H, W) + masks: Optional segmentation masks for point cloud filtering + + Returns: + Open3D PointCloud object + + Raises: + ValueError: If input dimensions don't match + RuntimeError: If point cloud generation fails + """ + try: + logger.debug("Generating 3D point cloud") + + if image.shape[:2] != depth_map.shape: + raise ValueError( + "Image and depth map dimensions must match" + ) + + height, width = depth_map.shape + + # Create intrinsic camera parameters (assuming standard camera) + fx = fy = width # Focal length approximation + cx, cy = ( + width / 2, + height / 2, + ) # Principal point at image center + + # Create coordinate grids + u, v = np.meshgrid(np.arange(width), np.arange(height)) + + # Convert depth to actual distances (inverse depth) + # MiDaS outputs inverse depth, so we invert it + z = 1.0 / ( + depth_map + 1e-6 + ) # Add small epsilon to avoid division by zero + + # Back-project to 3D coordinates + x = (u - cx) * z / fx + y = (v - cy) * z / fy + + # Create point cloud + points = np.stack( + [x.flatten(), y.flatten(), z.flatten()], axis=1 + ) + colors = ( + image.reshape(-1, 3) / 255.0 + ) # Normalize colors to [0, 1] + + # Filter out invalid points + valid_mask = np.isfinite(points).all(axis=1) & ( + z.flatten() > 0 + ) + points = points[valid_mask] + colors = colors[valid_mask] + + # Create Open3D point cloud + point_cloud = o3d.geometry.PointCloud() + point_cloud.points = o3d.utility.Vector3dVector(points) + point_cloud.colors = o3d.utility.Vector3dVector(colors) + + # Optional: Filter by segmentation masks + if masks and len(masks) > 0: + # Use the largest mask for filtering + largest_mask = max(masks, key=lambda x: x["area"]) + mask_2d = largest_mask["segmentation"] + mask_1d = mask_2d.flatten()[valid_mask] + + filtered_points = points[mask_1d] + filtered_colors = colors[mask_1d] + + point_cloud.points = o3d.utility.Vector3dVector( + filtered_points + ) + point_cloud.colors = o3d.utility.Vector3dVector( + filtered_colors + ) + + # Remove statistical outliers + point_cloud, _ = point_cloud.remove_statistical_outlier( + nb_neighbors=20, std_ratio=2.0 + ) + + logger.debug( + f"Generated point cloud with {len(point_cloud.points)} points" + ) + return point_cloud + + except Exception as e: + logger.error(f"Point cloud generation failed: {e}") + raise RuntimeError(f"Point cloud generation error: {e}") + + def process_image( + self, image_path: Union[str, Path] + ) -> Dict[str, Any]: + """ + Process a single image through the complete AI vision pipeline. + + Args: + image_path: Path to input image (JPG or PNG) + + Returns: + Dictionary containing all processing results: + - 'image': Original RGB image + - 'depth_map': Depth estimation result + - 'segmentation_masks': SAM segmentation results + - 'detections': YOLO object detection results + - 'point_cloud': Open3D point cloud object + + Raises: + FileNotFoundError: If image file doesn't exist + RuntimeError: If any processing step fails + """ + try: + logger.info(f"Processing image: {image_path}") + + # Load and preprocess image + rgb_image, pil_image = self._load_and_preprocess_image( + image_path + ) + + # Depth estimation + depth_map = self.estimate_depth(rgb_image) + + # Semantic segmentation + segmentation_masks = self.segment_image(rgb_image) + + # Object detection + detections = self.detect_objects(rgb_image) + + # 3D point cloud generation + point_cloud = self.generate_point_cloud( + rgb_image, depth_map, segmentation_masks + ) + + # Compile results + results = { + "image": rgb_image, + "depth_map": depth_map, + "segmentation_masks": segmentation_masks, + "detections": detections, + "point_cloud": point_cloud, + "metadata": { + "image_shape": rgb_image.shape, + "num_segments": len(segmentation_masks), + "num_detections": len(detections), + "num_points": len(point_cloud.points), + }, + } + + logger.success("Image processing completed successfully") + logger.info(f"Results: {results['metadata']}") + + return results + + except Exception as e: + logger.error(f"Image processing failed: {e}") + raise + + def save_point_cloud( + self, + point_cloud: o3d.geometry.PointCloud, + output_path: Union[str, Path], + ) -> None: + """ + Save point cloud to file. + + Args: + point_cloud: Open3D PointCloud object + output_path: Output file path (.ply, .pcd, .xyz) + + Raises: + RuntimeError: If saving fails + """ + try: + output_path = Path(output_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + + success = o3d.io.write_point_cloud( + str(output_path), point_cloud + ) + + if not success: + raise RuntimeError("Failed to write point cloud file") + + logger.success(f"Point cloud saved to: {output_path}") + + except Exception as e: + logger.error(f"Failed to save point cloud: {e}") + raise RuntimeError(f"Point cloud save error: {e}") + + def visualize_point_cloud( + self, point_cloud: o3d.geometry.PointCloud + ) -> None: + """ + Visualize point cloud using Open3D viewer. + + Args: + point_cloud: Open3D PointCloud object to visualize + """ + try: + logger.info("Opening point cloud visualization") + o3d.visualization.draw_geometries([point_cloud]) + except Exception as e: + logger.warning(f"Visualization failed: {e}") + + +# Example usage and testing +if __name__ == "__main__": + # Example usage + try: + # Initialize pipeline + pipeline = AIVisionPipeline( + model_dir="./models", log_level="INFO" + ) + + # Process an image (replace with actual image path) + image_path = "map_two.png" # Replace with your image path + + if Path(image_path).exists(): + results = pipeline.process_image(image_path) + + # Save point cloud + pipeline.save_point_cloud( + results["point_cloud"], "output_point_cloud.ply" + ) + + # Optional: Visualize point cloud + pipeline.visualize_point_cloud(results["point_cloud"]) + + print( + f"Processing completed! Generated {results['metadata']['num_points']} 3D points" + ) + else: + logger.warning(f"Example image not found: {image_path}") + + except Exception as e: + logger.error(f"Example execution failed: {e}") diff --git a/simulations/map_generation/map.png b/simulations/map_generation/map.png new file mode 100644 index 0000000000000000000000000000000000000000..5729535c4546d23a449130b02c7000769ca02a5e GIT binary patch literal 965560 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%0Dt>kX5Op#9ZfA$|RSI>re z|6&Y|+4fLdDC@5>6c|SJ8`5(89l4y~HtfU$75u9`D2(lA`~QgWH^;F!@qCAMo|^$t zfZ3odP^N6gZ2Bg6376++28JZfk*OL3oqiq9F5 zR=nE^zh2Lhb4fkQ5_!tZ@S5?}luh4|Bf&Bcj(9Ss2fvtA-70% z@AD44GX1cr@pOkgZTFU2r+~+mU&;`5ob?m;@9LRq)pYZ_~ZZZV~55f-%kC=hk57gNT=_`(&*(<-RE$Y=6#Z{_3rQKRuh;VK?xQCu=29GW^oLzD(U7TGs?5$1V zshAv?n~{wUehoATzXA$hM8O?7e!*XB17uKk5PlN8>M00_z>amJRFA^sfTjpw5PU5F zw{TY^^q*@iRBRwPI|yh34gx-c@6ZPUfj`YQMCT@oWdGa2V2GIXyjXgJb@XUKS&|iY z`i`*0f~e*TNOr@LM(5(*q8tdxB4N9C>mD&4JnZF$2akMEUJhP%URDlHPF*Pc`T^iq z5Gr8L08!?|zAPv^a4R4F{S{Sp`%4_OVKyR^46VJC-@ z!c7mp8YH-H33IZ)NoPVN?VTL#;n_oY%8DIsk2m|#@o4_BU)~9x1++7PC_CKb7;h3= z5JgK{OL(@CogPa2qdB>qx`m~wjR{0UT^%B=t|Y+4%`VOY<(7uBNbz&=vU5W@ZyF-t zG5>?EimS1WB@CjbE)LOvNBt0W7kDNL&N)Qf)y3i_59*m6gvAIALJookAzJ+iJn%T- zD`dp=F|2Dd*1_=a*)PPuxd5p9KTGDn4OtGpSozMH=s#GKH-YC+;S4}DO`R;wEMeb= z4fzqh+~k73SDF5U(VJB=^m~{BWoKvS=YVo@aI(Y06i)6RMXsA7)ad^mV{nQ;7-Reg zV}FwwJT>=2WIq<_Pa&T%}%Hd7f{_xMf& z2YdgTQpsctg&Z|ftL(Y6$k3=KlBBFSh<#tgJf_eQXs!(2@<%_)CO$~FU(}>&s@m~+ z9f4cg_h|K<6!6OErH5QRSi#$cy|yR2=zbI_#wJeXM0K%Gh82HE0(U?EnoOs1%Znup z_f!vCO>2D=g;SIKHHI8+bV7TsqYM&b4I$Rdl{G_cCrZ-+P*ql09`7m6hts<+rL%6s z81iR3kb6Z7W=Z0cRP6CgixYCe79ibpuj7>$ck#zGS(Y^vHl5FMPv7|&6dS7#h)8@%hwfJY1DbrRLA)`Ign9p*(4j(k*&CQn)?twi-8!zm{Taq_CcXwAD*Icl@r@ zgVz>t*KF}6V_)Vg33qd9shpORwW+h7#n7@_c|?nR`>V3842#-TapulKNytG?o*xwT zoWy;n0`8K24AE*;PZ!qxG%udJii96OYBAqQ4>o_$r$7C$8TtqHTY9obT&?2Wv}v2WK`Z_-dX_ z?XUGTtA&d#6z_&AEf@oe_KOk%3Y6q8-ta+S401GpI=~fR43GqfL$Pk!e-Dokp$PEs z=%1Fr1AY@$p?B{Lk)(i;>Bm&Cl`V>Mp@AVchfsihq25r>Z1-%}Fz5fqD13F!_Lm*f zazKB%2>9mjP6A%MaTD-A$$vNscn2H(E{J)tkm%sVb?;(|8gj>I#ZM`mm>!zZI#hKEFX{l6Nf`X=MF8<0b!99rgQBO4Y ze%J`{{>n2L-M4g4y8xoHblpH=S&8Y;!n!<;d+JG4->M3o8ZFx8J?A-Xm!)?OYl3r; z?i_sx$#_>{o}{=RG}5L&{q}T>lJaoedv;%g8Y{x@P&gU7;S6iQesc~z{p7@*A>s8p zOWoGBtc8}j^wwvC=SU;zRF6$gb4J5jh5K(sqQV|?HQ zH)EZe!neFMkV7x&J=O&5t37r@97Eag>!5qM@imrvQPEx6!D-+8x@96SmGqt5D2>|p zh4K`6*HSTU($y1!DT%&hTY7%ILnM2!C1q_)SNi0UWhKIy$;RjUXM=MdL-X3A$J;TB z!dP#04z(EY3gdL`g_J=QAIOPKYfGScF6i`m7Kis&)%x_^gH-zHkjcj4t^pVhNA!nmF9+Wz+)wTc{{JIJ`mc~*F`4PH-ks$Y1vr$fb1}5t7uRercX|u~WGp4betUI<-WpKA zSbhK2CxADQsG^`xxjr9}zqFpy5|*@AZ5?-{6}qG|@+$6}b|`?E82icm=-_rZ<@=5L zL5~G`L>3q4R+G&LB?;ES+x{3bb}(1F#65L(+Lx7;+i&Cc){FbwFbBu^goQb3>K3$- zhG*=pR?&`|t(d+^s>JUkp|lpgr$Lm>l%~@^9n*C186m=!?PwZ7o>b1rWx8z>_~O8I zAwOB-6)#_nCvFwt+1rm1C=Wr2tP*PB$NJGk;#=ndi$ja=PqTE-L#ZB34KphgoKye_ z6k-GT+aiZ9@}v?$$b3;#qNi?N5_VX*AF!ATe2%vnF_}MysaMy3w0bw=e9wQ=sYKuH z%~56P3x>p$*8(T2B5{VBV*H5KdMk+aYGV7i3{|X&{?2$`V9joA${{8`Bo8s^4LijM zmJdCqJu0Q_8Cuj-vkY<~(zZ@2l=Nl=Bfx|iP8KTZgD~&>gs$cMOrp@10_@9Y=rAIp zISFAImQPWSTry)M)w+4Sy)a|$<{{G0v;@9n#EVbfXU4%U?ku})2Uc)+8u2y-v7FWo zfr#td+uok1`ZPvwbsRUmnKc03Neh?-Ikbj-UCbp3NSVZOhT#`|v+;AA@-6R%ChsL- zQdRD7sdtuU7hhOB+mB@&wJ*T8Uv37H$N(2H2VN8qE9?a{gPyxA_5_ zrcfBvDBCdGAWZMSV$|P&^G67KgOlpw@ZX!z|5q3bFG}9j{RNPozrYZVkG4>2sO7IR z92^b)Ysdcrj{oajyFfAi3My_=`-C?o+M5i_KSw41bSeZn0BUyA4gxlW8bDQ%FutE$ z2;x-?_5}i={~H1$6eOgZ^BC}x4Zof9gQr`+L$vcW{!*Bl<};xB^VH;&;16HJ; zB@?R2TsxYBWrBGsQJ2AaQsc^Z);wu~>eyKZzj5;2XCQ}t^O%Y64HLc|FSP z-M2QFRHxyh;*#|@B~T6$klpb*StimSAU?a@}5(BHM zk@KhLBE`yMcO4}d(%uk{Kic7d@;XQEtJF0IFG_P`*r%sRhLy?VXk{HfaeF1TBcsXw zNICFRD3)bt;5`lrJXof6rt&@JMer-zs#Di6RI}X2UuX)R+;aq`l`%AJtoreB<*}J~ z*1mI8oUGmR{!}Y(WbU&T&2xzr z?>)y(k*`9B`an(d;g0M7_&WUG%LYEaP$M#JRzd0Im}E6q(1xIxCbgYh{42+Ys*!Wg zN(6_Eem89R**9i&{#-)-Ak=2T$ovj4~H{cROqpZQ_Erh@ISoB!aF80rJ^L z>gpbfZ}=**H_iA_Wo=T6P8l?Y8T3_?lukHvi`LkfHfXNCr_$q)Jqy7v=LZD zdy`(*JgQ$Lp;LGiHI~bM+JK!&g$3k&M7Cg?I$bmMB*OUQ9q{@g^9)Hvw7z^SJeX!89Wcf!I{-A)W#16;XnH;VKnqnb6!__a4vOeY|(1j%})1ai;2RieIeB z^_W{i3o^T}{HVnb5{Z~#Bo(w%XTh^Vx?V{n+;0PoKivX{eu@RF@fMS(;UuqTv7_6N zXy=NsjcfK0!cX(=c_1VEV_1)Vxw>vR=y9IAgLt^p+a~4m;`AU_j$mm|w|H8x3-yhk z1`+$aM4|gs8hrt~IZpz(P>VgiJUp91I~K+e7OhxlXz(AX`aYe+>XO(Pc+tc_3mAX%aO>fM6o(wTXKj-FoQQPKAE*{BD(3RKZ|5 zhy_7VmOqS~EKqWe-7>uFT!8Hld+&E6$YZPX6@;gyZthTab^D$=VPW9{6de>#fn8_e z+e==P<}Qc*X@+eFv$E%v?WMJruo&!4kPXY&{w*vadd$ z#x!U-4ZN7+$te-JSTk=r`g~@TxGS5Vy@dzHk)%7ZqMFlKhtLul!T{uS85w9YtULoUC$gfQ-I-k4t*TEbgPs@we?QvEXqb9QC!5?+9 zrqnx5=N8idD3;fU_g|h0@e4KgQm)C?GeMWtrMjINLW^ETDYGMkv?5Rjb+Bc6KZXW>-f+Q zTxLyd!k2YpzOBl=3~~xx#=I8^Dk(q4N|yuuNnaSf`lK?8t_E|Q6SSvHKt%;= zvGsPBCbQf7ooMD_=4VCE4T8+%Q~Iot#`%lS+2)z|7%pD9p^ar5)zIX$({GjJq!0fX z{psm5d4``(C`-mKyWi(Uk;e+yusFV!UwHkfqAz?qX_JhG3Ft>4QdhQ zBIA#lL}IBt?(TKqG;3Y>!tgz;ZR)O_Z2brPV@9xBfRF%lq2sr6JWUjH6orr=AFKJS z%e}_Em)F-nCu}T7QNrkl=R@`Eqq|VvWyrF^Xo?PX-lzu)cwzP9qtuoJA-jLljda0zo{uBG~WA)TvCT>``J zUx=mtnuo!^<{|5$=iR4c6rvxxv%WlUWbH-k^nW95XLquMg7jn;N%Y(8bJ;YyMCmU7mumpDIl|MRzja1XI<_WSwS*izee$LvmGe%Y5D-1{B6wvSXdcx&Y`TPNv_l&Q$_Vp7;a-&FXy+m=VaHEBYzp>QNnBUc=(88|q1;uAv zUiVuT=d<+h7h_YJnu#4&DI1zE@063++3^ssLV396`T8B*vka;_c8%lk!612uM!Yrt z*yiYk$Z@v=B=cI^4mm2?Pi%I={f{Y2M{YNt&TYm{U1>E7H_l&=jVzG1OgH8k3kEAO=| zpH6blM~%Ay_uMQO(E_vdI6lWXRC;0UxEs%J5M|C@tBTr=VQFefP3=8@FkHoCzq}h3 z+k81jcBlq)_3>Rrs!5lCh1j{NdOTzwvg7MdnL^Iw=7P~>;k>`j9ysl@M)k7dIC{5P z)h3}T!Ygv@WB0)xr%&{dN!j|GWfFt;{m03UEYIY$`rGP$+olsTaE4Fk27WmZ<>U)8 zp{ZJ?ty5;w%hng?n?hLqiy#?Yik774)%2hkvO%=W>hmt>_5Od;OQx0kov zi`g)YjZ{FZC<6=jnFs3%SI9Y4{tGcMu9{0Qs2^4Kb*K4AQHx zLbqeg?J7@)@7O(_JN zpW&be#j8yDnG(*)oib(0-$Ev#FlEZmgG_ud>sy~ zao30NX1E-5!OitB&N&#{6EcJzp?*b)&x*Y$q8+ra4kvvE6$(81z6kS-fFy#u1Oac{KrMR2???)XXHi zFc=gm@3y;ed{IUlk0p6kWNi0JT4x*gWQ^izdGn$eBRqcG4Z;4n41&Anm0tR@e}z2@ zu>a2uzJ@mr$f(<(n3tj{xWCl6$aB~5k78->r}po7{ppf^GG$hv{2aOgu%HKvN zp)lnqN~RC{Q3!9Mz&%!&^^HFd^7Q=vuu;K}QA2J;)2R3^D1eH)vXGvTV0&8O+41&B z@z))0)gEL}H_8p_v-^e!%=&`ue8fQ;h#<{?(JVl>k5Rl)+s|>w2>as+w*CvZbKK)= zyTXT!7ND}%L5|1GH?8%qePqq?k-;^L@U@Bn_7n^1^atF2s;xP~Hd$l)1xX+E8iT;N z+4Uex7*wY-)Vm4du02IPYF;UyJz6~MMtbZ+hzs7-`)a31Lg2I5jX@lYe{a9npa)_+ zeVfW{M~DVP1yb(gavIE34nUQq==R<75mLEs5*$g3?0R9`oDBR3W$!_rC%37vA9S^U zczODfp6MT-mY*(<`RNV#-qQX&%kv1Y9~X9d=8gW2cmILwaVo=k}(Li-lBtX)(K^-aaeNf68OozhQNM#N@*B0OTk0be{$v)(=&=R)2c>yFYK5@ON3TpYYwM z|Ma&Gd43XQ5(-23iOXrqk5q1;{F{}ce_vPLUH@-$g=ex$f0yh0sAY%j0bmJySz*sD z`ew?{u-sf+8NF`dUH!p!Kx61U8T5Rl`_;XNA{EczCe~8LHn1>Eo9gY3gcvNK*B+ z(Uh+!gnh|}$gC}zo_%K@)o zI~VEbvME4qG07##qp%`oaR2!q3dVf$ho3ee&;0NYyxpD%5)lgdjMFrJOmZtG+&|az zSQ0(;v7d}rnewBRrySxx+{_ctT{1uHL+mt}Mj$Bo?ZDnmeulOB6GGsB;J5x+upX08 z*b8_6DChB*?{G@ zMsqjB<6EEJH0`Hp{{YW?qo*MC$`NhZ+Z=L%0rP;gjc0~^e2#N~AFE7V;z)D`Mitgk9_h;ZML zF&6i0By;SFPYpv>v1n{;s8_4A7`Ck#F)L<8Fx1wCnEKp7P+bP&_{Q2o;)2VgCL>uF zNs#2qajodM9x2Z{xH-#dhy>;cfaCMg-Jv#p3@wVc3(K6;PVcLuyUPVs^N1pY3^bV0 z%wmNqt5hJlf-?xFAzD6kP@Y>R7J?QpNyr^JNsRFXqAb9Bt&g}Q<&a-KezKlct(TV% z--lnxI{^pJF7Cei`di&~y1rKT)(#Rtmy2w2&|*#$2E=GdB^8k%SIkD21e!IYIZh8g ze0zQTpf$MYxnabRgo1B1A6xu>o1ow70`l)x_G}pSy$!p$hh;}b){hudwk_UgFqPb0 zEL9?StmuwW#)7$&Ziu#)qql$mw3&X|gg$N3_ZaWi2X8p9;u(v<%$g!@7u)vuJpCBY z`_1!Q%+pu7Wrv^V8Mog*o9xOoNaC-)Pd?db;#eXyHlHT5j1+PwALW1T$bc2B<} zcAwn)#PLZlwP!g;(%K$^=;<|o`{rqiC8clhm4jBj4FEpjIX~gmKI!&PU1WUblY!rU z;qd18JnsRXcfr^&Xg5DG%-qup^9Lf#r)`U#8n>Gt;IJP1&wP5F{E3t0$Nd05X+pHa zYj3<|ws?2J%&f{42@tYPL5AzifA38lfDO!H(;)5sTtC+i((QVM1?#i*PXbk_DQxYc zHW$a6pWQ6JROe~keBa?po>pTP@Yt`D8zq|K(Y94ym=(48`1VD|zTWZ<$|_|phKC=% z^W@(MK=1&0wWkmUPHTS1~hSd3RsKK|&#cYswA#pUeI zD{p*GI0^(jYF;H&@u`EO4cP8o_S3YR=zQVIq~#cS1_-JN7%4%FK3k(hxN1}|W=l14 z;RE81o2imqnd%_~dQOahW;{Dzu1{Cr`R2F3@JqjO{p87m58pdG9z+5r7w8gL>CK(O zjxioTry_Z(9d`xO@xQnG-ibr6$M|gjt#KT~Qks)Cmif}Su}yuf5#`jXI?v~zZWpgh zY;iI{<%dB4w#~zC&1K^ASxaI(^ENs8kBR{Rj52oJTqN7*j0XAIy7Axs2r7F$c41eA z{qSu!{AsE^@&P1{!IUIx2Kkf6AAS7bJ@pPVsNKE)@{2FN((KF{d#`Mv2K!ce&%K?~ zctUM~?Jd)nH;!O$7?&{-qaDB&sqDbec=^11>1Q***Y$J~cs##%cITeUU=~pc=u&ri z^yDK*Hlr2$Bgp}(iU8_mTn?B+9R@Yh7Sq+k#}9FRxvnn9i?4n5|M*}1-`WeWUQ@0v z0k%NKR7(R@_3;LfvQ%1nPBJN2jB1%w zlxAemkFC9=JS~(3kYdq7yDkQ`S);)K(Ugv_RwAV$CAO*zZ1JB)kw(qs_JBiEY{$ga z1CRa%@xKu%!=qA!Y|Y$P(TxOSaKK=KV`jH~1%POzM?@5h2!zlK#^&zuE;uC60+F}< z%FJ7A-dU2vJ?Gd685)5G_^QQ@x_<@phUtJoTu$PHVgK3!kS zWy`_cY4Gj_bGb3dC%Ig8YQ|>eP+MSNWQ@adXp5P>{NiW6^%sBA*T-*s<}(*}UvxBq zfeb(`s{N77iOHM^Vt-Tj=CIIfV>nyb*ubgmz~_xAeu zV-PSqp51%pwa+@5oWpYtrvPd=o0zPd7H3oYQbz3D76y5-d+uA8xJiD<<=)p5*gtsx zd+H~k9nS8)`PnZ@aCgCJ##Ho#3}Yy7O4;n~W_t?$#_pdR->UzPPmU+uW_M%X#vinQ zcjacKZ76J40knDT?S`S?BBgbG|K0ao_{`_N)YT;?RIOt3#k5rysql%iUMB6_u(e}+ zHa(1)-A&l&tGc%zw!P12^s>2Z?d~$(dV9HImJ=u3n4Y23*zsc=_tl0B5<|$nI00oZWlz=d79+);ivGDLfM#_zSCw9PKvn!D{}pWS?q5AUf(-n`{D^7Y>vNUr<_OJm2QNLxAE z?&AXsZtHME5UD!3rah_gqUe-sV{c1=j40|xW}+TH`sl&??*RRllK#}mBouB6+iS0X z?&9LbwS#ms6OxiFHj8N&qAoEb*+ItcgQ~q(@WO=kr)|lE?m5UZilKzV=IpkV7TO78 zvEzw14NqJdiJkoT1|XEXM00QL?kTsYEs5o>0X1)|;_*1+74lcRx6qAC?VNgbKF^RO z+H~20x_9y7`uMT?wR}0d`||n4E7vE7Lr^23zQL`i+n12Yc;^p@AaQd?Zukr~3JZW0 zjURzp~$FAYgxpC zHwuVGvMj(}0U+h6^)Zkef*77AL+B0rRT1&tx|c`Z<3`oxlm+4ilMwmFF>ZrHVEvHe zHeQm|Y>q;u>!7%*DoK{_xarAmjqU#w<{@0AOQ5yZ8qMr(4(V`& zEsMi0PwqOw7&QV32$-{OqKVR`mYyJMm^}@w35jOza2nB?+tSXuUs7o2FMQ#be(ift zE-&t1yJd=w>WBt`5X;mEfI4y+=DkL~u5{A|Q==S`RGm$=j`}LQ~m4ziaYTBu& z3hnKPys@bP+^h}*@OB3Y8!#!p$5c8NIW;CTv*f86HBUFxqaGBcf1BEm<(G>sM_`Z5 zd>_~5@8f?-5NkdX8AKowyw$5i3O}2eFW)<|`}vkpJmO0DZU7l5&~k}4);k0NMZ^%5 zRR=(H69f;OuV#m(J$`ig!W(bB^7>oveEY9m20Lr*{?#QfuS8jQ_tQ#)df1>9667Wu z%EK(9U=36Y_XBur(V%-jbqQyO^Ur?%mp*><-r@Wn?9A-wax=JhvTYpC!0W{uZ4P_< z1N#saJ7ACqWN^*675zr!SqP-Yd|V;5EGnTq-$U>&5C-9NcIUCLPa1k(d1!YIXD>Pz zh^}s61w{^e+DrwFv=Iu4XGVRPYkQmF4O3InNtf*oV>w{~BAzPxO_3k#ytn+{+L^M*^F)~;wQZL3$oz4*{X#K2_Yw;~N2x+4 z-=GMXI=?3lMhm22nl{Ijci7GE_+%G@Q`j%bVwZDZhf;H+Z0fNz^f05djcMGeYB(Di z;Js6gmJdt>*3!y9Y_=#Yb9Ym=NwO zNWMhV80e^!Z?Lb!O>Nn_9nebuy|X$RxSFWmR|Ubx+Q4QT>bJvyk%sWT8^({dlsw${ z+=j7pQE>MZD#$=Uxvh7z;w+0@Bt|VCLm0%eKHQaGZ3$x+xup{YvNc=G79)H)KfC+V zsk28KOzwzGR4%j;Iuc$_5kC%g*bC)Hc8uzhQPQ#;K&_yBS&sLBgS-QsaHul}nvxGR zHGtEt3B930s@o4GkdZd3+7kkefl!s4Mcg>NAkvCDGdYtiMB(P4ua&f>lJm-lPVSXu zG6C5H$nhw{$aB14zFn~4!!^W{XnQ{F(-Scs8q0w< zk}Ihwj!|SW^f}{6YP)~UL``pgsQ%0wDp}}3W9by2(4aL+q4G8y6MYL3a(k}Ab6ZRZ zpyx8C3>5nAVK6S6NF^VjCM+(RtG=wqrss+`91w*9VToFDN(NwN4n|;wcvQzK2b#MP zb~qfJ$HVz6ZMgs!*U2c4GQkg^^?PV_Pk&a@Rb1L1@Zk%yrJ#%4So59g2h zF|ho?FaM)<@yg{j?_g;KXlR7*!U$1yY32)V$&@XyiBhML(H#G+Va@#n%qh%n3)_6C z^r3u6nRagf9xfrLiHxYbep?e1VM9%0YIWuD>e}I~hUVsFd4q@>Z&_E$Rj6`H!=7k& zvs>>&;6~c?<|l_A3129c=?sM$4I@QlNkfjr=!Q6+Q^`8fY;(%AvPdjW1+`%FRf;1n zww~reV9-UeU3b5_dVKlvD{npe@WJ)f_0gRNnc${ZmlatqY3 z4gIa@(qI(}SCFFA9l>3dbA+|Cpi$JAi0L)@7-|?oL}5suRdH3XNQZ6?&dBzVOjJZ)J-LWYM9I8 zK)Ny5JC=I4U{Gnya#efXPijyJ0=c7z((^#OM8NPcd6VqhS+SpcE!^}ZOd7WqYMz^$ zl|pUd#rb((PeIHQUKdc}%3*Y}nulAWtk}4k02L3i53dRemZ8IL5Q=8=oqX7x$(~8sD301JHa`fYE zd^h9c5v#(*6T>8DEJb^fnrSaDX=`t0gdNW=?)%l%`b6s*hFq5QRs}%{w=$MurKSq& zAY$_Huq4}ZJiEB_==xG_Z8@i%b(aff^(`?OyI7JKn6UJ7o4Wz+Jf9|DNTvXX{zoL@ zz?i1Zb(Os_R+N#8e0aaIJ!GX(_G@P>obp47s7BV0!@~_HlevNYqvYIyn@8VRo*Nom zlNBG|;LZ-=q1H*fkz%6Ob+y(6-n;kO>2%ev9~5HPx5@mek)Lxyfg5#ihd18*!r^e= zW$J*yhRi-Ci>h#Dg?wtZhil49?ue_K4t>)}q=}0Kq2k++*R4+1!+Q^}D31JYxzc_k zXsMfQ>0?^f8IIT$8cT|@UG#ACnd>^-9(ji!6I+ijZ&4tVPjo3y2pi9(@tXO)Xn&Q7 z`3`n~&3Y#f$K&0*r*$P(!h;?eUA0G8Kk(S_B*W)y^ObjYNm3I)n2WIHSj0nj!JVyV zGMC{nBRJ4T<8VB80j3(ZD$xXhT_IS&D<4jl#_R-Mz+>|`04e4~rFjl7Fl@9S14JLx zFJy_YWWde;@JZ|n6gxfa776TR7hZ`tptQBv7GVnA?c-V z1^u6@i}eZ^3YDs+elWW7;&8TDv;-Z4%a1{-8N(|WTsVT&!ef9_a} z6}=Vg+OZ&8s6XCmk3KBxAXxlge1+RO1_rf)f^m`!R)(m}_QdFyw@o$V!Qu$8Z24ZZ z0eZcA?RcR8xVw?GCb?w2OC-$J6?e}rXv=9`!Ih2PeY|?Lc^l7N7Go?h*862#nwcO+ zC5_7vd6e1bXsX67iPCLgHtq*d>}!f>$E#n|U{vEXZlUBoH;uQXLvol|7-ZY>N`PKP zq0D8Q$zPB{m&ulPCYWg68gM+>nd#7QXgmYDtB9z&;ur_pFlsKJupnlJ@)(u561F!P z%OcCFa+F2T2X9s3oQ4wBdf9&7rfcV6YC7t#LJ@Ffw!)odEU~*5$3qT|Zkr=j-AJ)x z^)Q>?s@KY~ujEZE;1S1$ONz+_cfjxlg}D7++?0m>0pIh&j+ z4Z~j^YJ&_rD7gDCHZ?#!?T!!USerQnNPn?II~+V03z%&7$s`HUfpPgad%(ZZ_t0w9kqPNR@Y z!`v~}QE?Q-N6LuW?^nmTsv_Q`ZpsUf5zCO9wM+){;{`Y*GG!Aq9O zt-%U=wo^Hco!b;-VN6;(w&mFU8f-w5I$=al6V0AW(p=m5xGa_1ztrwNp|=oV8(SR= z5K}boOIIN%#LudsWdWsO*-oC>xpZZ}abp3(~?28m^%!8q?px((Nt z4vuT>uD{DqmgbU*?E@rBZ8OcT1Z2$iVYR#?y~pjsEUXYKCF_*7<*Fhotd`vV&Eroy z7c#uc7ClxT2^c|F{FTkvOoUSujpkSl3jel4@&#TKHF7sG_3JU3VYHs#Rd#CI4jD z&C|Ht7|+tk@fztYM;Nio*NxZG7^*n2E@--B5f~UeODsw~#wHPR*Pf$*7RSw*+trc$ zOBU@Sx8x+`$M)>-sN;#Lt)b5iK_pvSXtjZsqM0b>uH{T77&)TaVY0AYYAoA=s5P2| zDc8LLMw&b~8H~6lfw)vAj+HvunX(+_0#~&C6XnuY)NxG#%>F{W4wEZ!sJj}B!( zK2!xLN*-j~d91+FUW~s=z7&(STVaP}!~fve-3I6$TX5 zrnHJRi+N`aB?p*&2%{FDR^?6ZZ1+Dq;%S(9CB?t5RjaT}To^WKkE(gjfl z_T2P(r}MjCCUUAARf0(`!@?>#$wyMoeqGS(EyG1KT6m^1+28={v2w>kmx((Y#-6U4jSXVuE`O=KVK!q8yGP;=}+AYUaOKJBQsm}wC z4UQ`nHm11TVqFOg3kEqwcU_ekhIKa(i8(n zftjhz;jcg`F<~m!n2~rJN)>Q7f^Ll$XNQ9|v!zF!Ez6Ni$N&>>i4nFaI0PpKSkCi zyKO2%(U8sW;3L3_gkHrUdk4i_(Lh|Tph=p9jYcE_@{$%~6j^zK6_7;8XV8dT!Zl|> zp%m@1Hl*dsavWo6c_6&NL*=5F1foF#TjG=r-VG(>;NhLZVq&~?!d(^%W{H_F za4t|Qv~IZP7>fo<@U;2If?4IlV)!>sww}2R!|<&v3yY!18RHiDl6Ne?R`<^^X5h&n zgL5F{UQZo>(jqErttsr9nUwWuG>+U+wCfxZR1HeHI%behQIT;n)bhRKiD%pS{8$9>Ur#KQ-$Z6HZR4yZr1t~6Ff*8>#nDyzS*VJ@}xa1RpX?#RPi%+S!)C~@_i z#O0~LV&t5*>;i^W5M9~CF21!5V@~~s@33(^`+%a%U0xt=6lMu#;TNp-`{V`8OmLBM zEzJO;yH^$j`Oz|BXmxkNYkQ7N(W%Iq&|R_&yW!m(I4q0V;;Vxc-0a|KrE|qYo-^-I zA`PILnI!L=pv!VnhaBhv=eWLBW1Q$Jhx_rHIU)e0I1i8C82!|!>irUv)L%LBG z%IOK^oUtDnqv1jgi>Y40}mV) z9~_KkeW!xD8$(*zA{*&*4+jy~5J#2Jw!-4wYj@cVX5O1IsrZ2y^UKvVh1*~PwMAiX zw6{zE0MaQbSl5VAjRtGYESehbN95)|(~1ovonxPNEna3&w;=ZhsK&5(6iI>yw#ah2 zm|1tY5lw(YT@pQawwE1Blgd%Ph5FMN*K~~mBHiUC_oi?{G8EFTCWVEnNJKA+#4$xC zF`23zI}=fqh&anFokWIz+lW`zCPuF2kBL^th@zmCHXUQ0+_u5Q9DuPi4JhP;!2ypr zaXe^&$L)6cZrXlgJi!2NK;>z7GhXhi9LEmBVxK!=%uPZy08wR=j;c@~GtiCgH@Uuq zYE2r1q37;_Kulny@RV(YQ@=x-g2t-tzXEczVy=+kt7EHho;yIfjYdOJR}hDyMfcDx zIg{heeSo%G8xby$gljamizIH;AO`kRE!F!B8-@Zi*iBmG zeo^7gz``3=(15(mKM91?vyDrmcww{Z zp^%0z7XeXq2RlM4C%uMBD7R7{gGAY8c$mz>0PQt3;Q)gOiVVfq!cf@=8j9Mu@$6%CeLQ%DzD2ukMJTzxP#j<(m7G~i*IXb#8jq%7(eu95 zW-A}Ud1D!6>w+<3%w0%T!p9^mtb)05gkM=U=C0>3s5jxtegJB{m#^Fm>9)9U&cY4X zk)JGPS_05%lCLzbju&2h?V;J_$M21S`e~7$H$tKEmA+(;5=Sf*T;{eCHpX#0h%#R&j3oGt$BfU!bGpKJz_uf@&|nLUW5ZIUEyyv? zs1-jhS>up@h$4qDlS=9Y$in7GN>Fmk35$#F4Wh{Rpv^oB(J2_m`q6GFvj*_%l!*OCzXghmH;JmchVxTSF7g^ zT<7YcX@OVM%Va2`!q|yRm@AYcMkXlBHi-eJ8Pz(B)xlsBqN7_ZsiEVHw0Y6_@iv)U z9o%Tf*og)zD2=Kd1zt?eVa|1)3P@BY2Ly{Lti`txX6fQ35H0nSRSuT9r&xlGKsA(( z3AHlMp`)SmK4JoEO&!KY!^yhNDdn zxv*JAfZIyexav(0QDMo3WA%(r-MC#@H;oV1V#VNL;#JcZgN1g7Q{KZqK}ivAgaS}u zg1eFKpkOdJKDoX;9L$ zb~u>#JaaOM3YpP;B`b%A5eg2ETW7=!_)kxt8JnJA!GS4VW2N$}X0yPw0mIBAW0*WB zfds-;aUfxYY7R;(Y0%<U+}K8)^F}2xGoGNtE`wWhc8@bYOJR}^ zNbSgC!|uJdQw_)8>p_T7vVe-v%Ib1cvQ+N~Bue{+!3kg&7Z4i(BN}iJO+2_eaI!jZQa3f9yN9^3GE!?9@iRA+Gg2wFBB~qgK%em!NfxCaCK|f?_4ReL zro^mK5XY>$ieVQZ5;fXKie zHH6#(go1)q!WqP36zWP@su_8VVSfe&L7-DgZX^P^#eEY2X5+1Za*S#i)OqdLmSjMT zjAII|m;I{g$g||i5yebtDDoJ?TB~D4Z%L+o0v5R-cjJH?`kBj+?6z3%ooKMbOE16q z0dV#3z2W2V(;z=@gaVRH=z8n(UvBNrlgrbxET?{o&Ba32QHQ6<*gVxOT`2#Y!Gex7U3S-y0(39yQj`&T58X9(t8abt_uKoy1vZg#{g@suO|RB zV`~~9tzIDgajmV&lZ-_;Zo;}zf(_JKOhYyx2nRT>$aC9_lmv~&z~&XxXhu(Gw4+x+ zcwy#2%-FE~iW*8_Z06s?ve@SUauoi70WFoVg_ivQM#3%%DRS051Gqg$xcPukjy!fu zP_;IS=zQAU<5a+EFYuVoTEn; z7^goBv?`+lT@G$Ep*Y7L4&>%))%HUHR=_;Hoa*nnhh=C}WU5n%!Z9R-O;cU!i4k*d z3u05Hm;}3lq=YE+9e7b=<%|`ueSEBk^s_3SC>5>fxqy(&vm zJ-rZB^WN9>$>n27OIrvtv$e1B!L}UE&MxA~@@3wdO0Hy;6Nkj^yzW^M6^-g1xjfPz+h7Pnod6iFYwW5H(4&;)d@>$y5k=xSXJW@bW9YRi)l zuG*l2($AolQK1Qw5=tUw#7kq-+%SjCm9klsBs1e4++)8opd+ygsAA`Q6x)EM1Z}&5 z2D>rVd;Pb#fP#n;WNB9rR80BD%5K*f)(VcnA4WU9mzU#Eyz=PTj*Pmq06I>2^sE*E zT)y_M^`+NdMeqIOhDNnIe^-E7Ywp=wq-^2V!&p15>y$TgvDhKDn8Df;;H7(a@7z5+ zeCIm`1_dVuPvLc{EJ1W1uARWyw3_Fdn$##8}o=DTsK15k9fpb_Bke zi7?B}hC)V<1I%gMI3|+;aT%xediU$o2k*UO?eOXwubvzvd+%f((G^#t)JOft;8!x> zbpxW!Kf>JVSBTyb^!0}*V!mYB_zRVrHYEZ2vXYg~Qg?0guT9!r3mE|UC%Wcf%#%^GI zKgPu@^8t!pvjiEfg2Dq^x#?WhY0dY^cI~c-R3$cG0aqV85#orMqEKth$5PwMF(3@U zCe$MVQACg+N~|_aEdSH%WnCzCm7CcwZ=Ic=mnZ8}LvfJ0IIwO@7HeD0SjbmMpRn`HHJ{56JcK(oO%)Uf2KLtRc&q{}+^!b@*H zT8@uD_#V)2-S{U~ezpmP{rC@i-Du};e)bE(a_X>V-B&Y$Jpdtm;FoGRmmw{qKrR!M zN(OB(+@*B6Sjrp>%)48AbseY@fB|-)?9*1eeRj{kwVm_P79mrt^oSDOVHsUN`1nKh zm1f3-6gwA=OzCP=`s1~+1Ldj|qHmRMeG=UL4%c@p9F_)|u%nVH-n zTpNHgP>)5el~n>3KsFjcp@MkAfy=h^%dl)36v_BGu)C2CYpn(S%7V;eWXc>DKrh&k zaA%}ffyghL9gsGqp18v4x>F7uVIA8$BEQbZ3kGaxJ#y=8Qx~Wf$25)QXyW=Kr&d54jEe& z4Q8E;*(l`OQD(V7#;QnA5ZUPpzcRMWL>??g(zCBbL;`NrcC^+`1sd7nVL-H@iUCv0 ze|x-FtifQsR$?Udj4-nFL3N#Eu`{X63`|ouJilomUGVOb&?sR&^;qe>_uhGa25?sq zlX*EpG_uFrd5@VlGXc%AA|j7$s+~iG(NTL)TbEmFX-T3wI%E5h^AsgIvJw?-G+Ffc zu5x6|f~zuj80oIUbdeq_#w`=MmA42^!CJmo9)}>KMK`Z(5jN1)u3AMv4;n=xtxL4p z0j$WUJT!;?9U;R$6qVXt`kW(ZZ0zDAG@BC#;dG#LMQo69AfA9_>`%Xl#%H?c=KlSLgko{EOdt>x*xE=iA>mw~ON$ zAaR9zY}PeeHmNFOnr|LGu07%!ha8!#BWQ$0wOeWjqMc0G9Z5lM(v&z9`NCo`3^lf4 zB?qq*oL51?SW4M5h~wfJZv%(~R~?MG1zAC1Nr9F|e2z+pnOP!i3;SN*mt3(;I|+jD zznSAl_QZ^3R5mZmRatH%tm|Nr;)|^{v$h!f^_8U8UVr`H{_eki=P$o^!3%S`z-FYo zOVLm%G|+7DNwuP4yqhJ06_|ZdU0A_?@-P02vsYez{OE)CAAPqyxjqwgN_Hd4R@^l@ z@8n3w*oR7>>|wrS+_r5IRmS@wlTJ4>oA~Eej!1u1X%CU-h#*5KQeHb2fa-RXw$TT5 z?O7XKRuKfU@T#&Y%pg!OwpAU`(K>X>2FS)9CO|aLb#CE=!)P+fNoatC`R`Eo-X z82)QmXQqAznD8xvkIpE`CZw%))B-^{kN_2NDTh}lWM~DgSv##xLo_y$3)Lv!5|YI_ z%4wvTdnXO(^}fRrC!JI4JO*$dR&x==M73KgL`NE7?&|K=4(Dg* zx!=vJ6AdEIvH?|jPxnDHgL@ZXi_y?mEX(rp%dZ5J>mHrU;m+2MBCRd%YXhTSNt}3{ z%wRM*j{;0rosTk7#R_}0f#pqGZ1h(s-yLUH8ldjplJSbwpu$Bhj}{U(efC~dPBqff zI!;zrZdJ4@H>vS=&(;bUtbE0Wp|qKhaIn?XqE;m^{%7-HRjisuZMR1yo-=>S(~g;# z%G}*xp80!FwCH(}#8aBQ;f?E(Nrvuzt&C8Zj?jPx5^)xh$*CSVN0!7?x)e(oRa`-G zB6lMbH!Xb3KzuX2+4)Paz42MWPU{+p3mV+J zRVY%3YQ&KVD6*oiQUXwS_g;AMuq?5~D-uZMK=b+HNz$PvM?Kzs#8FnglM~S-@KIT$!J*W9m!Sc{ zEs+(~`#Velsg3WXa}|?$CW3lbkOwt%Nz>#wgt6duUtpNY`cM4H^Wp(ns5~9d050u} zGN~s+osXjQ3$^BJ;xHz0|0|@CN=qlFgVwAbH}fH6@&#bz4gjI|)vT?de$k^yQ6^te z!bVM?z%&3c`ZY#~kFa3IjBkM`4UD;=GLKhAJ%Etyd}H8l3td9R_MySV;P=aS#a}Iv zGzPMXnT;e!UO4Ihow2ozMT$waYHyTdxiwsep&BfaZH#}*p-&1UeF?Is!|*Ppr`Zdx z0;F8M%!CIKJ(aZSUKT5qs^)?z?kHB~RwP4OW2Qfd!{PYSORs1wqHur#U_ABQtO1nV zM}8gztQC7grj>zr?*h?atu@tj=DyUbKq@;D3n=qLFpb#+%xJb@P@1)7W~?aQljY1d zS^}WO%8V7Inu|28BZQGW7(#0%7Z^RU0d}{#ZoMSVStHsfQ?lQmT_4&OQzxWGZy?l7}@jGwB0hrlB zYi7-27U@o9$u+4oi6XL>o+m~oX$@R#>D{kSkB={&eeECr;xGKhmyh?(Uii$bR9n}R z;A&|bkey*TKMLrq;;$ekhHP*{fCJ5xRk4+}+YY;x2^Q-V8}%y7=9=PtJZ8 zp((jR<3NG2bDm3U2jk&zIGwIP`|F>*d;jeHZ@#~N+)bjDIU*Ei167nz299~eebQ2~ z*_y^iTdXaxW*6sA=zsI}w_keU-sgYimmhrhTaUl@Ac_~{P6wMYT0;tH)+Nv_+g?^< zql#0*ZzxH#;EhMlLCo_b6)TNwjHT^aE?(TF08fjW8bS@Vuv}>wucF*Ye59NL6pNlN z_3mwH(foz=#WZ)l1JdwQX*TJ}i7M%>H19ABPn{Q!Xo7&`u5P5%Eqe{3iqb0}8G0L2 z70@i7UgOWPqEatQQwKXB4+=?StNk*PI;2Kxd3s@Jc?3LG9RgDOcRK_hFaE_BuB*k8aJ&7JLLn zSq@dY2k$OnS&~Si3DuG?kT6i?FX7>LLnElR}t> zvyaollJYaK!7WN)3jlALmMW7hG*E|?F38Nx8Z+n5 zvb8{-D(XWUQ_Zx2t{YOur3{C$cmqqD9Vzsr;1eO2n#Q;#9uLog`f$!f9GSpUS&X2e zAXrRxkH{i)DhxnDviOo)QWsXMEJ-;gMD;j>M>)K?uxfRMBOUB59PaJ~m|{-i$(b=u z0w-)tX8tNINIsbcD%mYlY_EAkA&OAJSgSN2s3d-FO{=$NT@F#eox3kC_T>-X`4(`! z%SAyutM-#4KdXeoP!bjwYryfP*FJye&P%70w`PfMyQ7)qhQwV+o$I>@BS1m2hG|X* ziO7#dhx33n@kMU_ER5mMepIv-ZY)1qYsLtQ0dr&oN-kAs~VprZ(K>|E$JXawRH)(DO_Nz_jp5P z>cZ+lG9*BkcX$VsXGFNN7OEkENnE(^UZhA$)G%?&qi%~Hgd%>SLz&zZXon8&Kox4# zk>A?=E73{fp*r?V;l|=jvQU=rW@$BsgDixt@arHWgks4E6HR+*Ck$jLRX`YK>D<25F zXQlEe2U{AKEySwK^#){*7xCi4(^07kYX+%Od58+aio>#c>YCF1XvI9LnvutQ7#I}} zh9d8hkqPyW;}H-Vlz~q8qV1$T1ZOmQ?QCctTchgKFfymM3Rt8(`6(*iM2+?6j3^>+ zZKr(}|P#0MoV9=?~sQW18ftd~NtmZM-T39VAJ7ol7@v-zP6;L)q zK-OOfa8`UXR*>gr?ySd70G%fw0J~&us@f=8*(s=d4@fnXDyv1nVX7fVL&XdYvTUj! zo;WhdY@iwOxIa1Ez4(=1{HZFgvwzCkYpXU13HaVK!??Z^28ntc-Og(QNY@Dnwsf0aSW{Do2Ky_8X>E ziWd+uizotcQ*hO$DrP=2?075~dZPT$4)rL@BB5f(Y8eIi%W-1#d&@$jvJs?UL>zyD zMqr#%XNSXKF|>ny{da!BfAdR!@rU31li&N((+6woO@J+jUA_e zYaw!>NzcSirJ`>DSd?^CVqXM<4N-1RjycQDDfu1*Ez0F$%q;zC^(_n6Ln0HE5vP%C z&HQRD!%apK)d=HxJ9D{{-MwHHC7DRD8NINw$j)p75L8vN!Kg{BS8vt`SOXL}c8+4P zFkMOHfkNzPSG91%V8P;yh78}?891YY2&jimgL7$z@apd9=JW_44c=ueO(v>=_T!hsA$F4TqbkL@}F(dE`?f~$wsWb_fR z&0Z%wPX3Zr>zHW9ND(4WlnyNyyqtzE0cyFkh0zipY)M9OIAsUe810-I%HJUylA!0r zA#GG>D>DH&8go`0Wh9rw@zk%SUY$`en8bhzADloXz=OzNlRNPN+vXLoH!W>Af1IM4tH&Z;)JH;@V9EgKikH z3Bl-sQ9C6!I5M&Y2@XodtDmbz4l}yXk?EYHovH(BoX~PDUZO>m4uEkp9 z3EJ@p5Wafv#znHTix*#g^Q-TD|1Y7d9HJY5{U=C%1__0lcotp{pMCNATVHC&J8M>$ zuGoN^21^_+v_g;Kp{$@h)fNMBT37FG)@*b(-?_jH+yf=W;z?Rf9Ken&UOLfFMpcQB zS%|L!l>91LWdA+}+=u{Rgrwtx<1yk9v|k5ARsCsOdJ*O3MO4q2S#@{M{cqU4L0xD7 zxqJZBY1A3!SMZZvf2_ijd|ZcsPB3ztl@Em&iI%K&Ow^kN(q5%oAQdnx;>I}M+H+#! zOfPr6@DZaT7F}?d&>U{9p+^XlSvwmDhs>J4@m?HO z9<^OLqnq)d=>TG1qALmvRGC3vz1l0HDgugnL4|xdpc{Uw+!71K$9dBcVJgddbJ2P1 zIWRNG8IZ**p=x(Eg5 z)q#$f=TZNm4#+E|pI{v$kf$-1|6nVEC988C(sso`ml}Ib$I+4~zdtUX{ynBKI3OGO zGE^x|@KV|<2rX*GMD?yi%N-VFf%CW)F&l+6T6eHDIht8lmt(P}j&?X+_4TT+zwq_1 z|N1}uo$G%6&O7g@KkC;fbjcxcX{`ZATh7|yK%Cqo)*|SxJaC0sIpXX9thHl1(g{+{ z&tALp8~^+_-u%VS9Pcf-JpIKVf8)_dPg*<3{NyJY)iXkL1nZjQS=g4?%*osrr`RVI zdk4CALZADC2{WtXKe5!?|+K=DXnsH(d}3|S#$v*3>4>M>u<%%wUF5Ku2tv5sP8{a^S7 zm%2ZC^ytZxCojG9veDQqclrQrSO>_M{dZzpT>3`As?!b#E+&sE-h@m@f5f6eh&p`I z*Ph2t)*ICx${r^j=JA}UYL=nBsDg&p!lJvb!)|-guq2mgmQ1sjr|pPzqBY74T$0|Q z8;x9gUqi`B>)we98N&9qw1LT*g}F7xLAtTou)8Rkm3n0iR||zT^ayvQvj6$ZXugsV zs*u#KHD)kkUWsmz%?D8*aFvX5{UF%gvmSkKk?%ofr zF?azX4P@FBNj-QsBD-n>=H3;MpvVL>CZkU(at+49>xDRyk%?OUG>YFVNAt{Jd|%Q9m>R$nWP@AyUQXw zA*+6t`Sl`0>nz}5aB}@+E3TRjFc{%{<+ub3)+g3Y${-9?u+qCTCRp{0@Gk8Q4~|Kw}Yfa25Hh(!%z^G z^{c!}=y*`H$jbnNwccIcuPz@S4+k#Ih_yQp=Wl%em)?2%uXX+LPx4ukKZAtA#uH_v z3_HB=na_N=`=PIafjtga@7}Auxi+=T|1x6eb;-Y1AV>NC9w5uryvZBl+DF z>?W+rbnCvXd5m3uF#wq>h$kLpc-2+3?_6Y@#h5wgtz!|lrgb@bF4C%VQsoV*RD_5$ z6)8kM#T8kO7#I>w>X~P&JemgAgW(XzWi8_~#u%!;Nfcqp5x0&P-^>99UX+L~GE8lY zcG)AgC73xVWd=M7R8PGdtu3b2&9VZD2Kh>WMz1048i16&3pAt2)Zv$>$1lA8@^Ae1 zZ`kqfpZ~>Qf;v5X@ZkOTKYstc{_t_=mOHw8yI6Q^t4^r72O}_0Z0d<}aDjdGv*Qvy zxL2HB|H|uM`RBiK|MT~820vZ@{(twczVW|(<8Hgx4oA;*+X7Aph2@?U7Xa0UmKe1( z09nHsbCf!kR99M$RIuH_;T5u@lDTDUYdsf{3Rbm<0Kj|VyDgkfHaA zZh*-f+3e8TaXFqHK>v6D>R-R~=4VfjF5h_b)${vz9-SWcyJx@fYrpvVXJ7gc|LPC= z$5+i_qpzE_$RGpky|Wp>Xvl#G%PK(_M%KL#bWsgc(w7OXm*)dmDZ^XJ|ctl4s z7nG}|-H!^C9!0RZ#V)~UscC+P$o;Eu2-Y*s08l^SeVh&i!&9}iTSSiC9r z#Uz!H{w4uHRwWbyuB_4yCxmJptla>>WL9G5?Mhz)3~d8}@=)6~Gu+;FhaM9I)yCm1 zgF4n$0Co#z&j!U!n&E|e7ijcVv8f4F8(0ifK3>@ks#=|ABUi$o8+42l3o3qJ*M;DWoT9)KTpd_;%KsP3(?wctj>E9|;!0&w2J#A3R5LibX+;jG!2`m_AJ_i&OfH>PcQDA`z z0Ce^ELW`ab0%>d!4qDhsMMqyoXj3L}%zbHFc?Bj&RRLUN+XJP84omBd{S1mYu#Q12 zwa6SKExMl-wSgP9wE#3=Q4v69;U9`2OVtg(m=>E}^i#&(;MNr(y7kmt)Vs1GlhGSN z)-rXJF=@G$gj3NevnWUR08YC{M|m|*RwrLQ$7s<1q%{WJRD~H9Z6YLCOXapGtw?Uu z*hJJut>$&6-FPo}XLMx`*Pp1Mx#*#x(aanTfY4zU$9Gl;7o&|LfQYI9Dy@i7ikAw* zVu{EZ?Nn_a0!cnfvkJsjU~F}iR0hU%Jf6zHGIu}#{^atqEzOn&k!)Mvz-nS=+1wAw z^|{AhP`S6(;$^y4Kf~94`D<@{;WMYc{>7jF8GY$bu0DG2`%fM|R$tGWwWY0{t=YPs z+)o#0$5UvWnPONY;)V4{wwfX&?A>AB&Xzkb-2d7?`IVQx`s&r;)E{5{hu{6*zV%1n zd}Vp1s()jhkQL469Er8uY3}6DrkSh^*Y10{piD&UwQFg{-6HyKm4En*AL!#a6Bv!)ICR%Y|9~Iz+GHKM(5E6qDRlP+?(8# znqO8elM6w=8U!=z%A7dU;AE6#QJLj+#!6=7TDrjS%BC9hovSlz%fTSOzV0}NB7=a;z+++9 z5qWTG8DK`EO<2*gUd(BDpGA@pt0z60G#;!HlVKhYzyiVA5_flZmCql$-^n1`h%BH} zmZ^c2F0R#Izzr3|#s2@D`;E1+ZMPWsVNeta_6nUc3I@j-Gq4(Bv0zE~PUL=aW)Fc# zpjuS+84d29e`+l?jEEx%3X@=z{Rc~B714q;vkpakmE8x#V5gAGOFGIeIL*Wf%Atn8 ziQc-(a!9poqKv3WiG^u-S{gU=mvi0W9!M}k;vjGXe+77GMs=0WsZ$0-Ps9$nywiH@ zl7Y3LMvS0oAbYbGW7|cSH$xt6;U2sNv!Q}XptXYhqh4Vvc`mO&AWyFiZcQ#`AECg* zmIj}!+P+ZvT5<(yZyDzzm2&r$#hR1B;{P>wXM&#=vk=`<5mWNY$cQv4%%DWc2!RAV`ZY2;d?z;69w!tzF`^v=O|=CcMNFumpm26}_R)tQh`4j- zE(5;Uc7X)(=@Ln1W@3Qq+|1L19ul(nLJ>3>REh|cTg%lOo>{xq>f?R$+G$&Lb+T3)c`4Mr5qRz8^-FESGi5}VvD&^ zwMoPKj-gZ~ihu;~^>)apBO)&33zauYY9rKIxep*BM%6jYtcWYB4%KPZ46s9tLV~)Z zdDf7^VFa5k9$Z`XW5R~%RNgL|PDWR=@$T{Oa7E?wU|1-l!hylaCkxf0F%}Cj z!n8zqn`>w69VgMsY#5`)9V-7R4RARK%`x^Z<#vbN$v5As%M}5!M z#kIf_g4Xuwz!nH!g`(IN+F@BPPCR(;XUnm1vDj7~s?XFSi^NpuM$%o}P*1%DsnN5| zfeKKGgq%wLDZGCCCQFraFGw_4xYo^ztjOQs%4B;vm4N z0Rin0dsL|w98aUk)fN+7S|N0-cVE5x<*$9^?n^Jd_u#$nedpbypFMp4-S7X++s%x2 zFk08TsvBh{qqk;2WXP~D2TB*7iU^F}#Ml7{) zEZCuO!w3xZ#!%5+rl^;{8AhxRt(N;{GHW&q+a*M;;SKs_+XaB{2 z`~Ui{?TQNzQ5Qv^Mdy)`FaQj>AE|XMYUV^|mXm*HABw-2b=s%D&F0CzEPi%QS5G~rL z3vfm4LCj^I++ab~5;U5lCvo6$x{VCV4s_9mT^SMO?ztdK;`YdvGNFQNcesi(g_-p( zVj9b~qeQ*N>KL;*!1YqS=1TkV%z00ANwV(`?B;(_8#t znSNz;_=BO^(1i1H$=He~X=t%;Q;gy!h20b4Owzt)=R35c4Az2(50gy7>;Ok?V`QD! z9FQ_Nm~c1rf|=nKl00CO+{`f8(mJ{;hG%T<45lJ01S{F0VQ)k62XGy+OYCQ}isi-< zs2H8di<&J0%v@QxKOO_M4UnxJlF3F3eH*V7&1}L?G0w9f5WB!s{=kDfNroakl(e-B zW~Hnmxv;U3dy}S$#Nr0+EIsZp9$_}(jDSRE;XO@~I;^!wiA5d?5Um$Jrig}7v54x^ z0f{|YN+e6h#g@em1gO4%QS?Vzv`&z_HFMWu2eL7aVhh8V)j$ojg!tL77$l3j9Azj8 zOY-%R0Z2IAWjSl@&`*LIctF%0*76!2HVJ7-DE-k#sj{jCcSS}23>DI>GQ{jS%q-ni%^-y8`h>ta@u!^kthHf! z#;oFn*Vz)eLI^<}3y(B2G-U>?YPUvZWDVnvR*fqtDY*h{wlw!ul9{G;UXNWhw0xy@0PMGG%`XMZF3Yfae*FTIL6coWS{Ri@3`Q7BCWu*9BP%8 z0@!_!+8IqC>U!)6uYXre-x?%B)m4W0yy1H1awE z!UQTDn=wwhGPu0EwQRhTuPNMkB2*?Bh&or)HUwJa9^~#txSK>YDz0^6$8W$J2!b_>-hEM-oEs%Du1)~Cc|V~?dBu3p4n#d6DaYz-ekYD03&r!8DMu)q z$KK^E>havDEQ?~3L|MXC{gFK42vK$wwiTn)ic5S1lp`EZTfbzo=!1Ary>V?3U&uwS zRe}1dC%z(k*$EGMB^VMO2yvQhjw_ z!C2~j4cw6+6;WX=6AcT=*bm`-rJ?(&>-2?R_}s7k&aa-lfB)@|Y{mKM@c!HHe*EEw zXYB|xJDkCNJ*`q4e6es@1a@~ifW}PCtwy6UIz+m>wL^^l$CnR(<2QcmzxcoWlW)HF zt-}kg5r6PM{{CP8(O=)USI{kX$`uq&uTT*};jN)lBP`#H!j|<-uThcmSR!Ec;{;;M zeT^tkwqDzE9?L!b+m=;MMN6lp^n@cU2{1|QW;0FUxZt*0;mA$bVF9=M#YZ=Unhat1 zGuqAPsf9G6s?AE39%kE)TRt@@upA&|l}rv5PI&q3#lQUHZ@%%l&%OGESK0xe`Nhxt z#(()+|Lgzs2kR5uwezgkuKQVR=2k^K>erW->*-{zk+w8jtP!?$oEV%MJGaGrmUX$p3O_!N#ekf^X@7;p}2D-Oq-ur1?z3>VMsxz%Ll&29JT7!`=7g{C3 z!$?FYy&mP-qYgEVdANe|M%hC_O#Qaec~5~)!kUnf%+UlFuCK07UB)uaP-Ki=Rp)ssOOj`=5nZ+^^*&xK5`zaWmjlM1GbR#>Kv6@I*Q=7MB!mP zs+)8;#Pn~Wka7Nr#>&t6iPo0xY6&Pv)Q6!aT*#>W0NwuDt z7mX-Ct`p`rFmD>Oh)XSCDhT=tkc^E6_07Rd16wt4#r?xDYpgHQy4Kc~)7sTc-NDF+ z1!9c6N`ckLWFhkfL)WP6SY}pKPQc(~NpcM~+W}7B6WpOZyrIuA3LXO*gj+6pW2GK) z<>u&HuIhlHK^p`$qto^2{QOwv3mLTU+(-so<6N@g{jdR(%tlACQmpPSpqa7z%EIu2 z`vaI=t!q-tt+V35wAlpF{je;6J-&Rh_EWPe^%9yvaM8Li$MaWSdj0%^x4#8lC1f2# z_+yoy1wvs{#!Rr<;rzwdUi+NO)T@=CJSvM-cqBj^$Jbe5LBf;BnP$EQ8Gso7d>}x+9meJXsWQnz|S%@l`WQSvW(wdWK3y!w5#_Nu*rJ;K_(V#fb$%7F$xz-lW z(HpI`r3q}+&1PvWX+a!mN2#6G%SaS-?=-ye#m~L?+8ZA~e(&L_|b)!$v zo1X_EME9(yu_}vha!2p0skJ!HE4w%Os;Fhu$=AnMj}K>OYHW>h(jQ^fpFEaAp_HmT z*upvlAPFEc*o3f$!K7K1>M{BQvSpop^E%}~N_YgyMCz2kCYBLdTI z4Lj1MpsIT1pu6|0zAndQvBP@3+H#;-W1Q%Q%y0o)rE zCJzoTq6-(jE$!^g?q1ya@NIV&%$GYy9-En4WZPlc1|6o_J)r?{YUml~C4@yu(3fs8 zkGu)@H(GEc7eo4uxKoir%mH)=d7v1giI^g5su%=ykZ5+iI2W!SKmM3TJG9g3WX8jC z6fHoWEsO$rNM;M7SDgFh)%C^w^I!dqU;DzBK7X~IE-!Jnz4*@e-}#%r{En`D!FfBJ zwYGH8%=`NBVr{h}5BDzn(nub#G@Pza*3Q-*%}hjyhHH0g)+7J1_80D)T|a*3z4yLJ z_TzH@+u!)spZwmRe&+C5b?ZHvz_T())Y$kIgro#!@vKJ3kS>lYx3gjFEmc&YI=xsi z2=bBb+Q8}-$75ic^l-xkVlW9q>8L#hs*6<)XUA^ro_k+|pok<=T?TJrs&~ndXV?e; z?%3M7X*&e7+*aN_xsm$bDEuwTB*Grlf8z<=GqFw5ulofrzV)Yn{pM$0H>|Gp*MIXL zed)8m^#A>T{MV<4r$tc_miuy<`93<}^b^*;uDEFHsUM|gFv_51S-Pw(7pK!TtsS{= z#XH}9=fTHUU;oGd=x}lV{=<)7{L*JGUb+9>zxa!X?>#;{E=z|AW=otm0GX>C>;f}c z>&(y#W-WN(Jf1R84IvbQFz*a4VQ4=jnEp#`|W<|E6 zTi4uT(K}l+5DRLqiq#k%B(0+ng3HgA1N+tG!;f9Gmei!lqw=Bo3=~y^xV76 z9j!$f&04#6|AklIeEI77s-I4+v1BytZ>43C{9%kI;aS;~sNIPC%J$nE9-#9&9Nx<~# zTd$nGaCUz0-s@@ykm|6k^`c+Gx`75u#{Ez?H@93=e^&!_Q!K*>L;LCt+1t$`_=18D6VvKq%8sc2a1f<>Lu(bGF z6d(B5JLv+uEVsqS@eabF9fdqBU3nI(k=`Y^*fS6?n!2A(r-x4-y!7fTr_<%>wD#Vk zy+0cd%klcjwcOAS5*|H#*me2xYp;Fb7ru0U@A$z7AAb1W!w2s_dhpJN@4fv2D6`g< zqu5TazOIJ`_ti|+4tHL9MQ0c7{@GW5{VQMi;%ENkkN)(pzxDpzLlOi?1s3ZwUQCt4vnr{S#jn=izVJ(5`OSawpIt4wymR*A>GJHQM|aKcF4i0{L!+Fu z#wE@%vBm<%U|UE;Aey-aMhTwL%f>*c&M>q_L(5}b!deJvDi~Rp2=%NE zf5;XYkr0+VkI;>15eOI^k$@msHIXbd11XC^C|Qf#{@HT;U;cmp%e&u$6R&>em9PKS z*PmQI$=*kf22(tMnL!88Va{VaJ6dD!-pHjH=@jJZ>MPY#Z)I+tfQ(V4L~}u03_;F{ z@Hpf%@+&=`!0a-5ol~|EK@? z_x=z6`S0Aj(;{mM4~R&nSErJoPv$V{wW46%gBvFR`ns;@9i51N%g!u>qs8vk+%{`@ z*Qd+2$d+)L$;L-^$`VwG z$Wj#Vk`%p21hZu^Th7jofAssm@9sDp-um2Ii}7$+5_cKBucWnBrZgCX%+G{XwaAl# zkOysNJ%E5&Yy(L;N@AY3Y6UI}1aE|LdKLE`4oHqVT8n~|{fC(6u^Q1J(Z~jiJ~I&z zX%ITT%4HEBEzuCSAq)g!BL;K7P`J^oG6}BBK)9{ZP;~Wv@xqJuUwq;Be)o5;udZIY zxaU1K;S!Y;VcWG+Uw0MOgPzIdF8El-Z`mX z!Ll%ThfjYD@-spx1aoJHJ1@TSmf5*-Z*YRpAm_5f(;i82i_sI~gd~F2o+&J=T`(%I z5Fxq@EhsD+g|-|HU-;6O&hA{i_u!ou?%g$LJzZa3Uj;p}_SG!fCb1`d9=YXeY!g(K z80#NU3-q7}&fUx;1EL#(Br%F_*IpVMxQC3B%|orDiF$^%u`LTiowOC@SVg2cbJWrq zB2JtFrRC6$$7pA}(hjYejm{KB6UH)*Gw`zGF9R7_a1wGfYv?`pSV}ye9nbHc$vfoB zg7fp^HM%#ru3?nf8eNfFo?QfMj2r{0IJXOHV~fT*YSy7H(A0J6?(}}=&iTC;?tk>? zJqj1cMqNLCcy(CTi?e2KE}+=~*#u#+g+zBZ<8f((U!JVn1=U9v}RLWm}I%#t)^*ACCDLR_cpxWnPG&^@PY?dXCz zd0bAuLjLMoubp4u(fZ{2bcyvODt6B{BjI_DVBX#{;UX{!{b>}@2%sUe?iD_`9KA=B z5#0jfx9IRJ!Ax`4Q0bCRfp&_h)hNK#yTGU>6p~`-ZnV~TesN)qA3wNy>Fj7tXNSYO zUPiT4~<=9T^rNbjDxEv1cthMt+2P}8mFa6e+Kl>}6|JGl>`|WRjuwMFoJGwiY zv1bCNOX#iP{ET;~l?1yEX70IUBQu8Fgp)`&aTarT-LRplTYF(f4y>xt@!JR(lDCUc zC)RV&cpNQ5w*Y zz4t!%>X+k9-PuH-LFPDh;}e|vnSb?bzwotR{?$MJU;g;v>H39NUpu|~A$hcB@|Cs% zCmvkAcR7KBQ{vg-?4sTM_MiRr_x|eLul?q4J#N<@K7RDZYcH!mzV6r7SMbo<;!$nw z-CBdXT#L2rgzPvh2Dq_hH4~gfBOBSGO&sV|%PmM)%exqf#cCy60}bsg2pbc;`>9_? zqRbUp2$57IUvC+OJ{VnlDYqLq~ty`zc#!wy4(th*S4=uny2<0DCk`3$hsQlDknI z{aRRnhIV~=^zH}WyMO;n7w0B%6(t6m>1!uESpO%N>#^Bkfw_2GqCbVJ`Wev}>`N3f zC`fG*?$$v&zc@f$8s~9Ak;s9>keDKDL_wF3f|Hq=4-z?81BDx z4~t*x$;xXpqP2|6lWe5XOr~*mBMdIG2^2bLtE(Lr@)BdwWwv{0=L)Br&bs4;lNPBQkA_LEk@XiGS^H7b;8mW+_{kS;)ppyBe| zqZ}$t5)8$x)gJXVX3gLpeNSj~Po3;s#R6-w00dX-tkcALM;vJ(H*-joMaPHIplmJC z%w)7fGecXf9qjCQ#@0^a)zy=K`cHrR$%6;q{PS-}SnQa4WoU|;m(3Ax4rzmKsG0+0 z(MT3xtR6;uiN>Ga(}N%{c7N*WUQ-!-pR}`S`=U&X0{u0Dfi&1pwgj?B1(d z&OPY;0BM1y^3E77k1|wTX2O4>3Iy;^nVaX5)Zyy= zq^PH;mV0QGLjXWHy5wv3-aD$=B8M)!H0KJ>)8VuGRP`z>9}_MusT&~9YZw_f&ytlo zt~&-eJ|2uiG!Y0xTr_;|-o5+UAHDPLncF#*?|<_Hg0%3y1$Q|DTR0ZQ1U@?G z$MGBRYNd&yl30>0Hd$kU;2ml}%3&sP6Qol%kgJ(@X*;x{R+Lo+kf7(*$*e_+ngKSW zE$F9qac{l8Iy*l+e)z%ldfnF!NADh6tvij1Gge}HgVS^Z^%u zRliowKIlOCVY?6jUfc<|waN2d=zeEyz1XJe;-G zjEkB}YQ_az=)GSPW_Ey6LDOUu9H7+` zaZm&q#Ek2g_NVRN-7&{=_u16M+~OR%7#6l_J67+=%r#wle0g>E;x1LYfbw892mFyg z_A`9tSHAM~fArP$^6K~g`~U8{-+23#d#^!8J08yOf{Xmgvh+s}-o9hFclZ9)HGSo^ zo6FkL9C+c*J<0#$zx{V#`|aQS?3aG+(bY$*{J7lp%O`!+x`IIOt9Ng0q0!w>SY2K2 zQm<2o28#kSSpb=|!C6NR_1vrIS(Xt#EOH0YbR_%{l|f7IYjK=WvYj)NJ=?2sL!}&} z-(k{AiA07+fzrR!^eE2+w&i$seSIbPsh=*NJbL>(@4WZngXnD*>?(yuiy}m);t;6T zEKq+rK#OF3RYD*)Pz8r;$SQJ0Uz(neD|PSQX_1@AVrIA28rwoU91iVpTn>iSiAAeE zc>DXuJ9pp`wR#sQxW)dKXdfBfnqy_enalzMLFIuwT326=r;Foy`RLLv_5QcN|K$A# ztFHwI%Xw!{ABgHbt?=Re%(l(v8@0AUMWuBn;N59#tqD<|AcWm0#*Ve6G8T811q7lb zAzfczU0z-O%2&R6_0gkCAib{{P!(l`bt>FPc2OOd*Kk{OVP)^`ZS>qNW*%E-p)hy# zQk$I;h%8)@T)ogzZL}<8sbMct8B~Y5L{V5gNgP2{KPUJ~R~;(9Ss^w6-Fx>nGBt;G zeD|I2HG`4eadCdOK7Rbf*JC@J9$$jdpx6az>_LgBx#$l7SxabDC?zUkjHL>jxtxhN z6{lJ>qnWjXHCqm6XI)XJVvW}2z2nM(s}4x$kz*jTqQnEYBatnmrJ4kIm&0IgAMowq z-`3T?``x!kp;I1T;9Y>3m8I>)Py=Jc6D%=SL9pIIWSCv24T(57;+;Gc#tLg@vt6$p zu#r+lniE$R{<50c7aWaqLYhS@F)*4%BQ{TFS+7R=Dyt~6x`#Lgj!gJcp?+ZRIN!w7 z>;U-ndfA%2{NgJwzxo=@PQ9Pj>*L}0^{@Zx-~9ErKYIV+(vI0dgYpq(CZAtrfJyQX zI2~G`T%%BIl<}@+aSnuMkN@&PWF9}}4m3kMd-0XGfWwoI-v|56EcidP@CSe7r$M+J z-~8M!J~=hfn!(VUwobXIto%5G326{FdNL+dQtU&|o;|3kPEcZDk`lQUN)v$=>z4Ol zeEF4^-#DC|UHkRL@o?vO_V54T4?cYVJzEyQVru|A8Vd_xPd+>OQK5$g0dgz#C@<(0 z^Te4k3Gxcy0ikdr^MI>NPsTe!inGA;kt?vtqt)_grKEWixh20+1C)1*wV7$^Fv_{f zf(0egT<2D*fkFph#EyC>s}#Kt@87%o{qKB_wsv(M&MwYhJhrp?hUBXNxiixKVg4mi zQEd)&s0movX}iHHXK`4gDCwbs)7rET$Ms&9hfx2^D!n7xMiN6S${_qp~G#W1ez@*GZbOg z>aA;Uq{bml4c>4jeKiA27;%S7u%tXxOPi8@tdC=kASc4-aACLIBv~Gu#?HEd^F?u zJ~!pAr(i%jjOZ-`7*z~Z>_V*4`dA$brBd&jufUqA5K_HLMTtoKE+sn-AxVD@1gXNx zpJ6+*wIoj)Ae)S8#`jyXo@$)a7zxdJL zeDtkveDjAO0$WjR~B`+B-_e(?6>pZ%YI>x;kj+Vz6}^oQU3h z$o@3|R69XP99!ByR*{Xf9LseP!Pi8YQ|&VywFR|3RX43L%B*evpSyp?I*EHm zI}d4;Zl`Aulg53?3zn%vDC<*5^+gh5rC zk!IVAO;G*`$vZ?%Zmi}SD0mqEWj%lC_%Hvff3aM&<8t;#|K>mZ+yCbGU%7a>uj+jT zI;AJ;L!ImMzy9T4`So9V>Eflo{F6WXcmL|&Tz+(F{jl(;)%xY?AHMUK|NdXEPd?z` zxZ=pe#r;=aYv&i&9le`(Z$#^B^NzJ!TQ1%E!q0#HvkxA;e{79?y}rI&*A+nv_iku3 zixzOCn%U*$!|UsH^);}2ofYKpH1D*uhupPtb&qyvh^ob?H5q(v+3m0zJ{EYgUnuPj z9nHf6Z4{mf84qS1U0H3fphQv>W2Y~H*czehCzs85I6HROsjEeO1snPryN4}YyjjI# za*u-fEY!FiU2R&K{Y)Y6%G*kNB?Xl#gHa+IUDX5Q6k)~wet6I%Pyg0C#Y zCucO}Bi*~Q(WD52HJB~UniaI9sVk@fx#Zn_C0lC^@@z3+l;_DBta3C5npswcZ%u8G z+rCsKLX~{hu=L0ve1sO8k*KK!LyBqu3&|$f1pn zAn7-jorNnAVN{a!nF}HpkM0D~fO&_-U1J|#8jxDKF#-THy-;44TT_*tKSg;bZK@ZL z9>d@OLze@;p-v%W4N%yQCUwbaT6j1twz_v;k$HP8%h}=L{OWXyetBliy(ldXY^?39 zF4T_eOd^I8kJeEhT{fbxXN1YdR-@#>0$QWoO{WL%y>~pGzxdM2r;|?IS9xwRh@yt5 z`Ap*;pjROb3y@n+Nx!$dcu>7FuVb)TCVz=^VBefXbs> zyTezlqFQ+>GNO^%X$uKz*5JClzVcNUckX@ui(h%;^PgFc?a7nJ*VmV=wTrX!zxk{0 zeEiV^bN0UGCLLy^Mq2fOwaO={LZ;AxEcG>`qEGY_3OzTbYufi?2Iwx6mdf2^7O8dDPrR@Bm~K}j7fEy?Q* z&4RK=Z^km=Im9r-s>k2PHIc&pc?+p}GhrDioSZULXVP56+v99=wGX+<7h}uD44zsSITH?}u~+=f?(9kg)@z1-p5 z-Gz1ahwEdV{PD+64rk{O4($kc(wVz?$;i$aH7Na|0UT%|$n~&8<9#vC7)5z{f>f&* zM#Fd_!%-kgfqG99m3oxtrFd-C0%(*&^1iNDPo79k9(%q#&TVVJLTNn#I<^Mr{Okbe z-S2!?GCI!B?_Hg)xwK_D9F}v}(i#kG^nU&E_4;^z2iCAG5`4Wrl4|GXtzY(c z{__2IzW3qhzVhx@f9b1V`RbSNzxBf1H(&p=-~V&JB36T-RWw6~y1IHpo(x*3H(t-` zH%ZS``XUw-cLDfV@MUZVCd*+bpkCrTFQ?`5x{O0X;2b_gk%teWi0H5HfOL}i@aWky78gy;j1o+H+#*Mk@H z0ejOfh{AC>9B2y&usf|C0$CcDtM$6``j`L7*S`MGe(~b9dmp|1-tYd`|IPRQ?7N4P z-SG>^;nC5z=RUQJOMLPE=%e@WM8;F9*h$b4z<~I=}qk?e44vJlzPK9BL6ml!O70^VmV`}VzVuT?G2OP>H zLQ%L02icW*CqlHSwKC9bF}R3>%M(fn)x0VOU+Fh;@Q}}sLIqn8%+l(OkkbZY3!c<^ zs1>ep;qq4H!%AxyC9mN}dp}((nkxX+8z~pghEeO83SXAvRe{`~{%gZql^jCl?naWs z$GR;`w84wS!aLe?*nz?j`CdvI97FrG|1^eDZ-!!gzG8+n)? zE}mST0w5hT2V)pqMZIBV$*meL5mFRRycOO!@GbHHc7w?Q)m{!g9Q&V*5kJ=y?FV$pLm% zBx9gz(~#w}N>F}2t8B{Krv7Gm$%3Y(T|c?{#((^?CzlUD_qi{gogdm!C+{CU{P?BU zUZwH<@4sugr`DjfCUvNc)y-l(dKuSLxf@Q3lCQ|xE4Ap@R7`=f#jcswn#*Wr?Co%- zY_|4elb?D*AuVNxck_-GyY<^)U9T6=V9_-b14cvv4+`2~_|}1McBVP)F5@_16%(8& zAEpiwOIy}{`o*t)?fl*gr|Xr?7PHI85C8l>{D<}Oa%n^{1*FB+nyktj$TD0t0A|+& zGe$==P@c?Rys+iLQPF-cHs_9vW+|m5Nl>brMPG1C%6iVE<6yQS zQSu#Ph>@Z(&r-sO8E?*?DxVRG6w9I>M)^w-$Xc&lP#G#SzF(AczLl!fqC)QAq?1an z0JVsIMqh~$gqGeG*%hj*0H9GBrfNSj-a=NSv&JNC{wQZJD<7bp!`jG#W1>r!}$C@OauW0xsyRPD?Td#Se|86B@F+%zp3g$OECuKN-jTlHAM`=wf7FL zf{|`j} zf)QRvD;0p=P(vh_EHB`oaXmVhgDs2P=)KO+5$TkBW3*&lUEOgwEN6~oIX`h->Fsdr zzzRFzAldN%b$|TP2mSH`uBQctPVxwtEq2u}MPGR6!eU3jPmlaNfBg14-+1tyfAqnt zzxcsRH_4AD{Fh-!LGWb(>0H+2A+FktrbN6SxmTgOV81{V; zW6rg9mzgK?^tY|sR9#k46v?V0CCZj$k(LBWwqX-CY(p~OC&RFRgyA(8 zasxGnTACD1vQ$N)SPiScbF&t%!2N!i&L39*IsMR zF~^AJi!Z(aR6^B7Dj|R?c-ZZqef-JU<)f()OwKA&4X(rBF)38Syo18%Wg+2VMp8sP z+}TCgm*sYUGl@{fxu`)Q0KEqG<^RcAEg4ogsy)W@i04YLX-?XBAG3eJwVW`L^VObv zspwT8RZb%?vP%Sp`gA>UflveJU`L2hWn!wXR5_YbIyF&5mV9Wr4^ojQDR^tQ z=y3 zI<+-T7Eq(c*r%%|vhSp5GbhKIyHhILNEb4Y2NEZx-1_J`l4H}a-U`H!9y*=O&*6Wk z*KqvxkP!$dG7`-LDtD{{2KE3BXNWX7<=;69SR5zJM|?;mh3#az>Qe^2YCZ8d1?a0q z^iy=2KUmLyZMxOyV0Dx{-R@dW$La0EE3cw9z^KTV5mV$j6A>hd6(quXgp0PU;go@r z`htitu46>TbxMS%(Os!jmIQtwF$r4gTmS)Kdd}pqG$DjSy}8{_^Y-HM!VHTonm(BD z94KX!DQtL)q z&Wk`ooOidLj9phc(>z7cryw+~*c$#0_Tz{H5moDr!Alx%i+UoF!DL0C49|RN6h4r5 zJmlCAHQ6%+lD8Bs_~4!Q&bJrt z%-&Q{NuNFpuvFN;o~V=g%6rQPQ^8Yk;0)^)LPqis5|&hpI3}}MBnIdGPv!Mrdq4pI z0VSJA57Ir2SrDK)h{%-BQ#hPdPX+Px2ZgMUcyt(>6Z|#8xk&LFsxTX~Ge@VYh{`v= z_6^x=_WQk1CbsLxkALv}H@w^Eg@_JT5mVX$i_I(Q5q_N!WoCDk2~&ZSQL(sG_#G7 zVvo>dMI|GK9+C$l+(K17tndB&;^MPUesX#5MJg8aQh5`i24vM}iLw`7lui$CBH=`+ zLjgF#!$jzzOSmej&Ao#qoyZYIrRuDZIlPHfcVn{IU}fc8mz3jP6^~Z5Aa`YcrJNFtar?2Fd9LptQ!Pdivn~ryG299A8Bg})c_VLf zbcK}js059}HC8(eP60td*ni@Qo}SK~V)8ib_0)m_hzAe~%@Gt95!!^Lc?Y})n#j_9 z=}QSfcu=N9zk~R^5K;O#C}+a*9_zUs zY0e2MIQ`oIdF#(yPd6(?cV-bP2I*$_&io=$#^#LGHDILpqwJEMS!F7;blVamvii+T z!w@5+9~YId=ABy}L1p3^tD_5GwDpmzW9KP}z*yTlmLMVFh@QP+A{2xW=Ecqt3DQ;P zhnKg#|L~2se(P6$f4aFmi`f9s%?~+Sm}Ca4R>oymV~+`9`{y_N!=u+;?1=6|t&}b^ ztao>SCTihFcz4hO60U)8F%a~`x0mJMW;u10Dx+b>ONd$qb$8sbh$7%_Ad*|TQp{G+ zr$UiTBy{}K$>6SgtsVudbL~VPTSJB@FJDVOa385?0$H1p)oN2Fs)Cx$GfYS!g)p|+FD z>9FFI0!JzlNW;E?o1>c&BCH$pkuMj&3GYL&36q8wv|R5VxIOC4uaAXaD= z7_nGBabO_sca*+j9MzHc-7y~{6CaOnjTQ;>W~$b8Bx| z)Et5sApsX*j_EPWT`i!xJ1Y*%tuKChm>hXT@{=R&Aqs_IIEB;Vrw2n@FDW8|LAU<& z*(Yt9FRm_@WkDSz3vdj8JOQYA^71R!`_H@QorhTmMcl&Bl#>K9gUZX38a)srZs%D2 zs_DeIoxIY-8!JAHiZ;ii8?j05e2&P*3w^>mJq2|a>z{L(PC;{2;^ZTI1PVu~kZcII zRBYyBkVs`)5YPHxu1M`PR$EAEIV6&s+nr{fonnG$O((YNr%(R)JI}xRGhcc5;zPK> z?fLc7hYug!ySVr6+wYDe6JZgl{#oEetd9o(LrATXURI9BVj@CRtt9iaE+ZAm=biNM z-gmc$WAC~Vl`cvTSx1EYlYjk`01Akd#4Bsom!{Vk-;+s)(0AOH9V--kIZz($c0>P*uS zPA^XjoNV<-s`8{^MVV1;-+3~}hO5HWDK=W;ztqNy^-!KHYT|5zWhB6t1AJwYb z_9K{ct`4M*U&d@)PwlYGD~@;yOUjCjlE45U*1A`PBqZ9DSpoWSnKo^Eb?^H6lb2t9 zb?M8oJ2-iPu)#`_1D-Sk5pChdoUhkZqGeT;k-eiP5=w*b1Phz=5Kj_hAm=!EH;5=T zDNAPFR4tioEKoV|^-2q60E$%#a;;S=|G^cm#u}w35i2D1I1>$xnqnZMg_5H6yvkgb zJ*qe+sE%UL|A!~#5g1i_dal(%4SgMd1=BAFPp6W;Vp)fy5qtFIXpTB zI#5TayvW^vV@;|n;Vd7y)+kPl5UiO@1;k?y^R&HcKVmEnOHG){fJOL}6Tf4)ncVJ~ z$(6tHSU>*`heYxTSEQUmq^yZ6spCKJn$HE*Z-Lh~XE9}EyMxTIiaxz2o2 zvX)}TDaxS!bc6=hIdY`B*b1yFV8G7ureq61)fPP((`LLXhcxx{F5>lL}xw zDMd+!cffOG4I>3K!h&sDx;yBGz8sgs%`PIXqR5O;uM}=U9BE$jS`bYqY18KXs%pl131=Es{rOsmf_XoTCSihf0=qoSGgg0EXK2{oHO^cE;o*8>SciyCx`hcE0`MwCSa?n} zS*(ZS;_T7I{L1pgx|t#zE=?+awJv62HUSQ1y)Z>P>J&MFh%U5A zaC3X}$!8x0ErBObsKabR3uplf>`KOB^StGxLMRylw?(t(zl?1?4-oL2C*w`AMzlV- z2`ZX>!yXRwogF=Lv zghWKky-Q$>JzkL%pon>7$^l3@&MLx=qJhWoC#yXvY~|$evp%78cL!!=gso@qbl~bE z)~qr{O=>)bLVS{>QOMyMVULZ%kp@R{E|anzBOJct_j5KvUazgD4+9E8uq+@~a3o5{ zZVed+S{m!650@Vy#h{8{4quaKLh~SPVvy_}U`#6B>4BgI5Vl-wnUgkCMX$^-%=6g` z5e9(-!>hxR`;w!JeM987{dENl>=|}Is{o^0fgHBO3i-4~}r9)6Bn2ikS^CPnyTe1B$UpPtW zoK9pQoWp44x#Ohn?7h=?xfaF#dOo|a=3{{Wd9cZzlkKr=L4@Oiv zpnKr%7v2d&3f%mB*`tVV=>x<)YoXq>!O%qR-@pIyhwo3D`Qq}v8(>v%NS84%KPD*< z%yAyE9DDPF+HO{rAXHUd8Wg)(4;!^-yZv4sE_!B|#r>>7?5T*g*4$fAn@Tu>7Kjpx zjaVVYu|DZRoj`<_Hc>bkrOT70O^B~|Ss@=tdUc3;gssHQG>VnxgG>-Qi|f8(7lt9p znVKzLNrLA430QPhj)|S-6)FX)duQymtSwgd&XL?$j5QwlVdWBtUv(DCm&nf`*gg0n6b{OJG`9wd=#!5= zy7$uS(ULQ$DpQZcgO^{s$7Q#_+2||)MLaajbL|X)=t7Srj?WLTzxE}=+$yKhw6LkeA7MTLgc*7>Or^* z4vqW9j*vAoF^32%Qt}vcg=%6YS4G}1p|1_F?ha}gg$;-j!WVehXv1=$gg_;i$UJMd z>0fF;heJoY+$`E88fL(*8`v%l6I8+}(I-F5av%+Yh}JXTsfn_*a21J!v5-T*fq+7( zC2LL=3zpeM9uN$kKTG^|I&*uz334h@!rQDPJI00 zD=+VM&!1mE*VclOsOWI#cjz-!oh+mZ?tGGx;`s_?o>>J`Wr^~cJKa;v2VS*#5?7#-sQk(RZ-T9>lkBP#=tA)#X}!`9~J zdU&hEP-lFNx$eZ8z!1vI;5hGT^u}?Gn8!IqA<}8e?_e8+CZ_umcW2hp-ASdpQ0`s6 zaJ#$N9}e639MAyVGXbsC3KTj#LPQ)wgmVAS{o{l=1aMWol=Ub2C#rd>cfB5Rd zt5+W0fA6!W|JQ&2w?5i)!P%zyzy2@&xi39@`s}kGN$BC&LRBIniWcSTDinoJGFPl| z!tmCT1t(3QiZ(&>*%1*iqq$k{<{lAdnPh@NA(a4~B00+kgLP1P6&X59)~BV3s89tW zASFc9B8OJymKs5eRmsSmlIb{wLdkjBD=|-!$5&;8z@0rI z0;mde$_DeEia0*xJrM3~QZolNJXBcBvZM;+#9oJcm^p%OL1j13dIA}g)I(xb^vfF8 z-8*b0vI#2s&1J1uXgVnO?6tF0DNKn2fe{@r(%}db^Jd-x;_#H!XH!yCq$x$5G>RfL z6BsK+Hl&b$WnbmH(X^>Rrwri4Ln06kvz`$_Zs4p;AV*J1B=a|&TM}8k<^(tpA_#iU z!OX?c6pkgkap_rkHBl&Q9*TzudF>~8vDQQ=lM5j~Islu}aI znYIV%gdxHs$8Lue$wmP0fC*!>nNkWH%}WMV08Pm&&4O@7aKhwIN(uCOR!<^IR4z(0 zpWp7&Az8Z`nrQ~6O;t~<%>Y2-*z@hV1SnX%T&vPk<^Pp`s) zj1^HyN#YzC2x4UktvA5&zYsti2%luF)j5?KY*XXhx}qyOml{JYp_#k|lPT|jOEYK| zrZblj)gX-wk0$!`<4^BBdU0-(LT+#O9rM-Mqs4A7+qndo)M~T{6Lm+|h;V5TBlZ5h zr-$S1^@o4`uYT*F`=_pU*B=~x-ggZMBdo`=ED>UTQId`(?(XT39f4p@-*Y!}LykVq zk+A7|2@$V{1w@ur1736IkbLg|BG^Phv<4Mq0t!gB42-b=nKHW}lrH8?X6v9zBb(lC z@8AF7Px!Zg?af6cz{~UF|MGwK-#$El^UXKkI)C`h-}~P64}N@W+=R#3-2d4>_m!`{ z?2Y?C^PoTiw8{n}k$xFlGyC#_H6|F*k@rfE=zx)difA))0_>HkGn7ae$ zVcyLl^l+tUP$>4OO;SW zWuBA~bJM9o)HZVqwwgc1_y z)|dTp_k7pp?JKW*X>L=tzGoh{N`d~9dGXHET3`RQ2Nd$E#9Al@O1G?jJtHR; zbAg(I$3@JGf!h^wKhz>55hc8VxmGZ!I&yG(I?J>V;>mxaqjf>o3u12tbA z{P@uI%PQ1f0R|s}2!pE@VulF0xc9=o-#&f%(Pne0Z6bBW!1JR7j1n%Q5ZmeS%K3-? zlfU+}4=>z9mdTDLn`u5gz1S+?AN}&rzxsn8Jo=CR(H{D6JMF*rI{wXn<15S4XBuWU zdv+YKA%tw{zKE=zl3al(7VBG?8Uch8Msc_7IIv<-hD#cs(}M#Ynq(M>G&eXQv}HE4 zdS78d>I%YHt&uV++w~-pS1nT0oilT1IEx~aZr%sLlue~CXn=1kRDZ}&!w&} zjbfmr!LHijfO|dPkk?KC5*{kr%|!%$I;r5$-C9!@3ehrAx5NuPqcBRuCUW$Aq-JQ&H@*j zB{pOn4Q|jcD8~k^vvQJMGeg(PR09o1baEYJn!PZ|ZcgFm z9IPcIRWw7BENh47*f9iF5ECV3PY7IHy39eD^ z@GE5-^21j~a;_RrZ;mPqjO1c9Cs~PIKQk9|^2T?+`#=5{|NPf}*1|tqoR;-NotP?zV54Y)s-kyO z#^HJcET{`3I+yHhrbi{5TwOZ~AS%$)EC!Duw86u>MUOG%j3~GmCq9b+;x10_2nZ~M z7kcltzx5BF{@q{O^|-g#aoN59&6hv>#s9_&hwIqB`mO)X|KV5PeDjvmB@Abq+kg3= zdGN3OGg~a5bTd!4CJlj_Qdn_Oc!aq{z;d<|0%3*MlVr_i6V4?Pm%l`^ru3<^wz!9= zq(2HbG%$PttV-o;xk&;Us?aBZSbxNrpF*TqLw#*V5>Z~L48}XjuN@pSk!~{*vf%nkd#iX6>QC?3UIfoSkA8 z7NO`ahrZ!#?X~~M-~6F)0-8u5ObGLe00>gpd#B3bcsSe~F0UTw)GV`+$jZSQM<-N- zlMjg1w#6{%9QK&F6O5m1ut)?%Ak-{Gkwg2S=xwQJosHh7oS%0DSy0J+6($oRD0jyb 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%0Amioz1|3K6##Kf^}rQ`y7uxtGT~33 zeDwUuv(xG8S8u+=(reYIKHY3q-S`Y;8rv9BDF6c<@PVK%kLgac){L1EYbEizs2i%b zUEFKo^RDX}V~{gqs<4~C}>ecZzsZQN7VP+?}2!C;JKIcC4P zebVL~yk+mPoO8LjQLrApu^sleCass_Hdb7-_sA}v*HD@4T5hEqKyV0vXGiydSQrq9p{(bs9(4LWhc=pcHmRt)F5)dpl{rqK)6moQixEL>F55WAk{-K#gR@1H-P<~cL_ znr3d$`Mlcx@bpP*)4p-OTrO2@&IU$lRazlCV6XO!G%k-B>@KMVa7^^KO2UR@BS+tPrTa@L823u@y`~RthZerk6aO#q5w^(8Vhy}%+-L?k*Ctty)0P!uwewI1rxGcv$+uxQMX0Yy28jOR1H+IwBf zM$E%#c2fhl=t|LSlQy_s%Ik3(=d>QeQHyXwkfoZsEmyYEqC|GtiV_>uulXaz89x;P~ z!K{f@N%31Shv@c1(v({`l*knO8lp{;PXyNputsW>jP$A8!*YwL&}d1iG5jBeeyx8) zj5;6O02tDSFxke%9Ynx7+#9P*C}YF~OfrgzWKbE(b@(}k#fsyrhr8Q*IJ<~a3}un5 z18CKe)fxps*0dYW5qFFA(+M~%hsHM|li4jmE0=P1>6_pF-ue9Y?aQy9+}&ob&9;PJ z%?IV)%AhfnlY5GE-psc*hXcX&&C=h#^mbU5^Q-e4G(3Oy;k5f80&kbEiATb1x?Scw zO_N!7fb^t;E!j%XcCZ6Rp&1x}bBK|sbrd=YIm`*aUdbWJH&8s_>XQeFmDWtMqGV#5 zf1tYA!rUOhl8FL+hEGk+V+R>P+=xKXxB~>$Yh@v!K!{nNzNj99M8o#RRql?o>?Q4HV4jbUr?;=BJniD;-~5f|AAk6ik&F)Yb=B4` zM)Y3yCq;8+W+tcRq`4urmg307bTg=Ng8?V^%SuA??%7wz!~g3q9%45|=2gEXGu;LS z2ojRVHS*^6U-*~*rN_t6DN}DV^3CHp6SP^O$t`-MO`CHON4TRj^S<<951^~MGJZCJ3-l#UW^?a5DIW$ozlq6(~T%vJ5v?a{Z1?1Qh8Q5>#1#Lj^ z*7S@;18j_g5h@7|m5Rza!|N1zZAdoo$~Jw+0_n+(7(&WXsEYXRd}0$+d8g?xQaeX5 z2gu~id@|?3SAK9{+ezsNBuGY-(Y0VsZ(qGWE2o{sM|1z=Mz*(A*_GZRmL8WSqc@s? zx?D8vPRHwC_>jEn0kbf;BCwXbph)gW%PnW^$$jR9@Pl0E$X%4D3a5%9)J4j>+|Jp z#8PiGt0}OZp$P&)>RFs<85p5ZYMS;Ep`<0T<|0Wf5rPqhNBn;WX<(eucYp%zy8c;H zMu3??HM2Ijl9n2Ss@gxOdSL8ekCD<=D;O-4Kqp$(?k+e_hs!zk`@?*5x32y8=GEG< z^ZEI+50}gF;qgsemS(eLwdXYkfa7V6%Z726v;hoPgs(VQRpJY=zJ2}G&E0d~??uk; z-Yny?E})?7A?)UZTf6jQ#D!2;*h%G1)RA{zVYEzrM zgWh|Ow<5I4BfQHQ^|Y?f@IKP{5`JTtRT)1{`JAD3HeVpB%$S;(C>b zJ9*Z4Ac}FPL_}E2gaXB`gn6Psyvtc#MsL&YG#zN3U<66Oy}fU{S?bZxUe4W{#myCF z+yaEQm@#U(kyQo_ug?A8c&?Xou4m2j+GVx_XGB-7lYtSXJCc%_CyI0Af+w@b+IDlb ztEdv5TcOEHd+%wBYGjsyD%6DeIz0fg%-05{PV@P_FhdYV>p4Upw=5_pyCf)5?j)Ff z@m+(A=0yUtswmhX0cj(@hK6L+Hb$meBy)dr*x%kQ%R?;3h0D6G zY?hfT$TST#9SCJtUfag5nxInKR(Ol!2?NSGBD5KW0(BL12@f;UK4CkS4|r9pjY& zAdIvAKA>>zO$uP)SgMu;S^W_Svqf<_U!FaCc78aw$)>4gq?x%}Yo3|zzIk_*u{ zrdo7{vWKK>O;vCzWdqsUv`e8>mgMaw)>r&z$JLB^+Zh@YXqbSk!Az;5YsjGkgQ`_e zG_tyT*0P~Ctt@UzQARU3dOy8+v%7mT&HF^81oQyR46z9=j1xBED9~ zYeZT;;gYuMJFoanopKUc*2Mz^E9k9El&h1k2UP5cGGH6lp|s%@T0Dpii5UO73IYPi zL`1?xM@hvtZ<(p=n_UBhlFDI5Mk$t7!I$L{!-$TMSqZs7v;tBg1081T@$G9a z>yur>iwW_ws4PIq^A#+8^xXQG5a116KGNFZxn*Q#5<8nH%?T$hxf zR#41-H|?gI%LT9Byo$qz|J(obA5OHC2D(oS( zUI1g7I@uvAoNOgqGm3IJ#DGLE0fCgHa0N03;&qMlR=`=XIAG4QRZ0+kWAm;*uKjqN z>>}az=5T!bHs#yMFu8$uBUu!BJMxV-wn3eVEgQWLEa>{Xf_*pY$~y+Z6}{V3HaBYe z20skkuzt$|U5YX?e1$|xsiA61Xb4PTWDagSE~CYLudmE72Qy~3WnIsGg+QeX6tfoP z)^1LdPRH}t4~xzB&iKa1H_q3~<(Mu2h~~hhN2^UtCSAQIxvj;hBnJvol~{!aULRYn z`Ow81hTEkt>x02}yZbfTt5?U<>ia^t>cJ6kW}Sa4 zL2xkBn`g!cclz$HP9H2c&u%|jeD3QBC528YI{TX4*R;r37Qf6lUmV#n=O9j2*PdCh zK#nt)p$c%@6CX{?wSylN&!P#tLwxn-i|ifBT15mhGkRp3c0e(632TCIc9cQfg zB?%cVx8>7O@oa~3{A$+-KjIj5Wv7D0Fdd&s4mEIG4?YO9=)D5r$T+zEuQD4UQnkjN zMs|g_Io6P{T>9=dZ8zP2@a*9$JidPY{Mr4@v^!lEMXe4r(gvG#xUIkU2(Nczi@C6| z@>_SRFkA>q9UtG^xV5(Hv5@orFg5GTX)zNrqNg>Vr@M9aetDeCQG(=%?B^)P)imXp zzhqIQoXylYOshpxM4#uD5q>Rz`Ezi<78`WEblbhh`=7}XDz>Qc3;p=z02H`&z}#k^ zTXUQv3c`k1&$xHk!lDNJbA_##L-|ZbSy`P%2pd!sP@IlOB8^jP^E4mC8Rlz`sqOFY zpQKUh#?>q{1#7wGkn`1af>3lh+R!(+`G@oW`*-u)RW|p2k29!j3h9@u2_!`k9YSU8Ha&_ zH@lhljea~W^E|n8_+nNuZljegkYXbU)QO<6iOJHcu30NFstXyMup#Ee!B^o{0&28c zDy}BSe=}3z1O{rTwTzG~IJ25$2{235KwV&fvMOFH_?so+C19B{bK^kebJJueKqjmN z90`hUSy-=|GKOS(4hDshJv|AuhD@gk*%~uz=wT_*)<%^cuxj;Yw1ucMv6UD@jAX0n z3}Z`W)F)e`A(FW%$8Ujh$hr?)p$0X^t-b-V2|tQOF&I;s#;;#~_5A*0_O;D(Kwtws zmAf{vcW>kCj}Th|Y04af&6305mh1}b80+H0-Z+qbXU zP!ytu3}7W{W1@VQ#yTMxh-8!;AVpcJftR39v1XK^vCyjoOVz{d9YkHPAg(W`?(Vzl zfJ(G^f8%pgg2c~6FC5vM3z(yZ3-dOEmtj2vRAYi%8lPOt}t}Kn#%uj&4*W zGWt;612AMC(c)Ne?0kI=T$SJf^|X!YYH6f(k?mVL%b%g0NESt$4W{nRGzXA3m= z?ZnSNeQ|s^&(qw|6=s|!HpS{!pvzbS4rUM#f#}xU+GKzHSAROq`tEnX_jrunlRQ~% z6O>Od2Ch)KV#rLg79c%{EZZLpwUa8X6B0u$iDf3=zWmY?4R+J!r_T?6_qRUImw!rs z%WkWEu)Ety$X@&?nX$FfkO?4)H|QX!3@JMhoEYcj;G5pFRy;K_S2iw ztNHM)hykqD2x8D;O3_wHVw&vhm#^RMZf|!tGjxge*Z6{2`;13(alx>7EUno zlKm2EmZcHkBFx=OWU4fYP33At2DNhSwd!8)mw>v6gaWq0!VcZ=`iq~uT^`oc3bVuE zcJ8=59;ew#YYjzPtoS^_aTGv4DkCJEgdsQO>AG`QeG?2QZ^U%yiePWJ;uhPk-C0&x zO;j$~koYT5#iSD&V;!)7`|7MVczSDIbWP>&UF$+AvnnAFyPG|yDb6!RXAPeU-J$4Z zy7u~be))J={6^D~J2U4rO=-3FLYjLE8JFHYVeW62V*KyXH2*lGZGzOamtLRNnl>%lYd!5jK^6U}mou?h-;u z+bm(xrW!env7T^s!Wr*K%|ihjK}UrE+nioJvKTr^9T%E+<8A2HM&cYd^vX z1DnBpC>D{mV}eq?H^G$U7E~xwIptx~@*5vp8CgHnJFw?EsI4;fN=W<#qY3zB0TeXw zt}0loJcKr-P#X=EC$S0J3hyDliA;i4C&b+JFrUE|CLqaOKc@Tx^==-Tu6m5rLG;WNvJc zQ7ic8XvWauPnX}v(? zgqhLYXN@wVHhR(5Wt!)R92NugRA%qq3X~*jy%O85m~sduj%l1>bA*y|?R}c<;q9A` zK8ZE5wOPPrIUiQ>;b@a>M!Yfw>N& zV|U1xh{%3(d*J3;S}#Fk^iU)d+ty0QnnTSuZIsF&>RAaW|DN~pgt3r(eTCaguIv<& znrU56JNJ67MNF)F^qy2;(Qvjljz!5SM8yaTWFoEFEfSfnZ4U!u*2qCu!S=ETHiD9? z9{sXFBqJ3mZ*31YSSBI`2LPFQh`k4G`D@?(#@)PIkG=K0+qG#%Z$Y1CO*4ElHMV+` zG$h!S$xI^Zut#M)6Ok4~Wegm2MWx@!|CL_SwgQRwM9?)k`BLQC`%ACThgGam;L#aU08nwNwKj zp^WHCF{!27SlK+LB~1-(SxLW-I-LTO0-guP{iUCspuK8 z<8q0<?TPSphy40t@Zi%=`eT^f0Kcdb6oX6$(r9 z18X0-TF9-08r&h#lm^JHH8+^il#xZyivTb}uoAiUVt_hwYHqYfBNaVTX=YYLDWoEL z-_V5wCdhK7j?3b^M)>1p3C>|%X7g^&h}L!zB3T3ws21%LUUXsI3Do|_hkRGNBx)o=r8^DLxZIGa3xoPFz{Hw^_vmE@bH(?o3K?e9DZ0<|{vGqAmefXa#K4U=J;(ZI zBXK|>v;OhZr%(3n^4JnKHA6-y-EBA=D@6;R!ZN|hY}a->&+bb{(5K#WSxb3@q^wEE z;}YR5;cbu+w4rk2nUuws1&lS=0I)ez5kmA zVBXETRf7zitcI6ynm;pr$4kxS5t$gX@MG{4p967dnoQ$pUk;wvp ztVSDd+fGeAk^*v)bgaGa4u`gzbiq8$ZgzfrxV*ZU`+l1C`#F;dw)xQeB?B;v2rmU_ zZ58Ht1-4a5)m{vqNZp7l`k@%)>=Hs-FOLs>+220Hv|D;iH+TJV%)Y{ll@qCSpLY9c zIh`WTD#D^N;{=2IZfdQ(&o}vJ2rg^9-svGaZKKH=*kLtpAje`7Wk#k(cW~{(24Upd zy7NU_R%226BW;r(pRB~SHr2DFxGtYx^O1-KzY-H{Z=WY(<)vYUNn z=?o8H|do}`yERn>Sk zAFXb#1|k?5{sCAHag7?s<}is63D4?6I7H)U0D8hii<;AphEb3VCRxzBQ*6SNQNlB} zc?id_%{8wm$&@iQGij5znFGnMh(|9q00|yyvN`GNa+D-*Xp<{jKR2uW2WlI6tJsbS zGl&cKDYvz^=KIU#yuX=Sivy;YU%lyaux$=+K;@A-T~Mj=W7IU*cB(O!Q*_-<-NsI1 zG_(mQ4CuSQz(}g4ZUAjF|Bci?$Qnh#lt?t%x~`|o@$T+!SywYJz`IAzheL;(HaBvz zs#Ff%_`ngd^*|g68rv1TA_$fk45D`<8eQS8sxYels2c}T=kwV@_cu?MR4T2^=%fuF z$MAk*8izP&V*@-lsEL3Sn?EA4As`rtSaLv50LJ3XHsv1{$n`|4MS}^->T3*&B@$$7 zpedsUO-4p8_xt?D(>Wd=zW40I?>_nPz%VLY@*+N4Vo_CyN=5<*d6b|5XmXEc)zX^D zQa~aD!K9&9wd)?qkfH@8(0qc43=YYf)fsMWk|rX02XNu^`Tm>#)nmLkp3kusKBp)a zQ?X`c&0fCz`pJjibZ^n4S&Qg4%ut3PYJ#M1@&m~&!i{{ffiPxeogpA2a)y!K7jpI= zz4-CJ_3!@M|H*&!e|r1X$>}t@dVxZc5PkcKf*4v$9M5em>?~dVlKbX2)Bdy1f82GUjs)4WYrqJabrx85qO2g3lM z;G~= z%ZL77{L?pUo1z$W1%vivO;Z)JD^sYfv>ml7l9k-t%_lSSM(LUBcmCFg%kou=IG0&4 zm`Y~Aon&qm5RLBT$Y>6C`0;o&!`&y(Zg1xo-~W?8`p5t9@$)a0;hx$1&TL~LlBji= zm&==c%k=^pq8q7Pi=^Vj1Wh(aZ)-$0Dv^;ANEIuhs-wZgQDkJeACM_oEyUJbr52W|DyyVXP!YC~ z)dt>}w^?~nU|G3l#^NmvP$ zYJg=hH^ktvCxYVIm&?PeX?M7L@-$KbT_T!^7V|tskLXGBw!h6wqGp6+Znao9uh*1m zOlK`}S;05;b`0-!s3&Re3e=#kDpQS4wtmuq@@){qFYM!&08psHC*N84 z*1J(-_8cpwz_pbh$_eAWBL;H>qc*C~tT&8+N@QT%|0Li(xmhavf@wF+yPKQa+eJ&n z8HM%<~-%7J?mki9FiP5bCD`aHK>d-T;O>*>9k zzX+CJ-oW|+6U*bqJhjcBs=-$%JAo>nODl3zY9ntUb=|o&Y|@K^p$r%-qGw;4H`1yW zSi8*3y;(1kB(4WtnGjN$-be={mIaeYxLIa_nrXI)wWF8<+j?M8dr2usCJ?!v9^X8D z{=sF9zAWw(d4K<*+ij0?Yg<2vVp#lKh#XU52!tUv0gmldKtK+g!}03J(2a1Z6!KCc|m=+C-2t7eBmw_5-YObp);Y zfr)~1vY(zjW^A+Pjr~%(3)_vW;YW>C1=%}+hg$m|9aw$hIQ{?zJ%p8v-V=WWAoMlfKA!HM zJ&Sc|Y^Wu+uBqMFbO@ZeQB{kaSzqei^}_XRi~z6&?)rmw;M!KxP(0ox1R&6b%{Ib;_!cl!3uogz8y=W)q2mu4+e#)MzQM zWim^y`JIlekP#8F0&0Ua--*UejKb(+#g8y8cf0BQ_LW`q-EaTuuio6PZ@yMQ@Q`a# zCphiR$Obo9rn5GEFkC0(O$`wyNEwQ2z0n~m? zc0i!FdFru7!mUl#a#?#u+utN?rhWS3ALe=8O?w$6x!7n9F;*g-UeM~At2t1c=OsM| zB)Xu?L|%+Jj0ug2^rBoin@?4A2%~LKrnK5d%_+KEY9O~Go>vt~psGF$iQo7BSFyhs z!rQ=;AlzW(7;vr6KoC~NDY8Hs&^9Goq_|!&M2udi+KL3sU?sdSprmb6kwc`W@>gz) zHWAyBA^=5*77>%PP`kc%smSuPdaQjpzdEtq+&=qYy{wJRy!8;2vF61Bj+-a*>ESgO ze(>RkFTQx(?WSU5WQsOP>C~5RzWVm}zWHzdkN?3R{J|gXZx72cJx8uD+|IDwhV7+c zBT_f_L?_(n!uIT78{;i^j?3f2WqG)1t>Sk|ZjElxj(*$PF0Cz?x&K)HE0U9G3cHfQ zkjhA7WQtZREJjln#O_zTUP?1>d7gK>$#-r>cgdV+RGl!4)M?mE=tzXp-5v6^2Y9&I zA9nMLKl%M1{>h)-F+ZLh4l{!0ZW$%(T4i!-+iw!q8ZzWY?N&lE+NjNu2{Q|!uh1&7 zNu;q_Q0AZZIqRBvJ?oJaTl#xlvP}1w0QG&zR z-p1osKl#zCkH7o!;q`ev9~99q#?qGCZUK}T=EXBIsrfX|-lo=SmLX=8>_z1#>OEfM z;2IQVx_B!!WGWHW@l$PmzLeGTQLL#6+^qUllpM)6Vy;;*(+!ZK_ho(D&xeWuTQ0;H zF>lp!wS0W{yavVQaQ}>U+#k%mHHMoeCnG1AMaFXJocbkV4KyzV$}}X~ffuuOzPY)*ou|xPqQf`(vO-j|z9bB4N8 zbMt7PV>oZn(hgp6Ez9&Vog)^GnSFFyN>#)yj}Od+awlTysb<#as0DT%QH%R#^N z{(RTJ{oyVzU$~eFu461TO&%GIjj|A0vrzPT`q}Gav>72A1;D6&1bDfSC?i*&e4EnL zI6xcVO>F}V)NvOwqi#rvG1M+aVNd2(XJqRL27p)~qC}cE-{oNDtco7F7`I-!G<$^W zDITNn0Vrr|$ThCiJLc~=swLqzz?VYDuA-lnSaR#(i_(zk?ht8Ki?^iT-rO&-lAL$* zG`GugzMLNpyXVt13Fb2|kB_svTQ)b1EP2IWMg><0!?r*aTXD(3Q~M6=Jx!UyUdrvk{^d zftxD8dN9SYb&yp-k>z3?uvAq5tVPV)=9kfFn5H>W)6M;Sb1yL>iveCk6NwU>P<9Mf zv+0UIZKN9xhI~QM1ECj7tVr9;hxOXlb){LG4?Z2tc4iGa@5~UTQtreGCEX@8X;`yp zxKvthA*rTmFbQE~^PLR?2n`$sW7DB7r;(|!;o~(F0WbgsIoIR)m?>|}9-GQr8qI2P z7A16OB>J+r?}qTDR4QCOy8Aq4ZyCnig3x$Bi;)ay(+Ao>NP|s!^Y*K6{QoP+&bYcwCKHp!?cmh%kQC%i&QRDX~q!%NB#|i zLp5+IvnWUk!^Q3S@BY2pzwx(@Z{k<)o_}4Fd#xy{n+pD`?p z@b&JH|N7tj;O2Lpp3aALcayyBiwu{k>zkkK{_-a;VEZlZYa5wtyp96JD#wkGHV9ZG za@*3W>T7s|*#;KZ;G?n%vTB@7jEZ>2mB|fh7y{Mf%>fN2zR=lF=_`q;79+mxx43 zD3oj=0cKGoG{{YWDQjqK0l#7!Lpz;Jq|s!-HqkP3W81=%`B9$EGTxeF(M^CJU;ySpZ(##@els~KmDhFG|h*{w{JwO(LrA> zm*}C4Y^`&>e}4OG-~QDf{pF92$CZI~>G!ufD1P$Cf7ry`bki;AMVRtnPWkq>Jv_O| z!~Q^@64^aCDx?tH|u~ z2L9F7r#Pf>2c~<)plob-ZzRpZ1;vPNqa*PlngnSZ^syvqE5eS_s>m>nU@2^nA%$2V z<`^Z6xdk)FpA(jAMyzEEpOV>CnXpRDT1${y+rf^XeY$?};&49SBvOjfW*#0}l)^1T z83A(I-yGUB0qW7KR*mecSxk;LQ?juk!!e52D9Y+CTys+`tX6I<$R4<|r7-ZntsgPi zuwvcD-zYT@r$j8vX}KJC^MU3gCt`56icUXFfr=Y0m29gLox4B#$>qs}(}mNN6SQ*! zdZSsQrO$S|yM6NV_ystb5NrF%r_1T9HO>z{F=C}R8nVOOVht$+`C{>TcN5F;w6>Cj z=;&z5?36QLn3yyr)QcF`nEm+WVYlCTx_OVq6uR^dULvEzkfuQ*3j%GP+Ovn38IPPN zG&Hwak(0r*-T16r-}v#bPG9mcM~1{G5k=$c%Hx8c&D8f|BDoYs2HiKIyr zP+uUSta5&gvLdVOb~??CMFmB%2NE*(9&vNnCrmPTZ_T5hj}NaLiOjVpeDc=J&wX9y ziHEr@OK@;OG49BA&g;(xS9K7Yw&{`tjcWu2H!ds$vP2Ek+(_@|Q?C7Rdw+j>d)fQ> z@$Kb&na#a%-{#A*^w50s1eZtSVpI^RP$tRJLDLxi$iP1KK4Ac(hLi7zv)5O@wJORe zpKq})?ZE3dY-byO|Nj7<@Jj+HuslehF$de=Cv06jDs2o5i|^2@@weZbO;#_f4a=n4 zkTF*&Dq3VN%WB^ChnqCd2uK{;qeQ};SxQ!;O~*Uq0KEUUZ!c^#PDV*qva(qtsnpPI zU6u#7Mw@Nkx7`i1>r#6IS!PP=a<3til}!3*@QhI|e6?;geVm5t+X zJtbI<(`!hX%Ivw6!9cW70s(Ur6o=Y+)o3FJ#tI>X7@CwMV9naPF4J^V=!fz;WFHxB)6HUk0U;g1g`iJ{`?EUbVJv5z9t0Qmrd-R)$7pafDGOCk$ z9woyOBgtKLBDN*2E7)5ap|tw6Ok6c2HAdTv4Sp{v$5EEh|^tZ3FgG$EG1MG1*>gLJahu`(*-wDW0 z$A_1nUfzB=v9?3Q5j1uIYBbNjx;ha7b&Qqw)5EcO+wJGBXv8;v^*cd?YGnh zzxFr(=Fh(R^f!O)JAeM){lR&8e18AD`TpgXFVDxzSD(L_5p$d2LHD#En`H3IuKnzP z`fq;pKl#1y>^#-koGa7|ltKNhN`Th&j>$}x)~w{$b377}%A9OVWeM!BaSofXID|@_ z1%TEGzruVNQdQWvNZ9HYZhv>c4O?uff=GO)mOnbSG3P7;OB8%{{27s|`;{&)U|_rLPt1v)%=`sURS?w{Q{OdF{X)umgpxkyVzH+aC-2<~rw{jdK{=X^m6 zWabjf8IaKT?o__{@+XabH&3uykxMCtm^2xgr_&jSu<~+>63{g%Ffwmaxe+4CYgYmm z!+#*bMG`822Pm_8DVE+L#ybAm7TObRgJy!!Rl)>>;)3h~Ggm!6>@!<)!TYBAPPgLI zZP;_E+rvuzmm}VrjZ%MDfYzZiN|{lR8O_YiMa0N2)npCJ%!+D5R9G}v8L^E18g+n1 zE~~Fy&UDsM1qHuFHDMOsg0MWtHV8;`m{6XbLgYlWUUPyP zgJncKK31#Ew>AD3_!YrUe%I@F1`S`njJKg1kON(AFWOg|G#t!ZIp?~Yi z5B{%zDk4(I^yYvNYjfNj_VJ(w%VlXwU6rayLy$Zz7fw0fJ^R_`52w}B%`^t!G0M*h z&aripB{B+S4*fJ>qc&A=HH_LNM?q9L6H%+FsU>@#d`21pmDnUoj}?{dvuxGiW{eO? zMP@`scGmm6F+;1i8#jl%QhTqn{cFQpb+rGXI$JskEo-YhccX>>DJhEi^=$H{GztG*tJt<<}c5Vq2x;?U3a zJRAbjh2D>;8EGY;?<6A54{zt&!?d4x_atoT=c9UWZr&yl<~H+YIdwA}ZtjW+rtRm; zv2I_wb<8m;wykX5CRzG9aDe4*S%T&70RF8>c~`2AsD@wk$1e$>z+qVz;}5Q2?C>)O zhG?M%JzJym`ME8d`Xg?xq(yDWEbB6WW)5R^k?66m%cbobiA*@XAm*{LPa$)7?QLMe zbtyyGhNEKyI=F2GB&y;;Za)LGoR8|K{oTDyEmOOj8EMPeH0|QNnlw#^bA+4HQkgmE z11xbC7{L4=z(lt3F|;{pJiCC}*NH$slCc+9}s2)fVnvYuKgCMVsj>Pokk zwJx&N`hp_a+MEeD8{0(csTBhd<=8r@C^0XDDBRoZ-8t`(3}# z%;xDS@Av-nNmrkCcc!~1Pd+&VKl%JcB#xi{Gaf&`v4?|#3BweXX3F%Al*VF4l_*}2 zaM$keQ4`gagU@+qM73upq81fpLM^YF~4M4Y05aHI=<+5H*lTFPJ-uB{@TqjR&Zm{N4 z_Lb&f&@0b1(Heb_cRY-lQkGMRXf*}aAU2IcZp$=dCJz@ZMkrGiW9HMUTr_2N{EPy8#qHuWq41AHJcN%i5&}=H0M;}4oPx1ImNE% zIWs*=M{u%tnNtC!vP0cmPBJDW>F$l@%%G)~oZw5d-eAIfxn%T9KA!L5&4*9#8ghxQ zozuDPp|L{0MsqXfWUVQfBa+MT-Ody?&>Vax+Uemsmv@Mw2qC z$!&Ls04n1Z3O5p41!`>}8W;pqjHt&^_0{STH$Y)SyQ$4fxH_@AeS9B%AF#yGIv(Fw zgF=4|tb;aq4Zq`pZ+xJkj=5d?3NG3%{sv@dy#CQey}PIchBukoSkWzBg+=`g-H>!K zzj6PK-~R6OgP)$&8l15Tp}l!VJbd-)a=Czc*zcb{dA7UXKY9M)Jnfd#dV0K^mcBdO zySq>Hz0x7JmtVg5;Dej*{N}g+;>SO||Ky|N!((5==(;`PuJHdU-s*wOsdBkHRBMHgrTGC#z51S)1tql1KwA zBeO`qiQEJ+2FLO`^9KO43Vs#C3q;LK&{tzOYBE^sX}#)yZZIzgKW*>;j8GyViPI#* zS~C{_WQc*HRw-jevHLeLnqX-%6=i;y!kW<-5pJ!>O9V5M74$}7MvlJ(n!$C252`XB z%NT&f=B!JWgK4^r5x50-Y}F1&S5_jc#Tqv+X2f>6+_sfS!fGzVW(F=P|Hh=QrGlhNj;wR7=vCeI7HUSa>$WGQW8hy0CRXCYERpi>J#9Ftz_RJhK zA~qs=-K&zukufuEtcFxWUN%Cb3$!w{79vIC?cF_2$8}jR%Q{VSJ=M`j4!))a_`iWv z<2KuThDE)Uq#2W*3b2#8W}$u8U}!^T znOg;#F!MwtSOukLRxuG&Lg`~g3K?{-Iy_T=rrB@e@ayxlU%yQIIJo=HtTEifTcVp| z{`#2z`G5WUX{{?^G*DRvRQAw^R*J$unb}&(TvQ1x-uj!0$8BtJE=Td;gDq zF(1DifC5HOFp8r0`T@TGe6Qur&wu_u_c6{N&FRkcAWXAb?dP4)OxlMgpVmbBo)bm0&(7V#+{m!r~ z4^WX{M@LdZ^U_L8Lfc^asv>=Ajbz5~p4i~)q3Fg!`msSXRa%VAb^)a#kJfzYnSGsh zJA#>H@eI5fB68i}i8}vd;tG|>ph3AV=iUA`Vi;AFrt@eyui31gimp%(s!z?xH?QA* z_=y?az52u0&GWoFM1QnVl9rNX}cv1qK*6Kx+Us8RF zNj|qpfE#Y^_6A!{>vE3afLLvxa>lN|Ieqcp{qf)Vd;g=K-aOIz@~}fE(%{~xMu^rD zcKhVH@17oR?@p)ll;OlQOV*iKPjATe_|@sc_Vo7ful^hV;Ah|em7o2y|Kj}RUoLOo zxH1)`H(K^IV`~l=>fy!eBootV;a3mO_w%>sr+0qHX_CpX#>D)H{#P&moAc*y$etWy zifuFnjy=_Pgr32}&9UMcDW znjXG26f7wBRv?4|VZLN^MCPK5HXY{Oll|c~PUqd+cC#NZJ=D#{d9?939CUFyowKj8 zcTP+Ln*!2ov|O3ld^-2bxj$;|Yn&zvT;y$XlC3R)Y`OMREL<-FyerHR6jehfTj}%M zn#r4Xju|u4&0Lld5#;K&f*mjWa`Cw9{-NwP!jl1US(i4qunw<1>>PD~M!eEM8_TAg z&Dht=+n1j{d-kz_%Y;r;!GH*tCW#iy57UwMBtiQ$kL zt%zn)N49KYXgi3aTOZr+pc;DUE44;$Rb?Jo>~j{rA|l@VXY zOWtlLuH4UpkhI0@9Owsn#rSbCht(lHGuR_eH*Nal={nOj!dmMXo z**AavZ#?{ z&&XUAff(xyR4NB8-OxjW;W#P^P$LNn$kcFh6-M4f(t&1~>_x~K1d<9BmDagUFaeHk z*1Vz3nchQDl(a$;0cAJ0FpZh{K!r)vE;-!NCXc8k&uS|yv*r>Q%IxKM4dhrLR@qtQ zMw|#(vXMCiIv^MWB$7>5yZ1;9d)rb?rUvJ<6iETlLAnK$nY3CCq?v<229$_CAUc-S zr=V_7M25`aS!(iy+0Z~M*^Qgq+aBxkc%HfJnk`InQF)IT3kjv!ndQox@ zo7ZX>lD2U)66P&4U>4g7lSZ*iWm!CVA~Ap}2}n6W7$X48y`#0g$s^)&zRdHE%_)S+ zCU&cmwzE`m099Tx23f774Qk&LrC>cSy0YM9RYJ&wJD^_H;iR!_FNh&Onxl_ZF=0WL zVyl?LVAvrF2~(E-uly=tNp>59usZVv%Q9-W->gU8UzQIqXMg>e{dB>4PFP0Q3YVwP zvd4qHxqRjmE=u(naz~bi_P8GY*Z=!}_|d%HdhXmO)6TU+ce3}K4A1ZHvafVo?d3oE zgV(Yr$`oR(+t4K_kTS9tR8d108+xJ~-!BJ@SRvX*QmFaNXfs414N}DFWyp%L zm4nUGhm3kVw50)e1DH|v*q9DGpQoj-wC2r8JU+gOSZ;5gfbG0?0j_mf`u)v)O!Km? zYv#1uUruk4Lt(!%)4Tfq{bu6&NJlU%_|EA39Q)=v4t8`IXN2R!9 zCCA*C)%U3qJItZ|mJziY(X6Fl<~7^oCX{n62{*1E2BbEmcMY@*fmZSLvK%wdP)5*w zIzBwS`tmVjF==YM@G=^}Zhz?OF(b+dMuYFI+DR*K25xm|M7g#xGXR&yB%|Wadmzk) zf?dJtl(is6xsk|>)ySxKBgedS`}V|OS3;>; z$|^TVZEozx(`i^OlE|QmsEgi+(W{*-k7oFSyVPv zH_ATYY>;wR`cS4d>(GSUc^bd`$onVHKl#h=e@@=6G1HrJrCWhYabAwEU$ouw$v5x5 zczv{xxvNvTOSC@pyYTSi&yb(PW&n`egwzTqLJ*j0Sm9y2=|BFb|Dl?H_dDO6?>_nT z>r<$=CT}Bx+J?s$N;0%MXeqi)kEi87{LlZ%iu>?8_e;~IrnPv+Y8gPh43CIm(x}pnJn~hec zsD);1E=6B1XENH(VT?@Jyk5?S-TJTp-QV0TC(^Tq+eSsCI-nl0ESJd|GToF=h-rk0 zRf(g7%shq2*t{4B$&7%Ua=~Z`v}Sv!!OUmh?H`Vh&GNAK-Qn&Z{nvl?^0G+xk2XhI zvs89iLXIj-t72n{jNqw9kMvB7m~D6J@CHMQk>(epZu*ihcF*$Tr~cuNmoFaPe11k^ zneUg~&UWqgkoV1XUUySF-0p5~r^(?xjkenEV*6=-u&Hs9lP%zANcM<`SeD~wZ(n?N zeDU{x_pjN4|I7dJkAz#|cIVcpW_CF(&01fDnlh_jV<^NyIzizu1DI_zqh7!KY`(dh zZx8h5iPP6Fjt_6!-RM1Bih>h_^l7P z>t9b9Ee@m17}ao%F;VA#R3+NxP9xY>Tdr#heht$cWJ+x?#1$BThC9Rfr+_?e10Iw; z&HFr%|()j zHpRaywp09*q4qqsEHU0VH%yRcuGynen;7N+EZumvE~6)Gg+*#D2?1x5h}KHVl#-2^BQOEYDzVpzEcjb&!9+hT~ZtEoh+rRY>B2l(2g{Y2x_}V!;-p!ClU+q-!K|q5sG9Woe)taf2i>Mb`pw`;gCx`tf zH;3KBiMf-{mTA<&OmdI5S4_95Wm)@@W|I-+)lC!9wEy(0cp;gBNn|$!wE_+}O|w1Z z+S9aZS=+PjPNWLSBEX=Al}19zzQ*M+-&Aam%x!S71;cl6?I_f_wVTv+pq+@csR05r zum;vPn$hIOyoK1Tf!5UtM#i!pGuOJxwtZu0co=S__zEpFl6sgMtg(Pq?0;Vy$&!lKhb8maWGr}l`<+?n)`f9gp*dQ!HVjvOG=XrnbA@$kK z&3lhFPaMLrBM;RkDF!-uMaU~#l*voIt(B6lh@6bh0#LZkH3dlxzMjwP+S&YYxV^o7 z+Shem&&{gbgiq$yD*BSOq*caaDk2ibylZ@h-?81d0U`{#i*d|MR8Rj3)ozv{quI;^qzG);XyUe1p< z`@N9K^vQZ(BY4=|o{o!U9Y7x%axl>nhLUhqi83VNWWz=QA1`p!qZ&4Q<4>=wq0!r8 z`y|Lx+1hTo95Yt3Z8f9_xOrwsP?>WAStInZ@CgV`kI2pPcTCsbqnM0MP^l)mX>1@# zS9VcwoR`Pb>0!5jg1Yf38lQde(NBN;{avfjQZ%F7Fq#4qwn&MsAm8v_ZbffJP=kpn zt?Uoqc!p1&KmFtHzwNlC71_GGH`Ar9Z~hnmv)}nwfBVDFfAq8e-Jkz}T-N&kvt|`Tf6OWYi9@AXyDTW1SU&ompN&XT~aL&%nf_J75_y zKlck|c0N7ZHB;GSx)+Ie5c&oMlUsK%T8wwte4zwSn;G3G1WHgfccn0^dJmo5$DM~ zmgTU&^>#e^+o{>1?QA!H^=$qsZvWZmUvYmA!c47U1tA|n0IZB8OSVX4?<-R48uQK! zj>rt8xu)AM-aOoX{Pq9xfAzc5?$eJydV-^^FZahU+v)Y``=5UC;*uYJ?^`!Jd~!S8 zSihZj(1Pf>u3Q7sO23F*dCAk-nsRQubw7l5t8lYxx4Xx`{q*)Bmv21z?D$qc`Ir4i zKj?2?cLkENHk~e)X`X3nXvRjZS9^eMKEJ(Lsx5pPom6u>o*wP?*8D(ViWQf)xxRgT z^Z8{rx842S&GRRBx0f}}T^G3SKK$tR$p<_C{ENTR(zn%PXsi$o0(Clra)?A^C)oR% z#oA%Q#0D}Z=hSxdw0E<9x?ov28HmWBCR9#8GLPpc5?G_~H7HspIq46KCX zq-|3tB(5n;J~%l?{czLV#>Awv|bC zpFaMz-@O0Uzwq_(-2AX-!*coR#iy6!TSuQLBO@(TUTwp?2Lxad=|gpoV@PY2Z_Uh^ zP+APNgt3rYy+md7ff)^<1zrctLXtI7*)UT|ER7qNOsT#^*#%%gTGzlysc#)=z22k{ z89mdpi;+kxiw1#isc>r!1KY*s$7r!iLi(JHCQ8`tXP>-x1>Kr;vd;}9BN=RBlSHki zE;%%XlpdS_8EebS4T6GMA}=lBFop*#VTqn?HAJ5{)Px26l^e)#J3?k>GxHjt(7geaN+cpk&+JC7alu%0g;e%8 z0L5gfnQ9o^nOQ@dQ;G~Tm69xoskv!mk<>jj8*3<~Ru_c@CATH>I;BhWw-2wU`Otfp zR#EOAYd>EuQ!5nXS`=R|W6>SU=!Z&xUW4uT07q=yJl-R>TFQFLoRUW38;BtWAxa}R z8>p?hA|qDPfB4TOdNjAEyF;$aMPVj-nhBYGQ!q^R zhvo8WIpSbGTIo$apf0zyfJb2=0HMqg&p|zA+AKk(IR*vB{9l5BISgfCkV2~zW^Drk z%Mvcj+UZ^WaxK$GB;D9B5Sh`tTibjJd&b3v40QGHqB0Uwn@cXVwFwzLQb=>hM*k#K zv-mpaV|r5xXjwGCfx?C&wB{&$YP6r(&#zy-I^5n*yC)fr%t|NNC`|nM zmtO~%R>cCuEFGfWm*sdlJsfZEcAUs*-rYP)Yx}$V(_<(V%i3)3=0Y^L-q+cix0`i6 zODfyuihY|Irbz~+89}&B1rWX~5CN9>N1gI*nlZBFJMdB)cv85G%o~U`R{CTH!UZH~ zW?)OLwKX>2!0gR-sX%%ceA@SA>5+QhCTGk?-s>1N25@zy>+T1VR4Bjv`o*`u^`s;- z%^>KoziIOfwd6&^5Yn$XxoNTm2NqZcmXxYExmO0%*&G-`}h9A@Ak{XpMU@N z7v7{qrV-TIoaf`|#mg`L@J~Pg|8VzbO}A~wbs#oI=3HwxUw6K9&h2q8Z~+h<36KOy zQEV-dlqi==QiUs2mhG^^esF{x_237;`A^t>a74Yh9Ck@nb~vOeg{xE~MM`2Tf`UYV z1kty*JNNYGYj(5NnwjZ`oNIpvw4&Z5M&SeQ>A$`AT5HannPZF`d8@;J{2%}2-}>BF z{_v|$cH=FZXKD&51>^SO>BC3Y+fk$lJ9zXP4R^6^y&R?efbZ6OPy!0d=%a3qp$z*DxM16 z58Z4Cv#QdGz?64hID7TY505xatX#W3Aib8pL@9jtdqa1A=M$g!k&VUWzG~?WT4iPZ zax5$97aXsied%9pFW*Cq<(1d&zVdSESCFoQbsWbiLn&uR=g+?RrDtFM;(`@vPm&(r zJUroO&NPR@y@k|tQkYF^axX*L~UcPRpv4Fr$A(a;giM7-L|q{uEd@_zJ3;X(pUIamHTn1L!hbz z-OW+EQpIKzs$AcgJ)c#lh+TCt^c%JH!8et)&!4U5&f zUz{!%Cllp($*1pq_x+1&Ftn&3=s}M#3q(!l_pIHMtZO!Si3U@Y4x4PM;g!_|Mx9*3 z!{AVLcZjP9+zEFN(XO&=?bcV`k&k`&Lu>BDCc4xJiQ)*KS)6FdTW$k)=A^8MjEMl< zgg`LI8lm|gw7I*j?`zIEB#@fyb_(Edfci`YrD}-DL`r%!m9l#Ar478S7O|f$9zWV$K3l+-VrIr1iXj>*p5kODNEfsj>8W{y zH`paAKo*kXMmL=%1d6hlTML&d(Px%&ZOS^@d6!RBJpDz@m0@11FpR}eR0&2`bYc5`nu>9%VdHLm@SS+{-7*Y)LQqu?P%4I9R*!SD!?Yx3^dIrM2nUVP>MCL6*@_CV~+7i z&eq%D%)EZ4d?^B?w{0%gD1sT|Yv^t+25VCdArLqWGz7E}SH&YBlJ?md0%A@(5e^!e zcG9!B3O5?w^e>+!H36oYFQG7e~!sGG?V5rs~;r@UZbkB@VjhdG^I8QF%>rMdX@?r%gZwjZZP!f9gh+@W8>wcY z8)q0b4-){eWUT;qj{sFVl8AA)lhQlXLJTCC)2{#xK*gtNSC%~&oevcOO?Ql@6Kq`} zE&FWuc!uyYzG$O3+i^R$#iu;;CU<@ASAT(D|9uA(T6&byoJ=#a_yroBnJDNCFwL>F z@!n~0z(%R(3(|uYVT89p8HYIQfDsD7J!*Khiqh_(x%Z-Ow!eJV^^3BwK!!GZ4sYW8 zF0H^z`Y1)uV0K5=y1RV#UX zER_)sc$k1W%|;}h^b(tM5!hsDv>@6+>QslWbZDgi;gQbdQ+lE#O_%kM*Q8e|0{W9D z4?pngN17HW;1PYlJU_p^dHRSTvyIYfLj&8`XXFY=RD4DzGo~~Q76`W*4lTkc5%|5| z`=kHpKltSrPXF}n%P}aT?Q9(*tQ^1fq-@5k`tY6q|?d-#+}#pZ@kgvZ9?-@lDh*rRK0dm2eCznAd-CXe+s*DHpZ(QtaqC)7WqBI+?%UIc zrPSts35G*eW*XHt7w^6G&EFs6*61sE4t)dNVbvWB^re4zx%K5)9Nk`?oUxQB%Sma) zNorl(`GtRPwO}d1PDC+{9?`=`i4prM-ExR^J*m^t1*Z#Q3NyG7vjHyhXv55EO6DPu zmZf-W$!_|uIQ$||RJYsfTF01&m?XaekJaiZjM8f`&Evh%bO|7}xPkc<5r`%|#d8C~ z=6}}qDdrLi$P!4pPNZbSF|#xw6YZ!=?T^-Hx9{)kSKfXsEH=Rb3pc{*I0ycSTE*RL z0@d9oLsxn-EQ~A`VKI@S)Lx-&H;I`eWbm4E0>dT=EDK9{&`tF-Z@&DI*I&N8oO(bz z9U$XmohF6Lo2eOXlNlk1&e0=|+;-Dcv2too*X^hg{`AGOXCJ&Pa`oP;$7k<<_u|R@ z&Ap3vFL&=9>FQ43EqZteeEH+Czc<~;7?VU%uI2Y+5>eH4H9tMO`&5^ewpGixv=BhG> z4hl$$yPH)FE26D6o*K}|0a^rYtk>hPxeQ(Ea=BV9)#63&W z!|0NB0J*!>2n+Z9e!t!CYptc2!aOF6C{kKSK?FrXR(Gqq;YUML;7I`R|*-TPFBQkq|;0jiC&LM~J&Lod}(Dis#Af4Un*4C7jja0hd4* zGhWC;!);{8RkpB-LgtYaY@8QUa!`|L3Ri@pT8o^!YZga+a>18 zDRS=Y48$g^&|FiR*pT!{Iz6m9#5xJ-#2MzOb01LxS%>N+pB_@an_bZnB0ko=c`4y@ zT`^B$LZp+=)@)d<<;{wq}6>5 z2}*b^oMgW(7PXSP4BP8>{_NhbzkR2Uy#_=qx|UEPRg;W8n0uYhXd6S&e zJ+zx2N;027R0D^_+FyJ1g?`n){qB<(addQiefexV>?*{yCKZ&Vw9lteIvxiQ4r$Ms zrSG-y__*9UFYM_>pI%(8a6Db_qNePgKz8yIoa@FM?+GZI*^y-Tcv~{v;=iN_6T^p97Yhu{BBv4&2`bEsp%Y|B4AiJ#W;yS^W7v?U5b-AOsUOsYn#a3 z5Jcp2bYTui1|me54Gsy420S}lqRpKO=0!Kq0gy$S<3;4R zyHC)CyGud9GHs5`=m8I^uQTLB?|?LeREowp zY>t<=7F{e!IZmEnTaaaNgwsw-kh!3m{4iyUn!U7g@)w zYK!2>>DlA=-&=G_w>8-k@RT_i%xc|;JO=VjrF0X@x8Hj)M6@)8p5bQOC`_2k(#89l!d? zS6}~_U0;0ft6!L&-g6ttQbc@wX74=pVHiC`!mAa8d)2ZUA3PrFu4i4l#l^eN-u-GQ zFJMnRLR>^4tGhq^S6}?(5550*dvv_iJ9py(Dva#JZ|gW1ts&cn{XU-R`iOcGEK=MN zN(rHds`lZVCl~kZ;d{Yu-z`Knpr$Z21{hLnOg2R9v(__H$N~(6p$2pXU1NVC26GKJ z6%mm(RM%%EA|ffZrWKl71P|DMW>AOK*Ki>CIPQjF)Awu4<5Y6?*mAL!vPc3Usm&&l z;Q@8XcjjRcws+tm7y0vOZ=1*+0gqBRGHe<P%*gg2S{{M zx2v-kR;y`uy_e{%hL!2ov!!GvMoLg+hN(ULfpmJhb^p{^dgH` zA1!RVANJ24JiF-C&Q6!NkB>yUB5R?K)gsC#KJpPS$N%;3{y`b1pZVmEtxH|-Nlu|8Es8-Yg*G<3@;MMSg;=;tqqQXtZKwohek^t0O=t$7vGEkR$Kc~IdWrp2-ClP+ z0S$IJ_R44>4m#oDrtadiI-rRG$e=<{4Rz|oQ72lZPZCvv3JW(QC5>_*VN{8(2wleO z?(zPGfBc*G|C|5z_g}u-^*#EcMf?3|CbURv4HtyENDeh%0YOZHnw|_|V#exH1!MD# z6$0AAdb?keX};$aO0AF_N|e>2fRyy^OoqzCBRO)FBGegd^|qqhUp5o@gq|}QE>TkW z>V&0rzmt)Ix%A;9lY7MBOs1g*CL$}GR=bj+qzZ)dKtzv-ggoehoFfvhp6&e=;XXxN z42wt8vB?L0q}E9t3ULp~x;u+9Fd1sE=3WuC7xakHJ+T}#{WUxog%E>G0_#Kpx}ef6 z#a4l^N+DGd^zE>H^cbr}=e0w2r8k|hXX(JCm#ONl%UTrSiF&t|yB4g{96=FI_l)U) zv51RiT+f0k$;LGIA}pp2NG&V#4wTtuggJIKr7coIym}q$FuD7LN_TOWG`fQXWmX7! zge#DN)DfaEOx1ls19)&afAI8gfA9EwsN=??ALBOr? zoj{(i7Vdyj91Kt3D1Wq+f;5hja&&Zj>%2QUUiT+`_w@S7)#lv?_iUG5A51|~`eddk zJ7+9s*Lt!rS*TgWWP={5ulCaG3oo8KVtak{^4a<0aXXUZMC9~75RzpaJz=6019^%9 zBC}Dn=Bdp6(upg@xtU7CnL;s#C*>9%IrvkOp*^^{!k8% zx0_~yA*W_!??fun1QJU?KVypDTeIz>ckQAtLNFqo@oX<8F=rh<MvQ2&5>88xW^8}uCtm&KU;gp`?7#Y*Z$Fy?-B_(! z8&ah}&N^m$wha+(FYB{{8t~d$S}09s*fQa~8wW^XzB_+`Uq1kV0)W(}Yvd{>n=f&; zzGP;Z${pv7>SeMit=p|3J<-aJ9PUB19Os}uNzMm>a8iY0Q(djqsspN21j+99`AcIM zA~8sBp_&=^X3SzfPTHptMmT-Cc=DcYFG|1c*T?6@IHQi4mM5~1XG*SeL#yob zP9B5`ht&@RMLLl_%v?=Wf|ODim5c)M)*2c@aSxEC@2o~R!o7es_m!l#)eJY!54XLS zBL$aeD0KFTv;_A0>hkvaQ9wz!k8XbJ)?J5^2v{a$4LF~l{^6=`;&eN|^1tT9+oTE} z7CKgW`t0gMZ@m0_fBYVrKTrS$DOwf|-%r~g`qZZ`pWMIy^g{aOQkRosa8C)V(2HMw z;Wz&aum>GBppKo5i?B`^;SzNj)vS&*(L#@o?0_8^r!W5aPyNEzzx4a#!?(_kyV1fz zY7$uv0HC`a5{N$ABp&SgsXP$j4&i)V8+l7yz=IUe)z9l z`qHF8jVf@FOq#v+w(X{?>Ei0UU;pa%;X79MwOTN(eACgpti&~E2(kp#SzKXma60;s zLhNI@(x{GEe@y~H%_hJ+auTD9lu0Inp6N~oRnHU4lTptU0TH&_T`m@j2&Vg~n>$gu zrIaqRSvq1i;+YpK4=cB!NZ)s01MTRir>~C} zCug^`Ked`R4ctiXGkbJh@*}R>22Idzkn*dhE1f4pw=mzMf>oSU|Uul1jF3W zlBsIpd%N81c73`C3PlO9Sr)s{gcD||Zk9DUHX_H#nvc~?i$TLiXZSE&z5ep6FWmCq z{o`-^*>{Gw-W|`+zWEcMx$`&w+MV;euU~Iog`aVJ+T*&Du7ad>#C99z3v_N}b8-Fd zezHDZ>twam!aDXv)LHuI)Q^{U7FR508ln=B;QMNuSECTtyAhHOl56_n(KO%c>2mH^8fiZ}cSTS(2<5NR8+l@8gh zkiKgTP;I^ghs4aY6C$^cb8d&`ytWdHKqLW&q$xASpLe1ol?TZK(eib?k#TLqGCb(B z-jk9F>*n>FRS9)|5%*$2gu|U_UM_tN@u^BRIJ2*AZkuRh1tTogCvetk?k$OKa}e8! z)70I;aGFPDxDuX|MMQ}ZxWc_oZXSgMi|A6AwWT!tt}WZ-vKdWzqzOkP!*P%;VutVY zK{^;%Ez8lOhnLCAR7au4+>o3;K~0P14)IRoXaCZt79BtL$KSo$6=xBZXrnhIuSSJM zih?SY5#m9woysgyZX|L9l1XM0bw|4~R5FKlIuX=tF-w102c|t$Kl$VJ7ykO=x_IH@ z8Gi3~aqnB*?y*T=P1GqM!|}=6ue``#`YT5-ytH}mvgc91UUtjg3Pq?QAxh1y6#zrv zG@}L0359ea;$Wbf;`KXUTr!l}O1{H{^11qehc#0MA{gH0BSkyHoT#~anc9_`Lk+{$-o3uQ z|M24M#iPYyxm*nU>#auVx)oKcRl5Pf8^76F3tCDkWROBf#Ha;jF$V0}#c*{obkK2k zrL08BVw)n2oIOb(fv)VJ%9!a9Chg#8BJiB!fHnvW=}0{bt|^cp)PkU7Z5!B(a*yPlDu-Qnk$TgHJg0>c9)Mf&%* z+t^<#oYJpK=>w&RXb~&!rJnTfJ-&>EYNBru5C+1{V@9dl&1p$vBfK&rYh`O@4c?kM zv)pJjw6{bj5$>WTZyy@N#~CxgOmf6?g)|#xN9Guvsa9o2Q5d||I*qA9Kx+epKuYPe zu3@xW7va@vcm)Jdp=NfJLgKV7L-=gFd-Lvb^O@Qc%!d_7*h>QJ$|?uaiy(;b8Xm%~ z^otywlq{~u%(0WA{bV6h1X9E|!%n->_5I;gnolE=>h4e|QHOo?p)b-cx?8tz?S`{j z_tQAUWV_9k)0gX|YHuNj7f3sUW-L1Ac#*lmOj-(NwM)wbQw2kS{(RX#H%CO|wBX#_ z%2I}D^V;e5fB(P!_bxa0eR=|U8C`(_(vT_4{XuL+NPEDCh%XJ!W7jWA>9d!l?Z$|} z;S_8;F>dnJzp$?#3_t-9bZ-x0*65*0X`18bO+_*PDBc2DiUnr1b1)^>+3eG(m>kR^ z8hH!^lQW5_sxd;zjui8#R)cUfWnzAkoadCE>4vMfXe-tO^ziZW>HXMU9WRgjlRL6F z_0pM;sIcv`i^o1(5C%8Z&MHa~E!b?H+`4^hbPZ#Xo-_c$&b$C3T0|8vCmrU^XVs;Y zHnSqqFSHqKr}8yF(P4ecO=l4reSbLJG8BM9Md_xY);e`X5h86c@JNbz6;UEJiRRV1 zvZy)Z%1u=OnLLj>*bu9a%_0fsf z9X!Ga&ez$rONXOiKIU3%Nvxl9OI{YPLBK01`rf_!ANlCZ5*{9AP>t#aY6Jti-;Cp< z>)UdCFV~!KKnh&X0gyt_EX)D4k?&BlHIKOv_4t;@PrS)x4FD}@uQz!p1-{1 ziZDtg5Kg5;REUEU7!5jo`Fa@MA^?7^@H^*dt$3aV$dT}A4Mc|W1C55U% zTCJT(5mTV31S}@A2_ChP2A(!lVPO`tzQT5s--;2GoI` z9vCK3j!qwL>z{t(!JmBP>F55;dGzA@7f-+ZwSV$|{XhRt{`Kd-^lQKQd%OMJlcT<< zF<7))6_l{PKQ8Q+M_it7-~7;ed;RcFzWUyPJG*_L%CN`f^-g;$vR%dHkGzTh;a~gE zj4>4zi z-Lz&B2oHK~Y=l!66UDix-fNLc&00+*xjA(PC^gca&5|9B?9PpJ_@sa;Ik+7}Y5@YY zm|KVD)lY9?+z=LtZ2JvowyGfuOKHAe`G3p#b`&8Bs8kKWV^XoEcR-_Q0`xFSj@+T5 znaa<3eo~zv)q)T>-D4COBf$dAOK1AjwaIWP0c~`VzF$0+}?Tj5D6{;btkd=1CYQ&PV>$OrW5H&TA5?<{9KZwUk{CXzhoA(3uY?+i+Ir9?@=+MF1tQ6Grt zz%_Wa$8_PcEH4lDZ++u)pMCtjquw@)#byf(!@84)+y3jrgF61SpRaKHJ11xKo;t9M}$q&(j~9wC^>2nvv6G!f03F-V5Y7BYe6O_V1? zU+%nca&{+(J;N3&wSB*Rw!5AtkF}S+r9k15j5J9k##-mxZl=@XM){p@jwZqZ-~tH? zg)ruKPYprNb8Tf#uEPUL)}S#QTJz^jGU7%acykWVXD@H+teNLJe5&s|-Xo>$jTLLV zEHieH;?7buR4+&Soo{{v(@5z9T1Z+HqAPkV>)!D6;qIaAayF2ABe8u}8f3^u)6Apg z@R)VZU>cvWH3Fn^2LY;dd0RvQARN_3O2;S%v}G*#7wgCxhHU7Spo-0cuqnBhmixV&!dQF6VC(% zEZPYbC>8)D8>up0~ECq9v(d8ps3!Od6xh zfsR>iXJ&zjI!$$~QWnYkSxP%|wt~+eZ)VQs&zs?v=@0^VQSP=^&z?Rg#L4Lk%jH>F zo(?8qHGK48H|%#lY?T#4uy_C}hDjB58pi#u=(<|A$I-n~vT-1<+?6@vAv%gjjJ0|o z+$cf@50KkjbH#i^6E{qw*>>bz7{K1#L7Kb43QFJimlqcw`NW$q-RYlP zd7Vb)93xc`(urx`?Vf$|Q$Ow84TR1r{@i+;b`CyEzyE`nK|n&&$Y3v5nD=5P|v|IFuBKwb=8;A%ES2Z}hLgosuf z#;Hyo1?a)9>*3kf-)f8uN5Nd>iLKvY9j5DhY?FsLCVa{M^62Yv<#9U!W?fP+~Lx3G~# zv<%XH>;0$O-}v5-{?yOhyL{@WM_Si^^`}4i_P4M8!9V!o4}J1)zw?e=Zl@7e0iwXB zs8K`^BXRZ0>-edk`%6~tUH!?uJ?~ys*tCEw$K`&CeVrzWtKE0sk>CGT_1&^t$<@Ql z$I!*G>B5PW;CH=f@a*KI#51oU()2LR%_vj)881 znCai!BJe@?F>4Qaz+Vs`EEuV~h)j6$y(3)(b64k5L_~Jodebj7BP^y#9Fa5lCoKuCIbt?{98rrknd&yx1ETIeO7E%OKyZY{3y!(ng z5YzRzJTd2F%L=ul4|L0W&#oCLcg~LQE?j|P?2wZia;Gwt$q5hu#%%qM6i;>tr4>Mh z5>ycncL{5by#kS5yIVtf{9FHUjbq9P?#E&w#~Mp^*!vYU`i-58)S}l>x}W$7f8ztg zs*J0pUx&uf0!c##8!<~VFwF|ec`=3qCNl=+5kYgR3j~rMsfc8`XJ$q9Xt7>uIX><0 z{`4pQ?2BK&|L)a7j$09ZC|Fto32)NZd6`56yi-~AM=+awa-Y2Ve!Brn>C0q& zS3A)Tq2Ta!v&14ogxM=fN;9W6Q|(kEClVq5fpqCjPR^bxZULh}sH6h(jFGVKOSq{s zPu`RZhfHWrrzs&ppFwP;bRhzYC;cg6HRoC>np}d3t7eHFA{6bHM>SkZS4t80%V*cZ zzF!(<&v)(#A z5lFftZB%ms<@M(2H`A=ho~Im@cXJcwEHm#{`mC<{<)ZB0c;%?~Z5>8K=TNY?)66BO zM(_qA!mRsfyP};<8DwDL`D2F3)PVnT}p`;o-PVVb!*n^j z&?g)r*`XxLsgBnd&&J)RNSqwsDvR5$Yhzb5_S>sz+}2?bpkxsxI$2GnlUo%*CuMWB zIXgS1sJXM`n1v!%(o2HbLF9*}%T5*%PEAW7Zz*ETH>d#%5fPFDAbE3~L<^2FLuWCg zvk=h?wr5)=;3{cHgv?}a2DTWpwAZ|Gl-J;UVf0KP$0s6QTj&@ zE_z)qmgU)Iq%O0QHy9!^nokqI^3Ic2Uw!Ffw|}&)6)b^$hw^(+I@$tRye*T~C z_Z!{sG=dZh)R1)6a5&u6vA6vr=O7DH-(FlU^!P`A=5O%L4}a-D|Gzi}N!?l#zeG?> z%dJ;F?EdZj{d+48()p4k@ZjX5R<=^|YtH<}E>lce#+V@>qbVaFsU;0Hsz{iNHi`q=_M+NBSflz5Z zG(rz0C5ncsltLvX&AHvp5R6(+7K>-^eslZyt@Lr54{i@Kw^rMf9>TKU4>LGriARLF zi1y3%<>sQaPAAL@IGRK#X5Hce{eN!hw|_k+&2uW()&K)4ov@q2JS3g}QeUgWX>xNK zN>KWKVdiB)G)$qrpSeebLy^AN`G=`=8&w^CK^xZ0_BEduN+ntkH55;Ufv9^DvtWn6>$#?qmR4YLZY~*eP$lOH(ucR#AZOSJ+KtXhrlI* z-gwOhEwywtbLK>VoX+XAnao5x+ z(o@^g)I6NE2r2Dv{To8jc9R578dJ#_X_`~S+nGOKH(CR>H(4MOb#dk!DuW7-n2E(% zGZ-G;_u3!v_)OMyx9%VRhd=xBul(f6RqYHhjR=TE_*4ZI>4GC6F&(c<{joRp&fPE# zqj^YJdh3MJRje!hqAYv*?ZrN>C$W%VghyDB1AKsn;N0+@bDRt&Sd(&(wv+@S#e&t! za=Bpdx{S@{@l~x)7e^ktr}Q05vB`adQcb(kxpsQAO{Jr<@?eA@W{uh2`d3tR3H_-6f>egtRJHc?L%5Yf^2G&w&+BIc`;`i^Ld&*GR_(GXpnq{S(*O~IrEARmjW`v zYLl&qQWbDI!b9@#KxdA{@|e1) zm7Pi{tFd|ryttOK=oa8^*luKRrRxCZyQhXEJg5=}Vt@^nnQJ0MpcNo^(>o5Qp!ZkyYH#z-fz_ z74CCD_FvT34*;ORb}9#|S<;k6`v|FKDOPMXqPJ9|4HAut634)mR`>TtmkHqR?xmYewy2S}} z<)PuWyLvQ@mxxK{3$zG?GhtHqq71XriF+OQgZ4*}opC9N5rMT_&=bta@&`oFt+rJu zEozG#Qk)Q^z|^DdGXM-6eDYKLHp`pPv7USop7 z+#)2Mwo$!Wt)LP&jBfkyx$`;qP5!R2J(*ehaDDOQ^z3|`CNa@cM9cBfX(@f6&be>4 zk0w#vUNLUy%>!Z*vp;bv#Aa0^1kyjgcnXx$kv(s*+F_NyQH?CSV-zdZIZQWmuF`u%$k-ey_8^x}tx zph`Db?R6m$t{PiXiT&jHOtRF6+i?mu|9*OF2BIXpO@bVC&rR-WO;=L(p1t7a}Lf^AfC zCbUq|2@s{ZC2LXL;l7`y{p1Boe&dojK9@T}FvF?`KSDo~ztT(Z->FV`2mOt~;AAbG(Y`lK>W1m>9 z&tC7!`9JyXw;nxv>C4}KczOM6uf2Ny@ejZFq1WHoT<`X~Y4WX?v0HVk)AfLObouCC zeg5~iwY%D!O;0!DbX?+Ct=c%4Z7=sa=*edL_z(W+m;Sf^vrqlRkDvZ8|L#Zs&fogX zfBOIW+VB4HW$8}LF->L*6%A-CI`utLq?$NFWMaWapT_)fTS!NP9vE|#+|K_DB5g)t z*4LO>kemk|?x78d%~UU2c+D(YQJ*rIS&bXHe1z#M8eUX zPEunIk_TSQf^GqJXf4<7Q4OpKs5A{A4u!ukO8%~G#b$JNkZ8_1S>%g1CcL$r9dPTL zWW7Pcv%+AmOrk}I!{<-Z6m6xnlMrIonAz|EDln(=hq+E^2oMOS95r!{!|Z24AfFa2 z6J5F|WaCnVMT0p4-hgO^pvXr95%sWn`FMk~C@O_5Ql&vsW)AV(5;B3gkAv@4w^!vl z`fhVp>I=(pkPc-r?Y%1<3(c%$mtxc8!**K=R0xU%gk^LKZ)BT?Io#_8+_Gqy##k?~ zU+!zneI!0JQTA8suK3iM6iR2c0-e*W zTd=5D6PHIgg9}Bg^dc(|5xU=OCTgN&O~;*15-n)w_7+hG;BBx6!y#UsSovPIKm8LQ z!~R*nUWP7Ljw7EIcI#wwvA(?Iqann;7{iONz4X<|d<3G1cC5QO6Umh}Yk#Qj^u#P@ zMkcL-iGt8srq*_xistk=!>GNT-&&sBS{mfy>be>t`ak``x4!YU_fJ;0=>$j5X@v(| zZ3a*QHA(!u@j571rg_>KNl_hb$^0L>i|rbHKPs6cSUA z!*)#E!dj72M@&@~$Ds=qU!VYKifVuhI(q6e+@TdE8b#1f-=;lnS+!Qr4t z7&gz)8|o|FlJk@EAcV?9HUfY(c}Du*1PNaqEw>Ne>9upW!{CEMnl|5{8JBLspaq1~ zUx02g6_HB5r8dc}D;IcBCHE=fp^aB9!L<-1$Up@)DBzeRn=Y zq@**Zd#H32iIl~KfnFO*UMO_;Jd%YFk?;`J$-Hzc%k8R+X?K0`v=sJzS93tIM8H)N zxe)X0B+FaFEW$y{agxLJSan>VzA%Q{P=j#A`lMSe`u+BDx4G{7W!LvY^{|6;htDgK z?PrNu4ltivEw7m6i1TA@-=vY1!aQt5RYVHei4?=aqlc?f396KcDUYMfM4DGc%RghD zYJXTs*~OEnURx5$owXK;{DgSUh2$^p>jwZ($b5(>5*F1fb6PwLSZ8eA3FMF?O6yng z`O%4J?lCwZj(|HR6AvlPssx!Hrd}EIT22vEwE3l=)9CuuVs+ZB&LF)Mt<$)>z8rQJ%Bg4$txVJM29rSoZiZ0OEjr4uyE^KZ!T`OR z3839-vLU6-BH~d+Tg1!!dmbdI8A4|gswp#@>(ZPWqNRfKF_f6x%;M#|jG#-Q@4bnH zSqP9GNE}=NVU1GYHiku|ImFBuA{3~OW~J}u8NBV`wp!o@w2oPE2eeR^24#5q?CI&P z+nF{Y5Ve-RJ6f-{`wLM)6PZhd<)_D7e>9z4`-7NcDbidc&*|_0-9$DOPcANBe&PIE z4=;>xYxz7?0;Wa7?S%f?8c!bADMsxVeUVJ@%%TXw{N(P-%NOoGdVGI>HDI)bhK9kX z^j!uK)w^)OuC(mW-{`iRN8kJQ`71xP5680iG0WQ|Pr+>d4vFrTzVOC)@~x=4zuqFE zTOE(jRJDLk2@lIwtUT5cw8?(&H~+DB$LKmRFd3wvP9;XME02m$Muf#eDFxHT#h?Az z7fe=xMfl(#caIn2L)cCh$4_p(ZL%WDl*Zt0U@^|UyIx@_^mB2O=4osSc2a=8hK}aS z%Kd}l7S+=4iGc{yOc8<%P05NBH5<0u>(i6l(^yMMJ->LwaS zKWR4ghqR5)#p`yg2P8*-Nm1<6{=Np%#)?ua=J~0t*+iIZq%QE}{;P zK;d|*^^H6H`A>f4;~#wG!f<+e{QkS|zkISfQFAW#9?u@%zk6KouEskbc;VsG^5Dru z3dN-dBa5G$&}a0MhAH#nzP&mHVMJlr_+Gt3A7{7@67Y?|#n zyxYGNqL3scTo9TaxCXb(yp-;4^s$;FT!h-lFElrrR?$YVnopCJq|75#U zpy44(bC=e136YX^FJ7H(-f%>q=E`M5djo+sOFSbt50@FUB~H4(c}v&R9g!Z!Sq|Lt zr1T(fk&lO5xIx9Bh?8*M0m;J@_`0ybFR1$9uR8e zY=btgJz@9k#BDiibJNYw9Fg`9O|jaj&cu}tR5@CEVgxPo;DCp<p z(;X&r_Ue&(ebtM{f#d@{L4S`!;k9)M?(!PjwBW^>ri0RI8COVBkhVpxT3l5 zCkk156%FQehX4wRN={uROsGPw zFf%U7^6UEm>)eOd3ie&3Nk!pRC6g`}#46$FJ7 zgAjUHY9ho#!kR|J8~8<~W#JBormYD8hS6mtacA|I_|D_EUff;8RD17s7j^sOVP7xq zKYIU;a_Q^a$EPP}FHKb$9V(Fq-b5|}9FlvWwM3baimbnr>!k`$7HG}Ysyg3cFMIfQ@#*3tF>Nexn$$%V+SdwTh ze`uAl2hH75X(h=yGi{v!VNOF)!1L@=AQC%pI*S7CZtjt`0?2+eDGqp017hykvIY+c z!lm^;wcA7-PU6;#7Ki%K94%5L{gl_65DtC6zXViEY3V2CWBK`mtsP|t+yrwr?i5(9 zVJ@oAnI*v8wTQdNTyxJb5ebMixrBRkrFY9(DL79*h}ps=>z5qnF*AG;hxDn{uXLXo zxHf|_00_gwYaJP$LpK|+PGFckso3)0_PO01C*RoSV8e!FaHvXvxv#GuRKz@Hs$e z8aOp}rwMQ%pjfg<$x=`v#B(r?%$ZkPLK#rJj)U}(!q(Z6CSl!_qi5ZnATUe8IBuRi zc?=u7Zm~K#>iZMw(n8=dZ7=qlYoGQ-0Hj*k092o{a1}7ZX0A!YD**v^R~M_}Ba4c_ z6mqmWv3ePGLX2~H91*UK{b>L9fQUBOlF78>=g$b9Ua<`+WvjL$#hyqgG}>440+b@% zY3Wbs3a`_&4^CE-dC*zsLlt$3h}LT1fGSwV$x2rOQ8ew^Dd^cDC6*3p$~=(WyL#!#Dc6l^ihx|G8fX@w6@jfiMTO`}#M(^Ttc( z|Kbbpa&Z>Yn@<{6un!Z)fWclqJ^T63e)@8J_pN7p3_~Z&y?0Z#1Pf8%EI;!<`1hYx z|J)z_(dNDHV)IaHNRv*bfw~rnI&7xp`sAno_6Pg;(m(t4&%AI_F@Eo83Cp(U*467l z2};2*ji3CAmm1`^|^@g-p_ki5gTA5EELCO3pS~LW#0i^)G+IyM>CeR;#h< zwX0hUkHhz5f3!N*==N7*)#{6G2oA$OrqLz5J1KJ3#S^JJ+w3l%JqC)*qdEwP6p$2Y zGmdBdGh0(oZbcDM3oTGZxL1ii#TuPYV|x}!G-k7&LrAIcadUNXe(S}l&3LAeZr*i0 zDU3FK8W9IJsevgsXD!?2K;yV)xmg4n3!FcQXwtbU%>Xe@qm+`^SyH-ns{3)`qU`rq zC#SmZ%hPE@>DnQUs#EB*9*x>4$f>0Yg7dy z#HXkhBj9E+RXaI3DVG<{Lb*NI`fKk@-+BN3=K7h%v37SCN9SSK`E?PU&}~N)?*clG z7sZ}^=p$uux|XuI|MnA~hL8Qw`SJPnr$36TpMCwG|Nihhe{k<=jLK!-pQ_hs7`a%4 z-!7{k|LxCx^Gjd&+?#LQ`Q|(5%S&+YN{FdBCD<>zWv{-iGl>7P>|k4)@*og10VCn8iLWpi4oDVQ44|EQG?{a2u8Htr!z=8X$p6D4Kf>!y0Si9$9f`=%R{;&*ier(~1!h z?x`!DvD2pZlA7Z}T8L|DdLYbVZm&ha0y#3BM4LsBbZ9#gQgDnmZU7PP$z8hxHPMHe z1`By1anSwdfKJX zFIk}M9gh^{v8{)>HlWSPTSI6Ngwv^sVk=awNXWs_#{)H~YUIjE6NvyKa_CGZ^aywh zgr2rEf)EipLT2Z__BeQ0;!z=7z4rHhY zy_Q->2-`p_DS3N6=xHzjuLuY_1*(HyCsSQNsV9{s#olafV)MCsNV*u!-U5Np?17&Z zpH7B|xUoVC%6PrI_xQc>+YfHxNYqVMl%kHYTMSFt^?a~8d+F}0o563d)~BlLq6_L| zF(USLDn1>HM1?`zjTWFxU=*p0nuc_6Q6XEWwmCw$AI6FrOiIW+;#&OZ$Nuv4o%jFY z?|=Tz?&)F}mQ{5lcZ+iWOh!BI*VBG)M<-Vw{NeuS)Rma_8eQYZhgn7bQWcE?> zP-o7mODfe<@fz6~AcPir$gs20jc3Qlu^59fj?jwgF@^P_0!{A{jqE#0DQ}j_64cs~ ztuPp=&FCWt>8cA6`|bX?kef>@YhRwZbk5FPt9t?VV}%bYK!`)yU|OVXT-phnYQr|x zYgSgJ(JW^8#xX}BK1Ts0spJ+8aU^6PU^8r170D9R4)R2Jh`XCddPzbXAs~_0NzgTNI$XZ&( z9W9(69`&q*J9JzGNeWs0U=0YfHYf)`xRVeOGWPpj={sRZc5MTvqqc;W1?h}-W;#=~ zokvfQttW;VKfyG{nvt{vQLjN)hb<%_?0QoaP{@8ZOJ#(P%-40Mjsx z``vPRRQh#L(~C}kW=$K`gdj8ZlSjIFNN6T&c`AkNubyp}>s8lxDl&xC2T zY&K7yJv}-;>-)~DSF56PJx;Wof4;=|-unSVoXdk~2}Pd&vrix0>89;8vcQBeiAtxi zhbglX@-i0tkx*s)mUntmRR&xGn+$8HQom z_s&qZobeDC=H4qrgqzc0`~CIhvo1umELOM5>I|}QP-wf&lil`7iRhs25oq?=lo0Wj zE}6}joP~)7LIhJC!~2{D-+uGMdn{iH+p@g)qznfw`XKhMr%DVZh zxhQI;^+3~(Hgy^3H99>mUFTll=u~5LrARiXp*f3Z+j#)}qLYw1Iy!djCQM-y$?Q}| ztFPHk?1X7D^v=?x`N4vMaTs>{-C}VBAk0J%;peyRy!YO_nM!NB7PiC`GZH(;$Cgdc znQ#8gCa*$d`(1zZCtv;c-~C%ZcV}hyF;+<5`wPp}Le~4qo?dPldU>s<=WiU}+T7Z0 zjyewe;pyI^EQb&;g|fJM^8SDM_x_(B{jtCDp;ta}@7eWm`BY#57q({a5SgZpK6|U~ z>iJLp)rW6=5trZi;y?aR^<+)kB|=3eJ|TcRiiCaj^Z(@AU;X`Ve^s~JQjWg-FaF{7 z@-4D5YLX_I0Fud4Qs4+X>G^?IUb?C=)ou6$*G_xHk=tpMNlM3MUrolv5M8W~7bh`d zbGbr5mY3x=WcB`+ul?D*V1Nq+lt#D;5$YL7yR@yMhv!&kpGz?|pw)*mf>DLz*eEq;(gaGETSR8eWb)9z71hCJAiW_{fCCTk>#vxI=3GadL z5A`I7aTv7-S=^?sC=rARNf$`B-CLY?)uJ!u?0Eg`@t!D2EQq8+H{b>cT(Vn!doet{ zuuD6C>w3Dnc5yjj+`qiuRoTS(we4O!Q(=8@aj}V$cIcty^8k{+4nz_%TBvF^ZmBl8 zImCr7FxH?21Sp;pR#RC=uWpl75Ta6jUv%u$Mwb}1m$Tv0_B^Tag2)QN-F{PizgTo8 zi^@V>IxTQ{>wNt555MvgKmOY8>fXcqn_vIkZ@=}{y^p`S_z(Ys*Is?ida>8;T%I32 z-R`#359|l&7e%_lT2HjE6C59Se0l!xOGCdnD|#g1VP+ncnrxp+H0C&-GvLaglr&oA zzjJtsxKVBfys51+B_)yF@d9HAJl*0w6Ed26HX#VBGqT&65pc5zYpr9L)rJY~BxRmr zB#6Ms=D29daD>iryCL9!v1MQpktCL|?TjGkt!@br;kD_7ljMQSjc2X(2_)NCvJ~EW zSXRz0+~qCHTm-YqqlIM9=E}Rl=s*N4T6K*udJ`LmrMQ5tn3@2gKwiH;YMX1+6d)01 z2^=28eK!vz-#B1lDf4jib{}TP-$Z8k}I#Gwee^7P#Y6t2ngtLaJi-DnFS)v z1*@q@7}K!Ph2e)1&9h^-glqU1gp``adcYZ>fK;1?Xlq zthIKk5$;|fw1~+}MG4O*C(^lrq{A{hwF>RTYJlqO9VMOOkRrrcGcuotJh;Ks zX3j>p!hsO=Qczv?7|Rhq{A0r#pL)`(-8#{7vSz2t({g%re%yat%ad1M-eWwG^6f8t z<-s@KlCAgs;?<9Q;Dy)D{@}NNtz2)9md7L1sDNwVpKVk8FUZ zTQ-v{Xot{&SD7j68BfmrV2PKy&t2iW(gF?L_k%w9~4h1B9{;SrFL1LPW)IhjT=9AJ7UCc?mC(UV5t4+PEO6e02rwD_i z4JC)^mZN+&IhE1l(<4m$7mV~3z6&7k0 zi}ewtb2AlHvE6p3qU-f(g;qCBCt^=w1oPt4)-n-dZ<=diTEpgD5<^&5%Cy^DUF=Sd z*UQz};`mkshwT=1dvwa{>&?~m)05+quIm}_Fgc{i2$eL=U`T5xYy!kX7)*bPCYA6A z=jotO9pXAN4n9CgPp8K32v>bz3fmvh&g&%x9 z;_!g}?SAq5O2Y5^`oRDcfTm6-vlb@YC#L0TruqngZPox1VgNmom?B9Egsp$2PIVYN z1%=CQDUz%O5|be5W)wlBx2m`q*!*Wy=J^hpgizd@{7E&2{dT{-Qbf00uU5Boc^d4b z^j7zm7ms4P?&xg@&-}cas&&TWS^&$kFas$Z5k)yod-Mt|0LMD^Kq)EpZ~bXPMMS`m z5llSXEg;%aJBA>qALV$nYy>5}<;V>}&CGq4EfC=%@P@@IAQpkB5;2Y=nP_zGjh6if zklKwp4XR5sODxqGb=r6R0`0rz@sQ7zdhM$P+jfH@0x%&qS5Nyc{cMAQYJR*vRb5Uo zh;X)nz)giY*BgH>P(HUQGq-Mqt$~65?fXyeoE^XU>gj{GwoVh+XI_8pbir+nuYG&F z*-yXuFTZ+r_YV4#*Iruv#7AFy@2&6t={KJ~(QtIquhyNY<8r@$_p4uc;pGpO{WR2c z)euYSIdhfa$>rC67wF!4@9V73mgA+r{l(}OM|JE{b(=)^ zZ}TurX|fa5Q7zov>L4*FMuNi5wgv`!RS~mE3p+tqIye?(Wn6}gMV`L$;_yqq^s$eA z^!4BW?LT2B_^<}xac)p2=OWi5n5QKM{u{Be3}Fl)o5BwG(J3|N~=|UR*%k*Yqa+1xmHiG zmp&KQU_?}R5?ww>&XqCSq3+=`${Ohg+LAxFXp(~G()_OLN~f%rReqYn&P^7L*aX8P z46w;m&hFM3Dn8U?K2Lnq*#=VX>t^QLM7H%Y@V%yHYijgF6)6XVo$9!ly~Ftn}L zdMDTdHD=eFR;EVVvj;RMoM`4ak$+6&Xg?e*I2k}#zP>bII&Hma#60kiP4mz+hOCa+ z4Z&j$S`jeNAPy$2Ni9xsFnwW=;3Ap=0xibPI1j=-6hcV20U=r(5efFq3Yr_*bA~)K zl@5upoB?cxFmuJqt2Zk|O3+8Q8nmjTi-?k4xEoAL?;g@#CEVvH(Wd4~*Gb|rh!Oz_ zo4F9=FXozR&P`@m_PNWPS1U=OrvyYzq#~wUuig2jzx7k={qE9jr%_#ZQN|i$iJ$MISUQL3i(QC|yt*}bC$LJPy>ox4?oS`% z;<1i{Qa}w@tL|nDnynu-p{+Q~w`yAr87dAU4@d@Gp+@)g{-!%B%af(KU0?3ISY*?F znzzga>_HK9(~pURGP+@umLOo7M&^iRH=iu7o=s7fSG%k0-7Z`tI^`lioQHQ^sR1Lv zP)e9Z80K-#gy;Dd@K7bB4iMqG^G|&E#ZTRN=@X0HOR#6d`G5V*%YU`ME+@C^X0YAU z552e;t{*?%>=(x`1|@X?kme-!T+2r0c{7?YSLkTGU5no>z|R-{=T7DsiGa?K*L<=c zM0{CUA6k_1+vQNKx2O2$P-uwIXXHX>@FGQ82LPePBDSPzRzn|B3?873IclXgPTQe9 z=eO}ZCeiAXc5(CX+kq`}O^}+zD5YnF$3(U{*#s#PZA?p)B9<*`uZF&ziQKNb~1Pq%rbv@?I9AU7K=5wUBOJbd zKmdhCJtG;Oxv5wsYL>(|_MAQs5zP*VoD&SvA{aua05)Q`i&3yX;u_MCdv{8DFeC<+ z05TX9z-@~CP?kNE;i)u5OV`|01n!H4KD*fLH=Dle`sI4Dx>L%rXz$^>-Lw7nSxj3E zZ_Y8E(#J&21c&BQ;$Rt-8MHU{EJ$uP*FBE9#Udc?K24LBGLB;rb&EjOKQa58N-!MN zVt`c*P!|?8VzhA&H^~{06u=H}a>gqQ2%5wUf?gM7p_G8;W)Lw%9jC2IqHJu5UDF#! zHY=D##3Ul&7Ao3lwNcSE2eJcDH!t8!v06^rz}#8Mj3~;>%cp0z&Q+8tbO*I8PET(= zeEJ@9zTx2`kg3sV=s{qH5jg+hA$sy)XpIQbMJ0yo-RtM4pa07H%jI+~)vms_zZ~7l zD{Hx0^zS~rfA8Yq-MhDVeRUKIdHU{4;k(V%b$@%Zqo-F>CuUZ4h<6{nr{R48kpssV zAPe2b&Ri{k;^Q^Ndu4$~9qA5qhVZ#5Hw~G9SocD%K$=6O^Z;Wb2x|JjWX0avd@_c) z)PD7;zw}GXyDvYvcp5gY*|AnDwb3&FDXr|W*@kH7KW zm-nCk$-i9ty8|_1u!w3XvgpF&@i*W5lYfT&7;~>}!`;ztd>}%@@^=uXS((iFQ<_iW z`>>!UH3C(-I+;(_s_TfLX5n{qbiCO-nG=iF$TG9kH`&1Vg^`<|+X9@6z1G{4BU@68 zDN@YLLlL4v_n?^2hy6xytJ9)^z7%i$@3~MN*>#X_;$bnIpY}id@gM!xgZKXUo%@^a zoZ6K@bzV)jq`-o8?~@kZhWElwg}?|Q90HBmfg1D~q?b;3Yq>t^>-8{|l8S7VsPqs9 zZ`k_uPRltXIfH=e4mVNOYK*SebPKg$?L`2%V~UcTfG&3y4`2V_{+IvChwk1w{*B-I z{eS%@-~8Ku?PrH!xO}$j{FK4_-@6{KU-<3Me~I$&7k}n6fAd%M^4au9pMUSG-&?(R ze_hH8?!q#ufK<02!reU3Qe-eHGn`Upf@5kcP-vS`CY0=Dhi_!xHIfM7p>oSw*)>c*F#k ztp5RVBd8(nNaaNc52dS^d(OLM3eRE=dW6#=TZ_|RAQ+SiQcJ)KT*5%{*7lJ7pLtti zZj_j7`|LJw7pCPM-5k>RH;>Tl|3`^-JO!JNS7hf!02FceS*w|#p=55Jn=zx$p<5v- z$K<$$5-?I6wNa7jk=ZtU+Mr298+D$K0;w9Mw3)3=cS}#qY;Bq?bCCMOi0oh$a&84- z39$%At>Ts5C+Y4U5pZIP2$7I^!VD19d4>QASpprjuoUfP-ZKB6CNH2;3aH}M09A&F zL>SzMI(A)eZms2`F1R$BAI7pKs$jH%}4w>CU?7-Ybi}S7YNL?MHEiomD0%N$? zgw56UB6>uKaGI=rBWJV1)U`b)Bt1MA0=d{*HWtnDC4Wolt~VxK8HSG4nDm3@Dh;Ed z@202@FvKY_dkDazWTzG)=oGCLrlJd3OxrhP`jwYn{NPEPY##Mb9=-VDYd`a&AOHLx zynTM>wUd|X`1aHP&ENP$-;MvtfBrj9*lo-K%|6yVaSlXIG=J>8M=}?nXvk)(tfYaN z3G#B@5i^d^0)Aw27qk4TeWh{(U^It|!||RAIR`pMv~+jofjuN5;nkK{rKwZQy=FNsSw&}~U!$pVs0q_Tu%+>c`3@hjnH24@eLzOF z+Hj~~k(?kt3qXQoabmeVcEC_60s7^|6BTGF)q@I^++=NHVmfW4qNTREJ2SL-bHgPf z(!YTTk<4AU_wT=ZbbPyLUlyyf)M>b0Nx$x2xVpN!c=Y(_^rS5M+9vyDNc1A&)rH#R zGxKHhu=dj7Bi)3w4-bJQW+n(txJ=53G6bx_6rtKi-v-1jYE3i`%zml^^aMl-kPnIn zhJ*`B9O&D`yK!ieX_B%3j)MQ+_SX*xppai}EhDl4NU7mbr>ZLH1t6+9J1_&2Qkdn7 zAmFf|pqOKV#%fW@vO79CIxX6BnnX*qw1xpt7>0D>5V%icw^)VCOo3!vVgx0if(P%v z8{tCL?zCT>nRJYZy5C(t-EW@}Rs;;Vi+S^>nQ`29aee?Ri5XUH@RfSUOmA_xx~ z&Gy2H&7YEUFE0*=W`poiNp(vgJUqfJRNLN7h@Q_Y)>~zdn_!Z!8x(RJ_MX~2>D)cw z)#Cj2?T1g^5g@{Zg^1RDG^4(3(=j;Gss#(BV(wb2giG^9-)a*CRmF^ZLoxPnoEt~{k zobRS5)D;ql@4fXcd-7QOvQQ4L3`SW1LmyL*{j#$_H%;nNCP6QYu+gUd|FD$d|Ni%X{R>~X{u_V& zXO%R6)~}r#S(Wn`-M6RDHg|sObKm&lSBJm(GcWz*$4_7W#noTe*Z;r&LwhgbZT4xa!P%I@-+4ZE;94*SsZ(g!{m0ejBm z>yWY|mq&I^G}d%h2zUd|+$>KrEdV^V;=)S{(JOYrEFcoSU0r_V>%8}n;H=%)(TaZH?9gw~NQe^S)?fVD~uMA6J z^x^pMX1$m`hM9=mjMJVIz_z0WY19(Aj1z&2a1nJHNfoS@{r1Vg=1wZ0c$kOljj#wa z`N11C9pE``CgWYh&DyUWFn8PUq;}KfWT$C^B8P<#+KY#AZV9&5Yry<|MT5iLqlDNr z6`4MH-u;i>y#2xb_xfi$9X7X5&hA|d|NQG;ljGNiu{-z8-}uWPd$W6VeAeIY{JqO@ zz(NUzwE>D*Y2RdT(b`t$!?tB*O_Vh&w&v4#uAL5O%KYc8#3ycQupB)r@aENmIitj^ zAZh7kD|F{L2oO00^f1Pwy;Xj)!>2vO{?=Ua9A7>IY}xIf`K916uzcR}b9g~RcW?6h zLypp3GY)zB|jUFj!xl~8dXcjM;j z&fOOw-tw6U3X3kP8Q|J(o@Na8utJ>|!+aa*d3ZIFAq1p|Mp!4hy?T0bdM2fhz_LHt zTwRpHqoXqzcbAu^w{Mqp5TDnfyJZ7>%H)E`%a3qSk^z{XT|(6!?%>R7v{`|PC8b42 zSh#8llhSK;-MLMILeLNEVBYF6zq~3v<&bDp1Yi`p*GB8MS%xzd@$cm8-}pv9Ab>(T zHe)__V?O%=2w2}SJkvrK!r|tX0k`mgyHngV6pJ!=dAZ$h1}(UI_s;3+Zr81SyHU|u zG{tCbp&*nJQEjYKPeQV%rHUqo7G0-N$M@fVyDL;F%cIk7eIBLnI@W3X?C~Q`Y4#Ig ztp`jgV3^mLR8GA^Ru1!)2D0+a;F5<#-}U2u^lpT7l)_S+6Far)LM5s+htX)btcO5C z1Z{WZ++8VU8JayFDWcO9TyPF`Em}8z)4}5NS?6vPk1!8wc~Uz^Gv7|WB-kcEi6|&; zWUVzQB3*N=Y0)<03TE6IbLt*5e+2|&?#?i?-EL2=x;{vU+c@lRo!){KXQ+tJ=DgLD zjhum*d9CCPW#+rBRP*c-ZNbZEUcT|x;~)CK-A5PK|Ma&%_q&L21Js-z4h;qt9nMcr zRrF+~F;1U<`(Ip4kbZI0A5R`d7*-Wl5HK-kgn9%u(};+aL#I_c!{H>Nq{1YvS<+z%OPOd6c#~g9p_-s#_M;#j^#?UOb~>+>Qt66TaT9u8_m7S zBB-UPb1^_l=VDV>OkUjL>`0DffB#Q@SDXt>mjhOBOS>o+>o8|h*%!BZ7B_eQibSl!tOef`LYGx_z zi2m=0zWnpQ%)+H9nF%z=g3`)0TD+7fgn5Hc` zR#eS4Sx9TojL6D5)klPrF7WW0Zq2p2Om<9gnzUQnR7LVSX`&-o;UU|=<(se6U;UL2 zeE9XVZ+-jyfBZXNdgsC2lcNtmc{uQgmZxXOrN2lPY_s*T*3;$7ufBNe^wtNz_$P1o zdwlH8?&##=r$2l2`00ap-We-(nmUO^c$sr5w*C+;)S{GLMCw#ZpFOwU*b&5`4vmI; z2Iid|0l1@j7^1V*cQ9?YkM_HL-*r?OhkdWI=+~Zhnc+kdBpMKRShSIFq(IdxXKiAO zY=`9}#G*)1^=zJ{GbRZWDI_QiMbN3lOnXU!t<4MVP^A0Z(Fry`xd=EZsroFWJ46(s zDjg6OweIWnutk7zGY&bt&Wra@t z;LUTy8H|vW0y~`HT~}-wA#HawF9v!+NVOEOdc$RM?-C4W)IhZy7?Fq(n4S9O;wfBo zWK_?NZL@K=fenS|Xeb>x!|G`Gz0-p2q9i05km>1xJS&pmNpAUsFwyV`6{fA9gW5h(Nc?dMP2Cor17d>kX_|@<5h_JQEnN^I zns*H8Kvx!?N;Xgt7n|$V(XnW;h~!#zgn^H88P|*I3oR<5flwF6p_oF zH3|p|M>wewE)oGp=yR%s=P~KOfUh46Kp_*OEcya%GD;vqmD4m0)3Doa1>%#1M-8u5 zrRgLSWX~uj9z@vHbK&u)Dgtc&KJwk#I5h1V}mi2MgOT+#)!Hd40Ws$&WI(8d-&5)qn?&d9pR|4kC^Iq>)VGRQaB20G$SHBM=L~I0}zCf znmu-K9Hxf=m>+bz0g0oh`Z|#U>Ga~_+3gozY97P67~GFmM=E`-qi8-%U_NbUu_A6x z$N7!EFR09HBU)L-h)`-ne43a$NZ4$WPZ2X%fFjlEqV)8NL+J(}%>YwN?8dOo&c65V zmDw5=BAbLPYWdxL13EdWae;1?Gy$W zL;+<7H@M~&AOOe=bsP#q1nCio+R;^_rb@5*PcT%R)yihoKmEnep8nJi-+k?F`O4S6 z{rCTycdzY39q+oA`wt%7f5g?DKDZpm5VgT}qhC!n^m?|vJiU1O^4Grm-KDPa&K$-Fz#VAqM7a2jP>Cjexe4?}~_|&Dl)| z#mp(SJqI+?c+Lwu`TV(8qe+ed%vYA7n~sC}t_8?^0yBC;LjniLgOF&mBoC)^%ed!L zG~tI_dh*=fn^g^o)If5AAzG#39$GE>VIGwMNwi>&0Qpl3q?y4v=%h0|`#S=uWSLFiL<+S7)y)GHF%cGkGjb>h8mSlu z^t`eA2huc&tCnjdz(@fr#^~y*ll8LiyV9#DS;zYH*8v4M4YUA3Mw@2QW>x z-;KfoO2Ap{?AEehGDg36vYUn|vLxAT|1sBnH=(i>QteEXu3D%@z?y9^i`F$y{{5127<+{tHu?1%PU4C5Hb>ljnRgMd`` z-F_bh%jMF5+}qiVnyt$kA$h}Et$|c-%=KIdA_Rquh*~R@<_<$lMzgZnF1pjsq^Hnb zq;rp1?KB7V+>dC=8>YcjlU0(`K$*EN1AT7BZV;U)mn#hzmYwZ_>{L6O2ALti7UOV` zc+N73CPTp7f|P%l648V3k|DVL(#hFTulI+|E_TBh=<@pnBA~LHtg^w)0aUU=D!h#Y zLj(T2y7hX7h(G$%@4WUCZ@&5CpWZ!r^#0>|wO@T>D%0@_-FnzQ`ddHx;r}0Zf7UBo znq-M#Yngp}yPx4sac@i+IcHX7&0SsHMUmYkn+vh?Av}$WHlev1*9Vo5%(TH=WO5m z`|LAYwrs6m`@DYe``@?;|3CfP|H9w@gn#?DzP|O-(RLo&`&jtuP86@Xjuk(s7+d;@ z(rq0r=txtjy*&PtSVQIdgs>)^Koo=1X9CJCqYzJKxJ>GtgYe0HMPYS*&g_~iHQzxMLkvphUMJ3E^vPb6uXD;Y@j z>}{pO9rGq9Te)!3qhw>0#giWilpA1f&CF)o7$>m_6uQ-kJY<8`m5#Ma_=`gm@Y4b);Ld#H(nif%kC
E!=$aym#B7DkmBgjl_tOX^m${@x;+DV*_rvym8v%Ji$q6mW`w#Db^ubF=94ZfVFKT8@fE;Bb zKMi&aeMEp-w{W_*^!DzDFCP8y<(~J@&f!Lc{W@;s4m;K(Or13VN3gMO!wX0Sic( zUR!V?%}vZSvW>g5A|Pc^G*D%c9CQql)q#YpSeOI)F1UEqB?)6W63Gbi`K#w&P*g*J z0~7OUnm3!x?s{iziAvsU0p0SKcy|enb*0c=hc3t)n<~7|V-)yNzJDxJCzzr-)y)WRM%u{Ojw@u8`eY$vmg;-UFaY$0w$RsLM3YfXl8T2q z@LVLqMq2|$Mik6uiEwWfgKH3eEhl93jFO#K1!q%9s49BUY|JW*wC|Yqt_|jaiPT+F zF-1qJJ0OU{0t!*mNUH88@;ZKc9)Bu;$TEc#S?=frY5-yU{yH>r^uTX_?T}S~6=Wc4 z=%|ZPJ*&~3uac>%2WDg{BctRq?TA9xxluC~0|Gym2tj1c*yXZh?*N*OaFQ7T20TJ^ zI}*fjWMWtFf{0{>RfiLvWQS@trs~yZBiR)x%hb|L2_z|FC^Vd?SdL&pu5|fYTq+-`0oErx#d&I$X|c38Bo_EO zDq>l6kH%e1L2RCxfArhG_Xu-Y%>^P$#Pris1#?hyM&58CNj`|#=2^&t(T*JPNaO=wA$c1ti1 zjoq*mjXfkHVUlC%djIO?aM*wI#r5xg{K+32@Xmw#fA7WV2QM$)yD^?T_PF?&&&>aa zfAg0feE)y=$`>CrYx8II>wo3f50|^&{N|6k+8rV>>n?GH%!-3?oGGdOdyD3;@Xav` zTo3Td8Ai4<64do97$tDQ7^CqtqLs!y94)gdibty-zIL(3FUTx$30d(Pv@#Q@wbEY$ zXZ@nGgc!0l>yFWSWn2-HV<>!2qTJ zrQBWX9L(^Nbc+mV$uMIpk2V{3&ajd>j=%fvl^wywV>B$N*?`Qvm_uR7xiwKT07s~dn0{XgbCUAuP$GHGFy>5ZG=l^uQu}PQI*3p zi?^-iJ0BM-HH){zv9Z{r?=8%HCJ4CuOg36B-J0V_*j^!aVWp=A;MS_#@4XikH*z;t za&bg{tH7-g*O2A5U6T%nLgvg{<^0ubv?_N73}G~*H-SAuXeTFUW>duRU>w;Wb^D3e z7e9R~?nXKgy1IDDTtFseWJ*i#=O?EhtCw{}X3}h>bdqaDR>f!`E-E^QD)lW27E#kE zfH*(jEx++c?`k?9$3zbjq2kL%rI0BwJ3+FkR2artmbdLg9)n}GpuCj12+Is3gsAZ> zvgl8vy;-uZ<pMB$v`Lkd7;Kvu&7Z;|( zM&?VG@$B^U>|{cF)|ZQP2B#e7JXItJ5_Ao+aWyf=<4T289q;2t(Wn}Zxx?r$S69dH zw9L7t+Bha}I>J2|3BEGid3E)2x4YV$+^eZ!Sou?Uo9Da^&}~I%Kz<5c|J2s{#~`~a zK}E+))mp9+HhR1S#K_28%)A4=V4!}OEStr$PL3M^GeJmA=nv0NzWDjKKK$Ur7t+ss z>2t4s^xnm?@9<`mf{EBpnt1u-{Pg^_H-GnAKX|#@c+k|-&Oj%X86--ek>;UD^E4vU z8I7UbL7hqm0^!EgQgavUt|8GRMlgZNOvE8`Gqvt{qy5$#;Gv7o)3*HeUw`B0fAQw6 zhc7MXKl{PSfA-%z+butP+8!fsn#fpsbfLEisYT9{v-7|PygyAhTJ{#c#T
  • GhL) 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�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

    LQ(0CrQp$t<==3EOy72{_lTw`|thy&wTFnQ9k&= z`R+*_i&F8-V_W#n?O65+kBqxV(;R5;2|8WYXP~9lGRJ3wVFL~S3|&kmozd1!ympi>z4q!%vN_@5>?Z7uPkJe zG4qPCQ5esuDYa+04HKrR@xBCq23>03Fac{%PiUVi;C8j1DKhpyXIfeQ?B&KYe|lzb zdHRVxycP4AR#rC8NJk=$Bb5Wh0}x@(6#mjMx3GhqdY*Q?v{!5zG6+Q&%7r6$1IkxLt;^)9Ztm1*O{%6~MJ7$XQm;gk zi?UO?awWg^;_jXA{L5MHFSe^U@8AFXfAcqUPv8E=AN;|;y%_bT>Y_SC3$$`h3Ld=> zCpCq6auT>O5}V+hu`v=g?L>e0;Ei0KJbtpiWXqrbr58_TG?WiM|FswV=8ON;fAitH zA3kvHvUSNUm6{-qJUUyV0O`pcR!c25v~WRyV>}ui4Kuuq0)iD{gOQmtITOLyoj}&W z=vFk7gNYM?x4WHl@1UX@TO%9{&Ey@t_lnJWK@cc6#*-UN$&B2*&Bcn^P|MB+%p71G z@p)Z!luF$tusfxBchJpV|I+KD?JCAEfAMp5yZ+{%y|LT)L4UGYt#v4rQzpsg^DJi6 z&1VTB$(?LdU@kMuOk4&KOYtNr&)j-LZ|)Ti3~(nCP&S(pV^kNfX5=uY!qiDdjZ}qp zz7P?@XoDCGBd8m>CrROrt!&LF#X~VAM&evmRjC#-C-Ou@tggnDMZKy)5;I!*3z5Lt znBZDWi>ZSsqVO0nGX_~TP7)T~6q-{)K{FU&LFCZ_u-IFSf|Y78z$B3oghxe{t~33YLAmcZ3&pE4M!6XqHJ2r~(ya zA}UrgJ!bmgV*9Xc*6Yo}>-|4@Q*K|pcy!Z;kBlo-%Wzb;3@i`6^z&a|KYDm^``+%+ zgWhXZH%?VqixFtX*^0Zmma5E@lQEMMRE1ce1Q176=Inra&&gfKQFr}zJ0PP>V2-=LT)|>lI>XM^QEbB+LK4)8MFpAI>@a_+1m)lM8ph3 zsl&J>(j??giBqkGK-q;LqtXxF{_yPVoX6GnVjLap9jKPNsbYqR_ig#bmJc42CtGzU=@rW}%4~+{mnvaTY_0 z=?LGCYueOeVyP3*_zzB@b7lyW#5vB)J|Q7KRbtaqA8WTYelZ(jVFr;=WRN?#>BuTV zJp=j-q4sIT+RDG_9Zr>fqlR&72>`fz$R4MNZU%vcf>gyUFl8o*czCrWeHqGYue|oz zFMe@kS?kc4dIS+dv@gv*Lcq>cT_EE~1(XvU)c2~TsMcB~+3 zSZ&YlKfGr}BR~br0pFQTvTKdYM9lq%AKW{=bb4}nX&48$WEieryGepa5AP?0V%Iq8 zPqomsm{$Vxv_HnB5Hvor#e~ygWW`E#GJ>;@WzDdbpy0;R_F5w_iJQWysofz2iu65V ziGGk%o*H~MGbUn*=#ZqQ1fiDF>eGER5R+*_oy%aG#~0hpDBWmtkaVEpKm~aohw!Oz zH#Hrnm_T^&`IJnXoXm|{q~-pZ0k!mWYGo%i>eqgf<^Y63IT3&T4}m89RUbdrfWm|r zdeF&z!jodEVIpkmZV-tSC37I>%#Fh(QE@QCY@~)sc_!(ko8<*uJCRwR$S)kI?i!Vr zgPGMd34;(kB-Qo|Fh|B@2owzrW-&DoRjp5rItkI}2Yt|kOE(E1KOwl96jH6&@clc- zejl2ciLvFhg%_DmXc7)5s^j+2lgGD(#@r|JIpuy-espn$aEyXuZzJP~hZo`h5x<8g z_gdop6Ys)KT-4XQ0mPgZ;91I*Q_f7RW_8Ci87gu&hZl0)Z60mcPlP&arfNh0b84Q8 ztvQUj=^o8(APc4?^a-)h{-|kFm)577YX}4a=QUFja01-LbfNUCU-+4;*ROwT{lWWX z{9YZ)K~8g09cRp~! zCn3Qeryzt&w)=k2<37ET7%d_+*^NKEzsuAXe9(ZNet!SwXa4;cQQEeIKCx`3D&y&G z_{8_q)2}|e92;=4FRJC)^M%1|#G2xP8OV{`3?X(Q7H2nCGpA~;cT#t%s^(R> zGg>O@(MS{^>I4!dBCA3wTzxFj7;x%J8Mmv! z>xxWfhnG|8V%$@z4posQfxXrwSSlGn-x?G854p*Mgu;;%WUTvImF<gS+!6S+=`dT)o@};vAKdQ_y3w#3cVIT*`N5oK^3k0S*P9hF)=w_T>!Zgj zLPwfaM>8c$49=W0k1j$EFjJRG9R{xzbxCYgr4wW_@mb=zQ6d+IW%kU38sY^2nm}d0 zhoz2l$C?BTXLpzcu7m_sVT1nZlFRTiy*jP>q0q zUfo?ire@6wN6_MstTiaMCsDVPors8}wO3DJ))XH6A(MED#!}q><``{`=BL~mvKUCB zp?Qsj2MR``Dk~&qV;XxZ$vepN$Cv#0aGqyZ=eG{(e73`b@BhiYKl#Ry3>ZdsR>!aW z)^GfqKloR(?dr?7Uf$90_~S>Wt|(?D-b5);y=h)#7OGx#G~sYc8c?gGL~%pV!XwE^ z1%vr&HhcbOerA1gblCaH2k+kb*Z=G_IllM)?Rgol93P$O_<&OXvp>6?&ByIV7E2F6 z7!J zyJ1B_)x3h&<9N6{SzGtue9OXJCMM)AZQYCd;B>y;%@ZH@W2rP+0%robRtYSH2=3;l zb#y41!9547fKuzWpTbVmu2YPuVm=h^U~uNHs;aJ;Wf(`6#wp{yH6uB|a0`?CspgCJ zsx_PYk}+!Bw&tHb^l880KP?m|1Q|@iEb!!>&BVa&#Up_b2%&zsX6yk9?4bdsrffCD zDHChi+}xq#)@sRJ9|9tGv~Qjm5_@!R0yu~!LISm-uqA_onBvz3)3#+%P$kGYn>Aku2*~1I6h3F0qbdjVqK>W|5qPi1MtTA0B^v z`y)!@<0l`TJ$zK^4#FatNg&Aq^Uz^Aw`~{RT%S~!A3l2YM;n1D+?@ z$D;lBf-L-)0}A^FLygRnWI#EtG2C4V)t#&wh}?A?2VEBq^`|5=RKQuS4qhu+qTHu` zdGYZ4_P9Ifap}^js+FPksiGEEbs(~I%na;mg==yMkuj87Qzqt4BtoR-m?nqSlw)2A zBalV4MSE^fn`}(&&?mZ|_VM)ecmKq9V-lRPTjj|wAa*AwI1E%*7a!??oSrTed^nqt zGfXbd)=;G+AlOthM6u%$2o%}Nu`e3M*IVOB!Oi3@cbnBF3Fm%UE3v1;Zh_(PNrx8? z=AVD%=I?+1;dT@3uxv%^Sh(vQY)-_iW*i&pes1Hfl}EgR*eQGhSK4<(ml*qMD{UuH zQ!C@#ok^nTlElHl;EDt4UOv9|^`HLwc1&*!oA=6kn0E=VU13HNVPywIgyW?MnK#2W z1Qkw{m@6dr2Xb`zV9|f+Z~g-99{b@kZ#O^xQ@>bN@BhxX{$O^z+*pCN{DtWsYADad zr3pbMPxYQ-uLX?5^OH=C_D(%UtzdzJV8W&M^%q|}`0Zc+xfgFNcjGQEmQ|H>+-){& z$|SX_ghY5B3>J?fHz3O?V%l~>$T9+sohlGO&E|h zH)G!~XPV{!6I*6~DdO$Nqd%c+&(1NPT`BwR;h8@?gF@{~`u~oNW&Z+bpu?t&W4}144z0f4 zoSol$TXy$*-4H39ORdU&a_Q1g=F9%TO@?8Nnu*wzd?dJ}N+d+3?5v-yxBlLJy}x_- z=Dp1qKli0CzNmN4@V&dbDc5qkBu2hlbDK?4Vm{0aLfKuAm$}1hkvv4AdvATbOxz9-h794YAd#4o@QP zX0C=xp+twnNmA}|pO82*F)SD*;bI7KjYx0;gpe4S&B~yqcr9d=+{RKy)haxjb;y(w zLv5Lt4jQ=DDm8MW+e9Co_hsWMA7 z6-}xI**SI0Qo1zjiLz)NxfHM_gyU=k1qC%b0OQH|E7}4QxVXj)odbXdrmPWlCMP3P zYkav_tVFfC786H-`NSM`gAr9wB`7;5wrX0<%}Wi24VWCNAXii71f?Cb_qj5qlu!qD z7qgJw3lI+E>b7r5I2vGB5+)*~WS(5p_M%=pT=bS^P_NZHINZ!bx532BE<7gMC7S~& zcn^ssG1oGh041sicXsvSq_6^23iU|iBZwPoBPAN9oh}gKNnH>}b(a^X*>x-%RW+|n z7UL;*5V9d)ix!qjlzajRyixp#xiz(|bJ>RzQzVoCn8KJ_6Fik1@J6bd^0ujclB}J` zPn8-(tt*NyuBlRp6=XrbB6g6Q!95X)i*Rp}i2KWwPD@_hJ+Hg<*+ogG$8^#^HoEnT zUmI3uyYKv2a;hSFd~(PAB^x$d z)Vi$|%Zz0v;RsP?mXvZzIra0joF(^@>o3T2C!{#ut{?t?{@ZJBy>siO&p2Q#d9#{t zS3~^)*YPHrn@A>ff*f;B(=hafz z`NOj{%oCxmFBUU7=;mGSp*{|~)8%nddpN8v%I?8>edl7c)B8wtX_4m`-21v2=8I*9 z1N`vp{ML=57oR)cesFF^W)e9s%>uZg3R>%iIQm%-BnH3DZzxTfiIpE%hk{Y0CzH5O4!)0x5URbgu8 z;ni4ci3&w%|Kl9g!oORu9|T@F9OljxUdwU#XiNGs*9u8F0JsSG-oG$!RqMpp^!@sJ z<_b6{@@Bc!L~#Y!$(?da#BCd?#a#G>PkrjspZ|28&jxoBHU|@{Hw!~%ikxMK7&(ck za~Zc(YN<-xNlK<(N>z0tH+4#)ESU9|Ze17MbaeXUVD{GAZ!&szHnegZF_Q!%!r-G+ zHwCoGc}iwA11VrH+7+dDt)%LopspJs(yVn}@6O z$GSC)WBBeoeeMIPB4VZ>pgD+{!#8;bRuasulu^emITJ)xnfDDsR80Z|`Nd&M4mUv>h*zseAERz&9bdnB^YXP9 zy42U*7FEfKNkH7fli1Y?oSAaCCkMsAOg$`Fh>0YSQz8Jxd~#J5(Zd7zjriH z6+!K8jkS9#(#X+-`)2NEXOFek`C>MVBe}3)k?86HJ$KwC9?ht_aF-X$Bc^iY>dnp8 z)mT!hHlnJ#PRYhoEA9TY;CJ%o=wQ>;c&6oUji*mTY{a{1yGu-q;BZe2c1oUa9bJ3* z%JbdvfgK&M-uc0k`|C@WE+5V0@!6w|?zqdTn-fSl!9nhl!lWxXxo|dDum-}rGdVrZ zKle*t{_WrX#dp8^r{DOaO}Rg&Jp00pS6?}r|7U;jJ9?0YVtX(8eH$HGZVL9pbW&pK zmHjVvJA2F57>VpPuF-dMBFQM9dEx4B{>D$dbiISF*W2B^Ul1^C&$}!>s*yO0xy3GQ zhQR*0CE&1AoT(E$W0XYBpWNTRzmYuKJbAJ{znJy;@|6=4vz!+v2Zti&c4aYhUjsfq z*)00awO1FXryJef>2hMqBCez!22Uhrldi&p7Zej`-m?|W^*!pSU#79@Brt zw@pqB(;{k_loLYp40eF2qIvomdgiCaKTddtMMS}ZZMp{a-7kFk>i_aLzr1_!Vb(^0 zByqEd*ix7#5i%Dhw;~Z1O{`wj>EO!cchB)>Z``|k;lxSOjAlo)JRVXi@I{^_C#0Q^ z8An$SUVLS?I9{EfZ8kVMy^?d6$~KR+TO2s$UGZg~`qOJ?Z~k6iNXyQgGKrAas_?Yh z4wD5cbqdl~QFJ%9i0 z-ODe1sqE;F|M**5opm`^*v`ieE@Y$@`!Kn|n&}#GOeaM${E>Qjf4s)H38eXL53b8MzynyDYhD69hAD6BBX;j9gtvgb5&v zVP-W~tj-2=pZDFY&s9g_rY$07BVx)N-rgLQjJa7#*_}%D#MF0bA)?$@b1*BM2u5@= zKOpDDy4ZGGOX>QVl=0BnQ<6-DKq)7%6(u6y6{~8AHL($x9jabyi}4OKK6^$gPatVK zL7Z%%Pmlf7LXdBGI{gN~i8u>r)GAEOT-ExN7Vd1uOr7LdJMPqhmbqJ3-IY3+*J@q0 z-f6IB793)-qryqmt6T2HNLhfo~KR!LDuuk!-FYyL8t z!K7L;bI<)d`hLJ&$(RHnSJgrUBBs43F?0OK%+iJ|tt1YnNaK4?qo*2nMjI1T z93Zfn#!f{b7!=AL{E-?2BGYO*FguAneZ8P&_uPJFBSVlki~^t#{zR6vyE7ADwU(-y zyBXwZtL6KJ4>W*o8k|h+>%JR3Zw4%#;!*p+u^Nu7B~h7oUIerN!|ncT}`A z<4J@pB2XX(Jg4mBNidGX#rpBp8<)TExi56?7Z1<=_>aG1RooMC0y)7Xz=-%={k-n% ze0?@MIQ`;JeC54&-dS%)&V7wih(%Sw66s(>ZdMb)&BB_#7RxEW_s&~S9zVQs>v`_y zX4Ws3ue|oz4?p$r|LNWGI@=$G6vn2ZzVImTc zw;#kDyRL}9N!9wQ4}_bzus8Hp&7h+!r??1r(FS}JvUaj;;5q0D5w zb$t2d6Yh&X{&>Bk{9yOsV&{;AaMX5JwXu$7R?Wf@8=P+6dGh8D-+A!(;rj6fnTX34 zZluk;r-ie$$0UXyG1z4$f>UVN+ae&d$YoQlJJ^0}`m1WJ^fSTRs>_Kq4s-vlA!DISCxvJdru~vn2I}YsUvYZC4i;yYkUT9~@qK zZXC^=oy85!Pu-nC%p4Tisp@F;R%6B>9&Q2D973#C$c=?#_}a_@U;-G}d=E~cZJNMS ziqzgi0nt{rjSN6Lx}BUrOlrg&lW!PNQBCANpB)@6=ckK>k2CHh<(=BL`cO;cWwoTR zpwd6BcP8*Om^c$z-(k!SB6AX^)q1lVwvgP-`qg=L&XkZ@$yOZ?r4+E3%X&LFaiZ?I z=U)7?KmP$sR@bWABq@n#sr#+9!P*l|!}n)WgG4`}37Ia-WFdz5ucBTeHzOgqCkTpP zJ-K%6==zBybiC{JVZUB}>hh&UTHU!Pxw~|Fxf|9YyCQF-<;2Wx9HnLOcFn+CoCi)v z&t1EGZSgZ-eW@GXfAt0)yt4e$+aH_R+wZ*h>p%OM6}JERH~z@DcddI4|AdD4{>6r< zE#9eltKA&{nA;ml4E9xs-i48z9OtIeIePoAe`?zvCNwK`JO zCdV?b+zvixj?_Scc?j3sl$qO%i-< z|7Ej$b|&C|;qx@HD4#w@n3#w^@*CO@YM{_GVAw-LAyWjS8V{@T=nl^Bxq%pJAQ1&S z6S;zH8a+qYXV3%{fVr$L^!&_nM}40Pu_Wo{J+m<(J5j9!CeZ28f$HwwyKf;MxZCN$ zf$KPUg{wl#u&Z8*=U&*QQ?Qd%BXguoOr)yQYm2-C7@@)|>aMK2dET9G+|7LGdVb;g zqj{I!`PeF7WtVUN{vZA77hgnX?T*>{Y`mN;D0Kxs>bSFUHI6n8NymBay%vj#BS^9YV+ON(?TEWgm2sP9oePgoR=e8|Of>aO z$yu`QjLn=Kqop#Mt998~dY?;Tz@4GB#FO2B)MxJuezxgGb&n`U zR&}tO5k_J|9=Qj+s?6DmEGVF@+7DBahNB0_!NjV~hE#|XLHeYX&L5o(yS15W;Aq5P z0)ydfL1Pi6R3RF4RdaEORUz^uwM*ga<*wpR&WTb*f-3-!F&Vj%JG%!^+JeL4xeLx9 zn$KN3Ukxs1081v2mT@9IJ6t*D50p&-mptQH# zK8$DQtJP|waO3QR_yrUb;fCNg2;Sm$22;VG(!QFCVQS-^V*JeHkt5*T=yfq|h^*Ei zpSh~?tdlO!R;$PV=J!7H@X@8DE;c~6re{z1WjkLu_C-fzuMZ#~@HfAwd6%3r*y zIwJQY39BZAydVVM7?ST?6-4klma3)z3pZkrS>mXDm^!IqLv@z$0463+ltytDou~5P z?e8A!265UAW$4m?GN*CIHE(u3WWnIeM}yHX7X?1t6jvvY6~5oYv)+~-<7sS9W@d9Ynqao15m+v0=T z_dojhMY%jbSk|)@Qg3F|o>U5(dIAvyMD7T;Ycq#7U=ag8L{{lYK*l7s80#YT+eL>g<=z{EmMfD)4=H8Ls}>oei{kN(l$ z+HE!;-F}YWyT^=gUX?ErG+WPJzy8nnb59gy^zH<7~YcGthw{PEl^zdWy za`k9&B{6MR4|bJIhnzmXeNh*e9S7o+oLtS_i)r)^C6C_|fgN)c3Eve9P4z zKD;kxZnXx^YwA@4y|obSyO_Cq)jGPFJ3HJ=$<1Bi#3{uvt37o40`)&Qrto75D8xtW zvZJ#y-KN$>n@dW$m##NQ*Y!CGvoI&(6y~(;kPO}m$lY0}m>FEKKDo5Ke!9qX?ZKnf z@BYEJKfb@YeD!+Qr`T4UES_jM+|6t#X6~fs!Xive?9SCx*pdJk7_JrypX%%)oP{Li z)utK=KH0$ot;a-bss6&F;c$L-N1_O=i)z@(ornbH3@Wv#nUmzj;^_1eGTcT{&O+`O zYu$}rYh?*+cu!d3iDOUu4LYV$t@8>;fQCOi6I_)z=feYJsa_l4!g?G^G#h1 z{f0*~B*|_&qVe+Od7t{BY<4?j^>xS!b#;Qr^}*!{kgyWweNiU?(hGz`;`+p<~_p;Q4_?v)v)NvU?UWF z#Yl-pr!+s9&HLwGz4BXs`)6N0&hq$P*4@#}H&(Zj4zJ$$?swn(`LBNJpZxy6q}&0{ z91i?XU)s~x++Ofg)V;y4lY}ES*r&3=03_TadV_5D)z3Wti(h`Jc2*GBnMo6fe1t1nT@`>5BL=td@J@*)lBH3c zD)eHrIb6=JUhe56pDqS38{0nOS)b{sY97IOK?p&(pZTs+Br2lIJ!JLINA(5Nu8+ygmbnTbPU?+hW8Q2rVpCE#YOMe-XUmJT&FOPjA-lx6=Ojv^ zoH++aF5m;39>rLhYDRFYB@y2J&iB`UK1j+iQZ`Kv5eB(caq9=|)w)?$k+;A3?Ki&p zO(an!?nqc{P=Ob%lhwy}?wKk;yDVyIaTBzfmVUm>%bAkpPByFax4-@Ec|Qk< zu}kW<+ilJ>5i(E)&n<~p1&Cd}fM~SJk^y#S<|GiWHBXz0IgzCJBb65W3A;0NzLdjG+I)YnJ?_R3_AaEfiNBzq_SX%x@T%FgyxQ|zKF(paAn zW*q-jt7yq=%8f`UxpoK4!`W5rZlE6MsLU1ayY*GWb?@K0{Q(|6yn5@o4<3G(Ze4vy zWyn@+T_780VlXioNEuOY3u;K1d#RO#$UIC2V9sq!DBS*?vZq#4=-I`uO5$Od-eLQe&fcCgT?Hl z4{wiUJUEml7zyAc3<%vtjG5iqJe5NxY=Q4lW7evtzAw~`PL_yBg*xsy<;*O(>*2mU zl3TAlx3YS+xi~zT&6jC3hNMb^C0sf^8Le&yI$pjY+P$&Un4N zYuB!=FY>!T_~A^??P6diBg;9@(yR<;-jQ@!tHPN{%wtz@H3geEfAGOa5ANR0>_7R_ zU;fgUe&U_C-~YYe`@_T8k$Kh0KLO^%Y%y;z1;pLJ>Q+o$6^g1_wil0mTmjBPu2jvR zS*QQ~eEe7g3hg6iu4Pyo*hrk@`Ilai+><2LCTB8GEJ{bXL1{=K-~=R)h7A>g%t%3_ zIoKA97 zTuNSUhO%?wSx&y;G`gUu7OT76&Pxds*)Wl65i2p`A#!U?6o)TVHD;m6vuZRPcdMk? zD-PzzMb(IV;$BPE)w~?cyWm>36sJsvn=~r^5?E! zef8Go@4fxO=Irt6$yOX<)~BR4n2#c991ZZTjg+kl^^5N6)ziyYUwH0QpZ(I$e)ahH z;J98~#pVE|3c9`@``IJWPVH#ypFbZK-E7@+9ob81dwn~N2#^rxwUWdee$%MfAd9)w z-n60_j5X;hf&k^Z+g#kd)E)I}f!)9JfSK!X4s{V62=!1XV|9X^QzJpGza6|n|z)*ydx zOKbW*+W)6f578&S@XQa71H;LPBG%Cq7Z*dV(T73Z)L^PCsR=ikiP6;R#Bk$qR8p-* zeSh%Ujhj!(=9_Q5N0y+Ys(Gz!&g$X>A`8TTfJU;cY)Zt8@YIgojKR(e@E31wB zPOWHhs>F%t9HdU>d9K_MNU*NE0j`q61~)qklYoecdw`${bX`xm_OsbIvdvB|A6>ro z!moVh#n<2X;T^A!D?uG>o{eXmP%8lfC-R_Kn;|i)D~NYPq14TeE|2QOox_q$OUWB- z#|U~d?~$lUS3`hL5_pD_h>){YsI@s`C!F91J}#+bGn7agW`h#kp^h4k{oH~kId@&( z&$Sv884;?@rDJ6)ZFz^06&X1!>_}nmWKPEHqI!P!e(hLY=N+*rPyrZ(oZTvtsG(0Y zvLT7(Vb{5fh^aHXnQ|+%!+KZeX-=T*mekpqDyu2T$K6nfJto1KSvZMAQZ4g@IgvY2 zHHB~`6R6uR!Q*!kS0Dl80(ZB_l|bYVai4+%J7(dbC?~1zW*EH?Sj~jcb-ix}83#_C zs}pDjr#g0=ciW4qmWkXY1Q(RBE#t|_wYwibJUUnka~%VfP3lUd#AN2gk@jHZU~%er z+<=&K-(^m)!Y~G)W|T-oDTIA0refjz2n8!Rfy@mylCjoNt*TMptCSIx0swGkWjMpM zSc_VSL;y@}$ppv;MoC6g86%t&X@Gb0xyY#Fme?uJs|WYH%=7q}8wao6I9PNWbn}&U zTRW;G+cE+W7csEcPMm7x>c@l^FQ2~h;&Y$>?D)n958u1}=)E7@Td#K^R|z48w_Wh* z$^UfE3&_5`)HwIe%|!qTeoYHbvf!ARL4svNPHaH6!EDWbp0F}6)@5}sB5-6I`l6)c zQQ!B#@e42M_vf>dODz2*-=#jZJ<$w`pwF|}EK-5j zoAdQJn2;o47N|3`MJ8EdcURYo-F7hTM0f%}0Gj1acV!$$0jg^w{aB*|M^G!nBX$n~ z9ZusPjy$V4TcbAjN%}u!s=^?`o#deyi*zY(cH^*XqZE)fMmLFhn|$!3ZDJB{4ucNY zwvqrRbqWyhAsf z-}&DAMDs2kX$1(J*%dH_qgF3P`{jx0e6v35XZ-R@H!oj4x^wUD*^>(nCNL_8JM8q1 zC^+3(SyD>LoSo$1gO5ucZ{B#(-8Q>%adPn5^0j++@1C7K&eDN|Qwn<+b0t_9Fk6^4 zw439T#*Kr4D3U$Rx=S|?FJJC^^b)i>p-k?^s-^?Y#*5B(#FqPQkBYJ>MMvZeMt;0n z?NoP~-o5=@+d=wz>2p_QR^A^ruDi=ix;kGR&$27eyFP74cFHNSmvCort)vi$!;u6c z4l*>~yjsmwKYa2&qx{;h{nF{p8dF0CGPSa(wZ;Mg(> zGhh<8%M5PDlAKcN=alm}Zl#~!dg;a6_g80+AM`_lS0^V)3<-X?Yi&k_6nJVOI6S(( zGVjwt9x~NLm5FK{Go^9du$D!aJoU>|&PKK*6jbO|$>JH2<+D@lX~)|?Bs7uk;``l* zlC?N1KRrCTe0;PmJJk(K?qx|L**6`PF6H%3ccw#CID{l(GMGFHZfmYu-1t`BUR2^N zB!>7PHi^Vo*LcB5Jubx55_p1CX+2aBWS?Bv>|E6-hd?)v2y zZeDx->J1dDbv>Lvpa-i%-PLhbyV=I-BO5MKn&*C#k4Eduu9Rd7V)sN($3N|EN0ubV z8=|MFw1J~gqa-{%(`YgQq$b(y*sq)%oy_`iweEXkqAsP(>L>iV8~pDGQN2T$yOao2wbW8wN01PdN0*&)F_?dN|MuCq^*(?7r+!jq3*u7F zMiEa5H!mL^cCI30HL$qVR&s=Od@~M>BPVh&(QdKZgsFXEOPp53q+4u7+kT$*qaQv! zGZbPSqKVYq%4JNy^;z*^(=0hK(GaFzxmOuYg9}W2l0A7O$p1UTk_8sOR4mLvDY7KJ zu{p!74Q%f2>g3AO&3QJf$Mc$PneVah`109meEa*4{_O3Kk5b9ZIW2p>($B9B zbY+8~z^b@fHR=<0FiS$6Bm*_?Wbksqgj%c&)ttC6Rr3NR2D_;iC)QO}><$hN5~W8^ zHp9CQu-^RPzx&9mr*zN@B~LbNZK%CuveANEYD6F+5mIJm>UOoxk1oxR4llOrB-z?r znF2nEhKXV)agVgIrsQhND_C083IMk-w;-OFL2l$CV5=I=7wTljE)=@;=Efh=02sp4 zuL)Qa5Vua59I)DWl(2|1wAK|xh1vDU(i z9&9RFsXWHv|dT|K#a@o-Dql9GrgLYse)gKvNDF6a=RK#pk7 zgSQQi5@6akqe*)TzESw#nOiMJ6io6slxe~rLi%oSb%Jsp%FMC2dhNl%)i=jsfgLAs ztusr^_Sk24mN)9--S+a~;TL}H&g~69RLhmDvZI2k8cexXF(+p*nUI2#ND#Ky5820 zKb}j1Yb;kaV~Y?MA_YC2rbf4FJk4i)D$MpwNgxKcF+)~I_s{?3zrA|%#u(Ihu{6|3)kNK?%O9G|oUAPLsffh{(-iDU_n*=4P%XKsj?|7I&Dt zSq=QzBFv8kZnbK7y#P)g{TufG4>*`65)jYkCvaHV#L*wcs!y|Rz#SITG$QwXrddM= zrwW9oF~oHIHxQ$d5?aj}sVmb_OjETGKWi#Fe9uexMAZ_ocT^M&UT8cF5QCTuaU=); ztDux$`P3^!xfG4v!=~W~;M}+!M2);tyCtrjE}lPm{?jkqygXm}PD`bJe)#y&qx%o< z_r0H-%&YBQeev??rN!Ite*EChD#?LE)C)^;W|$%D3=EWJ-2uwxqI%7XgO^@@_3X*n z58wJBu`xRnoMnm%r$#Udyr;(UWKOg5CudgQy>jK2QrZpUa=v)^Q=fWt|G@|Ee@Nt= zBrw?|K`B+G70>*1C`Te905emU5;#36v^=?@S1u`b)igB03T0-~GL&tlvKTYZsh_cV zsiO;fpR(7{cD0OaVxDKxL^hMkbzIIL=e%XA7rQ5S&%Sf-h(*5ZwITpac2$)YcH(Zt8%M zHr})=vAFZ$@io9#s|TvvFz9KI>C_SaWhL=n@5hfjpb(8TJQRGAZ$1A)H#-;yO(ZS$ zgW4FM8}dF41jOA(bq@P0W-6s-NxAD%PGKw>-X71r^fGnn?7=-RB_$zsHZh-~nh6Gm zBR0dy4mCH3nmEz)bL`^2+!ho_fmo zd;FI6$V6+6Fh`2o$*rHwFIFo~ET|`kC##Z%-FBJEY1Yn1BCeHoyV`sysWhWjpN@b= z44YVQ06VuM4IHV0oY=BoSCCdvINh0&bJS779nRJrXdQ;hL56pacgmRIV zcJbs9q;3+u_4wgA$_NI_c3o!ZPv(brt2;B3kZY66Ox!#KQW8ZQHFtAwU2Y)Ud(vqP z2r*&mF6No-sPki@!$t10C%6@Nt|!-yPp@Axn0gh(`h07}QMjvIM|IPAzhpCGIHQVI zS4m;0gTydY$q7uO`)=3~1dF%ni0^B$w3~4q`1T=@GhVTP3fvgwwWe##)vz3ZWAb+%Wl4hkYZ3 z95-GIlU8?gN~SuDE9L4!#aXLsaU0D#g3@$R+M&TP4Ml1IPw^5BZeGLB!Qd<%L1JBd ztNEV}1KaBH2|X`SyHAN~4YhN@k<_tm}FyiHh1_8IgNw z?#!9dQ8R<9Aa|>~S})(YS-{&`BR3&1-l)+yirWdXa|Z>E(AYQqQu z8CZ>olZc3<<>~yjpZr`}h@plQ7L3xJ%sYSrT1e9i`j>q1`pg|Dpumb9EN#!a2& zE=k^by>;cv`mGQC#sBr6Tsyv=j{29s{AoK%=ff71jPh(ghcIQ#7-t`R_~DBf?&kCP!D6cm1>DZ;VXNuxZo6Sr(=49YgzGSFi>Z=l1sZK6>!Dz5-qL$$N(TDBQtN%qu{Z9KJPKJ=xuT>(2H3 zgBbY4N$X{~X~a-#+$2#AO$cXxs_ zS%d^ZnAysVXR|HszVqJV`e!d_e*DXSXY-_-Zy(!sn`+5aB&AJ0+RP6B+{@~tI~Zr* z{myMiim^9)E1{D+Sq)4yMhD^^{zlHsLg2}&49*BmKa5gSC6jAPloL0zKv7E4&C~Je zeHOqHF(;|UjkuLYlzKdj^_{o;jrR{s%!*t1zXo~}@IP2!Rwba_eNWpCr{?9^oPwq+ z*E&506L;LteU@xB!*KnmpRtu)d9>2Qo{{+rKli!c`XBwufB9ehHy^%raaP7&#k7(n z;W?+Qx_$ZOlVAPSpFUVhpSyb>-Mx6YI)8Hh>6cGleC4Ip`eJo{^oxJ<7ayFh|BwHt zf5MsEv>I|E5fLKOYM||~gL(3HPB{W!fTTR?&Y~qi2f(v|7U2O=iH_E0Cvp z-8~xdRQu2#iG2+e1vfb?4ERXc9d3{;W-6eZGO?t^;cKtG>RPN?SilDqB1`~R_r&7v zZf-!q`1+?`{OT88m??G0q*~o)x%<^$_}TL(tA`IB-G6Xz*o{Z?Wf`_J9zOHJ=}}tW ze=u$bF5&J27hxf|q6X|jN&01Rwo-+CdCaeW;d5{O;CqN1plGW>`)mtlF;^loZ7EB_ z-EO`6X#L*lmFwyFa5L<N)D4(5lMTS?OUwj?Wwx`X+pjq_un zb*bW%s7o}a*)mz01o27dY#FYqdYJoBG#!V5ax7ra`pJ=@a~;=$6+-+hW^RL z_#gD+#~n~;gG822^6`}$H(9z`s_;Y{atk{YZ+3JHI5BCQ*F+@YL|wIxi6}8ttD6yL zW2uHwy+1g-_WVm1=a1{KHm4NZC{3q|#|VqbnOvJr*}cnt^|DGl z8sRPHBFz z(taTsMQ9{!yJU`TweeYhWZ=HbL*342U6NEw6_JR~17RF0b2sY`-GxZnc#oOF10V7J z+sFiHZ@i5~d>SV|`!U&uF^ikK+bkiIg1Tx9PcHo4-G}qB+xpZEw4_vY~GQ`ZaZOHYE^WTR-dpmOM&uYLOE7q1^aGoYkS4q00xCukIeK&CUtq<;D2Qb-Hu^;VfSn zin@U*HBv%j`Ayw;tHP+sbocHAtx)(8&|sRKg&h7zzC57_djQU{Kl~e-@C-ONea5Fb zlEGW#$TJg!=BPl^Y-0MF1Fh^LP!cio&9)3<;aQBZAS|k`G1q~4yWPz#+Z-Av9>Nxm z8QkaWU^T05RjH_DkJ+V7frkMHOf9o1nkg$WbD;PZ(4h3ry|;WX*kpDYU`UK)ykgQ}>iR<&A!)M}{PmW_U}UkpDPFbqHV#qhu3XTuE`HVnh=Hrf`{gVa*D z)TmYsES8F76{pN3bGVs#=QDo8-fKmKA7btABq2YmP&y!z_nv#sch0x>UTa1C;y1Xs za5dYFg9wNXWW-Ev>=sp_frF<2s8b2ZVjOqfQkY^}Cx=JjtgZ%~qV3Ttuwaik43xW$ zx-NHl2OGqFo_C|Bj+v7x8Hjtiz8Efjceq#-uRV8=5L*O5c?-N!8xH~wW^NlPISHE? ze+jD#cRPa`S>-LZ&ebBMp?+HK=rBcHQ)2n zrpa1c#I1husgH=eWkLdVtDd>*6OF_55|@APHy><1|KOkhZ;I8fadxDx3-)bVTDMcEpafSTqxc$NZ=RZAJZ*nPU(;m3*J4`nq zvAI`=QEPAZdq~@RsC{y}NF?4wC0xz!9v)h);KH?ZEM}~OW2P1V$a;+PB)9l?7I zZ&TN3;{qFxN_ZOQv@gGHz5|^?;0Q)eP45`tvDAWC8e=SuKnDmS1R~NXa=BR;AoLoP zL>z8`=z543V*&BB&fJ4e;$u$1+OKFfq}YQS1SW{9tCu7dFyXyVwb;ahy$!S6mn97n zY%-8o7_>(g{Ps&Fd=O|VN{PM{5VbFJT2RGQQW!J4J5Pzo5~nk8oG)Dj;R7 zOadlv!jL%RKtz)-C>dm@wT?TK(JNIJ5F7cd=i?=vuI_y8tyi3W{fjT2fB3=s+s*po z`BOoC?atX@n!o?)3#EmcFvDwQA`@qL)lsc7(`>OEH-mX`@@{qT^>6;h=bwIj{`@>i z7)61M-4T=&fI^rVx*;JZf>wS0?2A%&XAkbG+V#!N;&A!k)mJ|I=%MRKAju(Dl(-ve zni$Lw3Psv9Fa)h7-TC%=4}SgI`SIuJ+2bVC7ueN$mU=B@WVt(H7JdJNKS!uLF-DM7oj( zn^v5|2QiLQ2LM|nS|+010AhF2YQb?uwQk7_v*J?ABFhfyNY*&;)2T${4fLYM_NRvW z`rg9@fH@PpTf@N3oyp-Zy?SrCnsuEIWgNldc4SE%Xd!a2>_5M}Dngrav)NuG?xXT% zikOL{KmcO|3n?!s#HavU;3uspo@yb8bp6V>tJq4Wj$pn?NtvtD@@8H7+2K$!jM+(x z3O6%zrWB#TId`f$ZtQS*eDQSavbS7ekhfv(M9RFin#MFe-j7?`CxJ#AVKa~kU`A$T zgm#%1SL;!a`Yq+-gKoWhc72}u`FxQv5c#ZUI3=mpF=c>+gxo?B4=b$B zrPQLSe{{L~7eD;u+4K2qmsi`1DWZmJa31RI1?3fP4ga#anxGU z;n}_O%NL`{Fpk^D=ffwTdfwfAaPso&r>Ca}gQ^iW)iyhu>}elcde&X4q}b%t=z$ zxr?J))B0P#{hQC~mClmYxw~b!)?r;~H*$Y)R5shc|M!3M2S5KKD>gcZc?lW_6gqyN zKC{OZViC2Q*r4v)GaT&?wCCVDed#&af83%4O;+LXy(ng1?@V}9dx+b`jbgF=%&DMi zzynRig-@j-rY}H)Lzr2est4YzAT~4N3kD)!~)G^W1dhaY7Hm_G0GiaLLvxL;0adhNE}h#iL5}Psg6}7t-lVfKe(#yygqpC)w^=% zKzFl6E}5BGObIenE2W6BE2)tJ)kxKy90^X9K@f088!X1?n;6^?iBKX`b#*ees-ise-^vBa2$d9E#X5T$D_3FTa_zSEF)b2F&B;gBZ5akieeN-gssxd(S~5FK zUCP-?F`%D!PuJ_m7z-zX&%g}f*@448diaH1UU9Gh90nf~DarBKE6drTxm}Ofl^xDj zb2dwv7DP;W&|!0P(}N%+kghltO`f!-G!umpH^)hM@C+l5397K*n>1-5RSQNC5tD$+ zuDB+k6C(1#wU`F=M+{=@)m%bMjAlOI#PP?rpS8Zz2+gl3HbZf^5+g_O&cS>^>!&NsDTNzjy>|+kqT1R0bvQpO7r4wLvw8rKyK6&!R^Q*^K zxXC+bH)!w>2pSI^gPZt)+p!z8wcu_=>qImWwhe^vgV9)N0hDo66p6{K*Wupv#drTt z|3|%ljMv{H-Y^{4;hU(7#! zcx=@LH5%9O2v9|1Z+Pfhu-|QnZaMy6I%%f!$k{9+1#`+n8P6{+Q?k`T*`_i-J{vUm zeLgz?D=AaTi;J6KxjOqF|HuCzxf1EW^Xj(`55IH%#kq~OpQp{{@{1SG2gQx5j)lxw zwVL_$jvO6RHC5ey?HjLN>cQ{-@ec?jo~&vV8Kc&rdJ+L4hFGQz{3@l4s-by;h89d( z%Nys9c~qZpZRStY0m-*Tm^-F_@`gZp6CJ?GiHV#{iO@0{Ly#4_pSyy@$(SN9&%)%E zNWvb5DB=|mdVLPij~L_;Smo~3U7U$h%znW%6|PZ+i4(!u%n6k^S*f8$^NGFKPU%~~ zY&u7#i9*zDw>V+c8?lBcj1keY)9A}j#g}STLeSeAm4T>*@)F!V2}hmJ%vKFuXS3z< z-rbVOp-CtlwNy2FS&^uO`b3ARt^y0;{?e6C2_;`78XIDXs)q}6Rxw(9H_2MatFn1!! z9i^&p3iZ0N8k{My5MWN(7W1=T`ISLCzua~=H}h&vvkjKlmA8~A=hM~9F22y=S+&vU zeeM?t<~l-YEFi{`|%B<2PS1H3oO7+Ya^7^K(GzDzUEW)!N)? zpe%{BvYWVzkh>A15{fGeV1?_LVCGCBUB4Ksadk3cW;IeZVunZlP6O0prNn01lHI{^ zf^)MXaff;Aj?PH)lhrXNdh*%Lm!7J>gpa@SfWp)~rlBfCK;})X7%nX27ArL5qhw)l z;3Sj@T*gX7BAip?4heHkoJB;{W*v{Cq72}CdUkg8?DOsBMpeU8fSRwdPa9xZ540h} z!D7}CG!RN|JOX!}_nDlA>PV>!qPxd4lJ0*pTkP9Y`EPL1;vB5|G1p?xm8E zqZZ|U$;_b{BxW=_H*$9Y%rWogW}bu+*Njo?x?3`fDCZZ~*ZE|&+o{8IPU;1qlsg!r zd|{Bg_I=;={hP17`u>l8)~)6aWfrx@qmC;aXiMvZqVm3-VAJgbK~1$bfivVRBiv9H z?AJHz)OFX_^^d>z=ikJgS6*H1uAcTbW4d1Ch2)ij>yF%*X_h1jcU4EC@Jg>;?u3y# zbJ~<~>x^qT{)3-A|EGWc*$`&I4T(EvkgE}kQ%hPPQ8c z$^Go0_nK~`F5fttuMT(HE9{DGj>!pCfAQf9eEi~TUws!eo}A3KzAHAqxOg#}Ed`lG z`abQ3aXS`Dvm*Is&~CXh09AsnRdf`}cVGSbkH7cbpZw_Jm3wCg_wHS7{%~xH2GrnC z62cD98-~(OzUhn%IJBqwe3`)5M!ML8 z7SnCQR2ur1el42*G5RzIcr*IVpnjOZF|uT0R*j@`c(SmtzgYf4JFDyQ{m3IJtpGNXY!ALTXP8omy97a#MvW@N9SauO7aC zVB@{sj}JN~M@3@5$c%>VC`85rO3t9fT_+S(4uOSWiaI60psIUSsHc@9p-W4TDRy9d-H-Kzn z!hGrZq1Y|sIbg)F)7zjJFk*8^9M%9pN=<4O9bq`{u~*hgS{>R5zzut1)#*tJ|3#3I z#*oR>!=+IpD3U1=*eFb@A?#Jd;Vi%_XSiF~Ho8{v#6~)*m&ORrnUJ`SWhezO7NSHH zHg;$*NAxz{NL;|}zSI0=!c#ZswQfpsLSS@gIW|{Xxcx)!|;FK?z>k-8S0*8?r`$*VGy4eleGVW;1t2s+2BZ*dsGb~dPQp3Q=#j|l@B6edK z6OCn5F9@3qio7S9!YTk)CkMMx__H&Z&D_m9Nl0EtlPn-ICuR=YM{`uwu~rfZbrUtd zNodih&CB;dYJjhUMM$;V@ohpC8`2`GbG4DMh^FZdRN?2HnAc zVlCPUCM4pVrDI}n3Z`PCjX2avi zoSh;)(Hm)s$X&g;Xpw=KQ$X@zIl^fCgn;@<1mLg^AaY3*;A{>v57S^*nwge^uZV+>&eGc%??EL!D^g4Ah+2k70Lxhw%^J+d+ zRVY}*Yl~w6Nx$sgc(cE}eDUk9o5EaSe;VIvL&7`-1>X4t^=mxpJ`|8m5 zyYArKZ+v6;;-hDO@Oa_0nCH^_^*BH{K}2E$7&k+?UT?UF5#`*IgIzd@R@ZUE%qgcN zY-6??9^r+|Sdy`tb!@^u)%mlLBC2+vgPAq2TP?l!ib5JgL*esU+$i~CH1(ij+Rcf zn2lpC>SML4-ivn_g$k^o!qm!k2s}A3S^&aqPLle*>#0iF)e1?A!?)l0))${YdivyH zml$Nw{k}MzGgS|khpr~(2}q;@>$8C5dbH7#7_ksYK)l^c^Yq2Xck0z^ckV1smjA^M{>0`zV6HVLQD!xO zlKs7nt7(%s9@BBv4(e8$I6JvbRh?PiufG4I55DnWdD!=y$kn4H2B8$&Zpv=EUCow> z;oN97GGkll;{L(xZ2s~)U$rz|K7YPGU*B9+y)Joo(x?2h&p+?F;aEv};cl6vADp&h z;hAu!`EW6en=I+qe&aXZ;L8VrOz@y+R{CzY_6_V z2z&ahHECg7hW0plJikpq=N56 ztvLGfSfULi=wJI;Z6T65@gxQsm@le&Kl5?4mg#CNq)-sdU5Fqsx3Hfiik=nh5McqQ zpaD6Y$P8N5+$$4LG-_g2cVcOy5eFGH)ul$QWgvGiDK%=UfuqT62Hdv4whn@^8{r1q zKmGKBj(N^lEkuBvx|CRgW))Sm0Ziav85xfiw9(UZ%!j7y>4b7@A$_8!~6T!@CEu#)@L!B?@2C|Br*~YLdQ?R)M>OMM) zONxEs))&1o4;U@dl0(DcZXT1r+rxx_fZ$`tA!bTK$%UB&j-a`6&eA1pQP*1CwsTZ1<$p1Y!}_+L@x(XAeKyjpT&Yfn*rTj*Z+(5g4H(Ibi7wnbM$X5RBO@E4U^> zt;!^c6M}~g5JBAy$Ru1Wn@jA>Mh14bUWm1%s+4(K2aACP3n1)FH6JJ(zP^u^8dYg8s>5Mb_LF~dZ? zw#MZKO<}iTZ#nIsZqg`aFozMS`2cy_HO=6xAWf7qlb4wf5o^s?WzjcNGglN zl8@{}&LWu;VCvvnHRiMiR)WK58kw*$K|o}r*+FoDF)TDW1ZN?0Xw>J9$zHR%Tj$gH zp_(KqB%%+y?BrIR%n8t5z_k$6YE|-TaMhr7H8gtPS~iVK3W-T$6SfvQdfi)C(_10{ zxod>~xtqGa_SM&3e)j|?tKBXGfEB>YxRd0PgiNVos8*{=682tZj8Vx7i-Fk2Q0S2e z!BOhY2Th!}n_;uwbYkWD^5F+RTXYL^R)^#ScO%_ywuuqq59S0HW_FN}Y=%<9YmuFp zZITWnz_Dnvr!AwMxDx<1Rj0vx0YDTJvgTHvy4i6zJF4MaYbu1*>Nv)`fw3b@X(;4E zx|b4|dju^9-!K*|O?L#XD3n-1!Xb3ashe@`EG)T#V>I#AgoCz4RJ=mml_k3kHrWkD zy4f!e7xqP3`|-iLpa~$v#At93ixAONxX0RWA{c!64NOoJAyms^7W8C*8SX4W55}&R zdvCpc@8$biR7cB+nbTkfl0Hiu!&;RgL5o}3j5su#c^4a;#_4egg7PG_CE$I0ESLr&z8egLY5G+``E?jmKeSiSufKDx)&KfXw`JJsX6ue|pw@BHSwSkea%pZu$z ze7d;2>JruMsM|t`S1DVmyY0rvsut|ZuwIYb&C$`Fqq`@*$)oGeotVL#`u=3G=yF!o z%|M%aU5!mrg(yZABB7KxxtbNV)=B&&e6&UJS07MtAVz=$@z$gT$q>MoF7DI{&=eNnWY!Xs z97=6#Fo(KVrVMx03KxQ#)@mhnB1y;^FbV|Si`wK10Kpst5~J08rn(iDP!h4Q6PVF= zNxc@}dh`)~u<$H(u$}vyCN6l3;l(t^n07Flc2?SN?!;|fPH3mQ-?@8I)Mlw4D~giB zom4;q&Fp1VwN!T)HiamwRd#7xz8Mp|4vb_;hs|EaMCs zrj&QZKYYIZ$pG8ZE3ZD-Ci}w=fAswq&qlIjRT?0q z400%kK6~no+tY+wJf!&;Hy61^DkK6?WSvu`{_#!uqaS?u_y66ml5d=-x~rNHtkTWZ z=HAguIt~R$J-M?Q!0G7Zuv@G)W4XHC&d%oZ2d~Uuk~?nW)zxru4IS^f%S;iC<{sEz3mRzCtr+CoPBm@;qKO5#zscS}x7(S>zXmMCX-(5>+ztR^n6}3Y z+M$M^bYhR*-M*qv@EvZ282H!e46v5UXU3A$`T0#5X-Tfa=DQe-PhnQtF6Gj^2icHFLWrj&YN*0I=Fb#UVZ(6Adbr>d?* zEPdu)D6x~7vov(V_77}4uX||BojD4W=td^*?ERAtPK?QjBJ^%V3WGYEXF{Lcp;{$* zmZWMM9VHP+3Cb)4?K>8=#FSD>%%dquB61}wP)haQ-Fr7ro(r4-79mbk!UnMuA*R&3av|c86!DFTJte zR*YjGzK~HDxW?*e8Fu@PC^o*B=ud|^6Wla00IkNC<>3GQ2cO=2=Zy#buEXZ*U%mUa zcOSg@$@QQ7?4wV&nQfkB$x77}EW~c^%hl>=b#$=wq~oxuMf&Hv{ORLo&o2j+&Ismp zvg{1A!^YW(Qij`ZdCMl7IPB9y(jKRfY{W8eD~!nhU6itUFwMnHjlUu~68C6V12j;%#<2Sdz;O>OMmV*O! z#bAI7QI0esjrP^5nKPQ#PrM=-rXVg0T^Wqbd|&0$WDSJrfFTN5SzC&MTT(-!0e6}Xl% zF8iZpf1G&3oOau7W+&oex~nBh$t0b-t0@3-6HZkXlpK+T9NT`g*D!~MIqsKn`)19! zKO);ZZ%>M|Pu7I)%&Al6s-a&2k*T?vx+{}&+p}03O~Lja3FZb;fZU8YkkrX{fJ|Kj zM0Pir!i_n>law*2Lx2+|6R_y|nm&&?%|%kz&B|!E4`h+VG zf2mNwUSGK{5n6uM)OTT;XWU+0kGz-2%iz6hShIQn6N`fL!R(!HeqDVeLZ4f}cGo9U zua4C9L_Chg$(f~E!+!caKRG^Q)F#?!{Mpl+vN(GgU_gc0LvZ3%hya(AWVLjIRjXGUEk%gCoYbvqO(_!+k-YWx*PlOm z^z_je0%8_3WfBliNnQ;0dm1^7-m$ zE{Rnw8mp8#Cj#ZIW4oBW^p)4vtlQ1aczkgBwfF4Kz4gn-@6YREA)gJ;HrJ1i*k&`N z!OgJU4(@|g8+TjIB&2NBg=Yt=gM$O%S(3^mBt@Z2BqGQ+?mKN0Db^XUlaUPfXs|Ia@AY)@2zYG zfrKR(+RQO-{qhj(-`K}5{k*^WfC2!(-Q63@HJI_38zqBV%H+FC^iJY0MI|-Sa z5D}?B87A(r(GZxKV!XsDgM=u!UQS?fss;)iq3O$r$(B8Ls5XuQb(kuwd8GI(r&>xa z?#O^r^*MR9(Pb9hL?Hb55~@x6L2lcF9A2XZas+`HLafD!XnD9ey>nW}orsL1xrxBV zH8Eg?67LMVf;vtkM=J@r?P%m-U7)p&irMPqrI+rVudkjzf683i)Z$B`_5jR`xnIs! zM~6i{33Z9Nj%;I3$fgnQssdY2pXBYM75C7upKmFo&e*N{Yyn1%`?y{dRICn_BLzEzr>$u(A zte-x4`qASTKmY8-&py66znNJ%m~U!#`Qq@|{NAttoPzX}tDG8Be>9jak5*52DW6n5SjBFy759i)h-I-B(l#|MVzjF8ZrF49< zTHTpz@^PrjxE`2jh1kiIiAXZwM$GOeB3;Lv^5wC=! zPdBL#Ch=HX-ZV|esFA(mDcHj?^h-Cty?iSn_CI18D#ia|i*73S(3<$fY9#PpMTz#R zJ~10^KiSH~2}yQ0FsYiSs~u>u()D?wBwbgls(KZAIG88frT6>4t8;paEM`$JCsa8oT&_hmaQ9>i<8CbpeJUl%(>+xhlFb46}3yz zMBTT)3>hM$iULb-HwdliXo)s$By?NSwRsH*htAc}Vi<|R?y7246_bH>h|xi0=DH8< z1Ew))WSKRISA^%e8mVbj10SEBaqdb50qR9WEH;Q(Cn0=m{+e{FcO`Gb045N*k*Q0P zz(*>)csKHar$eA*+a!r3MS!Wg3C!5iEdS*F51*c2-`(t1{Q(%JHfSwgI7_uk%y&*t zUtDgyR_0y^=J2XUs#R<5&}9?W@F!z;wwgj?%OGgP;5pot*@!LmiM!62Dx4D)E2sow zCzgIzd)Ho+1DLQj@uH?$-rr?*g6q^EM$9F(E2~v{O&7^~*M3R{jJ5Y^)>>{?{{&2avHgo5cx`CL8 z5~J@@aW)GjA0TA#!Ij9AiAe=ktEsW28OeM!P6_bNgyGyXiF3-DSi2DmvoT~u#+@Kl zjkQK1W+tFyq2-N>FG|?e@t{k)qUd@S7PV~5l+1llU02;IuMTE6o2^12>JB(E5$agS zf;4;cop1NY_h;9`laD_vWt(ToL2e)?bEia+1jYmb+${#U?tn9~Op}7(99qICp1h=s zasDs=qP%|h?)v8PM-QLB`G8-2_4qr#{*`x2-Br{L3$hZ@Z06uDAMQ$0o;i`IZGCvY z*?s)*YEyHuX?ASW#3fV?Z7Yot8Mh9Cd~(oh-BWyx0U!&A9L7GyQ^mp~E5W=4NUB+rk+!FEnN~w+Kqz?0IKFr6&d1^EL*N+ zvn|`54%#U;yfTWG(!-Np%jNY9qHg`&Md=rfbFzuC#}%cseikbI80NJY332f z3b)G6z0JNt8_+(}^c!t7;u9BX4^q%Rk0=TWV-XOMkUNPaBQc~Z^4s6}j`W?ugyGf9 zEN604I6JZtF|#BAL901MkD}G^i}xSjIbIwd_hOoJfBoXBLqDi{-7XjTbk!YXU(JL` zwj2EL!zWkeXtr7yGsXO;tw2i>=sIg40u1hWalG2C*FIKLN=Ssb{;bMocIVz}ckjIP z@Z%4!E-o^64r`&G)VxtaPLW8(0(Mtp*yZJCH}(A9OK+SU-*@TXefPc9YW48Z`?J)u zPZpIVMr2)I=CkEHZ_Z!2Hza!|_3!*|{^Qkx{^g&4{^`??-n#P!c=ql)umAkh$45_} z_oC~%ku;}H>SiER}T*hJ!HgvR8@D^%EleDusFm?K?ZLO2Qwlf5^+?5b6{wb z2Byg!8apA1CT5lf2><1L5a56H0fh;YVIo6=`@(}U@XP&@-A`$5r&Vw#4}U<1hB#$H zJwrCjM8X`eEM}sramOM%03Ic5%iW6VpV>|id#M|(V~9?PnK-GL5P?YDcUpI@XV>Q5kpO<=SYd zH0}m3V`8{hf(B>HNv4Nc4^K~W*E#1}Z+v@WT96XrxF=#JgTaY7jY_9??jp^|OUKMw z5~+|$>NU-7cFNpUGm)g`4rZQh*7d!2?j0^@+zl7k=VdogW>+0cne_*%bh920@4h;0 z**9zWm{V2*NE`~^Pg4ALTpf2gdhgvy*JbC`?+12Iwrqsb^ z)s|8`Up`!3Kc#1%9hL33Uc0}XuQopX{A&Ekb}WadyYuH%H8Z<9Sj>Yqjwn;67TH?a z;d>mZJ+=GE6()&e>u0?(@ut*2{ozl3`_1>rT~$jp;lzx@$&!p^B%-Q?D|Nz5RI3Uo z!?n`-#kvlgv)L=%GO25=PSiPTg;n&jI$ErbmWw%jdtEoEM3xdnIDDSn+)`-kh19e- zxr41Hp*miV!&RTuQ3Zt|&hQ8nrZ6c3leCt<&4(w2e_PWL*5to*HQL(UUz`0`?@6fB zQjgJVYl7R=#_eBjcib=Sy&+->6l0?MPCsTE+tL2MP2-7%DYV~>TS2Li>n)|cG|IwR zIIHeB1o+`@N(~AOf72+TIG}n0;PXY_WhrhXme(7d_lXH2-Ryw4+l}kZ`O_sQ zk**S_#j0N|@~j_HS4OMcZ`CU_o8^9HlUT-m-)K$`RMg1MWIDaMHa#GpXsgXg&L{D} zm#+PO_!e(0sK@behpU7)8X){OzY)_M+$~mU)Zy`C8f}=T@ojMR8AK3OW9|=`#lyL_b2cK0<@Xz)RZz{02kFU<+OTfdH*E!D=nALKK$-cFGgTt zGa5@>P=ELA&V07`;_|V(P-KGw1-l2;-r?hI&4by9djU%(Yv@u z6%Ha8xljuTCxcbj@JaCfz%3${(XvMrFnBNNAZMo*jRj^k6=sJCsJeBWPG^h6J<1@x zsBtH$)NMjuQNFaq$fNR{(%gMne3@C@nGA_MC83nBc6z?a+{3veIh6iHQ!OE*a#$f# zaW<_vY1Z1A>sYOKbyDUE6djpd$qd!p6A{dlIhixmh8rg~^v)T`WV`I1n2H;v)-aeCtrkZQ?wo!1=KFyGlyPuX7Fw@& z%cDE<|Acv6%LOW*QM~Gu*WjlN-zn zkvIn_2Oge54MdJ_(Z&=bGSx^ys_KA8W6y23YL#4^8_~#N8s;<(1p%SxX{IFNrh$}L zs7a>dfxdL2#E`FP&D3nhox7 zA?verxqI`KH~#rQ`X~R@KlxX0fAiOV`)~e@d#@kUipT91GuSbd00961Nklj!bcw+AaNnm;{}{V#v|dQC}*+Zt@^hrsKz(zXw7<7pF!mBBCTE3;HUjfaMU6I2n<_HEt2P84xibrq$tU zTFc#uZ(Ampx*D(gmT>6l{qkf^6EiVU;(*jhYnYt z|NMu;o%08;yk)$4{mrkOzI5`__kX(E+M=6rNG6%DsxDrCEZNcu`r&DFeCt+nRF z4N-At5P{v`4(BFnHRCokC2u*DF$D64#7?a(?G?eT@cB20CeW9bK$O7l{}wP5+7li$ zgfJy;6Pkwhk@0>CciV-!JGU=sacgaIiP90bBnq&jn5=I*tN=62%sty4Xh~3oc2)3-Wy{gYI zp1_sZ1!Q$pH!uT<$%vdhTT1ActD}2EsbuJ9nYGU5Nw-xNM^87~jRL~Zs_^Q}i7WRn z&R=}==_jwgyu5RECQt0~lP52~_L8}m;z!Hw$+PE=uhyIvtFwF0*B|r*Gj$^-0oYul zFxXGHqpXWk53oBxhaOWiAR?M_prB~m%?_`-_WuR_mjv(Jn?DL83M>OGg z)|dnng~MBB3TP@Ifi@FrtG>xzChf8B10=~UnsWdX-4qk}zaI+3>+N^NHeToEQqgwk z>6dEi*>(uTBj)=*v|n_3Rk)f}EnW-NQnc>Ip_JXM%Sp0%gp+$4CO3y-AV%h3H$a(< z$=ptaJP((;;yPuL}fP271NY5zQrSi72#3g(=!t%K(PFq&4e?{ZY)^L zj$VFqOn10jk&4?Ek~p!dk$P3Pt92Q++oyi{@C)DS{e!dZ`PC;^S0cVxtX9i|Zr0Hb zrH-7Fb~_t=b#%;N0}XW~L4qWakcRhNf-0FGq}iZ@6l3BpX9ub&In70O-8>6%mxim& zr{Di^&K*&5r^-HBWq7KU#$wL(m@|t_1Pu_Q!IUvooDRJ<1SEqc3h(KFS%MPf>Qz|O ztCME)^N$~m+$^~(xTy9b%Wl?J_d3|6efme=*M;1mb~#hER9$Nsb^|GV(1Y$EWv-5= zAAbDwlTX2}U^tZFU{}y+e!V?DJaQ{czWLut{~Zj5y7;}n`**odJnvGUw!0g5^l4_J3uh-aGBB&_PF;*#n7Y2IuC*d1U(Qg9 zU#9hDC%`QGjI1lWz{?c5LR7@7`8Xu1ZWYQ*#KejF>)lw~o6E07*noMQp~N7nx(k$~ z>FoBXeE=u7okqiU;I_YY#0Y9ExuWp`iGr@-lU_C8VG5gShkHfP6WH6bn4o-*eVo6f zYi!HLTZn)&aRlkKu5DtZFtrCr9o5`eNnjqSnm)Dqm;i2Svnou0Nki1)UBY36$vl$a zF^Zg6V`zb_jfK|`wTUPw;r$VinHxzmw;=g36U@N8JUV>m>+kNyA$OTTo|VWF3A~Dg z3PIIX2@>>N&}f>w>@blYDHG`8(yp&=mJ1xLX4%WxVtI6QxaioaD9`@lgU=s+c5Z%1 z(uLj%Wa`S40@-wjL&+&tM*xzP#8gcULbHRVFXp?=dfW}B4tNq-|I1UdUyiaRck;>G!sr;98` zu1V*iIDdL2U0z#F2a7MxU%Z<7jd31LN#?WF z>Rx|!eR=WxhIBWVTx5N-UaK1WY_}_yH=D!5vjlh7UC|fg&TBqN+(cb0dMqi#N&uTk&7!d-C`Gz6V9r8-6!Wo^#HmZkVD6?C`K$yA(e-|; z7=~R9mTDE+zdBvM3Hd~Ln zLtU`)c;h3_4vxxrGdtz&u5^d}&pvp>-9nwyAgR_g}xi9!58< zURSFnvAGSezx2wVz5ls%v*pRj?#0u@9@*pGlzV}W?;D#h-QL!Gk~=)czYTD5s3k)s zxtYy{kGdV|$G>=Kxx9aN_d$Dz z?oz3z;(zbo{mpr|_}*Xq_@DpuXHPF)+_?5HoyseB`h1X!CijX7CGe)v2r&*v6V-Nu zX>LCCu5fLOkwb~lND5Pv8Ko=TULKmD{|P+!rRCp*2{fgdig07gqiK;^ zJsijREwI3oxS>tECg7J$jfD_8b2Xyr?Dsvk(SCeb3mDoUCCY3f>&R);q;=Sg1tOV~ z5miH@DYeEtxK9ZAyt@@6BX(j>T~3(`YSB`O=h022-C-4`D|awcatCN%_%_q<}36LYG=`0V|UM>k4oU=gsF z%EYSGjZ&ZcZmwpk`t;MMt|cm=0R9=moZ(1BVCIxu%jFkO#!rf*%qj2nYEVyPR!3Bw zck@h{h>cv$UOk=tPk;U0<;5qxjzzjrX5y0ILWG=aFLjxI_~h!p_@hf?=?GcXb?C@O z5J~3*XERcdq(v9zB$9R)H&?@p2p$oZ#JuQdnQ2#dWqWPL)%fZ;zWE!!QAgCV4%@8_ zqeB(88wcX9hw0U`EtlER@s{cCSKgT)9zXiv{o~v-Ib0bMBk$Wq(T)R~x)My)CYpJs z0}f%ZtNO65W)P(gg()A}V5J~SPaUU}GU|+q(dZS|H-ovPB+R6$W|au;%3zLRMZ*M-%HzI0kR_CEoQbsEh%LxMmmIbCeGzx8{+Yil`N-v9m&K6th* zPoAG_h=2Fr`?YWX_N%+$LYRQsbv-!k#v6t9{c?9x%8f4OGf9M=iOB!^X+av;RlIDrkN{@gSSb{(meMVQ@*3S8MxiwrYvso zP((a9fxO{#-XulMLDHsJ8wpc9OsEG17kK{(W(X4tZ2w&ZVvlFF-PHES_HEQGvVR$0 zcb_Q36IsJ)0z2Z7qgLCuhQp_U$pmvag8WND%uLKANo1?~-nV|0J4*xxG3V;!gyl@e zn+w~n6L(7DB<37b3JQHC1Lg(;)R_Q`YU8#Z-8ncqT)9>E@#1QH{`~6k(+f3`?hZmZ z-V431n^bu`-lm0s;wl?3f*{cJODH&u3#< zNMgxWD+rQAawmD*V^A=(1Rw&lxU(75yqd|LRZX!4JAs+RFq#{HVcc;f2?`McwQJnX z{fH&Dr*Jm8-5I9^_iykceqP(`|EdEDw`8kEJ|M&i(q_}KAw)msAm3Z#Algj(r5!JT zOC&hC34vjZ7O)&6awdTjL7X@eR9dUl=9$in)nZ0OYCaZAeNMuCN|ix%JIkq-VN{Ij zAj(2iU5NTbSDTUZ%z)P0w6>V{19)$kCMM!A;8wN+!&5(BEe;eJX|^~za>Tc(GgVVp z>&vbi6L_&Ji;b^L#(s`B-h8P#D%3#&Gp&x~USQjfN9cxt++|2nhUJVrgOHk=vFU&^ z79GEM@%YU*?q{4!?Z?`sWa`dbdqF4p>T)zYFbG`W1TY)dq1-z@DRtAWmenftT~AbY zqp^2g?mB*O_YQXlG zKu6C!CG2)LFP?2bdHHbp%5wG2y#MC$!3Zx>MlYK4s1=+C@3vzpb?4Y!ZP%sN8SCLP z9~_+etp6W=^1UB@`uKU#k>!TA*XzxL-Qs~KLhTZXIK09MFd;UNWbOSi1WZkG8*9*% zFXj-m#CS}nS>(w#GfZb=zZ~j|^>9$@;R-~_om1*d9g-xfzP;I6sJ>aclptPROa&yu z+1Q6#GC8p@P-`V}%3|bW8Q4uyv|&OJqePUn8YKcjT{#m#W$?^7m$a1C?s9yxJMSL! z+eE(XhCzd$NH7XB`tXgRfB@VrVuUbtK6~2&Z?!do_!A{=lq`D|ep~(wQV-I*$MX?a zn=HJPnmie8>a_=+9BvU`4G$Ys<(LV?B+Jz&(Ny$cUmmn0jG7!gd0jY&AO?2x%>478 z{^H4dXLk%eCwA()4sKowOqo5VlL8_#b7MymuzT5+-c#qbKg`)cb)?)AOLZT{VW?WP zfYidfOECJ}ke#`jad8_)W>F{+DJl^vkA%`M<_>r;ph6W55ED0+Z}2gj#C2){aO?3i zfpoFO-S+$Yy|bYYxPK8?l>dp-&gWQOQITjfi?oj}Nhv6Bw?|^$^X&(%hMZBLkf8fnw> z*(?ftH<=-KqN+?*mg(-Jj*dcFD6zP@!HJ_-3>T1a1~RB3Q8(-7)AL|%VOxa+I9Q2IT_v&u zse`q}jvBrro((&$ zN(H>@I^C$3&CITP+p!N!eb;xA9I9kSZaMs4TTLqNN)R_E34q0!M2*1QVzAx8X+HrY z+SA6EU0A70ox8!^Ssk@C8q+>9Z7vSLBa@G&h3ZPg-c$yylegevG>{n#Y5GM>ExtJu zy9Gr&d$1dr00@OUEkHC0>6#tM1eNiGn0j;Xzl{crzzQca0ZfTNMiN9pnj9vgjB!rx z4h-Y&QaB=F8P)_VbUUSw2HMGEdx?Z5r+e)D(#!4H1_$N%WR z``Bjp|LDh8&)3g>>u>+gpd0XL#m&iBnKC6kp3lDb`+xRd|L1@BAN}oraIw88Wf&bN z56%`x$44h8|LuSC-`sru={qkSedAZ&vhfCGeX`KM_nmjsZ25yvE`IR-=ZbmeG_&j9 zc;oDE{k31o$)3J+^0jZh_mBVhPygu;KHb8rKw!==V~I9>f|CgfQfF#hyZy~>G%&uc z$|pMEHm z(gJTH;>Zwcobm|D^*BU>^~mg0)yRz7q=#Fn@Fo1b9V6rug5cA+*vj!nQZ|r#jGZT> zykQ6prk|b++@jkls2%Q+)x^x=U}&PQM^10O`}*#=DW8HrG@dpoHehsU`tKs_YtM~4n9Q^Rlez>^z z%IUkm`p|fWJU)B8xO(>9n=k(uH$z9WH%{@h!(E?=4PI4ndhd?C|0Dxs%HUe7vWuv* z@v@&S@AN5GtCg@pfoxSx!SGxiCQ^$fRfSz`)^`9;d16EvwCXsT)nNTQld=&Rp(L-# zmCYDJDOt{ytXWC96FDbxhnlkcEM*ZfC96}+>I69xO`Q4$FKC>SQ@H5_TK=VdIR0&5 zDC`lTJvy^L`Ox@D6#icg;@|faJ{e~EUhyz>sYYOGQ}7lg&;UiBXlo9R%%LzQ4HH{; z0ZvxEn5LX_%A6?!l_bE08h{4VT;3E__ zsI?9|qAYp2S{*qFj7H_$_0={4CpONBi@I*5Z1U__=Z70!t$i^Vi562EK^3f^)Wl{| zt&p>#%MxKG;@OLqVXXIGy8qzK*H+8Jlj9S!@$*kVzP!5r_rLu=yL|MEKl~^E^v>!I zD2Z5?lIfTe!+f_c+$DvnS0Gm5e~)EUh~+o;k>!+9}!QO9SS z?WdQUyEA#^ec#US2x5v-=6cZPU=++EF|oR`o&`S>(iF-rDwQ3#sGp{5q%L)-CAJ#W+og{BQkdd z4d9D$d+&Ho0vLtaead30nX*^puD5DR&I$80yRl7}wW}8*f>(nQLA{Vb-Kx`Qx|(&+ z%9IUI1qswD3vrf6MVN-V{^Q^OgFpGsfB3uKdgYsMz5VL(ox>-e z0;vN>GA+PaG;d_Sy#?VDC>z=7{iTzHeaTYRQnz1su2p$Ogxf(*0lmFu?4GDtTf zH&X8YI41y6;yE)nY6KA+9l4D+Ovxx|8)r4BlG{*Y@1YRCgzamDXyeR$p8Bgxd;09^ z-o4pjPasOORO*-nX5I|`%_L=NOaWvDn{isw&bunh)ohi-#*x_6t(2YaUi2v~5}6w( zaW~1`pxLD_rWgyXCRZ^R2QjHMj{u>1IPX5Ml@h_7%p4qIV+x<)b{3IMPh@jxYzV@% zy|$GV7iwQAIZbo7xX4p4>kb5DF_o8%E#ySx91Fq$CTn9)AaZyo@FK3Y0TAKcZag?W zL+;EYdsUkYR;-5hocL|qF(Gu^;TF3}0(bWcq1Exh%WrQ?F%G?!&BfEa8)gC6 z^c|C|H!tA4IKAs+E@W&Dx2oW3u4)kJ7?iRE$V5!!5sc?!9aoEk@%pCAdD!k|M_s9! zb9Q1*vL1%{QNDlYRjp&sa8pjRTC3K9g--9D{o)rNmi3miXif*O-=pOm;=#FKXD$vk zqqLNzjCyhLY}qBB{1fPw(CtmT64%v;2m~@h84FZ&-1PITY@V~3g^|9RyE?m?)kYJx@gV<d)+VKBRI3WT2Vor=kDmvz009oY=@(RgSnesjl1g`z&Vi@buYti%#xiP zNhlg>W>tkW0ebXit!ZWsA*dm!@3voUHnI8*3c}^Z7c@HrB(WqGYj%sWTz+nbA-q(-odd)J^u=pJ;}=X+)iZM` zgV{hnx^#Xumgo3?{Gb1k)y-hZ@jg8{^Z%k#jco?m%jD&SO43;_(TarX+mH2vzl=3gw=t~MHtP5 zbDG_JN$5ehx8ato5kMXSr9S32$U&GxuV8!B4$JK2r_NqUruli z>sEJ1RDB`#56Lgd#7zUh%tY+g=re#j)U8<@0&dkN#z6bm{hivsuRY}6{u^qSAc4h~ zx?vys=+yi&eBxHN$FhxynuY}0NY2bt<F({j_t#Hu418oLN%bYl1a(jkj7T z_BMmNq$obkLO8mY*2j|nV9Mz52{|O9m_kJJ5PL(TLPZGlE#KK2 zi#$vm8=co+EnmSKARW{u2FxZ8l64_ML-AYwU~&;xQBq+I|cs5TFME;SV}^*#WeepLG3k0n~_`7PCc$ z1a9bXhZo(MZss{_x7eE9RHFRygad0MP*)J81&T&BX?RJK~w zc{D#hJ8_tM^`ZiHVsEBo+V$P_qZgT-396Va4t;$No0`&gI8CsTPmNsbZQNl@ZJbPo zcqBmHlGuTw_1k~_JF=J$&!4E8nqQ4}dA)l~_WpW2`sDiT>5G?7^J1A7hjX~Wb0@^A zyWKdLUfx^}yKzK!o>v#M)rNGpzPPz~T6Q}@S!HzNu2+Ri zY|y>Bg>IkE_V?hQlN$_Vk+@^V#O&amVdFYKTB5=$RjPqpsM;_V&MaM6;$b~($c)*@ z$1Zj31t776-B?{EGYP|mr5n|*uC5kc%6(68FgGf@SGVf%+YnLiKKStGCpO!T^k4no z|KaY%<}@$v%OX9yJOlR;&c;%-gwr7!Pu6_md%3f<);tDM?ZchhwQi zPaj=5@^Wg+g52QK{Au52N8h0nTImF5;SpX%K76j3y8}YkXPKp1qaKKE0ZbGL%5JW$ z{1GU!^4w*4KHNZ&;Zn$LyM6fSN7uv<&JLlJiG)dvd!?C?cjQ@}DUJ3 z^{^&WH9#1|k(JV}G$vkG9G0;SHfEhtdy5TixuyMprs`gJb~r=ObUjV2I(==Ahlq*Vw%qhr3}|F;t!8 z(QV&m8k8|VI=uf{fg~%tix+isliV20OlD+Ei8DD}K7ZWL7mMRl&SItnn{{a%3z(_T zM9EZ**jy+lf@(DoGr^8OIAtAOcFQ& zlJjzT|Lhz8-tWrU(OQecID!z(I`_RvMQqMMm2r*2z|nKrxt z#Dbxu-gR41&9qtSv5WzIoBwWdvyt)J3Vz#(*U}CNY1F?i33gD#fyYc$T zV^oDNiO9u9u{CYZhby|?sd)#>ph9FxX7i4v@TOm!9nXESRv{Rfl|l2eW6EB!oF5G9 zl5^5w&F-BfBq^%S#tf@amzfA3FU`^tMdlf#$a+6=?)*`v-z3EKycw#P{kN#mGE(H4Lv8vP2Uea4%w z-TCOF_ebqaGc%IA*q~*g!_`;6`qm$R|EE^c(P97S(G4Q_a2Qk?(_m1oOh-qD!_`_# z;fPIi5Batl%*<@;#8X<7S|ic6H5gGkxuZELg5U-nZk~)+kJv@1YoKckcU>?kPeY0j zy*h!NXp#em6BDAvYG?t488+F}vw4Flu}2H1=0En)X;9QEes3My?Ql7EHlo`D*DI7i|L8ybt?Ez9Zp$P>$*fw{#M$B7&F0SimsfW$ zk6(RDy4k^@@VpMgb02OtPhM25J0DIToX%&w*=ZRH6L<5acJphMnnAvM`6T_$uYLvF zn|{8ct=xIAyxt9x55}r>B;g1;Xni*kfykMil4)SL@d)h4bAqYfvL z-dx2D)g8PzxNji00xJY{H=n#;NIGYLgozzoBU1y!?i%M8D6&F`+}t=A`jdt2ec;yKcYBR*9cDZdh_FY^%WExf#rD($kK};q>h!|oeB2{B% zOl-c;J~TuX2=jId`XUj60WEAfz5VXn0^`9_>f`KIH7A;{7OUf<-TC>jE46HSARUxW zR@4i1T+P7I222k(gSj~q$2v2WYHq})&cfy@(oR>6j#Y$+ijCpj6}TKHSY+@t*^x7I zgb=W^xiLwtl|W#U`Fvn1*!iFl=Adwbbt!q`TGbeLUwUbKy?yrlX|)~PI62HU_)(zh z8p23-%6b0qlgGz?+TA^Vj`ThesuX`cbhw_R)V=zQD%@3fn>DGlDeE2uAX;LMbW31pTm3LR0-#YG7QV?#EGnwXnSWGiY}u^b*A7QFe0-wyC;4=3EB zb+-d_?1@i*`KZEQeLw*~>{dM0uD~Q-n*Q+?f^I9h?eC2=a=SrbLL%YK;k=c=0SGby zMor7_-WF!awAjH+e00#ybC-08*odHsF^ksSy|XjHIM%Wr{o-bGaPX4WwGffj?Bh|F zA6<^{MUZ?P=0uawmrcX;2xhg6gWH&A^Tqr?c&@60R%c4!)#3;?8dNenETPP(zPP+N zK6q`T%WXH?a<1r#4Q!T!9qtt-4j2myt2?2mSQZ8|F=wEF=*edn=b!&%Bw4oTm)4yCO2q<$H4VK&DS^PZ%$-^{l&`?O@q$Xb;x!c zE{Dw_`&;0cCZg)VYJFXQoYe+kYEsIX%VAziL7vTj>;ki2Z^Q1oePT z!fu+bHWhikzPwENz}(6(QZ@%vt1;B2Pm&T3JMm_f| z5Rifdq={j!xn{(t>@|JgtO!GH3f{)Kc0jzR#u za*V+MM9v7c-(W9oYr%3KJ(Mi_x#U612Ihd-IbdCMFi6 z&UJC{i_fkHlNlQ_G>zNxBCLh*%^~A2udzO$px|2E?Sdt`8JnmjR{KeBRJ`*7!2uYS)&0U1E5XiJj?tmOk zgj0>*f<0yFP_0WGbR#wIXLFF4QxYMDh2OX&7BO=cCQ5*gs*;nkut;UTQtKq$P&m7~ zlQBsGk~_Stx7V3S(5%c6aqYan{(BHlf^$c3HcikMmrtCvUoOuM=QzVUC=SDU6X;F-Jl}78JDB(=8KU?IdyE-GddQUbw-ssk>#8v zTP?)e3um{w8$g{qRW(VSdy+IWx+&xC`LizuM-e9wtj5GU=!?N+6eL%eSC9$3>~>If zEm>&0U8jDTa#pz0o=as!BsLx-dh&}O+f7r)yZr3K8ENtufigLmdUq!X zV6nqQFb)S69)_a2`L*}n`_?xOzx%zPe*E!;a-S)64iK(B|K!=Dhi9`>ySjdUaY40X zR$?W0Gc_>x^M0{>+pzLmTyN%dRIE7CUPsF1=CWuKU z#pKyNkrS$bBR|1Z+kP=sUST-v6aMQhBo7ESO{3u5QrHM!60?eSMz#@AE8q6N1c1p7 zVcN>@%Z2wIgdm@UYYpGP^vx4iF(D8XRk6d+(j7cbs1W4458=H}>$}zQIiKM0?$3vZfAepC{ri9T&yG9jcCp*-4tsv-rB}3EtDCB_z+IJPLuoLZ z+4^_B`OeaAo`3e)!O5L|u~;q_mzOW}cxL3x&ANh!Olxaiz+oopAmZe~tP2@tgGu&= z)B8?5n*Daad@2K?6bif`1S!(zGLAmc_W)uNCk}7&L}X6PtY$*V-K0%9)Tt3nDDWh2 zr`&Xiv|fzd;R-YajSvP5Fw-oVcvcliNyID+KBtOnf2>Rg({%Q?XX(p-Bn0Kp)rg46 zge!UgPpwZ^Q=qbW>dB$?m81y)3KkG3Uk6 z(eoFN&o7>sjbbF+MOv+c-7T?*uy6;sZO+GY-JadIo_ujwRb|w9kJ2xnr%oMLj8S#Q zDIXrvwv3}Fbz|>0$!jjF1)G;_o+Oz;oUAH|FmompxI1TNN1AtuvTVj;wPGydB!txW z(CT$(L}TPdh>!>oC6Z)XLsEn6AQDRK%u1+C!X~W39AVIAZscle7U_c1AO&h= za3^&z1Qy;ja2OGrDL6*Erd7#vKR?W?nNd=8!w6OqH>2DWF$`j;Sv1o)+&tr9eelZR z`JiK;t2lTN-@0uoOei^!I1wdK5!9w|bxXuVP&QD+{s~)R?w0+m7>2sltKm-W$#;Bn zGiXWJoy}`bM!TzCz{v+6C|giGoXiELWJ;aP68mD_YuOPs$#>j0CW4`kba_3RXGUTx zi*9yVwGl)Ja0_v;?Mb{Iv+(fG9A;4w@)~K|)=3esz&jU$*B5CySGllW}u% zes%rya=Udms$!H)5@!bzbq*#*a%WOxB~}k=W8bN&)s5NC%C1jY)g;f1)3#KkMCQpU zfjqjhFw?%RVd7Ep+ZJwms`s-2H1J>=6gy(V?7`GDBc8qg@r&Ph>;9qlDCz`jXiFsm zlUpg&CGlR&38fP;H)5(FPd(Fou{bE@YS?W#_uL)M67RO>g{V6`8WK7iQ7ao5Y^IVK zu3F(bnvQo@i~jWP|MRCm{Gz`8)px)8)+@_TZ=Qc+n9poJr^H?w0}!p1YGa~M%S}_F zC@x~4ZLRv+kDD+Rl-x&*bc_>~o*`@)Rj-E#`n4 zEz3xpgaNJLBg5a^Tv<{HRtP7n#LbXC)bEZ6@C;^FY#@kSi+IpHSiYN^t8mlcl*vTR3{;Ze|CKlS z?!lIMEIyY!17Ut_XJ_3Z-Cxol{U`sqe{n;pXW?1yXN!KBXV>d1tLo^;Bo$`P0|J|1 zi;1kewAgx2~a*?jT_+)!|o|s9I zIZJ~Atw%MRZ3DzJwJaspOu$iIsq*|`sCS8Ywj|%EW{7VQ7)Wj>%m={ zb%4RPwHC(l-P5oCwO@O3@$`cq|GZc4QgSnA1DwcUZdKRgwwuitNAq#BQ4J07Wc$^~ zS`({1mP*vT&Ep^jwh$7BQtV}zJ|DY5tA?itA2ghx(Zw4AK*A}f4&xRr9HOoE+wh2Z zwdrFzKKD%Z_+0x0PZc`8)N4-Q=UxfB{}HkEMdRGcz-buBpk~g+Yz^*?daLz?>hUns z^mFL;7j%$=)MIOO2}LbYpT*XjejH}I_2r{-egBniac8!i{qO#_{|g@8Pd<DvbjD_Q&^)Mi4!Qzy%*eK~GOGbp#VP`zi2zNK5j2uUVIWcyjiizE6Z8{g zrm0LL$uyEg8YR*wf+C9m!75Y-R3Y2Qj6UMU>u+i;h z@!pI3?m1^|U+$lOO@@5+#lzF}=CyBqW4GO1J^8X8uFmt6pFVu?=uusrZr*&O^rnOu zG6W$}A|SbIWNj!_0(`l5*u^9DnJHwtzGP|vc%=~wzCN5RU~;lXxefG;B|=Zww6->a zxLd8dJk)vK?)Iw6B&#lu(`a=j7Rh9l?%%m{a@_6ryQ^oHWim>{Oy&^D1{Y>9IX2}?-Sw*u3_d--dUSop1I(`HgV|nnJU;5{ zv+GSt*Uzq)c&hVZo)&ma!OjeE7V1d2B*@8e>7`Irs#Pl&07|I408e&!BL>h@#Z~3=@bQ5*$)n)`~IB z#6zZL$?RyzSg)IRe)=E&*Z<9& z6V!b|lB2Gja)7R}NN^Ot) z@@pBRZU^Z$FMCt_10aUDu>~kW(P(vFKD+to;q=2hM+rr>dSOJ+v=){yr4r%1?(#Iw zx$EZ|*qU?B+ned1{PADAlY@uV&4YVyK6qnQXgq)So7dJ4?8mqncuQIw#E?1{b9RV$ zLfQM_`ki;bb@!W>Z~o@L^Y8!9{@trjfBL8Y(f_8X>p^=Sh_vteGR`qR-TI4Sd3n*` z(SrZI0u08py7VSYOPp6gMa!^|Jwa7@wC0UzPkFe-}}z_zw$d5)2u|w z?0~{&x5^rjZ&FZ!9Gqs?lVSDVJNfbBFK(`PkTp01@d!pJKyxE%A5x=67y}K>pw{Tn zVk^|>`>{qQ11Ok*Sgcs$-23uP+m+3UK~O^kT@eG+GUA!+7EJNR0|Z>nh{?_3do!_H zgu^$*Gl)I*BOWj%7X3zh+v6~`X}ry%Z1GkHpEzKf;5D?K(Otf3^06Pb!ZB>L&nCDU;^I|Z3B;V(S=^ZzBenSrCr4ABucj-v!^UU8`kY_vuXl&*ecqHZ&RV+MgWPpDWF}^iIFT*1Rl=>8+ah9W zPr8Ad5x={OgEaVP&LDLdA}^vOOG1{Do6B}w|LLdmz5Tfp=BZDf`@?R{RZ(Zc?pmKt zH(!z}URyc!&Iuw$KG!-Itwp_3qQ!L#oRAwtXMyBd*xa{c)mZu-K`ai+2?i#C11{1J zkOYKndE@(-1t!O=Hn@$?KHq0QeeLx_?vLL1`so9G{Mjed_W3%~YO_X?>2RgBfQ*E_ zL9y|Kkb((mzgKg8vK{&8{u}S#zkc?7x4$NzWv-nYYB3!ltLhbCPSS5qPSz*Kci-aU zqpkFlBsb?!fD)+(&l<6fC*rY`t{XTHW7*{tuxGq>S?crc(9Vr!L;#W5LJOrfmAA>g zK@ba7W@)~b$8}*vE&uL-tGm}}t`WS~v~Mq$!|{AJazcdJQDZ~|C8xb-zML(zu#C2G zqUC3K`3;+ng5p0TEL(|J>QVEEbS03QIW&}|t!PF}5X0f2;lD*`fk6GJb(KuPiFb1^ zMQECLFEZbq#{J3NH%1(P@wD_g-#NBgCmsuPG6<0pL#;6Lu-ce%pVH}CcDy3{lyb@$(&R% zO*YR4Msx9}$aA(xD+ifrMJqj`@I}Ju?ydqv7<;R{!9oCo$$0^E+yRD?S0DonPjF`{ zLJ}st4lqd|<%|keAI`EqC*7Vpho)Cf%%K->tB7RdXn2v?(Y#Bf0bCX$BG z!&F2#<*HiX-~GmWXD6%uJPETfse3gAQI`{}CScfXuHEfuvtGSpPd+~r-aULOGx4e- zb(=Tu9wJlI%+v);O>;tO?UpeQ%7i9jcm+vVjJn4hLJKI=U^;EO4!eD_8fNEC91hvh zvxJ{#GOOflW@eg%yJ0x)vYF0T`)NC_xDb>Dk%q^Zu9P{#ln8aAsbf zOoD?M^X_VOM8~P`vjCJSWvw*MtK4m`pEHBm%ql2v&NfrWKIE%$e|mQG4CEDQwHXgz zUT>edTLq*~L!YNwv!SDms%4xGFK*Vk2m58&zxxM&dGbbc=uq3UuYKR3T!f)Nl3>w01hLfoav4|{fhcBTdX#Dp!D}*LCiY4FjVti?a zUUp53p7B@s_3I8OygHf0yU9xz*(T&hA!rjB8XoNpA`jmMZyOsTVxn+@ZB4U#FjAdC zWNwXzLXOY^vMEG_BzbjW8irKMXmx~JqU`Q)a>{*942+XbqaQ|dpv?OeDGp2;4!2

    GxILzE!c-0L@Fg7@BREN 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�w`0jDEoU-PCAx`xHJguB+VggAEN9Vk*Pt5?_p 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

    fX^xBa)H4agy%w*Af5~y|P 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

    qoVtCW-_j)P$1W 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

    FdG@jIa7lsjY^>gN8VQf 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