You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
16 KiB
512 lines
16 KiB
"""
|
|
Todo
|
|
- [ ] Test the new api feature
|
|
- [ ] Add the agent schema for every agent -- following OpenAI assistaants schema
|
|
- [ ] then add the swarm schema for the swarm url: /v1/swarms/{swarm_name}/agents/{agent_id}
|
|
- [ ] Add the agent schema for the agent url: /v1/swarms/{swarm_name}/agents/{agent_id}
|
|
"""
|
|
|
|
import asyncio
|
|
import multiprocessing
|
|
import queue
|
|
import threading
|
|
from typing import List, Optional
|
|
|
|
import tenacity
|
|
|
|
# from fastapi import FastAPI
|
|
from pydantic import BaseModel
|
|
|
|
from swarms.structs.agent import Agent
|
|
from swarms.structs.base_swarm import BaseSwarm
|
|
from swarms.utils.loguru_logger import initialize_logger
|
|
|
|
logger = initialize_logger("swarm-network")
|
|
|
|
|
|
# Pydantic models
|
|
class TaskRequest(BaseModel):
|
|
task: str
|
|
|
|
|
|
# Pydantic models
|
|
class TaskResponse(BaseModel):
|
|
result: str
|
|
|
|
|
|
class AgentInfo(BaseModel):
|
|
agent_name: str
|
|
agent_description: str
|
|
|
|
|
|
class SwarmInfo(BaseModel):
|
|
swarm_name: str
|
|
swarm_description: str
|
|
agents: List[AgentInfo]
|
|
|
|
|
|
# Helper function to get the number of workers
|
|
def get_number_of_workers():
|
|
return multiprocessing.cpu_count()
|
|
|
|
|
|
# [TODO] Add the agent schema for every agent -- following OpenAI assistaants schema
|
|
class SwarmNetwork(BaseSwarm):
|
|
"""
|
|
SwarmNetwork class
|
|
|
|
The SwarmNetwork class is responsible for managing the agents pool
|
|
and the task queue. It also monitors the health of the agents and
|
|
scales the pool up or down based on the number of pending tasks
|
|
and the current load of the agents.
|
|
|
|
For example, if the number of pending tasks is greater than the
|
|
number of agents in the pool, the SwarmNetwork will scale up the
|
|
pool by adding new agents. If the number of pending tasks is less
|
|
than the number of agents in the pool, the SwarmNetwork will scale
|
|
down the pool by removing agents.
|
|
|
|
The SwarmNetwork class also provides a simple API for interacting
|
|
with the agents pool. The API is implemented using the Flask
|
|
framework and is enabled by default. The API can be disabled by
|
|
setting the `api_enabled` parameter to False.
|
|
|
|
Features:
|
|
- Agent pool management
|
|
- Task queue management
|
|
- Agent health monitoring
|
|
- Agent pool scaling
|
|
- Simple API for interacting with the agent pool
|
|
- Simple API for interacting with the task queue
|
|
- Simple API for interacting with the agent health monitor
|
|
- Simple API for interacting with the agent pool scaler
|
|
- Create APIs for each agent in the pool (optional)
|
|
- Run each agent on it's own thread
|
|
- Run each agent on it's own process
|
|
- Run each agent on it's own container
|
|
- Run each agent on it's own machine
|
|
- Run each agent on it's own cluster
|
|
|
|
|
|
Attributes:
|
|
task_queue (queue.Queue): A queue for storing tasks.
|
|
idle_threshold (float): The idle threshold for the agents.
|
|
busy_threshold (float): The busy threshold for the agents.
|
|
agents (List[Agent]): A list of agents in the pool.
|
|
api_enabled (bool): A flag to enable/disable the API.
|
|
logging_enabled (bool): A flag to enable/disable logging.
|
|
|
|
Example:
|
|
>>> from swarms.structs.agent import Agent
|
|
>>> from swarms.structs.swarm_net import SwarmNetwork
|
|
>>> agent = Agent()
|
|
>>> swarm = SwarmNetwork(agents=[agent])
|
|
>>> swarm.add_task("task")
|
|
>>> swarm.run()
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str = None,
|
|
description: str = None,
|
|
agents: List[Agent] = None,
|
|
idle_threshold: float = 0.2,
|
|
busy_threshold: float = 0.7,
|
|
api_enabled: Optional[bool] = False,
|
|
logging_enabled: Optional[bool] = False,
|
|
api_on: Optional[bool] = False,
|
|
host: str = "0.0.0.0",
|
|
port: int = 8000,
|
|
swarm_callable: Optional[callable] = None,
|
|
*args,
|
|
**kwargs,
|
|
):
|
|
super().__init__(agents=agents, *args, **kwargs)
|
|
self.name = name
|
|
self.description = description
|
|
self.agents = agents
|
|
self.task_queue = queue.Queue()
|
|
self.idle_threshold = idle_threshold
|
|
self.busy_threshold = busy_threshold
|
|
self.lock = threading.Lock()
|
|
self.api_enabled = api_enabled
|
|
self.logging_enabled = logging_enabled
|
|
self.host = host
|
|
self.port = port
|
|
self.swarm_callable = swarm_callable
|
|
|
|
# Ensure that the agents list is not empty
|
|
if not agents:
|
|
raise ValueError("The agents list cannot be empty")
|
|
|
|
# Create a dictionary of agents for easy access
|
|
self.agent_dict = {agent.id: agent for agent in agents}
|
|
|
|
# # Create the FastAPI instance
|
|
# if api_on is True:
|
|
# logger.info("Creating FastAPI instance")
|
|
# self.app = FastAPI(debug=True, *args, **kwargs)
|
|
|
|
# self.app.add_middleware(
|
|
# CORSMiddleware,
|
|
# allow_origins=["*"],
|
|
# allow_credentials=True,
|
|
# allow_methods=["*"],
|
|
# allow_headers=["*"],
|
|
# )
|
|
|
|
# logger.info("Routes set for creation")
|
|
# self._create_routes()
|
|
|
|
def add_task(self, task):
|
|
"""Add task to the task queue
|
|
|
|
Args:
|
|
task (_type_): _description_
|
|
|
|
Example:
|
|
>>> from swarms.structs.agent import Agent
|
|
>>> from swarms.structs.swarm_net import SwarmNetwork
|
|
>>> agent = Agent()
|
|
>>> swarm = SwarmNetwork(agents=[agent])
|
|
>>> swarm.add_task("task")
|
|
"""
|
|
self.logger.info(f"Adding task {task} to queue")
|
|
try:
|
|
self.task_queue.put(task)
|
|
self.logger.info(f"Task {task} added to queue")
|
|
except Exception as error:
|
|
print(
|
|
f"Error adding task to queue: {error} try again with"
|
|
" a new task"
|
|
)
|
|
raise error
|
|
|
|
async def async_add_task(self, task):
|
|
"""Add task to the task queue
|
|
|
|
Args:
|
|
task (_type_): _description_
|
|
|
|
Example:
|
|
>>> from swarms.structs.agent import Agent
|
|
>>> from swarms.structs.swarm_net import SwarmNetwork
|
|
>>> agent = Agent()
|
|
>>> swarm = SwarmNetwork(agents=[agent])
|
|
>>> swarm.add_task("task")
|
|
|
|
"""
|
|
self.logger.info(
|
|
f"Adding task {task} to queue asynchronously"
|
|
)
|
|
try:
|
|
# Add task to queue asynchronously with asyncio
|
|
loop = asyncio.get_running_loop()
|
|
await loop.run_in_executor(
|
|
None, self.task_queue.put, task
|
|
)
|
|
self.logger.info(f"Task {task} added to queue")
|
|
except Exception as error:
|
|
print(
|
|
f"Error adding task to queue: {error} try again with"
|
|
" a new task"
|
|
)
|
|
raise error
|
|
|
|
# def _create_routes(self) -> None:
|
|
# """
|
|
# Creates the routes for the API.
|
|
# """
|
|
# # Extensive logginbg
|
|
# logger.info("Creating routes for the API")
|
|
|
|
# # Routes available
|
|
# logger.info(
|
|
# "Routes available: /v1/swarms, /v1/health, /v1/swarms/{swarm_name}/agents/{agent_id}, /v1/swarms/{swarm_name}/run"
|
|
# )
|
|
|
|
# @self.app.get("/v1/swarms", response_model=SwarmInfo)
|
|
# async def get_swarms() -> SwarmInfo:
|
|
# try:
|
|
# logger.info("Getting swarm information")
|
|
# return SwarmInfo(
|
|
# swarm_name=self.swarm_name,
|
|
# swarm_description=self.swarm_description,
|
|
# agents=[
|
|
# AgentInfo(
|
|
# agent_name=agent.agent_name,
|
|
# agent_description=agent.agent_description,
|
|
# )
|
|
# for agent in self.agents
|
|
# ],
|
|
# )
|
|
# except Exception as e:
|
|
# logger.error(f"Error getting swarm information: {str(e)}")
|
|
# raise HTTPException(
|
|
# status_code=500, detail="Internal Server Error"
|
|
# )
|
|
|
|
# @self.app.get("/v1/health")
|
|
# async def get_health() -> Dict[str, str]:
|
|
# try:
|
|
# logger.info("Checking health status")
|
|
# return {"status": "healthy"}
|
|
# except Exception as e:
|
|
# logger.error(f"Error checking health status: {str(e)}")
|
|
# raise HTTPException(
|
|
# status_code=500, detail="Internal Server Error"
|
|
# )
|
|
|
|
# @self.app.get(f"/v1/swarms/{self.swarm_name}/agents/{{agent_id}}")
|
|
# async def get_agent_info(agent_id: str) -> AgentInfo:
|
|
# try:
|
|
# logger.info(f"Getting information for agent {agent_id}")
|
|
# agent = self.agent_dict.get(agent_id)
|
|
# if not agent:
|
|
# raise HTTPException(
|
|
# status_code=404, detail="Agent not found"
|
|
# )
|
|
# return AgentInfo(
|
|
# agent_name=agent.agent_name,
|
|
# agent_description=agent.agent_description,
|
|
# )
|
|
# except Exception as e:
|
|
# logger.error(f"Error getting agent information: {str(e)}")
|
|
# raise HTTPException(
|
|
# status_code=500, detail="Internal Server Error"
|
|
# )
|
|
|
|
# @self.app.post(
|
|
# f"/v1/swarms/{self.swarm_name}/agents/{{agent_id}}/run",
|
|
# response_model=TaskResponse,
|
|
# )
|
|
# async def run_agent_task(
|
|
# task_request: TaskRequest,
|
|
# ) -> TaskResponse:
|
|
# try:
|
|
# logger.info("Running agent task")
|
|
# # Assuming only one agent in the swarm for this example
|
|
# agent = self.agents[0]
|
|
# logger.info(f"Running agent task: {task_request.task}")
|
|
# result = agent.run(task_request.task)
|
|
# return TaskResponse(result=result)
|
|
# except Exception as e:
|
|
# logger.error(f"Error running agent task: {str(e)}")
|
|
# raise HTTPException(
|
|
# status_code=500, detail="Internal Server Error"
|
|
# )
|
|
|
|
# def get_app(self) -> FastAPI:
|
|
# """
|
|
# Returns the FastAPI instance.
|
|
|
|
# Returns:
|
|
# FastAPI: The FastAPI instance.
|
|
# """
|
|
# return self.app
|
|
|
|
def run_single_agent(
|
|
self, agent_id, task: Optional[str], *args, **kwargs
|
|
):
|
|
"""Run agent the task on the agent id
|
|
|
|
Args:
|
|
agent_id (_type_): _description_
|
|
task (str, optional): _description_. Defaults to None.
|
|
|
|
Raises:
|
|
ValueError: _description_
|
|
|
|
Returns:
|
|
_type_: _description_
|
|
"""
|
|
self.logger.info(f"Running task {task} on agent {agent_id}")
|
|
try:
|
|
for agent in self.agents:
|
|
if agent.id == agent_id:
|
|
out = agent.run(task, *args, **kwargs)
|
|
return out
|
|
except Exception as error:
|
|
self.logger.error(f"Error running task on agent: {error}")
|
|
raise error
|
|
|
|
def run_many_agents(
|
|
self, task: Optional[str] = None, *args, **kwargs
|
|
) -> List:
|
|
"""Run the task on all agents
|
|
|
|
Args:
|
|
task (str, optional): _description_. Defaults to None.
|
|
|
|
Returns:
|
|
List: _description_
|
|
"""
|
|
self.logger.info(f"Running task {task} on all agents")
|
|
try:
|
|
return [
|
|
agent.run(task, *args, **kwargs)
|
|
for agent in self.agents
|
|
]
|
|
except Exception as error:
|
|
logger.error(f"Error running task on agents: {error}")
|
|
raise error
|
|
|
|
def list_agents(self):
|
|
"""List all agents."""
|
|
self.logger.info("[Listing all active agents]")
|
|
|
|
try:
|
|
# Assuming self.agents is a list of agent objects
|
|
for agent in self.agents:
|
|
self.logger.info(
|
|
f"[Agent] [ID: {agent.id}] [Name:"
|
|
f" {agent.agent_name}] [Description:"
|
|
f" {agent.agent_description}] [Status: Running]"
|
|
)
|
|
except Exception as error:
|
|
self.logger.error(f"Error listing agents: {error}")
|
|
raise
|
|
|
|
def get_agent(self, agent_id):
|
|
"""Get agent by id
|
|
|
|
Args:
|
|
agent_id (_type_): _description_
|
|
|
|
Returns:
|
|
_type_: _description_
|
|
"""
|
|
self.logger.info(f"Getting agent {agent_id}")
|
|
|
|
try:
|
|
for agent in self.agents:
|
|
if agent.id == agent_id:
|
|
return agent
|
|
raise ValueError(f"No agent found with ID {agent_id}")
|
|
except Exception as error:
|
|
self.logger.error(f"Error getting agent: {error}")
|
|
raise error
|
|
|
|
def add_agent(self, agent: Agent):
|
|
"""Add agent to the agent pool
|
|
|
|
Args:
|
|
agent (_type_): _description_
|
|
"""
|
|
self.logger.info(f"Adding agent {agent} to pool")
|
|
try:
|
|
self.agents.append(agent)
|
|
except Exception as error:
|
|
print(f"Error adding agent to pool: {error}")
|
|
raise error
|
|
|
|
def remove_agent(self, agent_id):
|
|
"""Remove agent from the agent pool
|
|
|
|
Args:
|
|
agent_id (_type_): _description_
|
|
"""
|
|
self.logger.info(f"Removing agent {agent_id} from pool")
|
|
try:
|
|
for agent in self.agents:
|
|
if agent.id == agent_id:
|
|
self.agents.remove(agent)
|
|
return
|
|
raise ValueError(f"No agent found with ID {agent_id}")
|
|
except Exception as error:
|
|
print(f"Error removing agent from pool: {error}")
|
|
raise error
|
|
|
|
async def async_remove_agent(self, agent_id):
|
|
"""Remove agent from the agent pool
|
|
|
|
Args:
|
|
agent_id (_type_): _description_
|
|
"""
|
|
self.logger.info(f"Removing agent {agent_id} from pool")
|
|
try:
|
|
# Remove agent from pool asynchronously with asyncio
|
|
loop = asyncio.get_running_loop()
|
|
await loop.run_in_executor(
|
|
None, self.remove_agent, agent_id
|
|
)
|
|
except Exception as error:
|
|
print(f"Error removing agent from pool: {error}")
|
|
raise error
|
|
|
|
def scale_up(self, num_agents: int = 1):
|
|
"""Scale up the agent pool
|
|
|
|
Args:
|
|
num_agents (int, optional): _description_. Defaults to 1.
|
|
"""
|
|
self.logger.info(f"Scaling up agent pool by {num_agents}")
|
|
try:
|
|
for _ in range(num_agents):
|
|
self.agents.append(Agent())
|
|
except Exception as error:
|
|
print(f"Error scaling up agent pool: {error}")
|
|
raise error
|
|
|
|
def scale_down(self, num_agents: int = 1):
|
|
"""Scale down the agent pool
|
|
|
|
Args:
|
|
num_agents (int, optional): _description_. Defaults to 1.
|
|
"""
|
|
for _ in range(num_agents):
|
|
self.agents.pop()
|
|
|
|
@tenacity.retry(
|
|
wait=tenacity.wait_fixed(1),
|
|
stop=tenacity.stop_after_attempt(3),
|
|
retry=tenacity.retry_if_exception_type(Exception),
|
|
)
|
|
def run(self, *args, **kwargs):
|
|
"""run the swarm network"""
|
|
app = self.get_app()
|
|
|
|
try:
|
|
import uvicorn
|
|
|
|
logger.info(
|
|
f"Running the swarm network with {len(self.agents)} on {self.host}:{self.port}"
|
|
)
|
|
uvicorn.run(
|
|
app,
|
|
host=self.host,
|
|
port=self.port,
|
|
# workers=get_number_of_workers(),
|
|
*args,
|
|
**kwargs,
|
|
)
|
|
|
|
return app
|
|
except Exception as error:
|
|
logger.error(f"Error running the swarm network: {error}")
|
|
raise error
|
|
|
|
|
|
# # # Example usage
|
|
# if __name__ == "__main__":
|
|
|
|
# agent1 = Agent(
|
|
# agent_name="Covid-19-Chat",
|
|
# agent_description="This agent provides information about COVID-19 symptoms.",
|
|
# llm=OpenAIChat(),
|
|
# max_loops="auto",
|
|
# autosave=True,
|
|
# verbose=True,
|
|
# stopping_condition="finish",
|
|
# )
|
|
|
|
# agents = [agent1] # Add more agents as needed
|
|
# swarm_name = "HealthSwarm"
|
|
# swarm_description = (
|
|
# "A swarm of agents providing health-related information."
|
|
# )
|
|
|
|
# agent_api = SwarmNetwork(swarm_name, swarm_description, agents)
|
|
# agent_api.run()
|