gpt-agents / swarmai /agents /AgentBase.py
alex-mindspace's picture
(hopefully) working swarm demo
b3509ba
from abc import ABC, abstractmethod
import threading
import queue
import time
from swarmai.utils.task_queue.Task import Task
class AgentJob(threading.Thread):
"""A class that handles multithreading logic
"""
def __init__(self, function, args):
threading.Thread.__init__(self)
self.function = function
self.args = args
def run(self):
self.function(*self.args)
class AgentBase(ABC, threading.Thread):
"""Abstract base class for agents in the swarm.
- Agents are the entities that perform the task in the swarm.
- Agents can have different roles and implementations, but they all need to implement a set of methods that would allow them to work together in a swarm.
- Implements the threading. Thread class to allow the swarm to run in parallel.
Attributes:
agent_id (int): The unique identifier of the agent
agent_type (str): The type of the agent, ex. worker, explorer, evaluator, etc.
swarm (Swarm): The swarm object
shared_memory (SharedMemoryBase implementation): The shared memory object
challenge (Challenge implementation): The challenge object
logger (Logger): The logger object
max_cycles (int): The maximum number of cycles that the agent will run
"""
def __init__(self, agent_id, agent_type, swarm, logger, max_cycles = 10):
"""Initialize the agent.
"""
threading.Thread.__init__(self)
ABC.__init__(self)
self.agent_id = agent_id
self.agent_type = agent_type
self.swarm = swarm
self.shared_memory = self.swarm.shared_memory
self.task_queue = self.swarm.task_queue
self.logger = logger
self.max_cycles = max_cycles
# some mandatory components
self.step = "init"
self.task = None
self.result = None
self.internal_memory = None
self.message_queue = queue.Queue()
self.current_step = "init"
self.ifRun = True
self.cycle = 0
def run(self):
while self.ifRun:
while self.task is None:
self._get_task() # gets the task from the task queue
if self.task is None:
time.sleep(15)
self.job = AgentJob(self.agent_iteration, ())
self.job.name = f"Agent {self.agent_id}, cycle {self.cycle}"
self.job.start()
self.job.join(timeout=600)
# there is no deadlock, but the agetns sometimes submit code with infinite loops, so need to kill the jobs
if self.job.is_alive():
self.log("Stuck. Dropping the thread.", level = "error")
self._reset_task()
self.cycle += 1
if self.cycle >= self.max_cycles:
self.ifRun = False
def agent_iteration(self):
"""Main iteration of the agent.
"""
ifSuccess = self.perform_task()
if ifSuccess:
self._submit_complete_task()
else:
self._reset_task()
@abstractmethod
def perform_task(self):
"""main method of the agent that defines the task it performs
"""
raise NotImplementedError
@abstractmethod
def share(self):
"""Main method of the agent that defines how it shares its results with the shared memory and the task queue
"""
raise NotImplementedError
def _submit_complete_task(self):
self.task_queue.complete_task(self.task.task_id)
self.task = None
def _reset_task(self):
self.task_queue.reset_task(self.task.task_id)
self.task = None
def _retrive_messages(self):
"""Retrive messages from the neighbors.
"""
# can't use .qsize of .empty() because they are not reliable
queue_full = True
while queue_full:
try:
message = self.message_queue.get(timeout=0.1)
self._process_message(message)
self.message_queue.task_done()
except queue.Empty:
queue_full = False
except Exception as e:
self.log(f"Error while processing the message: {e}", level = "error")
def _get_task(self):
"""Gets the task from the task queue.
It's not the job of the agent to decide which task to perform, it's the job of the task queue.
"""
self.task = self.task_queue.get_task(self)
if not isinstance(self.task, Task):
self.task = None
return
if self.task is not None:
self.log(f"Got task: {self.task.task_id}", level = "debug")
else:
self.log(f"No task found. Waiting for the proper task", level = "debug")
self.task = None
def _process_message(self, message):
"""Process the message from the neighbor.
Args:
message (dict): The message from the neighbor.
"""
self.log(f"Received message: {message}", level="debug")
self.internal_memory.add_entry(message["score"], message["content"])
def _send_data_to_neighbors(self, data):
"""Send data to the neighbors.
Args:
data (dict): The data to send: {"score": score, "content": content}
"""
for queue in self.neighbor_queues:
self.log(f"Sent message: {data}", level = "debug")
queue.put(data)
def _send_data_to_swarm(self, data):
"""Send data to the shared memory.
Args:
data (dict): The data to send: {"score": score, "content": content}
"""
self.log(f"To shared memory: {data}", level = "debug")
_ = self.shared_memory.add_entry(data)
def reset(self):
# Reset the necessary internal state while preserving memory
self.should_run = True
def stop(self):
# Set the termination flag
self.should_run = False
def log(self, message, level = "info"):
"""Need to extend the logging a bit to include the agent id and the step name.
Otherwise too hard to debug.
"""
if isinstance(level, str):
level = level.lower()
if level == "info":
level = 20
elif level == "debug":
level = 10
elif level == "warning":
level = 30
elif level == "error":
level = 40
elif level == "critical":
level = 50
else:
level = 0
message = {"agent_id": self.agent_id, "cycle": self.cycle, "step": self.current_step, "message": message}
self.logger.log(level, message)