diff --git a/.gitignore b/.gitignore index ffb45107..278fc7b8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ __pycache__/ .grit swarm-worker-01_state.json error.txt - +Devin Worker 2_state.json # C extensions *.so .ruff_cache diff --git a/playground/swarms/groupchat_example.py b/playground/swarms/groupchat_example.py new file mode 100644 index 00000000..ed49167e --- /dev/null +++ b/playground/swarms/groupchat_example.py @@ -0,0 +1,141 @@ +import subprocess + + +from swarms import ( + Agent, + Anthropic, + GroupChat, + GroupChatManager, + tool, +) + +# Model +llm = Anthropic( + temperature=0.1, +) + + +# Tools +@tool +def terminal( + code: str, +): + """ + Run code in the terminal. + + Args: + code (str): The code to run in the terminal. + + Returns: + str: The output of the code. + """ + out = subprocess.run( + code, shell=True, capture_output=True, text=True + ).stdout + return str(out) + + +@tool +def browser(query: str): + """ + Search the query in the browser with the `browser` tool. + + Args: + query (str): The query to search in the browser. + + Returns: + str: The search results. + """ + import webbrowser + + url = f"https://www.google.com/search?q={query}" + webbrowser.open(url) + return f"Searching for {query} in the browser." + + +@tool +def create_file(file_path: str, content: str): + """ + Create a file using the file editor tool. + + Args: + file_path (str): The path to the file. + content (str): The content to write to the file. + + Returns: + str: The result of the file creation operation. + """ + with open(file_path, "w") as file: + file.write(content) + return f"File {file_path} created successfully." + + +@tool +def file_editor(file_path: str, mode: str, content: str): + """ + Edit a file using the file editor tool. + + Args: + file_path (str): The path to the file. + mode (str): The mode to open the file in. + content (str): The content to write to the file. + + Returns: + str: The result of the file editing operation. + """ + with open(file_path, mode) as file: + file.write(content) + return f"File {file_path} edited successfully." + + +# Agent +agent = Agent( + agent_name="Devin", + system_prompt=( + "Autonomous agent that can interact with humans and other" + " agents. Be Helpful and Kind. Use the tools provided to" + " assist the user. Return all code in markdown format." + ), + llm=llm, + max_loops=1, + autosave=False, + dashboard=False, + streaming_on=True, + verbose=True, + stopping_token="", + tools=[terminal, browser, file_editor, create_file], +) + +# Agent +agent_two = Agent( + agent_name="Devin Worker 2", + system_prompt=( + "Autonomous agent that can interact with humans and other" + " agents. Be Helpful and Kind. Use the tools provided to" + " assist the user. Return all code in markdown format." + ), + llm=llm, + max_loops=1, + autosave=False, + dashboard=False, + streaming_on=True, + verbose=True, + stopping_token="", + tools=[terminal, browser, file_editor, create_file], +) + + +# Initialize the group chat +group_chat = GroupChat( + agents=[agent, agent_two], + max_round=2, + admin_name="Supreme Commander Kye", + group_objective="Research everyone at Goldman Sachs", +) + +# Initialize the group chat manager +manager = GroupChatManager(groupchat=group_chat, selector=agent) + +# Run the group chat manager on a task +out = manager("Generate a 10,000 word blog on health and wellness.") +print(out) diff --git a/Dockerfile b/scripts/docker_files/Dockerfile similarity index 100% rename from Dockerfile rename to scripts/docker_files/Dockerfile diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index 30657f02..2f4d4a50 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -11,7 +11,6 @@ from swarms.structs.base_workflow import BaseWorkflow from swarms.structs.block_wrapper import block from swarms.structs.concurrent_workflow import ConcurrentWorkflow from swarms.structs.conversation import Conversation -from swarms.structs.graph_workflow import GraphWorkflow from swarms.structs.groupchat import GroupChat, GroupChatManager from swarms.structs.majority_voting import ( MajorityVoting, @@ -99,7 +98,6 @@ __all__ = [ "block", "ConcurrentWorkflow", "Conversation", - "GraphWorkflow", "GroupChat", "GroupChatManager", "MajorityVoting", diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 928d3619..31ce5ce8 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -413,6 +413,12 @@ class Agent: role=self.user_name, content=tool_schema_str ) + # Name + self.name = agent_name + + # Description + self.description = agent_description + def set_system_prompt(self, system_prompt: str): """Set the system prompt""" self.system_prompt = system_prompt diff --git a/swarms/structs/conversation.py b/swarms/structs/conversation.py index d0a1b2d0..ffe4b0d7 100644 --- a/swarms/structs/conversation.py +++ b/swarms/structs/conversation.py @@ -411,3 +411,6 @@ class Conversation(BaseStructure): break self.conversation_history = truncated_history + + def clear(self): + self.conversation_history = [] diff --git a/swarms/structs/graph_workflow.py b/swarms/structs/graph_workflow.py deleted file mode 100644 index 8e1320d0..00000000 --- a/swarms/structs/graph_workflow.py +++ /dev/null @@ -1,173 +0,0 @@ -import logging - -from swarms.structs.base_structure import BaseStructure - - -class GraphWorkflow(BaseStructure): - """ - Represents a graph-based workflow structure. - - Attributes: - graph (dict): A dictionary representing the nodes and edges of the graph. - entry_point (str): The name of the entry point node in the graph. - - Methods: - add(node, node_value): Adds a node to the graph with the specified value. - start(node_name): Sets the starting node for the workflow. - connect(from_node, to_node): Connects two nodes in the graph. - set_entry_point(node_name): Sets the entry point node for the workflow. - add_edge(from_node, to_node): Adds an edge between two nodes in the graph. - add_conditional_edges(from_node, condition, edge_dict): Adds conditional edges from a node to multiple nodes based on a condition. - run(): Runs the workflow and returns the graph. - - Examples: - >>> from swarms.structs import GraphWorkflow - >>> graph = GraphWorkflow() - >>> graph.add("start", "Start") - >>> graph.add("end", "End") - >>> graph.start("start") - """ - - def __init__(self): - self.graph = {} - self.entry_point = None - - def add(self, node, node_value): - """ - Adds a node to the graph with the specified value. - - Args: - node (str): The name of the node. - node_value (str): The value of the node. - - Returns: - None - """ - self.graph[node] = {"value": node_value, "edges": {}} - logging.info(f"Added node: {node}") - - def start(self, node_name): - """ - Sets the starting node for the workflow. - - Args: - node_name (str): The name of the starting node. - - Returns: - None - """ - self._check_node_exists(node_name) - - def connect(self, from_node, to_node): - """ - Connects two nodes in the graph. - - Args: - from_node (str): The name of the source node. - to_node (str): The name of the target node. - - Returns: - None - """ - self._check_node_exists(from_node, to_node) - - def set_entry_point(self, node_name): - """ - Sets the entry point node for the workflow. - - Args: - node_name (str): The name of the entry point node. - - Returns: - None - - Raises: - ValueError: If the specified node does not exist in the graph. - """ - if node_name is self.graph: - self.entry_point = node_name - else: - raise ValueError("Node does not exist in graph") - - def add_edge(self, from_node, to_node): - """ - Adds an edge between two nodes in the graph. - - Args: - from_node (str): The name of the source node. - to_node (str): The name of the target node. - - Returns: - None - - Raises: - ValueError: If either the source or target node does not exist in the graph. - """ - if from_node in self.graph and to_node in self.graph: - self.graph[from_node]["edges"][to_node] = "edge" - else: - raise ValueError("Node does not exist in graph") - - def add_conditional_edges(self, from_node, condition, edge_dict): - """ - Adds conditional edges from a node to multiple nodes based on a condition. - - Args: - from_node (str): The name of the source node. - condition: The condition for the conditional edges. - edge_dict (dict): A dictionary mapping condition values to target nodes. - - Returns: - None - - Raises: - ValueError: If the source node or any of the target nodes do not exist in the graph. - """ - if from_node in self.graph: - for condition_value, to_node in edge_dict.items(): - if to_node in self.graph: - self.graph[from_node]["edges"][to_node] = condition - else: - raise ValueError("Node does not exist in graph") - else: - raise ValueError(f"Node {from_node} does not exist in graph") - - def run(self): - """ - Runs the workflow and returns the graph. - - Returns: - dict: The graph representing the nodes and edges. - - Raises: - ValueError: If the entry point is not set. - """ - if self.entry_point is None: - raise ValueError("Entry point not set") - return self.graph - - def _check_node_exists(self, node_name): - """Checks if a node exists in the graph. - - Args: - node_name (_type_): _description_ - - Raises: - ValueError: _description_ - """ - if node_name not in self.graph: - raise ValueError(f"Node {node_name} does not exist in graph") - - def _check_nodes_exist(self, from_node, to_node): - """ - Checks if the given from_node and to_node exist in the graph. - - Args: - from_node: The starting node of the edge. - to_node: The ending node of the edge. - - Raises: - NodeNotFoundError: If either from_node or to_node does not exist in the graph. - """ - self._check_node_exists(from_node) - self._check_node_exists(to_node) diff --git a/swarms/structs/groupchat.py b/swarms/structs/groupchat.py index 9db97604..501e15db 100644 --- a/swarms/structs/groupchat.py +++ b/swarms/structs/groupchat.py @@ -1,12 +1,9 @@ -import logging -from dataclasses import dataclass -from typing import Dict, List - - +from dataclasses import dataclass, field +from typing import List +from swarms.structs.conversation import Conversation +from swarms.utils.loguru_logger import logger from swarms.structs.agent import Agent -logger = logging.getLogger(__name__) - @dataclass class GroupChat: @@ -26,24 +23,32 @@ class GroupChat: """ - agents: List[Agent] - messages: List[Dict] + agents: List[Agent] = field(default_factory=list) max_round: int = 10 admin_name: str = "Admin" # the name of the admin agent + group_objective: str = field(default_factory=str) + + def __post_init__(self): + self.messages = Conversation( + system_prompt=self.group_objective, + time_enabled=True, + user=self.admin_name, + ) @property def agent_names(self) -> List[str]: """Return the names of the agents in the group chat.""" - return [agent.name for agent in self.agents] + return [agent.agent_name for agent in self.agents] def reset(self): """Reset the group chat.""" + logger.info("Resetting Groupchat") self.messages.clear() def agent_by_name(self, name: str) -> Agent: """Find an agent whose name is contained within the given 'name' string.""" for agent in self.agents: - if agent.name in name: + if agent.agent_name in name: return agent raise ValueError( f"No agent found with a name contained in '{name}'." @@ -52,7 +57,8 @@ class GroupChat: def next_agent(self, agent: Agent) -> Agent: """Return the next agent in the list.""" return self.agents[ - (self.agent_names.index(agent.name) + 1) % len(self.agents) + (self.agent_names.index(agent.agent_name) + 1) + % len(self.agents) ] def select_speaker_msg(self): @@ -65,9 +71,11 @@ class GroupChat: Then select the next role from {self.agent_names} to play. Only return the role. """ + # @try_except_wrapper def select_speaker(self, last_speaker: Agent, selector: Agent): """Select the next speaker.""" - selector.update_system_message(self.select_speaker_msg()) + logger.info("Selecting a New Speaker") + selector.system_prompt = self.select_speaker_msg() # Warn if GroupChat is underpopulated, without established changing behavior n_agents = len(self.agent_names) @@ -77,24 +85,16 @@ class GroupChat: " Direct communication would be more efficient." ) - name = selector.generate_reply( - self.format_history( - self.messages - + [ - { - "role": "system", - "content": ( - "Read the above conversation. Then" - " select the next most suitable role" - f" from {self.agent_names} to play. Only" - " return the role." - ), - } - ] - ) + self.messages.add( + role=self.admin_name, + content=f"Read the above conversation. Then select the next most suitable role from {self.agent_names} to play. Only return the role.", ) + + name = selector.run(self.messages.return_history_as_string()) try: - return self.agent_by_name(name["content"]) + name = self.agent_by_name(name) + print(name) + return name except ValueError: return self.next_agent(last_speaker) @@ -106,26 +106,11 @@ class GroupChat: """ return "\n".join( [ - f"{agent.name}: {agent.system_message}" + f"{agent.agent_name}: {agent.system_prompt}" for agent in self.agents ] ) - def format_history(self, messages: List[Dict]) -> str: - """Format the history of the messages. - - Args: - messages (List[Dict]): _description_ - - Returns: - str: _description_ - """ - formatted_messages = [] - for message in messages: - formatted_message = f"'{message['role']}:{message['content']}" - formatted_messages.append(formatted_message) - return "\n".join(formatted_messages) - @dataclass class GroupChatManager: @@ -147,6 +132,7 @@ class GroupChatManager: groupchat: GroupChat selector: Agent + # @try_except_wrapper def __call__(self, task: str): """Call 'GroupChatManager' instance as a function. @@ -156,17 +142,20 @@ class GroupChatManager: Returns: _type_: _description_ """ - self.groupchat.messages.append( - {"role": self.selector.name, "content": task} + logger.info( + f"Activating Groupchat with {len(self.groupchat.agents)} Agents" ) + + self.groupchat.messages.add(self.selector.agent_name, task) + for i in range(self.groupchat.max_round): speaker = self.groupchat.select_speaker( last_speaker=self.selector, selector=self.selector ) - reply = speaker.generate_reply( - self.groupchat.format_history(self.groupchat.messages) + reply = speaker.run( + self.groupchat.messages.return_history_as_string() ) - self.groupchat.messages.append(reply) + self.groupchat.messages.add(speaker.agent_name, reply) print(reply) if i == self.groupchat.max_round - 1: break diff --git a/swarms/tools/exec_tool.py b/swarms/tools/exec_tool.py index 5a351a94..53a7d562 100644 --- a/swarms/tools/exec_tool.py +++ b/swarms/tools/exec_tool.py @@ -98,7 +98,7 @@ def execute_tool_by_name( action = output_parser.parse(text) tools = {t.name: t for t in tools} - logger.info(f"Tools available: {tools}") + # logger.info(f"Tools available: {tools}") if action.name == stop_token: return action.args["response"]