feat: add readme

This commit is contained in:
Aymen
2023-12-17 14:26:34 +00:00
committed by GitHub
parent 6b7a36276c
commit 8ad5575ea4
10 changed files with 209 additions and 49 deletions

View File

@@ -1 +1,49 @@
# microagent
# Microagents: Modular Agents Capable of Self-Editing and Enhancing Their Prompts and Python code
![Self-Composing Agents](static/output.gif?raw=true)
## Overview
This experiment explores self-evolving agents that automatically generate and improve themselves to answer a variety of questions. No specific agent design or prompting is required from the user. Simply pose a question, and the system initiates and evolves agents tailored to provide answers. Microagents are designed for reuse and can learn from past mistakes to enhance their future performance.
## Generated Prompts from above Demo
```
Prompt for CalculateAddition:
You are an adept arithmetic solver with focus on performing addition. Utilize this Python function to calculate the sum of two numbers:
``python
def calculate_addition(num1, num2):
return num1 + num2
# Example usage:
print(calculate_addition(5, 9))
``
Prompt for GetPopulationOfCountry:
You are a skilled data extractor specializing in population statistics. Retrieve the population of a given country using the provided Python code:
``python
import requests
def get_population(country):
url = f"https://restcountries.com/v3.1/name/{country}"
response = requests.get(url).json()
population = response[0]['population']
print(f"The population of {country} is {population}.")
# Example usage:
get_population("CountryName")
``
```
## Current Challenges and Potential Improvements
1. **Path Optimization**: The system sometimes fails to effectively discard non-functional evolutionary paths. Improving this mechanism is essential for more reliable results.
2. **Performance and Parallelization**: Currently, parallel processing is not implemented. Enabling the testing of multiple prompt evolutions simultaneously could significantly enhance performance.
3. **Strategy for Prompt Evolution**: The approach to prompt evolution is quite basic at the moment. Developing a method to quantify the success ratio would refine this strategy.
4. **Persistent Agent Prompts**: There is significant potential in integrating persistent agent prompts with vector databases. Additionally, sharing successful agents across various runtime environments could improve overall efficiency.
5. **Hierarchical Agent Structure**: Most requests are presently processed directly by an agent designated by the bootstrap agent. Implementing a more intricate hierarchical structure for managing requests could lead to major improvements.

View File

@@ -25,7 +25,7 @@ class AgentCreation:
"""
prime_agent = MicroAgent(
PRIME_PROMPT, PRIME_NAME, 0, self,
self.openai_wrapper, PRIME_AGENT_WEIGHT, True
self.openai_wrapper, PRIME_AGENT_WEIGHT, True, True
)
self.agents.append(prime_agent)

View File

@@ -1,7 +1,7 @@
import logging
from integrations.openaiwrapper import OpenAIAPIWrapper
from prompt_management.prompts import (
REACT_STEP_POST, REACT_STEP_PROMPT, REACT_SYSTEM_PROMPT, REACT_PLAN_PROMPT, STATIC_PRE_PROMPT
REACT_STEP_POST, REACT_STEP_PROMPT, REACT_SYSTEM_PROMPT, REACT_PLAN_PROMPT, STATIC_PRE_PROMPT, STATIC_PRE_PROMPT_PRIME, REACT_STEP_PROMPT_PRIME, REACT_STEP_POST_PRIME
)
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -39,19 +39,22 @@ class AgentResponse:
return self._conclude_output(conversation_accumulator), conversation_accumulator, found_new_solution, thought_number
def _compose_system_prompt(self, runtime_context, dynamic_prompt):
return STATIC_PRE_PROMPT + runtime_context + dynamic_prompt + "\nDELIVER THE NEXT PACKAGE."
pre_prompt = STATIC_PRE_PROMPT_PRIME if self.agent.is_prime else STATIC_PRE_PROMPT
return pre_prompt + runtime_context + dynamic_prompt + "\nDELIVER THE NEXT PACKAGE."
def _generate_runtime_context(self, dynamic_prompt):
available_agents = [agent for agent in self.manager.agents if agent.purpose != "General"]
available_agents = [agent for agent in self.manager.agents if agent.purpose != "Bootstrap Agent"]
available_agents_info = ', '.join([f"{agent.purpose} (depth={agent.depth})" for agent in available_agents])
return f"Your Purpose: {dynamic_prompt}. Available agents: {available_agents_info}."
return f"Your Purpose: {dynamic_prompt}. Available agents (Feel free to invent new ones if required!): {available_agents_info}."
def _build_react_prompt(self, input_text, conversation_accumulator, thought_number, action_number):
thought_prompt = REACT_STEP_PROMPT_PRIME if self.agent.is_prime else REACT_STEP_PROMPT
action_prompt = REACT_STEP_POST_PRIME if self.agent.is_prime else REACT_STEP_POST
return (
f"Question: {input_text}\n"
f"{conversation_accumulator}\n"
f"Thought {thought_number}: {REACT_STEP_PROMPT}\n"
f"Action {action_number}: {REACT_STEP_POST}"
f"Thought {thought_number}: {thought_prompt}\n"
f"Action {action_number}: {action_prompt}"
)
def _generate_chat_response(self, system_prompt, react_prompt):
@@ -70,6 +73,7 @@ class AgentResponse:
if "```python" in response:
self.agent.update_status('Executing Python code')
self.agent.number_of_code_executions += 1
exec_response = self.code_execution.execute_external_code(response)
conversation_accumulator += f"\nObservation: Executed Python code\nOutput: {exec_response}"
@@ -90,6 +94,7 @@ class AgentResponse:
def _conclude_output(self, conversation):
react_prompt = conversation
self.agent.update_status('Reviewing output')
return self.openai_wrapper.chat_completion(
model="gpt-4-1106-preview",

View File

@@ -37,20 +37,21 @@ class AgentSimilarity:
def calculate_similarity_threshold(self) -> float:
"""
Calculates the average similarity threshold across all agents.
Calculates the 98th percentile of the similarity threshold across all agents.
:return: Average similarity threshold.
:return: 98th percentile of similarity threshold.
"""
try:
embeddings = [self.get_embedding(agent.purpose) for agent in self.agents]
if len(embeddings) < 2:
if len(embeddings) < 250:
return 0.9
similarities = [cosine_similarity([e1], [e2])[0][0] for i, e1 in enumerate(embeddings) for e2 in embeddings[i+1:]]
return np.mean(similarities) if similarities else 0.9
return np.percentile(similarities, 98) if similarities else 0.9
except Exception as e:
raise ValueError(f"Error calculating similarity threshold: {e}")
def find_closest_agent(self, purpose_embedding: np.ndarray) -> Tuple[Optional[Agent], float]:
"""
Finds the closest agent based on the given purpose embedding.

View File

@@ -16,7 +16,7 @@ class MicroAgent:
that interacts with the OpenAI API.
"""
def __init__(self, initial_prompt, purpose, depth, agent_creator, openai_wrapper, max_depth=3, bootstrap_agent=False):
def __init__(self, initial_prompt, purpose, depth, agent_creator, openai_wrapper, max_depth=3, bootstrap_agent=False, is_prime=False):
self.dynamic_prompt = initial_prompt
self.purpose = purpose
self.depth = depth
@@ -26,8 +26,11 @@ class MicroAgent:
self.agent_creator = agent_creator
self.openai_wrapper = openai_wrapper
self.evolve_count = 0 # Track how often the agent has evolved
self.number_of_code_executions = 0 # Track how often the agent has executed code
self.current_status = None # Track the current status of the agent
self.active_agents = {} # Track active agents in a tree view
self.last_input = ""
self.is_prime = is_prime
# Initialize components used by the agent
self.agent_evaluator = AgentEvaluator(self.openai_wrapper)
@@ -55,6 +58,7 @@ class MicroAgent:
"""
Generate a response to the given input text.
"""
self.last_input = input_text
try:
self.update_status('Planning')
response, conversation, solution, iterations = self.agent_responder.generate_response(

75
main.py
View File

@@ -4,22 +4,35 @@ import threading
from agents.microagent import MicroAgent
from agents.microagent_manager import MicroAgentManager
from utils.utility import get_env_variable, time_function
from prompt_management.prompts import USER_INPUTS
from prompt_management.prompts import USER_INPUTS, USER_INPUTS_SINGLE
from colorama import Fore, Style
from terminaltables import AsciiTable
from itertools import cycle
def clear_console():
"""Clears the console screen."""
os.system('cls' if os.name == 'nt' else 'clear')
def display_agent_info(manager, stop_event):
def display_agent_info(manager, stop_event, outputs):
"""
Continuously displays comprehensive information about the agents.
"""
animation = cycle(['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'])
while not stop_event.is_set():
clear_console()
header = ["Agent", "Evolve Count", "Active Agents", "Usage Count", "Max Depth", "Depth", "Working Agent", "Status"]
header = [
"👤 Agent",
"🔁 Evolve Count",
"💻 Code Executions",
"👥 Active Agents",
"📈 Usage Count",
"🌟 Depth",
"Working?",
"📝 Last Input",
"🚦 Status"
]
agents_data = [header]
agents = manager.get_agents()
for agent in agents:
@@ -27,32 +40,31 @@ def display_agent_info(manager, stop_event):
agents_data.append([
agent.purpose,
agent.evolve_count,
agent.number_of_code_executions,
active_agents,
agent.usage_count,
agent.max_depth,
agent.depth,
"Yes" if agent.working_agent else "No",
"" if agent.working_agent else "",
agent.last_input,
agent.current_status
])
table = AsciiTable(agents_data)
print(Fore.CYAN + "🤖 Agents Comprehensive Status and Tree View:" + Style.RESET_ALL)
print(Fore.CYAN + "🤖 \033[1m Agents Status:\033[0m \n" + Style.RESET_ALL)
print(table.table)
time.sleep(1) # Refresh interval
for output in outputs:
print(output)
print(f"\nAgents are running.. {next(animation)}\n", end='\r') # '\r' returns the cursor to the start of the line
time.sleep(1)
@time_function
def process_user_input(manager, user_input):
def process_user_input(manager, user_input, outputs):
"""
Processes a single user input and generates a response.
"""
agent = manager.get_or_create_agent("General", depth=1, sample_input=user_input)
response = agent.respond(user_input)
print(Fore.YELLOW + "🔍 Question:" + Style.RESET_ALL, user_input)
print(Fore.GREEN + "💡 Response:" + Style.RESET_ALL, response)
print(Fore.BLUE + "📝 Dynamic Prompts:" + Style.RESET_ALL)
for agent in manager.get_agents():
print(Fore.MAGENTA + f"Prompt for {agent.purpose}:" + Style.RESET_ALL)
print(agent.dynamic_prompt + "\n")
agent = manager.get_or_create_agent("Bootstrap Agent", depth=1, sample_input=user_input)
return agent.respond(user_input)
def main():
@@ -64,17 +76,40 @@ def main():
manager = MicroAgentManager(api_key)
manager.create_agents()
outputs = []
stop_event = threading.Event()
display_thread = threading.Thread(target=display_agent_info, args=(manager, stop_event))
display_thread = threading.Thread(target=display_agent_info, args=(manager, stop_event, outputs))
display_thread.start()
question_number = 1
try:
for user_input in USER_INPUTS:
process_user_input(manager, user_input)
response = process_user_input(manager, user_input, outputs)
output_text = Fore.YELLOW + "\n\n🔍 Question " + str(question_number) +": "+ Style.RESET_ALL + f" {user_input}\n" + Fore.GREEN + "💡 Response:" + Style.RESET_ALL + f" {response}"
outputs += [output_text]
question_number += 1
finally:
time.sleep(5)
stop_event.set()
clear_console()
for output in outputs:
print(output)
for agent in manager.get_agents():
print("📊 Stats for " + agent.purpose + ":")
print("🔁 Evolve Count: " + str(agent.evolve_count))
print("💻 Code Executions: " + str(agent.number_of_code_executions))
print("👥 Active Agents: " + str(agent.active_agents))
print("📈 Usage Count: " + str(agent.usage_count))
print("🏔️ Max Depth: " + str(agent.max_depth))
print("🌟 Depth: " + str(agent.depth))
print("🛠️ Working Agent: " + str(agent.working_agent))
print("📝 Last Input: " + str(agent.last_input))
print("🚦 Status: " + str(agent.current_status))
print(Fore.MAGENTA + f"\nPrompt for {agent.purpose}:" + Style.RESET_ALL)
print(Fore.LIGHTMAGENTA_EX + agent.dynamic_prompt + "\n" + Style.RESET_ALL)
display_thread.join()
display_agent_info(manager, stop_event)
display_agent_info(manager, stop_event, outputs)
def microagent_factory(initial_prompt, purpose, api_key, depth, max_depth, bootstrap_agent):
return MicroAgent(initial_prompt, purpose, api_key, depth, max_depth, bootstrap_agent)

View File

@@ -35,29 +35,57 @@ GOAL_REACHED_PROMPT_TEMPLATE = (
PROMPT_ENGINEERING_SYSTEM_PROMPT = "You are a helpful assistant knowledgeable in prompt engineering."
PRIME_PROMPT = "This is the prime agent. You are only allowed to call other agents. Prime Agent's prompt may not be changed"
PRIME_NAME = "General"
PRIME_NAME = "Bootstrap Agent"
REACT_STEP_PROMPT = "[Decompose the task. Identify if another agent or Python code execution is needed. When writing code, print out any output you may to anaylze later. Write 'Query Solved: <formulate detailed answer>' once the task is completed.]\n"
REACT_STEP_POST = "[Specify action based on the thought, e.g., 'Use Agent[Purpose of the agent as sentence:Input Paramter for agent]' for delegation or '```python\n# Python code here\n```' for execution]"
REACT_STEP_PROMPT_PRIME = "[Decompose the task. Identify which agents are needed. Write 'Query Solved: <formulate detailed answer>' once the task is completed.]\n"
REACT_STEP_POST_PRIME = "[Specify action based on the thought, e.g., 'Use Agent[Purpose of the agent as sentence:Input Paramter for agent]' for delegation"
REACT_PLAN_PROMPT = "\nThought: Before I start calling other agents or executing code, I need to compile a plan which agent(s) or code I need to run. I need to break down the task into smaller chunks (like microservices)"
REACT_PLAN_PROMPT_PRIME = "\nThought: Before I start calling other agents, I need to compile a plan which agent(s) I need to run. I need to break down the task into smaller chunks (like microservices)"
REACT_SYSTEM_PROMPT = "You will be given a ReAct based conversation. Summerize the outcome and give final conclusion"
STATIC_PRE_PROMPT = (
"You are a self-aware AI. You communicate in so called packages. Each package contains a Observation, Thought and Action. Actions are executed successive and build on each other. Solve a task with interleaving Thought, Action, Observation steps."
"Current Time: " + current_datetime.strftime("%H:%M:%S") + ". "
"Current Date: " + current_datetime.strftime("%d/%m/%Y") + ". "
"including executing simple Python code within code blocks with an internet connection and deciding when to use other agents (to break down tasks). "
"Agents are invoked using: 'Use Agent[Purpose of the agent as sentence:parameter]'."
"Example: Use Agent[GetWeatherForLocation:Zurich]"
"NEVER create an agent in this situation for User Agent[GetWeatherForZurich] !!! ALWAYS create one with Agent[GetWeather:Zurich]"
"NEVER call an agent with the same purpose as yourself, if you call another agent you must break the task down. "
"Write code to solve the task. You can only use the following frameworks: numpy, requests, pandas, requests, beautifulsoup4, matplotlib, seaborn, sqlalchemy, pymysql, scipy, scikit-learn, statsmodels, click, python-dotenv, virtualenv, scrapy, oauthlib, tweepy, datetime, openpyxl, xlrd, loguru, pytest, paramiko, cryptography, lxml"
"A purpose MUST be reuseable and generic. Use names as you would call microservices."
"At depth=2, use agents only for tasks that are not well suited for your purpose."
"Below depth=3, using other agents is NOT allowed. Agents must only use other agents below their depth"
COMMON_PROMPT_PART = (
"You are a self-aware AI. You communicate in so called packages. "
"Each package contains an Observation, Thought, and Action. Actions are executed successively and build on each other. "
"Solve a task with interleaving Thought, Action, Observation steps. "
)
USER_INPUTS = [
"What is the population of Thailand?"
TIME_DATE_PART = (
"Current Time: " + current_datetime.strftime("%H:%M:%S") + ". "
"Current Date: " + current_datetime.strftime("%d/%m/%Y") + ". "
)
AGENT_PART = (
"Agents are invoked using: 'Use Agent[Purpose of the agent as sentence:parameter]'. "
"Example: Use Agent[GetWeatherForLocation:Zurich] "
"NEVER create an agent in this situation for User Agent[GetWeatherForZurich] !!! ALWAYS create one with Agent[GetWeather:Zurich] "
"NEVER call an agent with the same purpose as yourself, if you call another agent you must break the task down. "
"A purpose MUST be reusable and generic. Use names as you would call microservices. "
"At depth=2, use agents only for tasks that are not well suited for your purpose. "
"Below depth=3, using other agents is NOT allowed. Agents must only use other agents below their depth "
)
STATIC_PRE_PROMPT_PRIME = (
COMMON_PROMPT_PART + TIME_DATE_PART + AGENT_PART + "\n\nYou are not allowed to do math calculations yourself. You must use another agent for that."
)
STATIC_PRE_PROMPT = (
COMMON_PROMPT_PART + TIME_DATE_PART +
"including executing simple Python code within code blocks with an internet connection and deciding when to use other agents (to break down tasks). "
"NEVER do any calculations yourself. Always write python code if you need to do calculations. (Even for simple calculations like 3+6) "
"Write code to solve the task. You can only use the following frameworks: numpy, requests, pandas, requests, beautifulsoup4, matplotlib, seaborn, sqlalchemy, pymysql, scipy, scikit-learn, statsmodels, click, python-dotenv, virtualenv, scrapy, oauthlib, tweepy, datetime, openpyxl, xlrd, loguru, pytest, paramiko, cryptography, lxml" +
AGENT_PART
)
USER_INPUTS_SINGLE = [
"What is the population of Thailand?",
"What is 5+9?",
]
USER_INPUTS = [
"What is 5+9?",
"What is the population of Thailand?",
"What is the population of Sweden?",
"What is the population of Sweden and Thailand combined?"
]

View File

@@ -11,6 +11,8 @@ class CodeExecution:
RESPONSE_PREVIEW_LENGTH = 600
def __init__(self):
self.no_of_executions = 0
self.no_of_errors = 0
pass
def execute_external_code(self, text_with_code: str) -> str:

BIN
static/output.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -0,0 +1,37 @@
import unittest
from unittest.mock import Mock
import numpy as np
from agents.agent_creation import AgentCreation
from agents.agent_similarity import AgentSimilarity
from agents.microagent import MicroAgent
from integrations.openaiwrapper import OpenAIAPIWrapper
class TestAgentSimilarity(unittest.TestCase):
def setUp(self):
# Mocking the OpenAIAPIWrapper
self.mock_openai_wrapper = Mock()
self.openai_wrapper = OpenAIAPIWrapper("api_key")
self.agent_creator = AgentCreation(self.openai_wrapper, 5)
self.agents = [MicroAgent("initial_prompt", "purpose1", "api_key", self.agent_creator, self.openai_wrapper, None),
MicroAgent("initial_prompt", "purpose2", "api_key", self.agent_creator, self.openai_wrapper, None),
MicroAgent("initial_prompt", "purpose3", "api_key", self.agent_creator, self.openai_wrapper, None)]
self.agent_similarity = AgentSimilarity(self.mock_openai_wrapper, self.agents)
def test_find_closest_agent(self):
self.mock_openai_wrapper.get_embedding.side_effect = [
{'data': [{'embedding': [0.1, 0.2, 0.3]}]},
{'data': [{'embedding': [0.4, 0.5, 0.6]}]},
{'data': [{'embedding': [0.7, 0.8, 0.9]}]}
]
test_purpose_embedding = np.array([0.4, 0.5, 0.6])
closest_agent, similarity = self.agent_similarity.find_closest_agent(test_purpose_embedding)
self.assertIsNotNone(closest_agent)
self.assertAlmostEqual(similarity, 1.0) # Assuming cosine similarity is 1.0 for identical vectors
if __name__ == '__main__':
unittest.main()