Spaces:
Runtime error
Runtime error
import os | |
import openai | |
import re | |
import random | |
import json | |
from swarmai.agents.AgentBase import AgentBase | |
from swarmai.utils.ai_engines.GPTConversEngine import GPTConversEngine | |
from swarmai.utils.task_queue.Task import Task | |
from swarmai.utils.PromptFactory import PromptFactory | |
class ManagerAgent(AgentBase): | |
"""Manager agent class that is responsible for breaking down the tasks into subtasks and assigning them into the task queue. | |
""" | |
def __init__(self, agent_id, agent_type, swarm, logger): | |
super().__init__(agent_id, agent_type, swarm, logger) | |
self.engine = GPTConversEngine("gpt-3.5-turbo", 0.25, 2000) | |
self.TASK_METHODS = { | |
Task.TaskTypes.report_preparation: self.report_preparation, | |
Task.TaskTypes.breakdown_to_subtasks: self.breakdown_to_subtasks, | |
} | |
def perform_task(self): | |
self.step = "perform_task" | |
try: | |
# self.task is already taken in the beginning of the cycle in AgentBase | |
if not isinstance(self.task, Task): | |
raise Exception(f"Task is not of type Task, but {type(self.task)}") | |
task_type = self.task.task_type | |
if task_type not in self.TASK_METHODS: | |
raise Exception(f"Task type {task_type} is not supported by the agent {self.agent_id} of type {self.agent_type}") | |
self.result = self.TASK_METHODS[task_type](self.task.task_description) | |
return True | |
except Exception as e: | |
self.log(message = f"Agent {self.agent_id} of type {self.agent_type} failed to perform the task {self.task.task_description[:20]}...{self.task.task_description[-20:]} of type {self.task.task_type} with error {e}", level = "error") | |
return False | |
def share(self): | |
pass | |
def report_preparation(self, task_description): | |
"""The manager agent prepares a report. | |
For each goal of the swarm: | |
1. It reads the current report. | |
2. It analyses which information is missing in the report to solve the global task. | |
3. Then it tries to find this information in the shared memory | |
Updating report: | |
If it finds the information: | |
it adds it to the report | |
else: | |
it adds the task to the task queue | |
Finally: resets the report preparation task | |
""" | |
global_goal = self.swarm.global_goal | |
goals = self.swarm.goals.copy() | |
random.shuffle(goals) | |
for _, goal in enumerate(goals): | |
idx = self.swarm.goals.index(goal) | |
report_json = self._get_report_json() | |
# find the goal. The format is the following: {1: {"Question": goal_i, "Answer": answer_i}, 2:...} | |
if idx in report_json: | |
prev_answer = report_json[idx]["Answer"] | |
else: | |
prev_answer = "" | |
missing_information_list = self._analyse_report(global_goal, goal, prev_answer) | |
for el in missing_information_list: | |
self._add_subtasks_to_task_queue([('google_search', f"For the purpose of {goal}, find information about {el}", 50)]) | |
# update the report | |
info_from_memory = self.shared_memory.ask_question(f"For the purpose of {global_goal}, try to find information about {goal}. Summarise it shortly and indclude web-lins of sources. Be an extremely critical analyst!.") | |
if info_from_memory is None: | |
info_from_memory = "" | |
conversation = [ | |
{"role": "system", "content": PromptFactory.StandardPrompts.summarisation_for_task_prompt }, | |
{"role": "user", "content": info_from_memory + prev_answer + f"\nUsing all the info above answer the question:\n{goal}\n"}, | |
] | |
summary = self.engine.call_model(conversation) | |
# add to the report | |
report_json = self._get_report_json() | |
report_json[idx] = {"Question": goal, "Answer": summary} | |
self.swarm.interact_with_output(json.dumps(report_json), method="write") | |
self.swarm.create_report_qa_task() | |
def _get_report_json(self): | |
report = self.swarm.interact_with_output("", method="read") | |
if report == "": | |
report = "{}" | |
# parse json | |
report_json = json.loads(report) | |
return report_json | |
def _analyse_report(self, global_goal, goal, prev_answer): | |
"""Checks what information is missing in the report to solve the global task. | |
""" | |
prompt = ( | |
f"Our global goal is:\n{global_goal}\n\n" | |
f"The following answer was prepared to solve this goal:\n{prev_answer}\n\n" | |
f"Which information is missing in the report to solve the following subgoal:\n{goal}\n\n" | |
f"If no information is missing or no extention possible, output: ['no_missing_info']" | |
f"Provide a list of specific points that are missing from the report to solve a our subgoal.\n\n" | |
) | |
conversation = [ | |
{"role": "user", "content": prompt}, | |
] | |
missing_information_output = self.engine.call_model(conversation) | |
# parse the output | |
missing_information_output = re.search(r"\[.*\]", missing_information_output) | |
if missing_information_output is None: | |
return [] | |
missing_information_output = missing_information_output.group(0) | |
missing_information_output = missing_information_output.replace("[", "").replace("]", "").replace("'", "").strip() | |
missing_information_list = missing_information_output.split(",") | |
if missing_information_list == ["no_missing_info"]: | |
return [] | |
if len(missing_information_list) == 1: | |
missing_information_list = missing_information_output.split(";") | |
return missing_information_list | |
def _repair_json(self, text): | |
"""Reparing the output of the model to be a valid JSON. | |
""" | |
prompt = ( | |
"Act as a professional json repairer. Repair the following JSON if needed to make sure it conform to the correct json formatting.\n" | |
"Make sure it's a single valid JSON object.\n" | |
"""The report ABSOLUTELY MUST be in the following JSON format: {[{"Question": "question1", "Answer": "answer1", "Sources": "web links of the sources"}, {"Question": "question2", "Answer": "answer2", "Sources": "web links of the sources"},...]}""" | |
) | |
conversation = [ | |
{"role": "user", "content": prompt+text}, | |
] | |
return self.engine.call_model(conversation) | |
def breakdown_to_subtasks(self, main_task_description): | |
"""Breaks down the main task into subtasks and adds them to the task queue. | |
""" | |
self.step = "breakdown_to_subtasks" | |
task_breakdown_prompt = PromptFactory.StandardPrompts.task_breakdown | |
allowed_subtusk_types = [str(t_i) for t_i in self.swarm.TASK_TYPES] | |
allowed_subtusk_types_str = "\nFollowing subtasks are allowed:" + ", ".join(allowed_subtusk_types) | |
output_format = f"\nThe output MUST be ONLY a list of subtasks in the following format: [[(subtask_type; subtask_description; priority in 0 to 100), (subtask_type; subtask_description; priority in 0 to 100), ...]]" | |
one_shot_example = ( | |
"\nExample: \n" | |
"Task: Write a report about the current state of the project.\n" | |
"Subtasks:\n" | |
f"[[({allowed_subtusk_types[0]}; Find information about the project; 50), ({allowed_subtusk_types[-1]}; Write a conclusion; 5)]]\n" | |
) | |
task_prompt = ( | |
"Task: " + main_task_description + "\n" | |
"Subtasks:" | |
) | |
# generate a conversation | |
conversation = [ | |
{"role": "system", "content": task_breakdown_prompt + allowed_subtusk_types_str + output_format + one_shot_example}, | |
{"role": "user", "content": task_prompt} | |
] | |
result = self.engine.call_model(conversation) | |
result = result.replace("\n", "").replace("\r", "").replace("\t", "").strip() | |
# parse the result | |
# first, find the substring enclosed in [[]] | |
subtasks_str = re.search(r"\[.*\]", result) | |
try: | |
subtasks_str = subtasks_str.group(0) | |
except: | |
raise Exception(f"Failed to parse the result {result}") | |
# then, find all substrings enclosed in () | |
subtasks = [] | |
for subtask_str_i in re.findall(r"\(.*?\)", subtasks_str): | |
subtask_str_i = subtask_str_i.replace("(", "").replace(")", "").replace("[", "").replace("]", "").replace("'", "").strip() | |
result_split = subtask_str_i.split(";") | |
try: | |
subtask_type = result_split[0].strip() | |
except: | |
continue | |
try: | |
subtask_description = result_split[1].strip() | |
except: | |
continue | |
try: | |
prio_int = int(result_split[2].strip()) | |
except: | |
prio_int = 0 | |
subtasks.append((subtask_type.strip(), subtask_description.strip(), prio_int)) | |
# add subtasks to the task queue | |
self._add_subtasks_to_task_queue(subtasks) | |
# add to shared memory | |
self.log( | |
message=f"Task:\n'{main_task_description}'\n\nwas broken down into {len(subtasks)} subtasks:\n{subtasks}", | |
) | |
# self._send_data_to_swarm( | |
# data = f"Task '{main_task_description}' was broken down into {len(subtasks)} subtasks: {subtasks}" | |
# ) | |
return subtasks | |
def _add_subtasks_to_task_queue(self, subtask_list: list): | |
if len(subtask_list) == 0: | |
return | |
self.step = "_add_subtasks_to_task_queue" | |
summary_conversation = [ | |
{"role": "system", "content": "Be very concise and precise when summarising the global task. Focus on the most important aspects of the global task to guide the model in performing a given subtask. Don't mention any subtasks but only the main mission as a guide."}, | |
{"role": "user", "content": f"""Global Task:\n{self.task.task_description}\nSubtasks:\n{"||".join([x[1] for x in subtask_list])}\nSummary of the global task:"""}, | |
] | |
task_summary = self.engine.call_model(summary_conversation) | |
for task_i in subtask_list: | |
try: | |
# generating a task object | |
taks_obj_i = Task( | |
priority=task_i[2], | |
task_type=task_i[0], | |
task_description=f"""For the purpose of '{task_summary}' Perform ONLY the following task: {task_i[1]}""", | |
) | |
self.swarm.task_queue.add_task(taks_obj_i) | |
except: | |
continue | |