parent
44f4cb318b
commit
bd18842584
@ -0,0 +1,102 @@
|
||||
import multion
|
||||
from swarms.structs.concurrent_workflow import ConcurrentWorkflow
|
||||
from swarms.models.base_llm import AbstractLLM
|
||||
from swarms.structs.agent import Agent
|
||||
from swarms.structs.task import Task
|
||||
|
||||
|
||||
class MultiOnAgent(AbstractLLM):
|
||||
"""
|
||||
Represents a multi-on agent that performs browsing tasks.
|
||||
|
||||
Args:
|
||||
max_steps (int): The maximum number of steps to perform during browsing.
|
||||
starting_url (str): The starting URL for browsing.
|
||||
|
||||
Attributes:
|
||||
max_steps (int): The maximum number of steps to perform during browsing.
|
||||
starting_url (str): The starting URL for browsing.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
multion_api_key: str,
|
||||
max_steps: int = 4,
|
||||
starting_url: str = "https://www.google.com",
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.multion_api_key = multion_api_key
|
||||
self.max_steps = max_steps
|
||||
self.starting_url = starting_url
|
||||
|
||||
multion.login(
|
||||
use_api=True,
|
||||
# multion_api_key=self.multion_api_key
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def run(self, task: str, *args, **kwargs):
|
||||
"""
|
||||
Runs a browsing task.
|
||||
|
||||
Args:
|
||||
task (str): The task to perform during browsing.
|
||||
*args: Additional positional arguments.
|
||||
**kwargs: Additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: The response from the browsing task.
|
||||
"""
|
||||
response = multion.browse(
|
||||
{
|
||||
"cmd": task,
|
||||
"url": self.starting_url,
|
||||
"maxSteps": self.max_steps,
|
||||
},
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return response.result, response.status, response.lastUrl
|
||||
|
||||
|
||||
# model
|
||||
model = MultiOnAgent(
|
||||
multion_api_key="535ae401948b4c59bc1b2c61eec90fe6"
|
||||
)
|
||||
|
||||
# out = model.run("search for a recipe")
|
||||
agent = Agent(
|
||||
agent_name="MultiOnAgent",
|
||||
description="A multi-on agent that performs browsing tasks.",
|
||||
llm=model,
|
||||
max_loops=1,
|
||||
system_prompt=None,
|
||||
)
|
||||
|
||||
|
||||
# Task
|
||||
task = Task(
|
||||
agent=agent,
|
||||
description=(
|
||||
"send an email to vyom on superhuman for a partnership with"
|
||||
" multion"
|
||||
),
|
||||
)
|
||||
|
||||
# Swarm
|
||||
workflow = ConcurrentWorkflow(
|
||||
max_workers=1000,
|
||||
autosave=True,
|
||||
print_results=True,
|
||||
return_results=True,
|
||||
)
|
||||
|
||||
# Add task to workflow
|
||||
workflow.add(task)
|
||||
|
||||
# Run workflow
|
||||
workflow.run()
|
@ -0,0 +1,20 @@
|
||||
import threading
|
||||
from typing import Callable, Tuple
|
||||
|
||||
|
||||
class AgentJob(threading.Thread):
|
||||
"""A class that handles multithreading logic.
|
||||
|
||||
Args:
|
||||
function (Callable): The function to be executed in a separate thread.
|
||||
args (Tuple): The arguments to be passed to the function.
|
||||
"""
|
||||
|
||||
def __init__(self, function: Callable, args: Tuple):
|
||||
threading.Thread.__init__(self)
|
||||
self.function = function
|
||||
self.args = args
|
||||
|
||||
def run(self) -> None:
|
||||
"""Runs the function in a separate thread."""
|
||||
self.function(*self.args)
|
@ -0,0 +1,197 @@
|
||||
import logging
|
||||
from functools import wraps
|
||||
from multiprocessing import Manager, Pool, cpu_count
|
||||
from time import sleep
|
||||
from typing import List
|
||||
|
||||
from swarms.structs.base_workflow import BaseWorkflow
|
||||
from swarms.structs.task import Task
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
|
||||
|
||||
# Retry on failure
|
||||
def retry_on_failure(max_retries: int = 3, delay: int = 5):
|
||||
"""
|
||||
Decorator that retries a function a specified number of times on failure.
|
||||
|
||||
Args:
|
||||
max_retries (int): The maximum number of retries (default: 3).
|
||||
delay (int): The delay in seconds between retries (default: 5).
|
||||
|
||||
Returns:
|
||||
The result of the function if it succeeds within the maximum number of retries,
|
||||
otherwise None.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
for _ in range(max_retries):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as error:
|
||||
logging.error(
|
||||
f"Error: {str(error)}, retrying in"
|
||||
f" {delay} seconds..."
|
||||
)
|
||||
sleep(delay)
|
||||
return None
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class MultiProcessingWorkflow(BaseWorkflow):
|
||||
"""
|
||||
Initialize a MultiProcessWorkflow object.
|
||||
|
||||
Args:
|
||||
max_workers (int): The maximum number of workers to use for parallel processing.
|
||||
autosave (bool): Flag indicating whether to automatically save the workflow.
|
||||
tasks (List[Task]): A list of Task objects representing the workflow tasks.
|
||||
*args: Additional positional arguments.
|
||||
**kwargs: Additional keyword arguments.
|
||||
|
||||
Example:
|
||||
>>> from swarms.structs.multi_process_workflow import MultiProcessingWorkflow
|
||||
>>> from swarms.structs.task import Task
|
||||
>>> from datetime import datetime
|
||||
>>> from time import sleep
|
||||
>>>
|
||||
>>> # Define a simple task
|
||||
>>> def simple_task():
|
||||
>>> sleep(1)
|
||||
>>> return datetime.now()
|
||||
>>>
|
||||
>>> # Create a task object
|
||||
>>> task = Task(
|
||||
>>> name="Simple Task",
|
||||
>>> execute=simple_task,
|
||||
>>> priority=1,
|
||||
>>> )
|
||||
>>>
|
||||
>>> # Create a workflow with the task
|
||||
>>> workflow = MultiProcessingWorkflow(tasks=[task])
|
||||
>>>
|
||||
>>> # Run the workflow
|
||||
>>> results = workflow.run(task)
|
||||
>>>
|
||||
>>> # Print the results
|
||||
>>> print(results)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_workers: int = 5,
|
||||
autosave: bool = True,
|
||||
tasks: List[Task] = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.max_workers = max_workers
|
||||
self.autosave = autosave
|
||||
self.tasks = sorted(
|
||||
tasks or [], key=lambda task: task.priority, reverse=True
|
||||
)
|
||||
|
||||
self.max_workers or cpu_count()
|
||||
|
||||
if tasks is None:
|
||||
tasks = []
|
||||
|
||||
self.tasks = tasks
|
||||
|
||||
def execute_task(self, task: Task, *args, **kwargs):
|
||||
"""Execute a task and handle exceptions.
|
||||
|
||||
Args:
|
||||
task (Task): The task to execute.
|
||||
*args: Additional positional arguments for the task execution.
|
||||
**kwargs: Additional keyword arguments for the task execution.
|
||||
|
||||
Returns:
|
||||
Any: The result of the task execution.
|
||||
|
||||
"""
|
||||
try:
|
||||
result = task.execute(*args, **kwargs)
|
||||
|
||||
logging.info(
|
||||
f"Task {task} completed successfully with result"
|
||||
f" {result}"
|
||||
)
|
||||
|
||||
if self.autosave:
|
||||
self._autosave_task_result(task, result)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
(
|
||||
"An error occurred during execution of task"
|
||||
f" {task}: {str(e)}"
|
||||
),
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
def run(self, task: Task, *args, **kwargs):
|
||||
"""Run the workflow.
|
||||
|
||||
Args:
|
||||
task (Task): The task to run.
|
||||
*args: Additional positional arguments for the task execution.
|
||||
**kwargs: Additional keyword arguments for the task execution.
|
||||
|
||||
Returns:
|
||||
List[Any]: The results of all executed tasks.
|
||||
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
with Manager() as manager:
|
||||
with Pool(
|
||||
processes=self.max_workers, *args, **kwargs
|
||||
) as pool:
|
||||
# Using manager.list() to collect results in a process safe way
|
||||
results_list = manager.list()
|
||||
jobs = [
|
||||
pool.apply_async(
|
||||
self.execute_task,
|
||||
(task,),
|
||||
callback=results_list.append,
|
||||
timeout=task.timeout,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
for task in self.tasks
|
||||
]
|
||||
|
||||
# Wait for all jobs to complete
|
||||
for job in jobs:
|
||||
job.get()
|
||||
|
||||
results = list(results_list)
|
||||
|
||||
return results
|
||||
except Exception as error:
|
||||
logging.error(f"Error in run: {error}")
|
||||
return None
|
||||
|
||||
def _autosave_task_result(self, task: Task, result):
|
||||
"""Autosave task result. This should be adapted based on how autosaving is implemented.
|
||||
|
||||
Args:
|
||||
task (Task): The task for which to autosave the result.
|
||||
result (Any): The result of the task execution.
|
||||
|
||||
"""
|
||||
# Note: This method might need to be adapted to ensure it's process-safe, depending on how autosaving is implemented.
|
||||
logging.info(f"Autosaving result for task {task}: {result}")
|
||||
# Actual autosave logic here
|
@ -0,0 +1,157 @@
|
||||
import threading
|
||||
from swarms.structs.base_workflow import BaseWorkflow
|
||||
import logging
|
||||
from concurrent.futures import (
|
||||
FIRST_COMPLETED,
|
||||
ThreadPoolExecutor,
|
||||
wait,
|
||||
)
|
||||
from typing import List
|
||||
from swarms.structs.task import Task
|
||||
import queue
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
|
||||
|
||||
class PriorityTask:
|
||||
"""
|
||||
Represents a task with a priority level.
|
||||
|
||||
Attributes:
|
||||
task (Task): The task to be executed.
|
||||
priority (int): The priority level of the task.
|
||||
"""
|
||||
|
||||
def __init__(self, task: Task, priority: int = 0):
|
||||
self.task = task
|
||||
self.priority = priority
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.priority < other.priority
|
||||
|
||||
|
||||
class MultiThreadedWorkflow(BaseWorkflow):
|
||||
"""
|
||||
Represents a multi-threaded workflow that executes tasks concurrently using a thread pool.
|
||||
|
||||
Args:
|
||||
max_workers (int): The maximum number of worker threads in the thread pool. Default is 5.
|
||||
autosave (bool): Flag indicating whether to automatically save task results. Default is True.
|
||||
tasks (List[PriorityTask]): List of priority tasks to be executed. Default is an empty list.
|
||||
retry_attempts (int): The maximum number of retry attempts for failed tasks. Default is 3.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
|
||||
Attributes:
|
||||
max_workers (int): The maximum number of worker threads in the thread pool.
|
||||
autosave (bool): Flag indicating whether to automatically save task results.
|
||||
retry_attempts (int): The maximum number of retry attempts for failed tasks.
|
||||
tasks_queue (PriorityQueue): The queue that holds the priority tasks.
|
||||
lock (Lock): The lock used for thread synchronization.
|
||||
|
||||
Methods:
|
||||
execute_tasks: Executes the tasks in the thread pool and returns the results.
|
||||
_autosave_task_result: Autosaves the result of a task.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_workers: int = 5,
|
||||
autosave: bool = True,
|
||||
tasks: List[PriorityTask] = None,
|
||||
retry_attempts: int = 3,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.max_workers = max_workers
|
||||
self.autosave = autosave
|
||||
self.retry_attempts = retry_attempts
|
||||
if tasks is None:
|
||||
tasks = []
|
||||
self.tasks_queue = queue.PriorityQueue()
|
||||
for task in tasks:
|
||||
self.tasks_queue.put(task)
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Executes the tasks in the thread pool and returns the results.
|
||||
|
||||
Returns:
|
||||
List: The list of results from the executed tasks.
|
||||
|
||||
"""
|
||||
results = []
|
||||
with ThreadPoolExecutor(
|
||||
max_workers=self.max_workers
|
||||
) as executor:
|
||||
future_to_task = {}
|
||||
for _ in range(self.tasks_queue.qsize()):
|
||||
priority_task = self.tasks_queue.get_nowait()
|
||||
future = executor.submit(priority_task.task.execute)
|
||||
future_to_task[future] = (
|
||||
priority_task.task,
|
||||
0,
|
||||
) # (Task, attempt)
|
||||
|
||||
while future_to_task:
|
||||
# Wait for the next future to complete
|
||||
done, _ = wait(
|
||||
future_to_task.keys(), return_when=FIRST_COMPLETED
|
||||
)
|
||||
|
||||
for future in done:
|
||||
task, attempt = future_to_task.pop(future)
|
||||
try:
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
logging.info(
|
||||
f"Task {task} completed successfully with"
|
||||
f" result: {result}"
|
||||
)
|
||||
if self.autosave:
|
||||
self._autosave_task_result(task, result)
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
(
|
||||
f"Attempt {attempt+1} failed for task"
|
||||
f" {task}: {str(e)}"
|
||||
),
|
||||
exc_info=True,
|
||||
)
|
||||
if attempt + 1 < self.retry_attempts:
|
||||
# Retry the task
|
||||
retry_future = executor.submit(
|
||||
task.execute
|
||||
)
|
||||
future_to_task[retry_future] = (
|
||||
task,
|
||||
attempt + 1,
|
||||
)
|
||||
else:
|
||||
logging.error(
|
||||
f"Task {task} failed after"
|
||||
f" {self.retry_attempts} attempts."
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def _autosave_task_result(self, task: Task, result):
|
||||
"""
|
||||
Autosaves the result of a task.
|
||||
|
||||
Args:
|
||||
task (Task): The task whose result needs to be autosaved.
|
||||
result: The result of the task.
|
||||
|
||||
"""
|
||||
with self.lock:
|
||||
logging.info(
|
||||
f"Autosaving result for task {task}: {result}"
|
||||
)
|
||||
# Actual autosave logic goes here
|
@ -0,0 +1,176 @@
|
||||
from dataclasses import asdict
|
||||
from typing import List
|
||||
|
||||
import networkx as nx
|
||||
import redis
|
||||
from redis.commands.graph import Graph, Node
|
||||
|
||||
from swarms.structs.agent import Agent
|
||||
from swarms.structs.base_swarm import AbstractSwarm
|
||||
|
||||
|
||||
class SwarmRelationship:
|
||||
JOINED = "joined"
|
||||
|
||||
|
||||
class RedisSwarmRegistry(AbstractSwarm):
|
||||
"""
|
||||
Initialize the SwarmRedisRegistry object.
|
||||
|
||||
Args:
|
||||
host (str): The hostname or IP address of the Redis server. Default is "localhost".
|
||||
port (int): The port number of the Redis server. Default is 6379.
|
||||
db: The Redis database number. Default is 0.
|
||||
graph_name (str): The name of the RedisGraph graph. Default is "swarm_registry".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = 6379,
|
||||
db=0,
|
||||
graph_name: str = "swarm_registry",
|
||||
):
|
||||
self.redis = redis.StrictRedis(
|
||||
host=host, port=port, db=db, decode_responses=True
|
||||
)
|
||||
self.redis_graph = Graph(self.redis, graph_name)
|
||||
self.graph = nx.DiGraph()
|
||||
|
||||
def _entity_to_node(self, entity: Agent | Agent) -> Node:
|
||||
"""
|
||||
Converts an Agent or Swarm object to a Node object.
|
||||
|
||||
Args:
|
||||
entity (Agent | Agent): The Agent or Swarm object to convert.
|
||||
|
||||
Returns:
|
||||
Node: The converted Node object.
|
||||
"""
|
||||
return Node(
|
||||
node_id=entity.id,
|
||||
alias=entity.agent_name,
|
||||
label=entity.agent_description,
|
||||
properties=asdict(entity),
|
||||
)
|
||||
|
||||
def _add_node(self, node: Agent | Agent):
|
||||
"""
|
||||
Adds a node to the graph.
|
||||
|
||||
Args:
|
||||
node (Agent | Agent): The Agent or Swarm node to add.
|
||||
"""
|
||||
self.graph.add_node(node.id)
|
||||
if isinstance(node, Agent):
|
||||
self.add_swarm_entry(node)
|
||||
elif isinstance(node, Agent):
|
||||
self.add_agent_entry(node)
|
||||
|
||||
def _add_edge(self, from_node: Node, to_node: Node, relationship):
|
||||
"""
|
||||
Adds an edge between two nodes in the graph.
|
||||
|
||||
Args:
|
||||
from_node (Node): The source node of the edge.
|
||||
to_node (Node): The target node of the edge.
|
||||
relationship: The relationship type between the nodes.
|
||||
"""
|
||||
match_query = (
|
||||
f"MATCH (a:{from_node.label}),(b:{to_node.label}) WHERE"
|
||||
f" a.id = {from_node.id} AND b.id = {to_node.id}"
|
||||
)
|
||||
|
||||
query = f"""
|
||||
{match_query}
|
||||
CREATE (a)-[r:joined]->(b) RETURN r
|
||||
""".replace("\n", "")
|
||||
|
||||
self.redis_graph.query(query)
|
||||
|
||||
def add_swarm_entry(self, swarm: Agent):
|
||||
"""
|
||||
Adds a swarm entry to the graph.
|
||||
|
||||
Args:
|
||||
swarm (Agent): The swarm object to add.
|
||||
"""
|
||||
node = self._entity_to_node(swarm)
|
||||
self._persist_node(node)
|
||||
|
||||
def add_agent_entry(self, agent: Agent):
|
||||
"""
|
||||
Adds an agent entry to the graph.
|
||||
|
||||
Args:
|
||||
agent (Agent): The agent object to add.
|
||||
"""
|
||||
node = self._entity_to_node(agent)
|
||||
self._persist_node(node)
|
||||
|
||||
def join_swarm(
|
||||
self,
|
||||
from_entity: Agent | Agent,
|
||||
to_entity: Agent,
|
||||
):
|
||||
"""
|
||||
Adds an edge between two nodes in the graph.
|
||||
|
||||
Args:
|
||||
from_entity (Agent | Agent): The source entity of the edge.
|
||||
to_entity (Agent): The target entity of the edge.
|
||||
|
||||
Returns:
|
||||
Any: The result of adding the edge.
|
||||
"""
|
||||
from_node = self._entity_to_node(from_entity)
|
||||
to_node = self._entity_to_node(to_entity)
|
||||
|
||||
return self._add_edge(
|
||||
from_node, to_node, SwarmRelationship.JOINED
|
||||
)
|
||||
|
||||
def _persist_node(self, node: Node):
|
||||
"""
|
||||
Persists a node in the graph.
|
||||
|
||||
Args:
|
||||
node (Node): The node to persist.
|
||||
"""
|
||||
query = f"CREATE {node}"
|
||||
self.redis_graph.query(query)
|
||||
|
||||
def retrieve_swarm_information(self, swarm_id: int) -> Agent:
|
||||
"""
|
||||
Retrieves swarm information from the registry.
|
||||
|
||||
Args:
|
||||
swarm_id (int): The ID of the swarm to retrieve.
|
||||
|
||||
Returns:
|
||||
Agent: The retrieved swarm information as an Agent object.
|
||||
"""
|
||||
swarm_key = f"swarm:{swarm_id}"
|
||||
swarm_data = self.redis.hgetall(swarm_key)
|
||||
if swarm_data:
|
||||
# Parse the swarm_data and return an instance of AgentBase
|
||||
# You can use the retrieved data to populate the AgentBase attributes
|
||||
|
||||
return Agent(**swarm_data)
|
||||
return None
|
||||
|
||||
def retrieve_joined_agents(self) -> List[Agent]:
|
||||
"""
|
||||
Retrieves a list of joined agents from the registry.
|
||||
|
||||
Returns:
|
||||
List[Agent]: The retrieved joined agents as a list of Agent objects.
|
||||
"""
|
||||
agent_data = self.redis_graph.query(
|
||||
"MATCH (a:agent)-[:joined]->(b:manager) RETURN a"
|
||||
)
|
||||
if agent_data:
|
||||
# Parse the agent_data and return an instance of AgentBase
|
||||
# You can use the retrieved data to populate the AgentBase attributes
|
||||
return [Agent(**agent_data) for agent_data in agent_data]
|
||||
return None
|
Loading…
Reference in new issue