Skip to content

Commit

Permalink
Improved genesis agency cli, added tools_folder parameter to Agent cl…
Browse files Browse the repository at this point in the history
…ass, addressed VRSEN#71
  • Loading branch information
VRSEN committed Feb 6, 2024
1 parent 219d465 commit 1158906
Show file tree
Hide file tree
Showing 60 changed files with 442 additions and 402 deletions.
39 changes: 19 additions & 20 deletions agency_swarm/agency/agency.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,27 @@ def run_demo(self):
"""
while True:
console.rule()
text = input("USER: ")
text = input("👤 USER: ")

if text.lower() == "exit":
break

recipient_agent = None
if "@" in text:
recipient_agent = text.split("@")[1].split(" ")[0]
text = text.replace(f"@{recipient_agent}", "").strip()
try:
recipient_agent = self.get_agent_by_name(recipient_agent)
except Exception as e:
print(e)
continue

try:
gen = self.main_thread.get_completion(message=text)
gen = self.main_thread.get_completion(message=text, recipient_agent=recipient_agent)
while True:
message = next(gen)
if message.sender_name.lower() == "user":
continue
message.cprint()
except StopIteration as e:
pass
Expand Down Expand Up @@ -507,23 +522,15 @@ class SendMessage(BaseTool):
message_files: List[str] = Field(default=None,
description="A list of file ids to be sent as attachments to this message. Only use this if you have the file id that starts with 'file-'.",
examples=["file-1234", "file-5678"])
caller_agent_name: str = Field(default=agent.name,
description="The agent calling this tool. Defaults to your name. Do not change it.")

@field_validator('recipient')
def check_recipient(cls, value):
if value.value not in recipient_names:
raise ValueError(f"Recipient {value} is not valid. Valid recipients are: {recipient_names}")
return value

@field_validator('caller_agent_name')
def check_caller_agent_name(cls, value):
if value != agent.name:
raise ValueError(f"Caller agent name must be {agent.name}.")
return value

def run(self):
thread = outer_self.agents_and_threads[self.caller_agent_name][self.recipient.value]
thread = outer_self.agents_and_threads[self.caller_agent.name][self.recipient.value]

if not outer_self.async_mode:
gen = thread.get_completion(message=self.message, message_files=self.message_files)
Expand Down Expand Up @@ -558,23 +565,15 @@ class GetResponse(BaseTool):
"""This tool allows you to check the status of a task or get a response from a specified recipient agent, if the task has been completed. You must always use 'SendMessage' tool with the designated agent first."""
recipient: recipients = Field(...,
description=f"Recipient agent that you want to check the status of. Valid recipients are: {recipient_names}")
caller_agent_name: str = Field(default=agent.name,
description="The agent calling this tool. Defaults to your name. Do not change it.")

@field_validator('recipient')
def check_recipient(cls, value):
if value.value not in recipient_names:
raise ValueError(f"Recipient {value} is not valid. Valid recipients are: {recipient_names}")
return value

@field_validator('caller_agent_name')
def check_caller_agent_name(cls, value):
if value != agent.name:
raise ValueError(f"Caller agent name must be {agent.name}.")
return value

def run(self):
thread = outer_self.agents_and_threads[self.caller_agent_name][self.recipient.value]
thread = outer_self.agents_and_threads[self.caller_agent.name][self.recipient.value]

return thread.check_status()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from agency_swarm import Agent
from agency_swarm.tools.coding import ReadFile
from agency_swarm.tools.genesis import CreateAgentTemplate, ImportAgent, GetAvailableAgents
from .tools.CreateAgentTemplate import CreateAgentTemplate
# from .tools.GetAvailableAgents import GetAvailableAgents
# from .tools.ImportAgent import ImportAgent
from .tools.ReadManifesto import ReadManifesto


class AgentCreator(Agent):
Expand All @@ -12,9 +14,10 @@ def __init__(self, **kwargs):

# Add required tools
kwargs['tools'].extend([CreateAgentTemplate,
GetAvailableAgents,
ReadFile,
ImportAgent])
# GetAvailableAgents,
ReadManifesto,
# ImportAgent
])

# Set instructions
if 'instructions' not in kwargs:
Expand Down
11 changes: 11 additions & 0 deletions agency_swarm/agency/genesis/AgentCreator/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# AgentCreator Agent Instructions

You are an agent that creates other agents as instructed by the user.

The user will communicate to you each agent that needs to be created. Below are your instructions that need to be followed for each agent.

**Primary Instructions:**
1. First, read the manifesto using `ReadManifesto` tool if you have not already done so. This file contains the agency manifesto that describes the agency's purpose and goals.
2. Think if the agent you are creating needs to utilize any APIs. If it does, tell the OpenAPICreator agent to create API schemas for this agent. Make sure to also communicate the agent description, name and a summary of the processes that it needs to perform. CEO agents do not need to perform any API calls or use any tools, so you can skip to step 7.
3. For agents that do not need to utilize any APIs to perform their roles, tell the ToolCreator agent to create tools for this agent. Make sure to also communicate the agent description, name and a summary of the processes that it needs to perform.
4. If there are no issues and tools or APIs have been created, notify the user that the agent has been created. Otherwise, try to resolve any issues with other agents before reporting back.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import shutil
from typing import List

from pydantic import Field, model_validator, field_validator
Expand Down Expand Up @@ -28,6 +29,14 @@ class CreateAgentTemplate(BaseTool):
)

def run(self):
self.shared_state.set("agent_name", self.agent_name)

os.chdir(self.shared_state.get("agency_path"))

# remove folder if it already exists
if os.path.exists(self.agent_name):
shutil.rmtree(self.agent_name)

create_agent_template(self.agent_name,
self.agent_description,
instructions=self.instructions,
Expand All @@ -41,21 +50,15 @@ def run(self):
with open("agency.py", "w") as f:
f.writelines(lines)

return f"Agent template has been created in {self.agent_name} folder."
os.chdir(self.shared_state.get("default_folder"))

if "ceo" in self.agent_name.lower():
return f"You can tell the user that the process of creating {self.agent_name} has been completed, because CEO agent does not need to utilizie any tools or APIs."

return f"Agent template has been created for {self.agent_name}. Please now tell ToolCreator to create tools for this agent or OpenAPICreator to create API schemas, if this agent needs to utilize any tools or APIs. If this is unclear, please ask the user for more information."

@model_validator(mode="after")
def validate_tools(self):
for tool in self.default_tools:
if tool not in self.allowed_tools:
raise ValueError(f"Tool {tool} is not allowed. Allowed tools are: {self.allowed_tools}")

@field_validator("agent_name", mode='after')
@classmethod
def agent_name_exists(cls, v):
if " " in v:
raise ValueError("Agent name cannot contain spaces.")
if not v.isalnum():
raise ValueError("Agent name cannot contain special characters.")
if os.path.exists("./" + v):
raise ValueError(f"Agent with name {v} already exists.")
return v
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os.path

from agency_swarm import BaseTool
from agency_swarm.tools.genesis.util import get_modules
from .util import get_modules
import importlib

class GetAvailableAgents(BaseTool):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import Field, field_validator

from agency_swarm import BaseTool
from agency_swarm.tools.genesis.util import get_modules
from .util import get_modules
from agency_swarm.util import create_agent_template

agent_paths = get_modules('agency_swarm.agents')
Expand All @@ -20,6 +20,8 @@ class ImportAgent(BaseTool):
description="Name of the agent to be imported.")

def run(self):
os.chdir(self.shared_state.get("agency_path"))

# find item in available_agents dict by value
import_path = [item for item in agent_paths if self.agent_name in item][0]

Expand All @@ -37,6 +39,8 @@ def run(self):
f.write(f"\nfrom {import_path} import {self.agent_name}\n")
f.write(f"{instance_name} = {self.agent_name}()")

os.chdir(self.shared_state.get("default_folder"))

return "Success. Agent has been imported. You can now use it in your agency."

@field_validator("agent_name", mode='after')
Expand Down
18 changes: 18 additions & 0 deletions agency_swarm/agency/genesis/AgentCreator/tools/ReadManifesto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os

from agency_swarm import BaseTool


class ReadManifesto(BaseTool):
"""
This tool reads a manifesto for the agency being created from a markdown file.
"""

def run(self):
os.chdir(self.shared_state.get("agency_path"))
with open("agency_manifesto.md", "r") as f:
manifesto = f.read()

os.chdir(self.shared_state.get("default_folder"))

return manifesto
File renamed without changes.
25 changes: 12 additions & 13 deletions agency_swarm/agency/genesis/GenesisAgency.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from agency_swarm import Agency
from .AgentCreator import AgentCreator
from agency_swarm.agents.browsing import BrowsingAgent
from agency_swarm.agents.genesis import GenesisCEO, AgentCreator
import os
from .GenesisCEO import GenesisCEO
from .OpenAPICreator import OpenAPICreator
from .ToolCreator import ToolCreator

from agency_swarm.agents.genesis import ToolCreator
from agency_swarm.agents.genesis import OpenAPICreator
new_agency_path = None


class GenesisAgency(Agency):
Expand All @@ -19,20 +20,18 @@ def __init__(self, **kwargs):

browsing_agent.instructions += ("""\n
# BrowsingAgent's Primary instructions
1. Browse the web to find the most relevant API that the requested agent needs in order to perform its role. If you already have an idea of what API to use, search google directly for this API documentation.
2. After finding the right API to use, navigate to its documentation page. Prefer to do this by searching for the API documentation page in google, rather than navigating to the API's website and then finding the documentation page, if possible.
3. Ensure that the current page actually contains the necessary API endpoints descriptions with the AnalyzeContent tool. If you can't find a link to the documentation page, try to search for it in google.
4. If you have confirmed that the page contains the necessary API documentation, export the page with ExportFile tool and send the file_id back to the user along with a brief description of the API.
5. If not, continue browsing the web until you find the right API documentation page.
6. Repeat these steps for each new requested agent.
1. Browse the web to find the API documentation requested by the user. Prefer searching google directly for this API documentation page.
2. Navigate to the API documentation page and ensure that it contains the necessary API endpoints descriptions. You can use the AnalyzeContent tool to check if the page contains the necessary API descriptions. If not, try perfrom another search in google and keep browsing until you find the right page.
3. If you have confirmed that the page contains the necessary API documentation, export the page with ExportFile tool. Then, send the file_id back to the user along with a brief description of the API.
4. Repeat these steps for each new agent, as requested by the user.
""")


kwargs['agency_chart'] = [
genesis_ceo,
genesis_ceo, tool_creator, agent_creator,
[genesis_ceo, agent_creator],
[agent_creator, browsing_agent],
[agent_creator, openapi_creator],
[openapi_creator, browsing_agent],
[agent_creator, tool_creator],
]

if 'shared_instructions' not in kwargs:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from agency_swarm import Agent
from agency_swarm.tools.genesis import CreateAgencyFolder, FinalizeAgency
from .tools.CreateAgencyFolder import CreateAgencyFolder
from .tools.FinalizeAgency import FinalizeAgency


class GenesisCEO(Agent):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
2. Ask user about their goals for this agency, its mission and its processes, like what APIs would the agents need to utilize.
3. Propose an initial structure for the agency, including the roles of the agents and their communication flows. Focus on creating at most 2 agents, plus CEO, unless instructed otherwise by the user. Output the code snippet like below.
4. Upon confirmation use `CreateAgencyFolder` tool to create a folder for the agency. If any modifications are required please use this tool again with the same agency name and it will overwrite the existing folder.
5. Tell AgentCreator to create these agents one by one, starting with the CEO. Each agent should be sent in a separate message using the `SendMessage` tool. If one of the agents needs to utilize a specific API, as instructed by the user, please make sure to communicate this as well.
5. Tell AgentCreator to create these agents one by one, starting with the CEO. Each agent should be sent in a separate message using the `SendMessage` tool. Please make sure to communicate if this agent needs to utilize any APIs or tools to perform its role.
6. Once all agents are created, please use the `FinalizeAgency` tool, and tell the user that he can now navigate to the agency folder and start it with `python agency.py` command.


### Example of communication flows

Here is an example of how communication flows are defined in agency swarm. Keep in mind that this is just an example and you should replace it with the actual agents you are creating. Typically, no agents should be able to initiate communication with CEO, unless instructed otherwise by the user.
Here is an example of how communication flows are defined in agency swarm. Essentially, agents that are inside a double array can initiate communication with each other. Agents that are in the top level array can communicate with the user.

```python
agency = Agency([
ceo, # CEO will be the entry point for communication with the user
[ceo, dev], # CEO can initiate communication with Developer
[ceo, va], # CEO can initiate communication with Virtual Assistant
[dev, va] # Developer can initiate communication with Virtual Assistant
], shared_instructions='manifesto.md') # shared instructions for all agents
```
], shared_instructions='agency_manifesto.md') # shared instructions for all agents
```
Keep in mind that this is just an example and you should replace it with the actual agents you are creating.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import shutil
from pathlib import Path

from pydantic import Field, field_validator

import agency_swarm.agency.genesis.GenesisAgency
from agency_swarm import BaseTool

import os

current_agency_name = None

class CreateAgencyFolder(BaseTool):
"""
Expand All @@ -21,25 +22,28 @@ class CreateAgencyFolder(BaseTool):
examples=["[ceo, [ceo, dev], [ceo, va], [dev, va] ]"]
)
manifesto: str = Field(
..., description="Manifesto for the agency, describing it's goals and additional context shared by all agents "
"in markdown format."
..., description="Manifesto for the agency, describing its goals and additional context shared by all agents "
"in markdown format. It must include a brief description of each agent, its purpose and "
"whether it needs to utilize any tools or APIs.",
)

def run(self):
folder_path = "./" + self.agency_name + "/"

global current_agency_name
if current_agency_name is not None:
if os.getcwd().strip("/").strip("\\").endswith(current_agency_name):
os.chdir("..")
shutil.rmtree(current_agency_name)

current_agency_name = self.agency_name

# create folder
os.mkdir(folder_path)

os.chdir(folder_path)
if not self.shared_state.get("default_folder"):
self.shared_state.set('default_folder', Path.cwd())

if self.shared_state.get("agency_name") is None:
os.mkdir(self.agency_name)
os.chdir("./" + self.agency_name)
self.shared_state.set("agency_name", self.agency_name)
self.shared_state.set("agency_path", Path("./").resolve())
elif self.shared_state.get("agency_name") == self.agency_name and os.path.exists(self.shared_state.get("agency_path")):
os.chdir(self.shared_state.get("agency_path"))
for file in os.listdir():
if file != "__init__.py" and os.path.isfile(file):
os.remove(file)
else:
os.mkdir(self.shared_state.get("agency_path"))
os.chdir("./" + self.agency_name)

# check that agency chart is valid
if not self.agency_chart.startswith("[") or not self.agency_chart.endswith("]"):
Expand All @@ -66,23 +70,8 @@ def run(self):
with open(path, "w") as f:
f.write(self.manifesto)

return f"Agency folder has been created in {folder_path}."

@field_validator('agency_name', mode='after')
@classmethod
def check_agency_name(cls, v):
global current_agency_name

if os.path.exists("./" + v):
raise ValueError("Agency with this name already exists.")

if current_agency_name is not None:
if current_agency_name != v:
raise ValueError("You can only create 1 agency at a time. Please tell the user to restart the system if he wants to create a new agency or use the same agency_name to modify an exisiting agency.")

if not os.getcwd().strip("/").endswith(current_agency_name):
raise ValueError("Please tell the user to restart the system if he wants to create a new agency.")
os.chdir(self.shared_state.get('default_folder'))

return v
return f"Agency folder has been created. You can now tell AgentCreator to create agents for {self.agency_name}.\n"


Loading

0 comments on commit 1158906

Please sign in to comment.