From f85759d6fe6372025859ea988245f723cb9b683e Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 23 Feb 2025 16:59:44 -0800 Subject: [PATCH] cleanup and swarm docs --- docs/swarms_cloud/swarms_api.md | 21 +- swarms/agents/__init__.py | 1 + .../structs/meme_agent_persona_generator.py | 24 +- swarms/structs/swarm_router.py | 31 ++- swarms_api.py | 220 ++++++++++++++++++ 5 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 swarms_api.py diff --git a/docs/swarms_cloud/swarms_api.md b/docs/swarms_cloud/swarms_api.md index 5ae7f2d7..b6d47520 100644 --- a/docs/swarms_cloud/swarms_api.md +++ b/docs/swarms_cloud/swarms_api.md @@ -49,7 +49,7 @@ Run a single swarm with specified agents and tasks. | description | string | Optional | Description of the swarm (max 500 chars) | | agents | array | Required | Array of agent configurations | | max_loops | integer | Optional | Maximum number of iterations | -| swarm_type | string | Optional | Type of swarm workflow | +| swarm_type | string | Optional | Type of swarm workflow (e.g., "AgentRearrange", "MixtureOfAgents", "SpreadSheetSwarm", "SequentialWorkflow", "ConcurrentWorkflow", "GroupChat", "MultiAgentRouter", "AutoSwarmBuilder", "HiearchicalSwarm", "auto", "MajorityVoting") | | task | string | Required | The task to be performed | | img | string | Optional | Image URL if relevant | @@ -67,6 +67,25 @@ Run a single swarm with specified agents and tasks. | role | string | Optional | "worker" | Role of the agent | | max_loops | integer | Optional | 1 | Maximum iterations for this agent | + +## Available Swarm Types + +--- +| Swarm Type | Description | +|----------------------|-----------------------------------------------------------------------------| +| AgentRearrange | Rearranges agents dynamically to optimize task execution. | +| MixtureOfAgents | Combines different agents to leverage their unique capabilities. | +| SpreadSheetSwarm | Utilizes spreadsheet-like operations for data manipulation and analysis. | +| SequentialWorkflow | Executes tasks in a predefined sequential order. | +| ConcurrentWorkflow | Runs tasks concurrently to improve efficiency and reduce execution time. | +| GroupChat | Facilitates communication and collaboration among agents in a chat format. | +| MultiAgentRouter | Routes tasks to multiple agents based on their expertise and availability. | +| AutoSwarmBuilder | Automatically constructs a swarm based on task requirements and agent skills.| +| HiearchicalSwarm | Organizes agents in a hierarchy to manage complex task dependencies. | +| auto | Automatically selects the most suitable swarm type for the task. | +| MajorityVoting | Uses majority voting among agents to reach a consensus on task outcomes. | + + #### Example Request ```json { diff --git a/swarms/agents/__init__.py b/swarms/agents/__init__.py index 835954de..98f4a58a 100644 --- a/swarms/agents/__init__.py +++ b/swarms/agents/__init__.py @@ -10,6 +10,7 @@ from swarms.structs.stopping_conditions import ( check_stopped, check_success, ) + # from swarms.agents.tool_agent import ToolAgent from swarms.agents.create_agents_from_yaml import ( create_agents_from_yaml, diff --git a/swarms/structs/meme_agent_persona_generator.py b/swarms/structs/meme_agent_persona_generator.py index 0fb19cc4..1866ea45 100644 --- a/swarms/structs/meme_agent_persona_generator.py +++ b/swarms/structs/meme_agent_persona_generator.py @@ -278,15 +278,15 @@ class MemeAgentGenerator: ) -if __name__ == "__main__": - example = MemeAgentGenerator( - name="Meme-Swarm", - description="A swarm of specialized AI agents collaborating on generating and sharing memes around cool media from 2001s", - max_loops=1, - ) - - print( - example.run( - "Generate funny meme agents around cool media from 2001s" - ) - ) +# if __name__ == "__main__": +# example = MemeAgentGenerator( +# name="Meme-Swarm", +# description="A swarm of specialized AI agents collaborating on generating and sharing memes around cool media from 2001s", +# max_loops=1, +# ) + +# print( +# example.run( +# "Generate funny meme agents around cool media from 2001s" +# ) +# ) diff --git a/swarms/structs/swarm_router.py b/swarms/structs/swarm_router.py index 0aaa17be..ae48ba0b 100644 --- a/swarms/structs/swarm_router.py +++ b/swarms/structs/swarm_router.py @@ -17,6 +17,9 @@ from swarms.structs.sequential_workflow import SequentialWorkflow from swarms.structs.spreadsheet_swarm import SpreadSheetSwarm from swarms.structs.swarm_matcher import swarm_matcher from swarms.utils.loguru_logger import initialize_logger +from swarms.structs.hiearchical_swarm import HierarchicalSwarm +from swarms.structs.majority_voting import MajorityVoting + logger = initialize_logger(log_folder="swarm_router") @@ -28,7 +31,10 @@ SwarmType = Literal[ "ConcurrentWorkflow", "GroupChat", "MultiAgentRouter", + "AutoSwarmBuilder", + "HiearchicalSwarm", "auto", + "MajorityVoting", ] @@ -258,6 +264,8 @@ class SwarmRouter: ConcurrentWorkflow, GroupChat, MultiAgentRouter, + MajorityVoting, + HierarchicalSwarm, ]: """ Dynamically create and return the specified swarm type or automatically match the best swarm type for a given task. @@ -292,6 +300,18 @@ class SwarmRouter: *args, **kwargs, ) + + elif self.swarm_type == "HiearchicalSwarm": + return HierarchicalSwarm( + name=self.name, + description=self.description, + director=self.agents[0], + agents=self.agents, + max_loops=self.max_loops, + return_all_history=self.return_entire_history, + *args, + **kwargs, + ) elif self.swarm_type == "MixtureOfAgents": return MixtureOfAgents( name=self.name, @@ -303,7 +323,16 @@ class SwarmRouter: *args, **kwargs, ) - + + elif self.swarm_type == "MajorityVoting": + return MajorityVoting( + name=self.name, + description=self.description, + agents=self.agents, + consensus_agent=self.agents[-1], + *args, + **kwargs, + ) elif self.swarm_type == "GroupChat": return GroupChat( name=self.name, diff --git a/swarms_api.py b/swarms_api.py new file mode 100644 index 00000000..3fc7d209 --- /dev/null +++ b/swarms_api.py @@ -0,0 +1,220 @@ +import asyncio +import os +from typing import List, Optional + +import httpx +from loguru import logger +from pydantic import BaseModel, Field +from tenacity import retry, stop_after_attempt, wait_exponential + +from swarms.structs.agent import agent_roles +from swarms.structs.swarm_router import SwarmType + + +class Agent(BaseModel): + agent_name: Optional[str] = None + description: Optional[str] = None + system_prompt: Optional[str] = None + model_name: Optional[str] = None + role: Optional[agent_roles] = "worker" + max_loops: Optional[int] = Field(default=1, ge=1) + + +class SwarmRequest(BaseModel): + name: Optional[str] = None + description: Optional[str] = None + agents: Optional[List[Agent]] = None + max_loops: Optional[int] = Field(default=1, ge=1) + swarm_type: Optional[SwarmType] = "ConcurrentWorkflow" + task: Optional[str] = None + + +class SwarmResponse(BaseModel): + swarm_id: Optional[str] = None + status: Optional[str] = None + result: Optional[str] = None + error: Optional[str] = None + + +class HealthResponse(BaseModel): + status: Optional[str] = None + version: Optional[str] = None # Make version optional + + +class SwarmAPIError(Exception): + """Base exception for Swarms API errors.""" + + pass + + +class SwarmAuthenticationError(SwarmAPIError): + """Raised when authentication fails.""" + + pass + + +class SwarmValidationError(SwarmAPIError): + """Raised when request validation fails.""" + + pass + + +class SwarmClient: + """Production-grade client for the Swarms API.""" + + def __init__( + self, + api_key: Optional[str] = None, + base_url: str = "https://swarms-api-285321057562.us-east1.run.app", + timeout: int = 30, + max_retries: int = 3, + ): + """Initialize the Swarms API client. + + Args: + api_key: API key for authentication. If not provided, looks for SWARMS_API_KEY env var + base_url: Base URL for the API + timeout: Request timeout in seconds + max_retries: Maximum number of retries for failed requests + """ + self.api_key = api_key or os.getenv("SWARMS_API_KEY") + + if not self.api_key: + raise SwarmAuthenticationError( + "API key not provided and SWARMS_API_KEY env var not found" + ) + + self.base_url = base_url.rstrip("/") + self.timeout = timeout + self.max_retries = max_retries + + # Configure logging + logger.add( + "swarms_client.log", + rotation="100 MB", + retention="1 week", + level="INFO", + ) + + # Setup HTTP client + self.client = httpx.Client( + timeout=timeout, + headers={ + "x-api-key": self.api_key, + "Content-Type": "application/json", + }, + ) + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=4, max=10), + reraise=True, + ) + async def health_check(self) -> HealthResponse: + """Check the API health status.""" + try: + response = self.client.get(f"{self.base_url}/health") + response.raise_for_status() + return HealthResponse(**response.json()) + except httpx.HTTPError as e: + logger.error(f"Health check failed: {str(e)}") + raise SwarmAPIError(f"Health check failed: {str(e)}") + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=4, max=10), + reraise=True, + ) + async def create_swarm( + self, swarm_request: SwarmRequest + ) -> SwarmResponse: + """Create and run a new swarm. + + Args: + swarm_request: SwarmRequest object containing the swarm configuration + + Returns: + SwarmResponse object with the results + """ + try: + response = self.client.post( + f"{self.base_url}/v1/swarm/completions", + json=swarm_request.model_dump(), + ) + response.raise_for_status() + return SwarmResponse(**response.json()) + except httpx.HTTPStatusError as e: + if e.response.status_code == 401: + raise SwarmAuthenticationError("Invalid API key") + elif e.response.status_code == 422: + raise SwarmValidationError( + "Invalid request parameters" + ) + logger.error(f"Swarm creation failed: {str(e)}") + raise SwarmAPIError(f"Swarm creation failed: {str(e)}") + except Exception as e: + logger.error( + f"Unexpected error during swarm creation: {str(e)}" + ) + raise + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=4, max=10), + reraise=True, + ) + async def create_batch_swarms( + self, swarm_requests: List[SwarmRequest] + ) -> List[SwarmResponse]: + """Create and run multiple swarms in batch. + + Args: + swarm_requests: List of SwarmRequest objects + + Returns: + List of SwarmResponse objects + """ + try: + response = self.client.post( + f"{self.base_url}/v1/swarm/batch/completions", + json=[req.model_dump() for req in swarm_requests], + ) + response.raise_for_status() + return [SwarmResponse(**resp) for resp in response.json()] + except httpx.HTTPStatusError as e: + if e.response.status_code == 401: + raise SwarmAuthenticationError("Invalid API key") + elif e.response.status_code == 422: + raise SwarmValidationError( + "Invalid request parameters" + ) + logger.error(f"Batch swarm creation failed: {str(e)}") + raise SwarmAPIError( + f"Batch swarm creation failed: {str(e)}" + ) + except Exception as e: + logger.error( + f"Unexpected error during batch swarm creation: {str(e)}" + ) + raise + + def close(self): + """Close the HTTP client.""" + self.client.close() + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self.close() + + +# Example usage + +async def main(): + async with SwarmClient(api_key=os.getenv("SWARMS_API_KEY")) as client: + health = await client.health_check() + print(health) + +if __name__ == "__main__": + asyncio.run(main())