From 96cdde7ff5436eeb126004e339ee1369748c3e91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:34:01 +0000 Subject: [PATCH 01/19] Bump autofix-ci/action from 1.2 to 1.3 Bumps [autofix-ci/action](https://github.com/autofix-ci/action) from 1.2 to 1.3. - [Release notes](https://github.com/autofix-ci/action/releases) - [Commits](https://github.com/autofix-ci/action/compare/d3e591514b99d0fca6779455ff8338516663f7cc...dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a) --- updated-dependencies: - dependency-name: autofix-ci/action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/autofix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index d5e2c3fa..21129735 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -22,4 +22,4 @@ jobs: - run: ruff format . - run: ruff check --fix . - - uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc + - uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a From 12f213f9e737200b2f35ed237b0832de5982e14e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:34:23 +0000 Subject: [PATCH 02/19] Update ruff requirement from >=0.0.249,<0.4.10 to >=0.5.0,<0.5.1 Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.249...0.5.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 04d5eaf7..1af49197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ networkx = "*" [tool.poetry.group.lint.dependencies] black = ">=23.1,<25.0" -ruff = ">=0.0.249,<0.4.10" +ruff = ">=0.5.0,<0.5.1" types-toml = "^0.10.8.1" types-pytz = ">=2023.3,<2025.0" types-chardet = "^5.0.4.6" From df38840937114f604166b9328a9ad00a753bfaf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:34:43 +0000 Subject: [PATCH 03/19] Bump tenacity from 8.3.0 to 8.4.2 Bumps [tenacity](https://github.com/jd/tenacity) from 8.3.0 to 8.4.2. - [Release notes](https://github.com/jd/tenacity/releases) - [Commits](https://github.com/jd/tenacity/compare/8.3.0...8.4.2) --- updated-dependencies: - dependency-name: tenacity dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 04d5eaf7..d0b011de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ pypdf = "4.1.0" ratelimit = "2.2.1" loguru = "0.7.2" pydantic = "2.7.4" -tenacity = "8.3.0" +tenacity = "8.4.2" Pillow = "10.3.0" psutil = "*" sentry-sdk = "*" From 8e6c246e708d0a562945d0431f06276f51022559 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Thu, 4 Jul 2024 18:38:55 -0700 Subject: [PATCH 04/19] [READM]E --- docs/swarms/structs/agent_registry.md | 84 +++++++++ example.py | 11 -- playground/structs/agent_registry.py | 79 ++++++++ swarms/structs/Untitled-1.py | 250 ++++++++++++++++++++++++++ 4 files changed, 413 insertions(+), 11 deletions(-) create mode 100644 playground/structs/agent_registry.py create mode 100644 swarms/structs/Untitled-1.py diff --git a/docs/swarms/structs/agent_registry.md b/docs/swarms/structs/agent_registry.md index be267b4f..82afc1f1 100644 --- a/docs/swarms/structs/agent_registry.md +++ b/docs/swarms/structs/agent_registry.md @@ -143,6 +143,90 @@ Finds an agent by its name. agent = registry.find_agent_by_name("Agent1") ``` + +### Full Example + +```python +from swarms.structs.agent_registry import AgentRegistry +from swarms import Agent, OpenAIChat, Anthropic + +# Initialize the agents +growth_agent1 = Agent( + agent_name="Marketing Specialist", + system_prompt="You're the marketing specialist, your purpose is to help companies grow by improving their marketing strategies!", + agent_description="Improve a company's marketing strategies!", + llm=OpenAIChat(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="marketing_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent2 = Agent( + agent_name="Sales Specialist", + system_prompt="You're the sales specialist, your purpose is to help companies grow by improving their sales strategies!", + agent_description="Improve a company's sales strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="sales_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent3 = Agent( + agent_name="Product Development Specialist", + system_prompt="You're the product development specialist, your purpose is to help companies grow by improving their product development strategies!", + agent_description="Improve a company's product development strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="product_development_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent4 = Agent( + agent_name="Customer Service Specialist", + system_prompt="You're the customer service specialist, your purpose is to help companies grow by improving their customer service strategies!", + agent_description="Improve a company's customer service strategies!", + llm=OpenAIChat(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="customer_service_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + + +# Register the agents\ +registry = AgentRegistry() + +# Register the agents +registry.add("Marketing Specialist", growth_agent1) +registry.add("Sales Specialist", growth_agent2) +registry.add("Product Development Specialist", growth_agent3) +registry.add("Customer Service Specialist", growth_agent4) + +``` + ## Logging and Error Handling Each method in the `AgentRegistry` class includes logging to track the execution flow and captures errors to provide detailed information in case of failures. This is crucial for debugging and ensuring smooth operation of the registry. The `report_error` function is used for reporting exceptions that occur during method execution. diff --git a/example.py b/example.py index c8319346..737e4eb4 100644 --- a/example.py +++ b/example.py @@ -30,16 +30,6 @@ def generate_report(company_name: str, profit: float): return f"The profit for {company_name} is ${profit}." -EMAIL_DETECT_APPOINT = """ - -if the user gives you an email address, then call the appointment function to schedule a meeting with the user. - -SCHEMA OF THE FUNCTION: - - -""" - - def write_memory_to_rag(memory_name: str, memory: str): """ Writes the memory to the RAG model for fine-tuning. @@ -66,7 +56,6 @@ agent = Agent( llm=Anthropic(), max_loops="auto", autosave=True, - sop_list=[EMAIL_DETECT_APPOINT], # dynamic_temperature_enabled=True, dashboard=False, verbose=True, diff --git a/playground/structs/agent_registry.py b/playground/structs/agent_registry.py new file mode 100644 index 00000000..79541665 --- /dev/null +++ b/playground/structs/agent_registry.py @@ -0,0 +1,79 @@ +from swarms.structs.agent_registry import AgentRegistry +from swarms import Agent +from swarms.models import Anthropic + + +# Initialize the agents +growth_agent1 = Agent( + agent_name="Marketing Specialist", + system_prompt="You're the marketing specialist, your purpose is to help companies grow by improving their marketing strategies!", + agent_description="Improve a company's marketing strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="marketing_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent2 = Agent( + agent_name="Sales Specialist", + system_prompt="You're the sales specialist, your purpose is to help companies grow by improving their sales strategies!", + agent_description="Improve a company's sales strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="sales_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent3 = Agent( + agent_name="Product Development Specialist", + system_prompt="You're the product development specialist, your purpose is to help companies grow by improving their product development strategies!", + agent_description="Improve a company's product development strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="product_development_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + +growth_agent4 = Agent( + agent_name="Customer Service Specialist", + system_prompt="You're the customer service specialist, your purpose is to help companies grow by improving their customer service strategies!", + agent_description="Improve a company's customer service strategies!", + llm=Anthropic(), + max_loops="auto", + autosave=True, + dashboard=False, + verbose=True, + streaming_on=True, + saved_state_path="customer_service_specialist.json", + stopping_token="Stop!", + interactive=True, + context_length=1000, +) + + +# Register the agents\ +registry = AgentRegistry() + +# Register the agents +registry.add("Marketing Specialist", growth_agent1) +registry.add("Sales Specialist", growth_agent2) +registry.add("Product Development Specialist", growth_agent3) +registry.add("Customer Service Specialist", growth_agent4) diff --git a/swarms/structs/Untitled-1.py b/swarms/structs/Untitled-1.py new file mode 100644 index 00000000..903e034f --- /dev/null +++ b/swarms/structs/Untitled-1.py @@ -0,0 +1,250 @@ +# ! pip install ai21 +# ! pip install swarms +import os +from typing import List + +from ai21 import AI21Client +from ai21.models.chat import ChatMessage +from dotenv import load_dotenv + +from swarms import Agent, BaseLLM +from swarms.utils.loguru_logger import logger + +load_dotenv() + + +class Jamba(BaseLLM): + def __init__( + self, + api_key: str = os.getenv("AI21_API_KEY"), + temperature: int = 0.8, + max_tokens: int = 200, + ): + """ + Initializes the Jamba class with the provided API key. + + Args: + api_key (str): The API key for the AI21Client. + """ + os.environ["AI21_API_KEY"] = api_key + self.api_key = api_key + self.temperature = temperature + self.max_tokens = max_tokens + self.client = AI21Client() + + def run(self, prompt: str, *args, **kwargs) -> str: + """ + Generates a response for the given prompt using the AI21 model. + + Args: + prompt (str): The prompt for generating the response. + + Returns: + str: The generated response. + + Raises: + Exception: If there is an issue with the API request. + """ + try: + response = self.client.chat.completions.create( + model="jamba-instruct-preview", # Latest model + messages=[ChatMessage(role="user", content=prompt)], + temperature=self.temperature, + max_tokens=self.max_tokens, + *args, + **kwargs, + ) + return response.choices[0].message.content + except Exception as e: + print(f"Error: {e}") + raise + + +model = Jamba( + max_tokens=4000, +) + + +BOSS_PLANNER = """ +You're the swarm orchestrator agent + +**Objective:** Your task is to intake a business problem or activity and create a swarm of specialized LLM agents that can efficiently solve or automate the given problem. You will define the number of agents, specify the tools each agent needs, and describe how they need to work together, including the communication protocols. + +**Instructions:** + +1. **Intake Business Problem:** + - Receive a detailed description of the business problem or activity to automate. + - Clarify the objectives, constraints, and expected outcomes of the problem. + - Identify key components and sub-tasks within the problem. + +2. **Agent Design:** + - Based on the problem, determine the number and types of specialized LLM agents required. + - For each agent, specify: + - The specific task or role it will perform. + - The tools and resources it needs to perform its task. + - Any prerequisite knowledge or data it must have access to. + - Ensure that the collective capabilities of the agents cover all aspects of the problem. + +3. **Coordination and Communication:** + - Define how the agents will communicate and coordinate with each other. + - Choose the type of communication (e.g., synchronous, asynchronous, broadcast, direct messaging). + - Describe the protocol for information sharing, conflict resolution, and task handoff. + +4. **Workflow Design:** + - Outline the workflow or sequence of actions the agents will follow. + - Define the input and output for each agent. + - Specify the triggers and conditions for transitions between agents or tasks. + - Ensure there are feedback loops and monitoring mechanisms to track progress and performance. + +5. **Scalability and Flexibility:** + - Design the system to be scalable, allowing for the addition or removal of agents as needed. + - Ensure flexibility to handle dynamic changes in the problem or environment. + +6. **Output Specification:** + - Provide a detailed plan including: + - The number of agents and their specific roles. + - The tools and resources each agent will use. + - The communication and coordination strategy. + - The workflow and sequence of actions. + - Include a diagram or flowchart if necessary to visualize the system. + +**Example Structure:** + +**Business Problem:** Automate customer support for an e-commerce platform. + +**Agents and Roles:** +1. **Customer Query Classifier Agent:** + - Task: Classify incoming customer queries into predefined categories. + - Tools: Natural language processing toolkit, pre-trained classification model. + - Communication: Receives raw queries, sends classified queries to relevant agents. + +2. **Order Status Agent:** + - Task: Provide order status updates to customers. + - Tools: Access to order database, query processing toolkit. + - Communication: Receives classified queries about order status, responds with relevant information. + +3. **Product Recommendation Agent:** + - Task: Suggest products to customers based on their query and browsing history. + - Tools: Recommendation engine, access to product database. + - Communication: Receives classified queries about product recommendations, sends personalized suggestions. + +4. **Technical Support Agent:** + - Task: Assist customers with technical issues. + - Tools: Access to technical support database, troubleshooting toolkit. + - Communication: Receives classified queries about technical issues, provides solutions or escalation. + +**Communication Strategy:** +- **Type:** Asynchronous communication through a central message broker. +- **Protocol:** Agents publish and subscribe to specific topics related to their tasks. +- **Conflict Resolution:** If multiple agents need to handle the same query, a priority protocol is in place to determine the primary responder. + +**Workflow:** +1. Customer Query Classifier Agent receives and classifies the query. +2. Classified query is routed to the appropriate specialized agent. +3. Specialized agent processes the query and sends a response. +4. If needed, the response triggers further actions from other agents. + +**Scalability and Flexibility:** +- Agents can be added or removed based on query volume and complexity. +- System adapts to changes in query types and business needs. + +**Output Plan:** +- Diagram illustrating agent roles and communication flow. +- Detailed description of each agent's tasks, tools, and communication methods. +- Workflow sequence from query intake to resolution. + + +""" + + +# Initialize the agent +planning_agent = Agent( + agent_name="Boss Director", + system_prompt=BOSS_PLANNER, + agent_description="Generates a spec of agents for the problem at hand.", + llm=model, + max_loops=1, + autosave=True, + dynamic_temperature_enabled=True, + dashboard=False, + verbose=True, + streaming_on=True, + # interactive=True, # Set to False to disable interactive mode + saved_state_path="boss_planner.json", + # tools=[calculate_profit, generate_report], + # docs_folder="docs", + # pdf_path="docs/accounting_agent.pdf", + # tools=[browser_automation], +) + + +# Name, system prompt, +def create_worker_agent(name: str, system_prompt: str) -> List[Agent]: + """ + Creates a worker agent with the specified name, system prompt, and description. + + Args: + name (List[str]): The name of the worker agent. + system_prompt List(str): The system prompt for the worker agent. + + Returns: + List[Agent]: A list of worker agents created based on the input. + """ + # return agents + name = Agent( + agent_name=name, + system_prompt=system_prompt, + llm=model, + max_loops=1, + autosave=True, + dynamic_temperature_enabled=True, + dashboard=False, + verbose=True, + streaming_on=True, + # interactive=True, # Set to False to disable interactive mode + saved_state_path=f"{name.lower().replace(' ', '_')}_agent.json", + # tools=[calculate_profit, generate_report], + # docs_folder="docs", + # pdf_path="docs/accounting_agent.pdf", + # tools=[browser_automation], + ) + + out = name.run(system_prompt) + return out + + +# Boss Agent creator +boss_agent_creator = Agent( + agent_name="Boss Agent Creator", + system_prompt="Create the worker agents for the problem at hand using the specified names and system prompt tools provided.", + agent_description="Generates a spec of agents for the problem at hand.", + llm=model, + max_loops=1, + autosave=True, + dynamic_temperature_enabled=True, + dashboard=False, + verbose=True, + streaming_on=True, + # interactive=True, # Set to False to disable interactive mode + saved_state_path="boss_director_agent.json", + # tools=[calculate_profit, generate_report], + # docs_folder="docs", + # pdf_path="docs/accounting_agent.pdf", + tools=[create_worker_agent], +) + + +def run_jamba_swarm(task: str = None): + logger.info(f"Making plan for the task: {task}") + out = planning_agent.run(task) + + memory = planning_agent.short_memory.return_history_as_string() + + # Boss agent + return boss_agent_creator.run(memory) + + +# Example usage +run_jamba_swarm( + "Create a swarm of agents for automating customer support for an e-commerce platform." +) From bbbbc9e3e3da62b4408507eaa29bda24dc3eefb7 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 7 Jul 2024 19:15:23 -0700 Subject: [PATCH 05/19] [CLEANUP] --- example.py | 22 +- json_log_cleanup.py | 2 +- .../agents.py | 12 - .../social_media_swarm_agents.py | 392 ++++++++++++++++++ swarms/structs/sequential_workflow.py | 2 +- 5 files changed, 395 insertions(+), 35 deletions(-) create mode 100644 playground/demos/social_media_content_generators_swarm/social_media_swarm_agents.py diff --git a/example.py b/example.py index 737e4eb4..00e58596 100644 --- a/example.py +++ b/example.py @@ -1,6 +1,4 @@ -from swarms import Agent -from langchain_community.llms.anthropic import Anthropic - +from swarms import Agent, Anthropic def calculate_profit(revenue: float, expenses: float): """ @@ -30,24 +28,6 @@ def generate_report(company_name: str, profit: float): return f"The profit for {company_name} is ${profit}." -def write_memory_to_rag(memory_name: str, memory: str): - """ - Writes the memory to the RAG model for fine-tuning. - - Args: - memory_name (str): The name of the memory. - memory (str): The memory to be written to the RAG model. - """ - # Write the memory to the RAG model for fine-tuning - from playground.memory.chromadb_example import ChromaDB - - db = ChromaDB(output_dir=memory_name) - - db.add(memory) - - return None - - # Initialize the agent agent = Agent( agent_name="Accounting Assistant", diff --git a/json_log_cleanup.py b/json_log_cleanup.py index c2704d81..d5f9f71b 100644 --- a/json_log_cleanup.py +++ b/json_log_cleanup.py @@ -58,4 +58,4 @@ def cleanup_json_logs(name: str = None): # Call the function -cleanup_json_logs("sequential_workflow_agents") +cleanup_json_logs("social_media_swarm") diff --git a/playground/demos/social_media_content_generators_swarm/agents.py b/playground/demos/social_media_content_generators_swarm/agents.py index c074044a..0ee20cff 100644 --- a/playground/demos/social_media_content_generators_swarm/agents.py +++ b/playground/demos/social_media_content_generators_swarm/agents.py @@ -12,18 +12,6 @@ Example: from swarms import Agent, OpenAIChat -# # Memory -# memory = ChromaDB( -# output_dir="social_media_marketing", -# docs_folder="docs", -# ) - -# Memory for instagram -# memory = ChromaDB( -# output_dir="social_media_marketing", -# docs_folder="docs", -# ) - llm = OpenAIChat(max_tokens=4000) diff --git a/playground/demos/social_media_content_generators_swarm/social_media_swarm_agents.py b/playground/demos/social_media_content_generators_swarm/social_media_swarm_agents.py new file mode 100644 index 00000000..ba18260d --- /dev/null +++ b/playground/demos/social_media_content_generators_swarm/social_media_swarm_agents.py @@ -0,0 +1,392 @@ +""" + +Problem: We're creating specialized agents for various social medias + +List of agents: +- Facebook agent +- Twitter agent +- Instagram agent +- LinkedIn agent +- TikTok agent +- Reddit agent +- Pinterest agent +- Snapchat agent +- YouTube agent +- WhatsApp agent + +""" + +from swarms import Agent, OpenAIChat, MixtureOfAgents +import os +import requests + + +# Model +model = OpenAIChat(max_tokens=4000, temperature=0.8) + +# Content Variables +facebook_content = "Here is the content for Facebook" +twitter_content = "Here is the content for Twitter" +instagram_content = "Here is the content for Instagram" +linkedin_content = "Here is the content for LinkedIn" +tiktok_content = "Here is the content for TikTok" +reddit_content = "Here is the content for Reddit" +pinterest_content = "Here is the content for Pinterest" +snapchat_content = "Here is the content for Snapchat" +youtube_content = "Here is the content for YouTube" +whatsapp_content = "Here is the content for WhatsApp" + +# Prompt Variables +facebook_prompt = f""" +You are a Facebook social media agent. Your task is to create a post that maximizes engagement on Facebook. Use rich media, personal stories, and interactive content. Ensure the post is compelling and includes a call-to-action. Here is the content to work with: {facebook_content} +""" + +twitter_prompt = f""" +You are a Twitter social media agent. Your task is to create a tweet that is short, concise, and uses trending hashtags. The tweet should be engaging and include relevant media such as images, GIFs, or short videos. Here is the content to work with: {twitter_content} +""" + +instagram_prompt = f""" +You are an Instagram social media agent. Your task is to create a visually appealing post that includes high-quality images and engaging captions. Consider using stories and reels to maximize reach. Here is the content to work with: {instagram_content} +""" + +linkedin_prompt = f""" +You are a LinkedIn social media agent. Your task is to create a professional and insightful post related to industry trends or personal achievements. The post should include relevant media such as articles, professional photos, or videos. Here is the content to work with: {linkedin_content} +""" + +tiktok_prompt = f""" +You are a TikTok social media agent. Your task is to create a short, entertaining video that aligns with trending challenges and music. The video should be engaging and encourage viewers to interact. Here is the content to work with: {tiktok_content} +""" + +reddit_prompt = f""" +You are a Reddit social media agent. Your task is to create an engaging post for relevant subreddits. The post should spark in-depth discussions and include relevant media such as images or links. Here is the content to work with: {reddit_content} +""" + +pinterest_prompt = f""" +You are a Pinterest social media agent. Your task is to create high-quality, visually appealing pins. Focus on popular categories such as DIY, fashion, and lifestyle. Here is the content to work with: {pinterest_content} +""" + +snapchat_prompt = f""" +You are a Snapchat social media agent. Your task is to create engaging and timely snaps and stories. Include personal touches and use filters or AR lenses to enhance the content. Here is the content to work with: {snapchat_content} +""" + +youtube_prompt = f""" +You are a YouTube social media agent. Your task is to create high-quality videos with engaging thumbnails. Ensure a consistent posting schedule and encourage viewer interaction. Here is the content to work with: {youtube_content} +""" + +whatsapp_prompt = f""" +You are a WhatsApp social media agent. Your task is to send personalized messages and updates. Use broadcast lists and ensure the messages are engaging and relevant. Here is the content to work with: {whatsapp_content} +""" + + +def post_to_twitter(content: str) -> None: + """ + Posts content to Twitter. + + Args: + content (str): The content to post on Twitter. + + Raises: + ValueError: If the content is empty or exceeds the character limit. + requests.exceptions.RequestException: If there is an error with the request. + """ + try: + if not content: + raise ValueError("Content cannot be empty.") + if len(content) > 280: + raise ValueError( + "Content exceeds Twitter's 280 character limit." + ) + + # Retrieve the access token from environment variables + access_token = os.getenv("TWITTER_ACCESS_TOKEN") + if not access_token: + raise EnvironmentError( + "Twitter access token not found in environment variables." + ) + + # Mock API endpoint for example purposes + api_url = "https://api.twitter.com/2/tweets" + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + } + data = {"text": content} + response = requests.post(api_url, headers=headers, json=data) + response.raise_for_status() + + print("Content posted to Twitter successfully.") + except ValueError as e: + print(f"Error: {e}") + raise + except requests.exceptions.RequestException as e: + print(f"Error: {e}") + raise + + +def post_to_instagram(content: str) -> None: + """ + Posts content to Instagram. + + Args: + content (str): The content to post on Instagram. + + Raises: + ValueError: If the content is empty or exceeds the character limit. + requests.exceptions.RequestException: If there is an error with the request. + """ + try: + if not content: + raise ValueError("Content cannot be empty.") + if len(content) > 2200: + raise ValueError( + "Content exceeds Instagram's 2200 character limit." + ) + + # Retrieve the access token from environment variables + access_token = os.getenv("INSTAGRAM_ACCESS_TOKEN") + user_id = os.getenv("INSTAGRAM_USER_ID") + if not access_token or not user_id: + raise EnvironmentError( + "Instagram access token or user ID not found in environment variables." + ) + + # Mock API endpoint for example purposes + api_url = f"https://graph.instagram.com/v10.0/{user_id}/media" + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + } + data = { + "caption": content, + "image_url": "URL_OF_THE_IMAGE_TO_POST", # Replace with actual image URL if needed + } + response = requests.post(api_url, headers=headers, json=data) + response.raise_for_status() + + print("Content posted to Instagram successfully.") + except ValueError as e: + print(f"Error: {e}") + raise + except requests.exceptions.RequestException as e: + print(f"Error: {e}") + raise + + +def post_to_facebook(content: str) -> None: + """ + Posts content to Facebook. + + Args: + content (str): The content to post on Facebook. + + Raises: + ValueError: If the content is empty. + requests.exceptions.RequestException: If there is an error with the request. + """ + try: + if not content: + raise ValueError("Content cannot be empty.") + + # Retrieve the access token from environment variables + access_token = os.getenv("FACEBOOK_ACCESS_TOKEN") + if not access_token: + raise EnvironmentError( + "Facebook access token not found in environment variables." + ) + + # Mock API endpoint for example purposes + api_url = "https://graph.facebook.com/v10.0/me/feed" + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + } + data = {"message": content} + response = requests.post(api_url, headers=headers, json=data) + response.raise_for_status() + + print("Content posted to Facebook successfully.") + except ValueError as e: + print(f"Error: {e}") + raise + except requests.exceptions.RequestException as e: + print(f"Error: {e}") + raise + + +# Prompts +prompts = [ + facebook_prompt, + twitter_prompt, + instagram_prompt, + linkedin_prompt, + tiktok_prompt, + reddit_prompt, + pinterest_prompt, + snapchat_prompt, + youtube_prompt, + whatsapp_prompt, +] + + +# For every prompt, we're going to create a list of agents +for prompt in prompts: + agents = [ + Agent( + agent_name="Facebook Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="facebook_agent.json", + ), + Agent( + agent_name="Twitter Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + tools=[post_to_twitter], + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="twitter_agent.json", + ), + Agent( + agent_name="Instagram Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + tools=[post_to_instagram], + state_save_file_type="json", + saved_state_path="instagram_agent.json", + ), + Agent( + agent_name="LinkedIn Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="linkedin_agent.json", + ), + Agent( + agent_name="TikTok Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="tiktok_agent.json", + ), + Agent( + agent_name="Reddit Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="reddit_agent.json", + ), + Agent( + agent_name="Pinterest Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="pinterest_agent.json", + ), + Agent( + agent_name="Snapchat Agent", + system_prompt=prompt, + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="snapchat_agent.json", + ), + ] + + +# Final agent +final_agent = Agent( + agent_name="Final Agent", + system_prompt="Ensure the content is optimized for all social media platforms.", + llm=model, + max_loops=1, + dashboard=False, + streaming_on=True, + verbose=True, + dynamic_temperature_enabled=True, + stopping_token="", + state_save_file_type="json", + saved_state_path="final_agent.json", +) + + +# Create a mixture of agents +swarm = MixtureOfAgents( + agents=agents, + final_agent=final_agent, + layers=1, + verbose=True, +) + +# parallel_swarm = AgentRearrange( +# agents=agents, +# flow=f"{agents[0].agent_name} -> {agents[1].agent_name}, {agents[2].agent_name}, {agents[3].agent_name}, {agents[4].agent_name}, {agents[5].agent_name}", +# max_loops=1, +# verbose=True, +# ) + +# Run the swarm +swarm.run( + """ + + +[Workshop Today][Unlocking The Secrets of Multi-Agent Collaboration] + +[Location][https://lu.ma/tfn0fp37] +[Time][Today 2:30pm PST -> 4PM PST] [Circa 5 hours] + +Sign up and invite your friends we're going to dive into various multi-agent orchestration workflows in swarms: +https://github.com/kyegomez/swarms + +And, the swarms docs: +https://docs.swarms.world/en/latest/ + + +""" +) diff --git a/swarms/structs/sequential_workflow.py b/swarms/structs/sequential_workflow.py index 48ebc20a..e3ac5bb3 100644 --- a/swarms/structs/sequential_workflow.py +++ b/swarms/structs/sequential_workflow.py @@ -1,5 +1,5 @@ from typing import List -from swarms import Agent +from swarms.structs.agent import Agent from swarms.utils.loguru_logger import logger from swarms.structs.rearrange import AgentRearrange From e8921a1ed08b3c5287f275e411b6636da6e2e59c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:29:56 +0000 Subject: [PATCH 06/19] Bump pydantic from 2.7.1 to 2.8.2 Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.7.1 to 2.8.2. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.7.1...v2.8.2) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec96d346..c2f7d45f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ toml = "*" pypdf = "4.1.0" ratelimit = "2.2.1" loguru = "0.7.2" -pydantic = "2.7.4" +pydantic = "2.8.2" tenacity = "8.4.2" Pillow = "10.3.0" psutil = "*" From 99d6198cba4de2aeec051fcabaa7faca2f79dba7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:34:30 +0000 Subject: [PATCH 07/19] Update ruff requirement from >=0.5.0,<0.5.1 to >=0.5.1,<0.5.2 --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec96d346..a8ac9183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ networkx = "*" [tool.poetry.group.lint.dependencies] black = ">=23.1,<25.0" -ruff = ">=0.5.0,<0.5.1" +ruff = ">=0.5.1,<0.5.2" types-toml = "^0.10.8.1" types-pytz = ">=2023.3,<2025.0" types-chardet = "^5.0.4.6" From b8f31279ff0f7a4b8d0ef26f4eda480524fc925c Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Mon, 8 Jul 2024 12:04:53 -0700 Subject: [PATCH 08/19] [CLEANUP][Feat][Swarms Memory] --- README.md | 438 ++++-------------- docs/swarms/structs/index.md | 2 +- .../agents/agent_with_long_term_memory.py | 2 +- .../agents/agent_with_longterm_memory.py | 2 +- playground/agents/new_perplexity_agent.py | 2 +- playground/agents/perplexity_agent.py | 2 +- .../demos/patient_question_assist/main.py | 2 +- .../swarm_mechanic/swarm_mechanic_example.py | 2 +- .../swarm_of_complaince/compliance_swarm.py | 2 +- .../mixture_of_agents/agent_ops_moa.py | 2 +- .../mixture_of_agents/moa_with_scp.py | 2 +- .../swarm_network_example.py | 2 +- playground/swarms/movers_swarm.py | 2 +- playground/swarms_example.ipynb | 2 +- pyproject.toml | 3 +- requirements.txt | 3 +- .../cleanup/json_log_cleanup.py | 0 17 files changed, 95 insertions(+), 375 deletions(-) rename json_log_cleanup.py => scripts/cleanup/json_log_cleanup.py (100%) diff --git a/README.md b/README.md index c3acd954..e720054b 100644 --- a/README.md +++ b/README.md @@ -118,179 +118,7 @@ agent.run("Generate a 10,000 word blog on health and wellness.") import os from dotenv import load_dotenv from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB -import logging -import os -import uuid -from typing import Optional -import chromadb -from swarms.utils.data_to_text import data_to_text -from swarms.utils.markdown_message import display_markdown_message -from swarms.memory.base_vectordb import BaseVectorDatabase - -# Load environment variables -load_dotenv() - - -# Results storage using local ChromaDB -class ChromaDB(BaseVectorDatabase): - """ - - ChromaDB database - - Args: - metric (str): The similarity metric to use. - output (str): The name of the collection to store the results in. - limit_tokens (int, optional): The maximum number of tokens to use for the query. Defaults to 1000. - n_results (int, optional): The number of results to retrieve. Defaults to 2. - - Methods: - add: _description_ - query: _description_ - - Examples: - >>> chromadb = ChromaDB( - >>> metric="cosine", - >>> output="results", - >>> llm="gpt3", - >>> openai_api_key=OPENAI_API_KEY, - >>> ) - >>> chromadb.add(task, result, result_id) - """ - - def __init__( - self, - metric: str = "cosine", - output_dir: str = "swarms", - limit_tokens: Optional[int] = 1000, - n_results: int = 3, - docs_folder: str = None, - verbose: bool = False, - *args, - **kwargs, - ): - self.metric = metric - self.output_dir = output_dir - self.limit_tokens = limit_tokens - self.n_results = n_results - self.docs_folder = docs_folder - self.verbose = verbose - - # Disable ChromaDB logging - if verbose: - logging.getLogger("chromadb").setLevel(logging.INFO) - - # Create Chroma collection - chroma_persist_dir = "chroma" - chroma_client = chromadb.PersistentClient( - settings=chromadb.config.Settings( - persist_directory=chroma_persist_dir, - ), - *args, - **kwargs, - ) - - # Create ChromaDB client - self.client = chromadb.Client() - - # Create Chroma collection - self.collection = chroma_client.get_or_create_collection( - name=output_dir, - metadata={"hnsw:space": metric}, - *args, - **kwargs, - ) - display_markdown_message( - "ChromaDB collection created:" - f" {self.collection.name} with metric: {self.metric} and" - f" output directory: {self.output_dir}" - ) - - # If docs - if docs_folder: - display_markdown_message( - f"Traversing directory: {docs_folder}" - ) - self.traverse_directory() - - def add( - self, - document: str, - *args, - **kwargs, - ): - """ - Add a document to the ChromaDB collection. - - Args: - document (str): The document to be added. - condition (bool, optional): The condition to check before adding the document. Defaults to True. - - Returns: - str: The ID of the added document. - """ - try: - doc_id = str(uuid.uuid4()) - self.collection.add( - ids=[doc_id], - documents=[document], - *args, - **kwargs, - ) - print("-----------------") - print("Document added successfully") - print("-----------------") - return doc_id - except Exception as e: - raise Exception(f"Failed to add document: {str(e)}") - - def query( - self, - query_text: str, - *args, - **kwargs, - ): - """ - Query documents from the ChromaDB collection. - - Args: - query (str): The query string. - n_docs (int, optional): The number of documents to retrieve. Defaults to 1. - - Returns: - dict: The retrieved documents. - """ - try: - docs = self.collection.query( - query_texts=[query_text], - n_results=self.n_results, - *args, - **kwargs, - )["documents"] - return docs[0] - except Exception as e: - raise Exception(f"Failed to query documents: {str(e)}") - - def traverse_directory(self): - """ - Traverse through every file in the given directory and its subdirectories, - and return the paths of all files. - Parameters: - - directory_name (str): The name of the directory to traverse. - Returns: - - list: A list of paths to each file in the directory and its subdirectories. - """ - added_to_db = False - - for root, dirs, files in os.walk(self.docs_folder): - for file in files: - file_path = os.path.join(root, file) # Change this line - _, ext = os.path.splitext(file_path) - data = data_to_text(file_path) - added_to_db = self.add(str(data)) - print(f"{file_path} added to Database") - - return added_to_db +from swarms_memory import ChromaDB # Get the API key from the environment api_key = os.environ.get("OPENAI_API_KEY") @@ -334,218 +162,108 @@ An LLM equipped with long term memory and tools, a full stack agent capable of a ```python import logging -import os -import uuid -from typing import Optional - -import chromadb from dotenv import load_dotenv - -from swarms.utils.data_to_text import data_to_text -from swarms.utils.markdown_message import display_markdown_message -from swarms.memory.base_vectordb import BaseVectorDatabase from swarms import Agent, OpenAIChat +import subprocess +# Making an instance of the ChromaDB class +memory = ChromaDB( + metric="cosine", + n_results=3, + output_dir="results", + docs_folder="docs", +) -# Load environment variables -load_dotenv() +# Tools +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) +def browser(query: str): + """ + Search the query in the browser with the `browser` tool. + Args: + query (str): The query to search in the browser. -# Results storage using local ChromaDB -class ChromaDB(BaseVectorDatabase): + 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." - ChromaDB database +def create_file(file_path: str, content: str): + """ + Create a file using the file editor tool. Args: - metric (str): The similarity metric to use. - output (str): The name of the collection to store the results in. - limit_tokens (int, optional): The maximum number of tokens to use for the query. Defaults to 1000. - n_results (int, optional): The number of results to retrieve. Defaults to 2. - - Methods: - add: _description_ - query: _description_ - - Examples: - >>> chromadb = ChromaDB( - >>> metric="cosine", - >>> output="results", - >>> llm="gpt3", - >>> openai_api_key=OPENAI_API_KEY, - >>> ) - >>> chromadb.add(task, result, result_id) + 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." - def __init__( - self, - metric: str = "cosine", - output_dir: str = "swarms", - limit_tokens: Optional[int] = 1000, - n_results: int = 3, - docs_folder: str = None, - verbose: bool = False, - *args, - **kwargs, - ): - self.metric = metric - self.output_dir = output_dir - self.limit_tokens = limit_tokens - self.n_results = n_results - self.docs_folder = docs_folder - self.verbose = verbose - - # Disable ChromaDB logging - if verbose: - logging.getLogger("chromadb").setLevel(logging.INFO) - - # Create Chroma collection - chroma_persist_dir = "chroma" - chroma_client = chromadb.PersistentClient( - settings=chromadb.config.Settings( - persist_directory=chroma_persist_dir, - ), - *args, - **kwargs, - ) - - # Create ChromaDB client - self.client = chromadb.Client() - - # Create Chroma collection - self.collection = chroma_client.get_or_create_collection( - name=output_dir, - metadata={"hnsw:space": metric}, - *args, - **kwargs, - ) - display_markdown_message( - "ChromaDB collection created:" - f" {self.collection.name} with metric: {self.metric} and" - f" output directory: {self.output_dir}" - ) - - # If docs - if docs_folder: - display_markdown_message( - f"Traversing directory: {docs_folder}" - ) - self.traverse_directory() - - def add( - self, - document: str, - *args, - **kwargs, - ): - """ - Add a document to the ChromaDB collection. - - Args: - document (str): The document to be added. - condition (bool, optional): The condition to check before adding the document. Defaults to True. - - Returns: - str: The ID of the added document. - """ - try: - doc_id = str(uuid.uuid4()) - self.collection.add( - ids=[doc_id], - documents=[document], - *args, - **kwargs, - ) - print("-----------------") - print("Document added successfully") - print("-----------------") - return doc_id - except Exception as e: - raise Exception(f"Failed to add document: {str(e)}") - - def query( - self, - query_text: str, - *args, - **kwargs, - ): - """ - Query documents from the ChromaDB collection. - - Args: - query (str): The query string. - n_docs (int, optional): The number of documents to retrieve. Defaults to 1. - - Returns: - dict: The retrieved documents. - """ - try: - docs = self.collection.query( - query_texts=[query_text], - n_results=self.n_results, - *args, - **kwargs, - )["documents"] - return docs[0] - except Exception as e: - raise Exception(f"Failed to query documents: {str(e)}") - - def traverse_directory(self): - """ - Traverse through every file in the given directory and its subdirectories, - and return the paths of all files. - Parameters: - - directory_name (str): The name of the directory to traverse. - Returns: - - list: A list of paths to each file in the directory and its subdirectories. - """ - added_to_db = False - - for root, dirs, files in os.walk(self.docs_folder): - for file in files: - file_path = os.path.join(root, file) # Change this line - _, ext = os.path.splitext(file_path) - data = data_to_text(file_path) - added_to_db = self.add(str(data)) - print(f"{file_path} added to Database") - - return added_to_db +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. -# Making an instance of the ChromaDB class -memory = ChromaDB( - metric="cosine", - n_results=3, - output_dir="results", - docs_folder="docs", -) + 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." -# Initialize a tool -def search_api(query: str): - # Add your logic here - return query -# Initializing the agent with the Gemini instance and other parameters +# Agent agent = Agent( - agent_name="Covid-19-Chat", - agent_description=( - "This agent provides information about COVID-19 symptoms." + 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=OpenAIChat(), + llm=llm, max_loops="auto", autosave=True, + dashboard=False, + streaming_on=True, verbose=True, + stopping_token="", + interactive=True, + tools=[terminal, browser, file_editor, create_file], + code_interpreter=True, + # streaming=True, long_term_memory=memory, - stopping_condition="finish", - tools=[search_api], ) -# Defining the task and image path -task = ("What are the symptoms of COVID-19?",) - -# Running the agent with the specified task and image -out = agent.run(task) +# Run the agent +out = agent("Create a new file for a plan to take over the world.") print(out) ``` diff --git a/docs/swarms/structs/index.md b/docs/swarms/structs/index.md index b4ab01c3..ca5c9111 100644 --- a/docs/swarms/structs/index.md +++ b/docs/swarms/structs/index.md @@ -72,7 +72,7 @@ agent.run("Generate a 10,000 word blog on health and wellness.") ```python from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB # Copy and paste the code and put it in your own local directory. +from swarms_memory import ChromaDB # Copy and paste the code and put it in your own local directory. # Making an instance of the ChromaDB class memory = ChromaDB( diff --git a/playground/agents/agent_with_long_term_memory.py b/playground/agents/agent_with_long_term_memory.py index 3a07f246..d8fc2861 100644 --- a/playground/agents/agent_with_long_term_memory.py +++ b/playground/agents/agent_with_long_term_memory.py @@ -1,5 +1,5 @@ from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB from swarms.models.tiktoken_wrapper import TikTokenizer # Initialize the agent diff --git a/playground/agents/agent_with_longterm_memory.py b/playground/agents/agent_with_longterm_memory.py index dc73b8c1..36e32081 100644 --- a/playground/agents/agent_with_longterm_memory.py +++ b/playground/agents/agent_with_longterm_memory.py @@ -4,7 +4,7 @@ from dotenv import load_dotenv # Import the OpenAIChat model and the Agent struct from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB # Load the environment variables load_dotenv() diff --git a/playground/agents/new_perplexity_agent.py b/playground/agents/new_perplexity_agent.py index 5e2032bd..272041de 100644 --- a/playground/agents/new_perplexity_agent.py +++ b/playground/agents/new_perplexity_agent.py @@ -1,6 +1,6 @@ from swarms import Agent from swarms.models.llama3_hosted import llama3Hosted -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB from swarms.tools.prebuilt.bing_api import fetch_web_articles_bing_api # Define the research system prompt diff --git a/playground/agents/perplexity_agent.py b/playground/agents/perplexity_agent.py index 6390a873..0faab2cf 100644 --- a/playground/agents/perplexity_agent.py +++ b/playground/agents/perplexity_agent.py @@ -10,7 +10,7 @@ $ pip install swarms """ from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB from swarms.tools.prebuilt.bing_api import fetch_web_articles_bing_api import os from dotenv import load_dotenv diff --git a/playground/demos/patient_question_assist/main.py b/playground/demos/patient_question_assist/main.py index 1c3d7133..45b31cb4 100644 --- a/playground/demos/patient_question_assist/main.py +++ b/playground/demos/patient_question_assist/main.py @@ -1,6 +1,6 @@ from swarms import Agent, OpenAIChat from typing import List -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB memory = ChromaDB( metric="cosine", diff --git a/playground/demos/swarm_mechanic/swarm_mechanic_example.py b/playground/demos/swarm_mechanic/swarm_mechanic_example.py index 9fa2104d..5875c2e8 100644 --- a/playground/demos/swarm_mechanic/swarm_mechanic_example.py +++ b/playground/demos/swarm_mechanic/swarm_mechanic_example.py @@ -15,7 +15,7 @@ task -> Understanding Agent [understands the problem better] -> Summarize of the from swarms import Agent, llama3Hosted, AgentRearrange from pydantic import BaseModel -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB # Initialize the language model agent (e.g., GPT-3) llm = llama3Hosted(max_tokens=3000) diff --git a/playground/demos/swarm_of_complaince/compliance_swarm.py b/playground/demos/swarm_of_complaince/compliance_swarm.py index 62f296a2..63cee018 100644 --- a/playground/demos/swarm_of_complaince/compliance_swarm.py +++ b/playground/demos/swarm_of_complaince/compliance_swarm.py @@ -11,7 +11,7 @@ Todo [Improvements] from swarms import Agent from swarms.models.llama3_hosted import llama3Hosted -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB # Model diff --git a/playground/structs/multi_agent_collaboration/mixture_of_agents/agent_ops_moa.py b/playground/structs/multi_agent_collaboration/mixture_of_agents/agent_ops_moa.py index 5b7df470..2274f956 100644 --- a/playground/structs/multi_agent_collaboration/mixture_of_agents/agent_ops_moa.py +++ b/playground/structs/multi_agent_collaboration/mixture_of_agents/agent_ops_moa.py @@ -1,6 +1,6 @@ from swarms import Agent, OpenAIChat from swarms.structs.mixture_of_agents import MixtureOfAgents -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB SEC_DATA = """ diff --git a/playground/structs/multi_agent_collaboration/mixture_of_agents/moa_with_scp.py b/playground/structs/multi_agent_collaboration/mixture_of_agents/moa_with_scp.py index 1596bfff..e61d1536 100644 --- a/playground/structs/multi_agent_collaboration/mixture_of_agents/moa_with_scp.py +++ b/playground/structs/multi_agent_collaboration/mixture_of_agents/moa_with_scp.py @@ -1,6 +1,6 @@ from swarms import Agent, OpenAIChat from swarms.structs.mixture_of_agents import MixtureOfAgents -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB SEC_DATA = """ diff --git a/playground/structs/multi_agent_collaboration/swarm_network_example.py b/playground/structs/multi_agent_collaboration/swarm_network_example.py index 69cbe0ef..d0f01a3e 100644 --- a/playground/structs/multi_agent_collaboration/swarm_network_example.py +++ b/playground/structs/multi_agent_collaboration/swarm_network_example.py @@ -6,7 +6,7 @@ from swarms import ( OpenAIChat, TogetherLLM, ) -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB from dotenv import load_dotenv # load the environment variables diff --git a/playground/swarms/movers_swarm.py b/playground/swarms/movers_swarm.py index 7600ec64..c4625876 100644 --- a/playground/swarms/movers_swarm.py +++ b/playground/swarms/movers_swarm.py @@ -10,7 +10,7 @@ $ pip install swarms """ from swarms import Agent, OpenAIChat -from playground.memory.chromadb_example import ChromaDB +from swarms_memory import ChromaDB from swarms.tools.prebuilt.bing_api import fetch_web_articles_bing_api import os from dotenv import load_dotenv diff --git a/playground/swarms_example.ipynb b/playground/swarms_example.ipynb index ece6101d..83951984 100644 --- a/playground/swarms_example.ipynb +++ b/playground/swarms_example.ipynb @@ -96,7 +96,7 @@ "outputs": [], "source": [ "from swarms import Agent, OpenAIChat\n", - "from playground.memory.chromadb_example import ChromaDB\n", + "from swarms_memory import ChromaDB\n", "\n", "# Making an instance of the ChromaDB class\n", "memory = ChromaDB(\n", diff --git a/pyproject.toml b/pyproject.toml index ec96d346..81b0f860 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms" -version = "5.3.2" +version = "5.3.3" description = "Swarms - Pytorch" license = "MIT" authors = ["Kye Gomez "] @@ -55,6 +55,7 @@ openai = ">=1.30.1,<2.0" termcolor = "*" tiktoken = "*" networkx = "*" +swarms-memory = "*" diff --git a/requirements.txt b/requirements.txt index 4273cc06..22ba41f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,5 @@ pytest>=8.1.1 termcolor>=2.4.0 pandas>=2.2.2 fastapi>=0.110.1 -networkx \ No newline at end of file +networkx +swarms-memory diff --git a/json_log_cleanup.py b/scripts/cleanup/json_log_cleanup.py similarity index 100% rename from json_log_cleanup.py rename to scripts/cleanup/json_log_cleanup.py From 2d43838c3b6dcc7ffdde252513287db7f4cee198 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Mon, 8 Jul 2024 12:11:46 -0700 Subject: [PATCH 09/19] [DOCS][Swarms Memory] --- docs/mkdocs.yml | 8 ++ docs/swarms_memory/chromadb.md | 141 ++++++++++++++++++++++++++ docs/swarms_memory/index.md | 0 docs/swarms_memory/pinecone.md | 179 +++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 docs/swarms_memory/chromadb.md create mode 100644 docs/swarms_memory/index.md create mode 100644 docs/swarms_memory/pinecone.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 29b98dc4..d26bae03 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -167,6 +167,14 @@ nav: - Getting Started with SOTA Vision Language Models VLM: "swarms_cloud/getting_started.md" - Enterprise Guide to High-Performance Multi-Agent LLM Deployments: "swarms_cloud/production_deployment.md" - Under The Hood The Swarm Cloud Serving Infrastructure: "swarms_cloud/architecture.md" + - Swarms Memory: + - Overview: "swarms_memory/memory/index.md" + - Memory Systems: + - ChromaDB: "swarms_memory/memory/chromadb.md" + - Pinecone: "swarms_memory/memory/pinecone.md" + - Redis: "swarms_memory/memory/redis.md" + - Faiss: "swarms_memory/memory/faiss.md" + - HNSW: "swarms_memory/memory/hnsw.md" - References: - Agent Glossary: "swarms/glossary.md" - List of The Best Multi-Agent Papers: "swarms/papers.md" diff --git a/docs/swarms_memory/chromadb.md b/docs/swarms_memory/chromadb.md new file mode 100644 index 00000000..188e024c --- /dev/null +++ b/docs/swarms_memory/chromadb.md @@ -0,0 +1,141 @@ +# ChromaDB Documentation + +ChromaDB is a specialized module designed to facilitate the storage and retrieval of documents using the ChromaDB system. It offers functionalities for adding documents to a local ChromaDB collection and querying this collection based on provided query texts. This module integrates with the ChromaDB client to create and manage collections, leveraging various configurations for optimizing the storage and retrieval processes. + + +#### Parameters + +| Parameter | Type | Default | Description | +|----------------|-------------------|----------|-------------------------------------------------------------| +| `metric` | `str` | `"cosine"`| The similarity metric to use for the collection. | +| `output_dir` | `str` | `"swarms"`| The name of the collection to store the results in. | +| `limit_tokens` | `Optional[int]` | `1000` | The maximum number of tokens to use for the query. | +| `n_results` | `int` | `1` | The number of results to retrieve. | +| `docs_folder` | `Optional[str]` | `None` | The folder containing documents to be added to the collection.| +| `verbose` | `bool` | `False` | Flag to enable verbose logging for debugging. | +| `*args` | `tuple` | `()` | Additional positional arguments. | +| `**kwargs` | `dict` | `{}` | Additional keyword arguments. | + +#### Methods + +| Method | Description | +|-----------------------|----------------------------------------------------------| +| `__init__` | Initializes the ChromaDB instance with specified parameters. | +| `add` | Adds a document to the ChromaDB collection. | +| `query` | Queries documents from the ChromaDB collection based on the query text. | +| `traverse_directory` | Traverses the specified directory to add documents to the collection. | + + +## Usage + +```python +from swarms_memory import ChromaDB + +chromadb = ChromaDB( + metric="cosine", + output_dir="results", + limit_tokens=1000, + n_results=2, + docs_folder="path/to/docs", + verbose=True, +) +``` + +### Adding Documents + +The `add` method allows you to add a document to the ChromaDB collection. It generates a unique ID for each document and adds it to the collection. + +#### Parameters + +| Parameter | Type | Default | Description | +|---------------|--------|---------|---------------------------------------------| +| `document` | `str` | - | The document to be added to the collection. | +| `*args` | `tuple`| `()` | Additional positional arguments. | +| `**kwargs` | `dict` | `{}` | Additional keyword arguments. | + +#### Returns + +| Type | Description | +|-------|--------------------------------------| +| `str` | The ID of the added document. | + +#### Example + +```python +task = "example_task" +result = "example_result" +result_id = chromadb.add(document="This is a sample document.") +print(f"Document ID: {result_id}") +``` + +### Querying Documents + +The `query` method allows you to retrieve documents from the ChromaDB collection based on the provided query text. + +#### Parameters + +| Parameter | Type | Default | Description | +|-------------|--------|---------|----------------------------------------| +| `query_text`| `str` | - | The query string to search for. | +| `*args` | `tuple`| `()` | Additional positional arguments. | +| `**kwargs` | `dict` | `{}` | Additional keyword arguments. | + +#### Returns + +| Type | Description | +|-------|--------------------------------------| +| `str` | The retrieved documents as a string. | + +#### Example + +```python +query_text = "search term" +results = chromadb.query(query_text=query_text) +print(f"Retrieved Documents: {results}") +``` + +### Traversing Directory + +The `traverse_directory` method traverses through every file in the specified directory and its subdirectories, adding the contents of each file to the ChromaDB collection. + +#### Example + +```python +chromadb.traverse_directory() +``` + +## Additional Information and Tips + +### Verbose Logging + +Enable the `verbose` flag during initialization to get detailed logs of the operations, which is useful for debugging. + +```python +chromadb = ChromaDB(verbose=True) +``` + +### Handling Large Documents + +When dealing with large documents, consider using the `limit_tokens` parameter to restrict the number of tokens processed in a single query. + +```python +chromadb = ChromaDB(limit_tokens=500) +``` + +### Optimizing Query Performance + +Use the appropriate similarity metric (`metric` parameter) that suits your use case for optimal query performance. + +```python +chromadb = ChromaDB(metric="euclidean") +``` + +## References and Resources + +- [ChromaDB Documentation](https://chromadb.io/docs) +- [Python UUID Module](https://docs.python.org/3/library/uuid.html) +- [Python os Module](https://docs.python.org/3/library/os.html) +- [Python logging Module](https://docs.python.org/3/library/logging.html) +- [dotenv Package](https://pypi.org/project/python-dotenv/) + +By following this documentation, users can effectively utilize the ChromaDB module for managing document storage and retrieval in their applications. \ No newline at end of file diff --git a/docs/swarms_memory/index.md b/docs/swarms_memory/index.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/swarms_memory/pinecone.md b/docs/swarms_memory/pinecone.md new file mode 100644 index 00000000..edc66e7e --- /dev/null +++ b/docs/swarms_memory/pinecone.md @@ -0,0 +1,179 @@ +# PineconeMemory Documentation + +The `PineconeMemory` class provides a robust interface for integrating Pinecone-based Retrieval-Augmented Generation (RAG) systems. It allows for adding documents to a Pinecone index and querying the index for similar documents. The class supports custom embedding models, preprocessing functions, and other customizations to suit different use cases. + + + +#### Parameters + +| Parameter | Type | Default | Description | +|----------------------|-----------------------------------------------|-----------------------------------|------------------------------------------------------------------------------------------------------| +| `api_key` | `str` | - | Pinecone API key. | +| `environment` | `str` | - | Pinecone environment. | +| `index_name` | `str` | - | Name of the Pinecone index to use. | +| `dimension` | `int` | `768` | Dimension of the document embeddings. | +| `embedding_model` | `Optional[Any]` | `None` | Custom embedding model. Defaults to `SentenceTransformer('all-MiniLM-L6-v2')`. | +| `embedding_function` | `Optional[Callable[[str], List[float]]]` | `None` | Custom embedding function. Defaults to `_default_embedding_function`. | +| `preprocess_function`| `Optional[Callable[[str], str]]` | `None` | Custom preprocessing function. Defaults to `_default_preprocess_function`. | +| `postprocess_function`| `Optional[Callable[[List[Dict[str, Any]]], List[Dict[str, Any]]]]`| `None` | Custom postprocessing function. Defaults to `_default_postprocess_function`. | +| `metric` | `str` | `'cosine'` | Distance metric for Pinecone index. | +| `pod_type` | `str` | `'p1'` | Pinecone pod type. | +| `namespace` | `str` | `''` | Pinecone namespace. | +| `logger_config` | `Optional[Dict[str, Any]]` | `None` | Configuration for the logger. Defaults to logging to `rag_wrapper.log` and console output. | + +### Methods + +#### `_setup_logger` + +```python +def _setup_logger(self, config: Optional[Dict[str, Any]] = None) +``` + +Sets up the logger with the given configuration. + +#### `_default_embedding_function` + +```python +def _default_embedding_function(self, text: str) -> List[float] +``` + +Generates embeddings using the default SentenceTransformer model. + +#### `_default_preprocess_function` + +```python +def _default_preprocess_function(self, text: str) -> str +``` + +Preprocesses the input text by stripping whitespace. + +#### `_default_postprocess_function` + +```python +def _default_postprocess_function(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]] +``` + +Postprocesses the query results. + +#### `add` + +Adds a document to the Pinecone index. + +| Parameter | Type | Default | Description | +|-----------|-----------------------|---------|-----------------------------------------------| +| `doc` | `str` | - | The document to be added. | +| `metadata`| `Optional[Dict[str, Any]]` | `None` | Additional metadata for the document. | + +#### `query` + +Queries the Pinecone index for similar documents. + +| Parameter | Type | Default | Description | +|-----------|-------------------------|---------|-----------------------------------------------| +| `query` | `str` | - | The query string. | +| `top_k` | `int` | `5` | The number of top results to return. | +| `filter` | `Optional[Dict[str, Any]]` | `None` | Metadata filter for the query. | + +## Usage + + +The `PineconeMemory` class is initialized with the necessary parameters to configure Pinecone and the embedding model. It supports a variety of custom configurations to suit different needs. + +#### Example + +```python +from swarms_memory import PineconeMemory + +# Initialize PineconeMemory +memory = PineconeMemory( + api_key="your-api-key", + environment="us-west1-gcp", + index_name="example-index", + dimension=768 +) +``` + +### Adding Documents + +Documents can be added to the Pinecone index using the `add` method. The method accepts a document string and optional metadata. + +#### Example + +```python +doc = "This is a sample document to be added to the Pinecone index." +metadata = {"author": "John Doe", "date": "2024-07-08"} + +memory.add(doc, metadata) +``` + +### Querying Documents + +The `query` method allows for querying the Pinecone index for similar documents based on a query string. It returns the top `k` most similar documents. + +#### Example + +```python +query = "Sample query to find similar documents." +results = memory.query(query, top_k=5) + +for result in results: + print(result) +``` + +## Additional Information and Tips + +### Custom Embedding and Preprocessing Functions + +Custom embedding and preprocessing functions can be provided during initialization to tailor the document processing to specific requirements. + +#### Example + +```python +def custom_embedding_function(text: str) -> List[float]: + # Custom embedding logic + return [0.1, 0.2, 0.3] + +def custom_preprocess_function(text: str) -> str: + # Custom preprocessing logic + return text.lower() + +memory = PineconeMemory( + api_key="your-api-key", + environment="us-west1-gcp", + index_name="example-index", + embedding_function=custom_embedding_function, + preprocess_function=custom_preprocess_function +) +``` + +### Logger Configuration + +The logger can be configured to suit different logging needs. The default configuration logs to a file and the console. + +#### Example + +```python +logger_config = { + "handlers": [ + {"sink": "custom_log.log", "rotation": "1 MB"}, + {"sink": lambda msg: print(msg, end="")}, + ] +} + +memory = PineconeMemory( + api_key="your-api-key", + environment="us-west1-gcp", + index_name="example-index", + logger_config=logger_config +) +``` + +## References and Resources + +- [Pinecone Documentation](https://docs.pinecone.io/) +- [SentenceTransformers Documentation](https://www.sbert.net/) +- [Loguru Documentation](https://loguru.readthedocs.io/en/stable/) + +For further exploration and examples, refer to the official documentation and resources provided by Pinecone, SentenceTransformers, and Loguru. + +This concludes the detailed documentation for the `PineconeMemory` class. The class offers a flexible and powerful interface for leveraging Pinecone's capabilities in retrieval-augmented generation systems. By supporting custom embeddings, preprocessing, and postprocessing functions, it can be tailored to a wide range of applications. \ No newline at end of file From c3fc06ed4382df94dc79949803800a349ad0e4e4 Mon Sep 17 00:00:00 2001 From: Kye Gomez <98760976+kyegomez@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:03:47 -0700 Subject: [PATCH 10/19] Update README.md --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index e720054b..f2252e77 100644 --- a/README.md +++ b/README.md @@ -927,7 +927,7 @@ autoswarm.run("Analyze these financial data and give me a summary") Inspired by Einops and einsum, this orchestration techniques enables you to map out the relationships between various agents. For example you specify linear and sequential relationships like `a -> a1 -> a2 -> a3` or concurrent relationships where the first agent will send a message to 3 agents all at once: `a -> a1, a2, a3`. You can customize your workflow to mix sequential and concurrent relationships. [Docs Available:](https://swarms.apac.ai/en/latest/swarms/structs/agent_rearrange/) ```python -from swarms import Agent, AgentRearrange, rearrange, Anthropic +from swarms import Agent, AgentRearrange, Anthropic # Initialize the director agent @@ -990,16 +990,6 @@ output = agent_system.run( ) print(output) - -# Using rearrange function -output = rearrange( - agents, - flow, - "Create a format to express and communicate swarms of llms in a structured manner for youtube", -) - -print(output) - ``` ## `HierarhicalSwarm` From 4058cc682637f40c27232f99380c5bf8a19643fd Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Mon, 8 Jul 2024 19:43:01 -0700 Subject: [PATCH 11/19] [DOCS] --- docs/swarms_memory/index.md | 172 ++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/docs/swarms_memory/index.md b/docs/swarms_memory/index.md index e69de29b..3d96b4ef 100644 --- a/docs/swarms_memory/index.md +++ b/docs/swarms_memory/index.md @@ -0,0 +1,172 @@ +# Announcing the Release of Swarms-Memory Package: Your Gateway to Efficient RAG Systems + + +We are thrilled to announce the release of the Swarms-Memory package, a powerful and easy-to-use toolkit designed to facilitate the implementation of Retrieval-Augmented Generation (RAG) systems. Whether you're a seasoned AI practitioner or just starting out, Swarms-Memory provides the tools you need to integrate high-performance, reliable RAG systems into your applications seamlessly. + +In this blog post, we'll walk you through getting started with the Swarms-Memory package, covering installation, usage examples, and a detailed overview of supported RAG systems like Pinecone and ChromaDB. Let's dive in! + +## What is Swarms-Memory? + +Swarms-Memory is a Python package that simplifies the integration of advanced RAG systems into your projects. It supports multiple databases optimized for AI tasks, providing you with the flexibility to choose the best system for your needs. With Swarms-Memory, you can effortlessly handle large-scale AI tasks, vector searches, and more. + +### Key Features + +- **Easy Integration**: Quickly set up and start using powerful RAG systems. +- **Customizable**: Define custom embedding, preprocessing, and postprocessing functions. +- **Flexible**: Supports multiple RAG systems like ChromaDB and Pinecone, with more coming soon. +- **Scalable**: Designed to handle large-scale AI tasks efficiently. + +## Supported RAG Systems + +Here's an overview of the RAG systems currently supported by Swarms-Memory: + +| RAG System | Status | Description | Documentation | Website | +|------------|--------------|------------------------------------------------------------------------------------------|---------------------------|-----------------| +| ChromaDB | Available | A high-performance, distributed database optimized for handling large-scale AI tasks. | [ChromaDB Documentation](https://chromadb.com/docs) | [ChromaDB](https://chromadb.com) | +| Pinecone | Available | A fully managed vector database for adding vector search to your applications. | [Pinecone Documentation](https://pinecone.io/docs) | [Pinecone](https://pinecone.io) | +| Redis | Coming Soon | An open-source, in-memory data structure store, used as a database, cache, and broker. | [Redis Documentation](https://redis.io/documentation) | [Redis](https://redis.io) | +| Faiss | Coming Soon | A library for efficient similarity search and clustering of dense vectors by Facebook AI. | [Faiss Documentation](https://faiss.ai) | [Faiss](https://faiss.ai) | +| HNSW | Coming Soon | A graph-based algorithm for approximate nearest neighbor search, known for speed. | [HNSW Documentation](https://hnswlib.github.io/hnswlib) | [HNSW](https://hnswlib.github.io/hnswlib) | + +## Getting Started + +### Requirements + +Before you begin, ensure you have the following: + +- Python 3.10 +- `.env` file with your respective API keys (e.g., `PINECONE_API_KEY`) + +### Installation + +You can install the Swarms-Memory package using pip: + +```bash +$ pip install swarms-memory +``` + +### Usage Examples + +#### Pinecone + +Here's a step-by-step guide on how to use Pinecone with Swarms-Memory: + +1. **Import Required Libraries**: + +```python +from typing import List, Dict, Any +from swarms_memory import PineconeMemory +``` + +2. **Define Custom Functions**: + +```python +from transformers import AutoTokenizer, AutoModel +import torch + +# Custom embedding function using a HuggingFace model +def custom_embedding_function(text: str) -> List[float]: + tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + model = AutoModel.from_pretrained("bert-base-uncased") + inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512) + with torch.no_grad(): + outputs = model(**inputs) + embeddings = outputs.last_hidden_state.mean(dim=1).squeeze().tolist() + return embeddings + +# Custom preprocessing function +def custom_preprocess(text: str) -> str: + return text.lower().strip() + +# Custom postprocessing function +def custom_postprocess(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + for result in results: + result["custom_score"] = result["score"] * 2 # Example modification + return results +``` + +3. **Initialize the Wrapper with Custom Functions**: + +```python +wrapper = PineconeMemory( + api_key="your-api-key", + environment="your-environment", + index_name="your-index-name", + embedding_function=custom_embedding_function, + preprocess_function=custom_preprocess, + postprocess_function=custom_postprocess, + logger_config={ + "handlers": [ + {"sink": "custom_rag_wrapper.log", "rotation": "1 GB"}, + {"sink": lambda msg: print(f"Custom log: {msg}", end="")}, + ], + }, +) +``` + +4. **Add Documents and Query**: + +```python +# Adding documents +wrapper.add("This is a sample document about artificial intelligence.", {"category": "AI"}) +wrapper.add("Python is a popular programming language for data science.", {"category": "Programming"}) + +# Querying +results = wrapper.query("What is AI?", filter={"category": "AI"}) +for result in results: + print(f"Score: {result['score']}, Custom Score: {result['custom_score']}, Text: {result['metadata']['text']}") +``` + +#### ChromaDB + +Using ChromaDB with Swarms-Memory is straightforward. Here’s how: + +1. **Import ChromaDB**: + +```python +from swarms_memory import ChromaDB +``` + +2. **Initialize ChromaDB**: + +```python +chromadb = ChromaDB( + metric="cosine", + output_dir="results", + limit_tokens=1000, + n_results=2, + docs_folder="path/to/docs", + verbose=True, +) +``` + +3. **Add and Query Documents**: + +```python +# Add a document +doc_id = chromadb.add("This is a test document.") + +# Query the document +result = chromadb.query("This is a test query.") + +# Traverse a directory +chromadb.traverse_directory() + +# Display the result +print(result) +``` + +## Join the Community + +We're excited to see how you leverage Swarms-Memory in your projects! Join our community on Discord to share your experiences, ask questions, and stay updated on the latest developments. + +- **🐦 Twitter**: [Follow us on Twitter](https://twitter.com/swarms_platform) +- **📢 Discord**: [Join the Agora Discord](https://discord.gg/agora) +- **Swarms Platform**: [Visit our website](https://swarms.ai) +- **📙 Documentation**: [Read the Docs](https://docs.swarms.ai) + +## Conclusion + +The Swarms-Memory package brings a new level of ease and efficiency to building and managing RAG systems. With support for leading databases like ChromaDB and Pinecone, it's never been easier to integrate powerful, scalable AI solutions into your projects. We can't wait to see what you'll create with Swarms-Memory! + +For more detailed usage examples and documentation, visit our [GitHub repository](https://github.com/swarms-ai/swarms-memory) and start exploring today! From 47e9c6c3785dfea566a30bf15b987dcd25cb584a Mon Sep 17 00:00:00 2001 From: Kye Gomez <98760976+kyegomez@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:16:19 -0700 Subject: [PATCH 12/19] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f2252e77..eba1466c 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ An LLM equipped with long term memory and tools, a full stack agent capable of a import logging from dotenv import load_dotenv from swarms import Agent, OpenAIChat +from swarms_memory import ChromaDB import subprocess # Making an instance of the ChromaDB class From 1fe23bf1428ab85fe3fc07e5e2e60a7a06eb8816 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Mon, 8 Jul 2024 21:17:47 -0700 Subject: [PATCH 13/19] [DOCS] --- docs/mkdocs.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index d26bae03..299c9a97 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -168,13 +168,13 @@ nav: - Enterprise Guide to High-Performance Multi-Agent LLM Deployments: "swarms_cloud/production_deployment.md" - Under The Hood The Swarm Cloud Serving Infrastructure: "swarms_cloud/architecture.md" - Swarms Memory: - - Overview: "swarms_memory/memory/index.md" + - Overview: "swarms_memory/index.md" - Memory Systems: - - ChromaDB: "swarms_memory/memory/chromadb.md" - - Pinecone: "swarms_memory/memory/pinecone.md" - - Redis: "swarms_memory/memory/redis.md" - - Faiss: "swarms_memory/memory/faiss.md" - - HNSW: "swarms_memory/memory/hnsw.md" + - ChromaDB: "swarms_memory/chromadb.md" + - Pinecone: "swarms_memory/pinecone.md" + - Redis: "swarms_memory/redis.md" + - Faiss: "swarms_memory/faiss.md" + - HNSW: "swarms_memory/hnsw.md" - References: - Agent Glossary: "swarms/glossary.md" - List of The Best Multi-Agent Papers: "swarms/papers.md" From 38ac04771122865c5f7792432be0a18e309aeb96 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 9 Jul 2024 09:37:56 -0700 Subject: [PATCH 14/19] [DEMO][FIX] --- .../demos/ai_research_team/main_example.py | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/playground/demos/ai_research_team/main_example.py b/playground/demos/ai_research_team/main_example.py index dc6e54ae..d77d560d 100644 --- a/playground/demos/ai_research_team/main_example.py +++ b/playground/demos/ai_research_team/main_example.py @@ -9,9 +9,9 @@ from swarms.prompts.ai_research_team import ( ) from swarms.structs import Agent from swarms.utils.pdf_to_text import pdf_to_text +from swarms import rearrange # Base llms -# Environment variables load_dotenv() anthropic_api_key = os.getenv("ANTHROPIC_API_KEY") openai_api_key = os.getenv("OPENAI_API_KEY") @@ -30,6 +30,7 @@ llm2 = Anthropic( # Agents paper_summarizer_agent = Agent( + agent_name = "paper_summarizer_agent", llm=llm2, sop=PAPER_SUMMARY_ANALYZER, max_loops=1, @@ -38,6 +39,7 @@ paper_summarizer_agent = Agent( ) paper_implementor_agent = Agent( + agent_name = "paper_implementor_agent", llm=llm1, sop=PAPER_IMPLEMENTOR_AGENT_PROMPT, max_loops=1, @@ -46,9 +48,28 @@ paper_implementor_agent = Agent( code_interpreter=False, ) -paper = pdf_to_text(PDF_PATH) -algorithmic_psuedocode_agent = paper_summarizer_agent.run( - "Focus on creating the algorithmic pseudocode for the novel" - f" method in this paper: {paper}" +pytorch_pseudocode_agent = Agent( + agent_name = "pytorch_pseudocode_agent", + llm=llm1, + sop=PAPER_IMPLEMENTOR_AGENT_PROMPT, + max_loops=1, + autosave=True, + saved_state_path="pytorch_pseudocode_agent.json", + code_interpreter=False, ) -pytorch_code = paper_implementor_agent.run(algorithmic_psuedocode_agent) + + +paper = pdf_to_text(PDF_PATH) +task = f""" + Focus on creating the algorithmic pseudocode for the novel + f" method in this paper: {paper} +""" + + +agents = [paper_summarizer_agent, paper_implementor_agent, pytorch_pseudocode_agent] + +flow = "paper_summarizer_agent -> paper_implementor_agent -> pytorch_pseudocode_agent" + +swarm = rearrange(agents, flow, task) +print(swarm) + From 19c31e99a267915cee0e4e1565154af6e96109bf Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 9 Jul 2024 16:50:51 -0700 Subject: [PATCH 15/19] [PLAYGROUND][CLEANUP][FEAT][FireWorksAI] --- CONTRIBUTING.md | 4 - README.md | 313 ++---------------- docs/applications/business-analyst-agent.md | 2 +- docs/mkdocs.yml | 4 +- example.py | 1 + .../{examples => agents}/example_agent.py | 0 .../{examples => agents}/example_task.py | 2 +- .../{examples => agents}/example_toolagent.py | 0 playground/{youtube => agents}/tool.py | 0 .../tools/agent_with_tools_example.py | 0 .../{ => agents}/tools/func_calling_schema.py | 0 .../tools/function_to_openai_exec.py | 0 .../{ => agents}/tools/new_tool_wrapper.py | 0 .../creation_engine/omni_model_agent.py | 80 ----- .../demos/ai_research_team/main_example.py | 13 +- .../business-analyst-agent.ipynb | 0 playground/demos/octomology_swarm/api.py | 69 +--- playground/examples/README.md | 0 .../Screenshot from 2024-02-20 05-55-34.png | Bin 115988 -> 0 bytes playground/examples/example_dalle3.py | 14 - playground/examples/example_huggingfacellm.py | 36 -- playground/examples/example_mixtral.py | 10 - .../example_simple_conversation_agent.py | 45 --- playground/examples/example_worker.py | 35 -- playground/examples/example_zeroscopetv.py | 12 - playground/memory/chromadb_example.py | 186 ----------- playground/memory/mongodb.py | 14 - playground/memory/pinecone.py | 2 +- playground/memory/qdrant.py | 25 -- playground/memory/weaviate_db.py | 180 ---------- playground/models/anthropic_example.py | 4 +- .../models/distilled_whiserpx_example.py | 13 - .../{examples => models}/example_anthropic.py | 0 .../{examples => models}/example_gpt4vison.py | 0 .../{examples => models}/example_idefics.py | 0 .../{examples => models}/example_kosmos.py | 0 .../example_qwenvlmultimodal.py | 0 playground/models/miqu.py | 12 - playground/models/mistral_example.py | 7 - playground/models/mpt_example.py | 9 - playground/models/openai_example.py | 7 - playground/models/openai_model_example.py | 8 +- playground/structs/autoscaler_example.py | 45 --- .../example_concurrentworkflow.py | 0 .../example_recursiveworkflow.py | 0 .../example_sequentialworkflow.py | 0 .../example_swarmnetwork.py | 0 .../swarms/auto_swarm_example.py | 0 .../{ => structs}/swarms/automate_docs.py | 0 .../{ => structs}/swarms/build_a_swarm.py | 0 .../swarms}/example_logistics.py | 0 .../heinz_docs/Geo Finance Frag and.pdf | Bin .../heinz_docs/Geo Frag costs.pdf | Bin .../GeoEconomic Literature IMF 21 June 23.pdf | Bin .../heinz_docs/Investment and FDI.pdf | Bin .../heinz_docs/PIIE Econ war uk.pdf | Bin .../heinz_docs/duplicate not needed.pdf | Bin .../heinz_docs/wpiea2021069-print-pdf.pdf | Bin .../heinz_docs/wpiea2023073-print-pdf.pdf | Bin .../rag_doc_agent.py | 0 .../{ => structs}/swarms/groupchat_example.py | 0 .../swarms/hierarchical_swarm.py | 0 .../{ => structs}/swarms/mixture_of_agents.py | 0 .../{ => structs}/swarms/movers_swarm.py | 0 .../{ => structs}/swarms/relocation_swarm | 0 .../{ => structs}/swarms/swarm_example.py | 0 playground/swarms_example.ipynb | 4 +- playground/utils/pandas_to_str.py | 14 - pyproject.toml | 7 +- scripts/cleanup/code_quality_cleanup.py | 7 + swarms/models/__init__.py | 2 + swarms/models/popular_llms.py | 8 + swarms/structs/Untitled-1.py | 2 +- tests/models/test_cohere.py | 6 +- 74 files changed, 83 insertions(+), 1119 deletions(-) rename playground/{examples => agents}/example_agent.py (100%) rename playground/{examples => agents}/example_task.py (95%) rename playground/{examples => agents}/example_toolagent.py (100%) rename playground/{youtube => agents}/tool.py (100%) rename playground/{ => agents}/tools/agent_with_tools_example.py (100%) rename playground/{ => agents}/tools/func_calling_schema.py (100%) rename playground/{ => agents}/tools/function_to_openai_exec.py (100%) rename playground/{ => agents}/tools/new_tool_wrapper.py (100%) delete mode 100644 playground/creation_engine/omni_model_agent.py rename playground/{ => demos/business_analysis_swarm}/business-analyst-agent.ipynb (100%) delete mode 100644 playground/examples/README.md delete mode 100644 playground/examples/Screenshot from 2024-02-20 05-55-34.png delete mode 100644 playground/examples/example_dalle3.py delete mode 100644 playground/examples/example_huggingfacellm.py delete mode 100644 playground/examples/example_mixtral.py delete mode 100644 playground/examples/example_simple_conversation_agent.py delete mode 100644 playground/examples/example_worker.py delete mode 100644 playground/examples/example_zeroscopetv.py delete mode 100644 playground/memory/chromadb_example.py delete mode 100644 playground/memory/mongodb.py delete mode 100644 playground/memory/qdrant.py delete mode 100644 playground/memory/weaviate_db.py delete mode 100644 playground/models/distilled_whiserpx_example.py rename playground/{examples => models}/example_anthropic.py (100%) rename playground/{examples => models}/example_gpt4vison.py (100%) rename playground/{examples => models}/example_idefics.py (100%) rename playground/{examples => models}/example_kosmos.py (100%) rename playground/{examples => models}/example_qwenvlmultimodal.py (100%) delete mode 100644 playground/models/miqu.py delete mode 100644 playground/models/mistral_example.py delete mode 100644 playground/models/mpt_example.py delete mode 100644 playground/models/openai_example.py delete mode 100644 playground/structs/autoscaler_example.py rename playground/{examples => structs}/example_concurrentworkflow.py (100%) rename playground/{examples => structs}/example_recursiveworkflow.py (100%) rename playground/{examples => structs}/example_sequentialworkflow.py (100%) rename playground/{examples => structs}/example_swarmnetwork.py (100%) rename playground/{ => structs}/swarms/auto_swarm_example.py (100%) rename playground/{ => structs}/swarms/automate_docs.py (100%) rename playground/{ => structs}/swarms/build_a_swarm.py (100%) rename playground/{examples => structs/swarms}/example_logistics.py (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/Geo Finance Frag and.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/Geo Frag costs.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/GeoEconomic Literature IMF 21 June 23.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/Investment and FDI.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/PIIE Econ war uk.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/duplicate not needed.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2021069-print-pdf.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2023073-print-pdf.pdf (100%) rename playground/{ => structs}/swarms/geo_economic_forecast_docs/rag_doc_agent.py (100%) rename playground/{ => structs}/swarms/groupchat_example.py (100%) rename playground/{ => structs}/swarms/hierarchical_swarm.py (100%) rename playground/{ => structs}/swarms/mixture_of_agents.py (100%) rename playground/{ => structs}/swarms/movers_swarm.py (100%) rename playground/{ => structs}/swarms/relocation_swarm (100%) rename playground/{ => structs}/swarms/swarm_example.py (100%) delete mode 100644 playground/utils/pandas_to_str.py create mode 100644 scripts/cleanup/code_quality_cleanup.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21f4b51c..9c9a6c11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,7 +153,3 @@ Please replace `/path/to/directory` with the actual path where the `code-quality If you're asking for a specific content or functionality inside `code-quality.sh` related to YAPF or other code quality tools, you would need to edit the `code-quality.sh` script to include the desired commands, such as running YAPF on a directory. The contents of `code-quality.sh` would dictate exactly what happens when you run it. - -## 📄 license - -By contributing, you agree that your contributions will be licensed under an [MIT license](https://github.com/kyegomez/swarms/blob/develop/LICENSE.md). \ No newline at end of file diff --git a/README.md b/README.md index eba1466c..97fba589 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ [![GitHub issues](https://img.shields.io/github/issues/kyegomez/swarms)](https://github.com/kyegomez/swarms/issues) [![GitHub forks](https://img.shields.io/github/forks/kyegomez/swarms)](https://github.com/kyegomez/swarms/network) [![GitHub stars](https://img.shields.io/github/stars/kyegomez/swarms)](https://github.com/kyegomez/swarms/stargazers) [![GitHub license](https://img.shields.io/github/license/kyegomez/swarms)](https://github.com/kyegomez/swarms/blob/main/LICENSE)[![GitHub star chart](https://img.shields.io/github/stars/kyegomez/swarms?style=social)](https://star-history.com/#kyegomez/swarms)[![Dependency Status](https://img.shields.io/librariesio/github/kyegomez/swarms)](https://libraries.io/github/kyegomez/swarms) [![Downloads](https://static.pepy.tech/badge/swarms/month)](https://pepy.tech/project/swarms) -[![Join the Agora discord](https://img.shields.io/discord/1110910277110743103?label=Discord&logo=discord&logoColor=white&style=plastic&color=d7b023)![Share on Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Share%20%40kyegomez/swarms)](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20AI%20project:%20&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on Facebook](https://img.shields.io/badge/Share-%20facebook-blue)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on LinkedIn](https://img.shields.io/badge/Share-%20linkedin-blue)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=&summary=&source=) +![Share on Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Share%20%40kyegomez/swarms)](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20AI%20project:%20&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on Facebook](https://img.shields.io/badge/Share-%20facebook-blue)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on LinkedIn](https://img.shields.io/badge/Share-%20linkedin-blue)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=&summary=&source=) [![Share on Reddit](https://img.shields.io/badge/-Share%20on%20Reddit-orange)](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=Swarms%20-%20the%20future%20of%20AI) [![Share on Hacker News](https://img.shields.io/badge/-Share%20on%20Hacker%20News-orange)](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&t=Swarms%20-%20the%20future%20of%20AI) [![Share on Pinterest](https://img.shields.io/badge/-Share%20on%20Pinterest-red)](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&media=https%3A%2F%2Fexample.com%2Fimage.jpg&description=Swarms%20-%20the%20future%20of%20AI) [![Share on WhatsApp](https://img.shields.io/badge/-Share%20on%20WhatsApp-green)](https://api.whatsapp.com/send?text=Check%20out%20Swarms%20-%20the%20future%20of%20AI%20%23swarms%20%23AI%0A%0Ahttps%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) @@ -39,21 +39,12 @@ Swarms is an enterprise grade and production ready multi-agent collaboration framework that enables you to orchestrate many agents to work collaboratively at scale to automate real-world activities. -| **Feature** | **Description** | **Performance Impact** | **Documentation Link** | -|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|-------------------------------| -| Models | Pre-trained models that can be utilized for various tasks within the swarm framework. | ⭐⭐⭐ | [Documentation](https://docs.swarms.world/en/latest/swarms/models/) | -| Models APIs | APIs to interact with and utilize the models effectively, providing interfaces for inference, training, and fine-tuning. | ⭐⭐⭐ | [Documentation](https://docs.swarms.world/en/latest/swarms/models/) | -| Agents with Tools | Agents equipped with specialized tools to perform specific tasks more efficiently, such as data processing, analysis, or interaction with external systems. | ⭐⭐⭐⭐ | [Documentation](https://medium.com/@kyeg/the-swarms-tool-system-functions-pydantic-basemodels-as-tools-and-radical-customization-c2a2e227b8ca) | -| Agents with Memory | Mechanisms for agents to store and recall past interactions, improving learning and adaptability over time. | ⭐⭐⭐⭐ | [Documentation](https://github.com/kyegomez/swarms/blob/master/playground/structs/agent/agent_with_longterm_memory.py) | -| Multi-Agent Orchestration | Coordination of multiple agents to work together seamlessly on complex tasks, leveraging their individual strengths to achieve higher overall performance. | ⭐⭐⭐⭐⭐ | [Documentation]() | - -The performance impact is rated on a scale from one to five stars, with multi-agent orchestration being the highest due to its ability to combine the strengths of multiple agents and optimize task execution. - ---- ## Requirements - `python3.10` or above! -- `.env` file with API keys from your providers like `OpenAI`, `Anthropic` +- `.env` file with API keys from your providers like `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` +- `$ pip install -U swarms` And, don't forget to install swarms! ## Install 💻 @@ -65,7 +56,6 @@ $ pip3 install -U swarms # Usage Examples 🤖 -### Google Collab Example Run example in Collab: Open In Colab @@ -630,14 +620,7 @@ In traditional swarm theory, there are many types of swarms usually for very spe ### `SequentialWorkflow` -Sequential Workflow enables you to sequentially execute tasks with `Agent` and then pass the output into the next agent and onwards until you have specified your max loops. `SequentialWorkflow` is wonderful for real-world business tasks like sending emails, summarizing documents, and analyzing data. - - -✅ Save and Restore Workflow states! - -✅ Multi-Modal Support for Visual Chaining - -✅ Utilizes Agent class +Sequential Workflow enables you to sequentially execute tasks with `Agent` and then pass the output into the next agent and onwards until you have specified your max loops. ```python from swarms import Agent, SequentialWorkflow, Anthropic @@ -676,253 +659,7 @@ workflow.run( ``` - - -### `ConcurrentWorkflow` -`ConcurrentWorkflow` runs all the tasks all at the same time with the inputs you give it! - - -```python -import os - -from dotenv import load_dotenv - -from swarms import Agent, ConcurrentWorkflow, OpenAIChat, Task - -# Load environment variables from .env file -load_dotenv() - -# Load environment variables -llm = OpenAIChat(openai_api_key=os.getenv("OPENAI_API_KEY")) - -agent = Agent(llm=llm, max_loops=1) - -# Create a workflow -workflow = ConcurrentWorkflow(max_workers=5) - -# Create tasks -task1 = Task(agent, "What's the weather in miami") -task2 = Task(agent, "What's the weather in new york") -task3 = Task(agent, "What's the weather in london") - -# Add tasks to the workflow -workflow.add(tasks=[task1, task2, task3]) - -# Run the workflow -workflow.run() -``` - - - -### `SwarmNetwork` -`SwarmNetwork` provides the infrasturcture for building extremely dense and complex multi-agent applications that span across various types of agents. - -✅ Efficient Task Management: SwarmNetwork's intelligent agent pool and task queue management system ensures tasks are distributed evenly across agents. This leads to efficient use of resources and faster task completion. - -✅ Scalability: SwarmNetwork can dynamically scale the number of agents based on the number of pending tasks. This means it can handle an increase in workload by adding more agents, and conserve resources when the workload is low by reducing the number of agents. - -✅ Versatile Deployment Options: With SwarmNetwork, each agent can be run on its own thread, process, container, machine, or even cluster. This provides a high degree of flexibility and allows for deployment that best suits the user's needs and infrastructure. - -```python -import os - -from dotenv import load_dotenv - -# Import the OpenAIChat model and the Agent struct -from swarms import Agent, OpenAIChat, SwarmNetwork - -# Load the environment variables -load_dotenv() - -# Get the API key from the environment -api_key = os.environ.get("OPENAI_API_KEY") - -# Initialize the language model -llm = OpenAIChat( - temperature=0.5, - openai_api_key=api_key, -) - -## Initialize the workflow -agent = Agent(llm=llm, max_loops=1, agent_name="Social Media Manager") -agent2 = Agent(llm=llm, max_loops=1, agent_name=" Product Manager") -agent3 = Agent(llm=llm, max_loops=1, agent_name="SEO Manager") - - -# Load the swarmnet with the agents -swarmnet = SwarmNetwork( - agents=[agent, agent2, agent3], -) - -# List the agents in the swarm network -out = swarmnet.list_agents() -print(out) - -# Run the workflow on a task -out = swarmnet.run_single_agent( - agent2.id, "Generate a 10,000 word blog on health and wellness." -) -print(out) - - -# Run all the agents in the swarm network on a task -out = swarmnet.run_many_agents("Generate a 10,000 word blog on health and wellness.") -print(out) -``` - -### Majority Voting -Multiple-agents will evaluate an idea based off of an parsing or evaluation function. From papers like "[More agents is all you need](https://arxiv.org/pdf/2402.05120.pdf) - -```python -from swarms import Agent, MajorityVoting, ChromaDB, Anthropic - -# Initialize the llm -llm = Anthropic() - -# Agents -agent1 = Agent( - llm = llm, - system_prompt="You are the leader of the Progressive Party. What is your stance on healthcare?", - agent_name="Progressive Leader", - agent_description="Leader of the Progressive Party", - long_term_memory=ChromaDB(), - max_steps=1, -) - -agent2 = Agent( - llm=llm, - agent_name="Conservative Leader", - agent_description="Leader of the Conservative Party", - long_term_memory=ChromaDB(), - max_steps=1, -) - -agent3 = Agent( - llm=llm, - agent_name="Libertarian Leader", - agent_description="Leader of the Libertarian Party", - long_term_memory=ChromaDB(), - max_steps=1, -) - -# Initialize the majority voting -mv = MajorityVoting( - agents=[agent1, agent2, agent3], - output_parser=llm.majority_voting, - autosave=False, - verbose=True, -) - - -# Start the majority voting -mv.run("What is your stance on healthcare?") -``` -## Build your own LLMs, Agents, and Swarms! - -### Swarms Compliant Model Interface -```python -from swarms import BaseLLM - -class vLLMLM(BaseLLM): - def __init__(self, model_name='default_model', tensor_parallel_size=1, *args, **kwargs): - super().__init__(*args, **kwargs) - self.model_name = model_name - self.tensor_parallel_size = tensor_parallel_size - # Add any additional initialization here - - def run(self, task: str): - pass - -# Example -model = vLLMLM("mistral") - -# Run the model -out = model("Analyze these financial documents and summarize of them") -print(out) - -``` - - -### Swarms Compliant Agent Interface - -```python -from swarms import Agent - - -class MyCustomAgent(Agent): - -    def __init__(self, *args, **kwargs): - -        super().__init__(*args, **kwargs) - -        # Custom initialization logic - -    def custom_method(self, *args, **kwargs): - -        # Implement custom logic here - -        pass - -    def run(self, task, *args, **kwargs): - -        # Customize the run method - -        response = super().run(task, *args, **kwargs) - -        # Additional custom logic - -        return response` - -# Model -agent = MyCustomAgent() - -# Run the agent -out = agent("Analyze and summarize these financial documents: ") -print(out) - -``` - - -### Compliant Interface for Multi-Agent Collaboration - -```python -from swarms import AutoSwarm, AutoSwarmRouter, BaseSwarm - - -# Build your own Swarm -class MySwarm(BaseSwarm): - def __init__(self, name="kyegomez/myswarm", *args, **kwargs): - super().__init__(*args, **kwargs) - self.name = name - - def run(self, task: str, *args, **kwargs): - # Add your multi-agent logic here - # agent 1 - # agent 2 - # agent 3 - return "output of the swarm" - - -# Add your custom swarm to the AutoSwarmRouter -router = AutoSwarmRouter( - swarms=[MySwarm] -) - - -# Create an AutoSwarm instance -autoswarm = AutoSwarm( - name="kyegomez/myswarm", - description="A simple API to build and run swarms", - verbose=True, - router=router, -) - - -# Run the AutoSwarm -autoswarm.run("Analyze these financial data and give me a summary") - - -``` +------ ## `AgentRearrange` Inspired by Einops and einsum, this orchestration techniques enables you to map out the relationships between various agents. For example you specify linear and sequential relationships like `a -> a1 -> a2 -> a3` or concurrent relationships where the first agent will send a message to 3 agents all at once: `a -> a1, a2, a3`. You can customize your workflow to mix sequential and concurrent relationships. [Docs Available:](https://swarms.apac.ai/en/latest/swarms/structs/agent_rearrange/) @@ -1070,12 +807,12 @@ out = swarm.run("Prepare financial statements and audit financial records") print(out) ``` +---------- + ## Onboarding Session Get onboarded now with the creator and lead maintainer of Swarms, Kye Gomez, who will show you how to get started with the installation, usage examples, and starting to build your custom use case! [CLICK HERE](https://cal.com/swarms/swarms-onboarding-session) - - --- ## Documentation @@ -1083,6 +820,13 @@ Documentation is located here at: [docs.swarms.world](https://docs.swarms.world) ---- + +## Docker Instructions +- [Learn More Here About Deployments In Docker](https://swarms.apac.ai/en/latest/docker_setup/) + + +----- + ## Folder Structure The swarms package has been meticlously crafted for extreme use-ability and understanding, the swarms package is split up into various modules such as `swarms.agents` that holds pre-built agents, `swarms.structs` that holds a vast array of structures like `Agent` and multi agent structures. The 3 most important are `structs`, `models`, and `agents`. @@ -1115,19 +859,12 @@ Swarms is an open-source project, and contributions are VERY welcome. If you wan ---- -## Community -Join our growing community around the world, for real-time support, ideas, and discussions on Swarms 😊 -- View our official [Blog](https://swarms.apac.ai) -- Chat live with us on [Discord](https://discord.gg/kS3rwKs3ZC) -- Follow us on [Twitter](https://twitter.com/kyegomez) -- Connect with us on [LinkedIn](https://www.linkedin.com/company/the-swarm-corporation) -- Visit us on [YouTube](https://www.youtube.com/channel/UC9yXyitkbU_WSy7bd_41SqQ) -- [Join the Swarms community on Discord!](https://discord.gg/AJazBmhKnr) -- Join our Swarms Community Gathering every Thursday at 1pm NYC Time to unlock the potential of autonomous agents in automating your daily tasks [Sign up here](https://lu.ma/5p2jnc2v) +## Swarm Newsletter 🤖 🤖 🤖 📧 +Sign up to the Swarm newsletter to receive updates on the latest Autonomous agent research papers, step by step guides on creating multi-agent app, and much more Swarmie goodiness 😊 ---- +[CLICK HERE TO SIGNUP](https://docs.google.com/forms/d/e/1FAIpQLSfqxI2ktPR9jkcIwzvHL0VY6tEIuVPd-P2fOWKnd6skT9j1EQ/viewform?usp=sf_link) ## Discovery Call Book a discovery call to learn how Swarms can lower your operating costs by 40% with swarms of autonomous agents in lightspeed. [Click here to book a time that works for you!](https://calendly.com/swarm-corp/30min?month=2023-11) @@ -1138,15 +875,19 @@ Accelerate Bugs, Features, and Demos to implement by supporting us here: +## Community -## Docker Instructions -- [Learn More Here About Deployments In Docker](https://swarms.apac.ai/en/latest/docker_setup/) - +Join our growing community around the world, for real-time support, ideas, and discussions on Swarms 😊 -## Swarm Newsletter 🤖 🤖 🤖 📧 -Sign up to the Swarm newsletter to receive updates on the latest Autonomous agent research papers, step by step guides on creating multi-agent app, and much more Swarmie goodiness 😊 +- View our official [Blog](https://swarms.apac.ai) +- Chat live with us on [Discord](https://discord.gg/kS3rwKs3ZC) +- Follow us on [Twitter](https://twitter.com/kyegomez) +- Connect with us on [LinkedIn](https://www.linkedin.com/company/the-swarm-corporation) +- Visit us on [YouTube](https://www.youtube.com/channel/UC9yXyitkbU_WSy7bd_41SqQ) +- [Join the Swarms community on Discord!](https://discord.gg/AJazBmhKnr) +- Join our Swarms Community Gathering every Thursday at 1pm NYC Time to unlock the potential of autonomous agents in automating your daily tasks [Sign up here](https://lu.ma/5p2jnc2v) -[CLICK HERE TO SIGNUP](https://docs.google.com/forms/d/e/1FAIpQLSfqxI2ktPR9jkcIwzvHL0VY6tEIuVPd-P2fOWKnd6skT9j1EQ/viewform?usp=sf_link) +--- # License Apache License diff --git a/docs/applications/business-analyst-agent.md b/docs/applications/business-analyst-agent.md index a7c2f504..39873d03 100644 --- a/docs/applications/business-analyst-agent.md +++ b/docs/applications/business-analyst-agent.md @@ -1,6 +1,6 @@ ## Building Analyst Agents with Swarms to write Business Reports -> Jupyter Notebook accompanying this post is accessible at: [Business Analyst Agent Notebook](https://github.com/kyegomez/swarms/blob/master/playground/business-analyst-agent.ipynb) +> Jupyter Notebook accompanying this post is accessible at: [Business Analyst Agent Notebook](https://github.com/kyegomez/swarms/blob/master/playground/demos/business_analysis_swarm/business-analyst-agent.ipynb) Solving a business problem often involves preparing a Business Case Report. This report comprehensively analyzes the problem, evaluates potential solutions, and provides evidence-based recommendations and an implementation plan to effectively address the issue and drive business value. While the process of preparing one requires an experienced business analyst, the workflow can be augmented using AI agents. Two candidates stick out as areas to work on: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 299c9a97..85f35aa8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -172,9 +172,9 @@ nav: - Memory Systems: - ChromaDB: "swarms_memory/chromadb.md" - Pinecone: "swarms_memory/pinecone.md" - - Redis: "swarms_memory/redis.md" + # - Redis: "swarms_memory/redis.md" - Faiss: "swarms_memory/faiss.md" - - HNSW: "swarms_memory/hnsw.md" + # - HNSW: "swarms_memory/hnsw.md" - References: - Agent Glossary: "swarms/glossary.md" - List of The Best Multi-Agent Papers: "swarms/papers.md" diff --git a/example.py b/example.py index 00e58596..24263e17 100644 --- a/example.py +++ b/example.py @@ -1,5 +1,6 @@ from swarms import Agent, Anthropic + def calculate_profit(revenue: float, expenses: float): """ Calculates the profit by subtracting expenses from revenue. diff --git a/playground/examples/example_agent.py b/playground/agents/example_agent.py similarity index 100% rename from playground/examples/example_agent.py rename to playground/agents/example_agent.py diff --git a/playground/examples/example_task.py b/playground/agents/example_task.py similarity index 95% rename from playground/examples/example_task.py rename to playground/agents/example_task.py index c2ade96a..07b65ee7 100644 --- a/playground/examples/example_task.py +++ b/playground/agents/example_task.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv -from swarms.structs import Agent, OpenAIChat, Task +from swarms import Agent, Task, OpenAIChat # Load the environment variables load_dotenv() diff --git a/playground/examples/example_toolagent.py b/playground/agents/example_toolagent.py similarity index 100% rename from playground/examples/example_toolagent.py rename to playground/agents/example_toolagent.py diff --git a/playground/youtube/tool.py b/playground/agents/tool.py similarity index 100% rename from playground/youtube/tool.py rename to playground/agents/tool.py diff --git a/playground/tools/agent_with_tools_example.py b/playground/agents/tools/agent_with_tools_example.py similarity index 100% rename from playground/tools/agent_with_tools_example.py rename to playground/agents/tools/agent_with_tools_example.py diff --git a/playground/tools/func_calling_schema.py b/playground/agents/tools/func_calling_schema.py similarity index 100% rename from playground/tools/func_calling_schema.py rename to playground/agents/tools/func_calling_schema.py diff --git a/playground/tools/function_to_openai_exec.py b/playground/agents/tools/function_to_openai_exec.py similarity index 100% rename from playground/tools/function_to_openai_exec.py rename to playground/agents/tools/function_to_openai_exec.py diff --git a/playground/tools/new_tool_wrapper.py b/playground/agents/tools/new_tool_wrapper.py similarity index 100% rename from playground/tools/new_tool_wrapper.py rename to playground/agents/tools/new_tool_wrapper.py diff --git a/playground/creation_engine/omni_model_agent.py b/playground/creation_engine/omni_model_agent.py deleted file mode 100644 index 03428ef5..00000000 --- a/playground/creation_engine/omni_model_agent.py +++ /dev/null @@ -1,80 +0,0 @@ -from swarms import Agent, Anthropic, tool - -# Model -llm = Anthropic( - temperature=0.1, -) - - -# Tools -@tool -def text_to_video(task: str): - """ - Converts a given text task into an animated video. - - Args: - task (str): The text task to be converted into a video. - - Returns: - str: The path to the exported GIF file. - """ - import torch - from diffusers import ( - AnimateDiffPipeline, - MotionAdapter, - EulerDiscreteScheduler, - ) - from diffusers.utils import export_to_gif - from huggingface_hub import hf_hub_download - from safetensors.torch import load_file - - device = "cuda" - dtype = torch.float16 - - step = 4 # Options: [1,2,4,8] - repo = "ByteDance/AnimateDiff-Lightning" - ckpt = f"animatediff_lightning_{step}step_diffusers.safetensors" - base = "emilianJR/epiCRealism" # Choose to your favorite base model. - - adapter = MotionAdapter().to(device, dtype) - adapter.load_state_dict( - load_file(hf_hub_download(repo, ckpt), device=device) - ) - pipe = AnimateDiffPipeline.from_pretrained( - base, motion_adapter=adapter, torch_dtype=dtype - ).to(device) - pipe.scheduler = EulerDiscreteScheduler.from_config( - pipe.scheduler.config, - timestep_spacing="trailing", - beta_schedule="linear", - ) - - output = pipe( - prompt=task, guidance_scale=1.0, num_inference_steps=step - ) - out = export_to_gif(output.frames[0], "animation.gif") - return out - - -# 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="auto", - autosave=True, - dashboard=False, - streaming_on=True, - verbose=True, - stopping_token="", - interactive=True, - tools=[text_to_video], -) - -# Run the agent -out = agent("Create a vide of a girl coding AI wearing hijab") -print(out) diff --git a/playground/demos/ai_research_team/main_example.py b/playground/demos/ai_research_team/main_example.py index d77d560d..96f2e417 100644 --- a/playground/demos/ai_research_team/main_example.py +++ b/playground/demos/ai_research_team/main_example.py @@ -30,7 +30,7 @@ llm2 = Anthropic( # Agents paper_summarizer_agent = Agent( - agent_name = "paper_summarizer_agent", + agent_name="paper_summarizer_agent", llm=llm2, sop=PAPER_SUMMARY_ANALYZER, max_loops=1, @@ -39,7 +39,7 @@ paper_summarizer_agent = Agent( ) paper_implementor_agent = Agent( - agent_name = "paper_implementor_agent", + agent_name="paper_implementor_agent", llm=llm1, sop=PAPER_IMPLEMENTOR_AGENT_PROMPT, max_loops=1, @@ -49,7 +49,7 @@ paper_implementor_agent = Agent( ) pytorch_pseudocode_agent = Agent( - agent_name = "pytorch_pseudocode_agent", + agent_name="pytorch_pseudocode_agent", llm=llm1, sop=PAPER_IMPLEMENTOR_AGENT_PROMPT, max_loops=1, @@ -66,10 +66,13 @@ task = f""" """ -agents = [paper_summarizer_agent, paper_implementor_agent, pytorch_pseudocode_agent] +agents = [ + paper_summarizer_agent, + paper_implementor_agent, + pytorch_pseudocode_agent, +] flow = "paper_summarizer_agent -> paper_implementor_agent -> pytorch_pseudocode_agent" swarm = rearrange(agents, flow, task) print(swarm) - diff --git a/playground/business-analyst-agent.ipynb b/playground/demos/business_analysis_swarm/business-analyst-agent.ipynb similarity index 100% rename from playground/business-analyst-agent.ipynb rename to playground/demos/business_analysis_swarm/business-analyst-agent.ipynb diff --git a/playground/demos/octomology_swarm/api.py b/playground/demos/octomology_swarm/api.py index 203ba051..cccf4dfe 100644 --- a/playground/demos/octomology_swarm/api.py +++ b/playground/demos/octomology_swarm/api.py @@ -1,12 +1,10 @@ import os from dotenv import load_dotenv -from pydantic import BaseModel, Field from swarms import Agent from swarms.models import OpenAIChat from swarms.models.gpt4_vision_api import GPT4VisionAPI from swarms.structs.rearrange import AgentRearrange -from typing import Optional, List, Dict, Any # Load the environment variables load_dotenv() @@ -74,69 +72,6 @@ def TREATMENT_PLAN_SYSTEM_PROMPT() -> str: """ -class LLMConfig(BaseModel): - model_name: str - max_tokens: int - - -class AgentConfig(BaseModel): - agent_name: str - system_prompt: str - llm: LLMConfig - max_loops: int - autosave: bool - dashboard: bool - - -class AgentRearrangeConfig(BaseModel): - agents: List[AgentConfig] - flow: str - max_loops: int - verbose: bool - - -class AgentRunResult(BaseModel): - agent_name: str - output: Dict[str, Any] - tokens_generated: int - - -class RunAgentsResponse(BaseModel): - results: List[AgentRunResult] - total_tokens_generated: int - - -class AgentRearrangeResponse(BaseModel): - results: List[AgentRunResult] - total_tokens_generated: int - - -class RunConfig(BaseModel): - task: str = Field(..., title="The task to run") - flow: str = "D -> T" - image: Optional[str] = None # Optional image path as a string - max_loops: Optional[int] = 1 - - -# @app.get("/v1/health") -# async def health_check(): -# return JSONResponse(content={"status": "healthy"}) - - -# @app.get("/v1/models_available") -# async def models_available(): -# available_models = { -# "models": [ -# {"name": "gpt-4-1106-vision-preview", "type": "vision"}, -# {"name": "openai-chat", "type": "text"}, -# ] -# } -# return JSONResponse(content=available_models) - - -# @app.get("/v1/swarm/completions") -# async def run_agents(run_config: RunConfig): -# Diagnoser agent diagnoser = Agent( # agent_name="Medical Image Diagnostic Agent", agent_name="D", @@ -167,4 +102,6 @@ rearranger = AgentRearrange( ) # Run the agents -results = rearranger.run("") +results = rearranger.run( + "Analyze the medical image and provide a treatment plan." +) diff --git a/playground/examples/README.md b/playground/examples/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/playground/examples/Screenshot from 2024-02-20 05-55-34.png b/playground/examples/Screenshot from 2024-02-20 05-55-34.png deleted file mode 100644 index c9f469942967c830aac81fd06fe2034629b8431f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115988 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

5U4+zE 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 AM*si- diff --git a/playground/examples/example_dalle3.py b/playground/examples/example_dalle3.py deleted file mode 100644 index ec3367d2..00000000 --- a/playground/examples/example_dalle3.py +++ /dev/null @@ -1,14 +0,0 @@ -"""from swarms.models import Dalle3 - -# Create an instance of the Dalle3 class with high quality -dalle3 = Dalle3(quality="high") - -# Define a text prompt -task = "A high-quality image of a sunset" - -# Generate a high-quality image from the text prompt -image_url = dalle3(task) - -# Print the generated image URL -print(image_url) -""" diff --git a/playground/examples/example_huggingfacellm.py b/playground/examples/example_huggingfacellm.py deleted file mode 100644 index b21cf773..00000000 --- a/playground/examples/example_huggingfacellm.py +++ /dev/null @@ -1,36 +0,0 @@ -from swarms.models import HuggingfaceLLM -import torch - -try: - inference = HuggingfaceLLM( - model_id="gpt2", - quantize=False, - verbose=True, - ) - - device = "cuda" if torch.cuda.is_available() else "cpu" - inference.model.to(device) - - prompt_text = ( - "Create a list of known biggest risks of structural collapse" - " with references" - ) - inputs = inference.tokenizer(prompt_text, return_tensors="pt").to( - device - ) - - generated_ids = inference.model.generate( - **inputs, - max_new_tokens=1000, # Adjust the length of the generation - temperature=0.7, # Adjust creativity - top_k=50, # Limits the vocabulary considered at each step - pad_token_id=inference.tokenizer.eos_token_id, - do_sample=True, # Enable sampling to utilize temperature - ) - - generated_text = inference.tokenizer.decode( - generated_ids[0], skip_special_tokens=True - ) - print(generated_text) -except Exception as e: - print(f"An error occurred: {e}") diff --git a/playground/examples/example_mixtral.py b/playground/examples/example_mixtral.py deleted file mode 100644 index e1fddb05..00000000 --- a/playground/examples/example_mixtral.py +++ /dev/null @@ -1,10 +0,0 @@ -from swarms.models import Mixtral - -# Initialize the Mixtral model with 4 bit and flash attention! -mixtral = Mixtral(load_in_4bit=True, use_flash_attention_2=True) - -# Generate text for a simple task -generated_text = mixtral.run("Generate a creative story.") - -# Print the generated text -print(generated_text) diff --git a/playground/examples/example_simple_conversation_agent.py b/playground/examples/example_simple_conversation_agent.py deleted file mode 100644 index 49c7694c..00000000 --- a/playground/examples/example_simple_conversation_agent.py +++ /dev/null @@ -1,45 +0,0 @@ -import os - -from dotenv import load_dotenv - -from swarms import ( - OpenAIChat, - Conversation, -) - -conv = Conversation( - time_enabled=True, -) - -# Load the environment variables -load_dotenv() - -# Get the API key from the environment -api_key = os.environ.get("OPENAI_API_KEY") - -# Initialize the language model -llm = OpenAIChat(openai_api_key=api_key, model_name="gpt-4") - - -# Run the language model in a loop -def interactive_conversation(llm): - conv = Conversation() - while True: - user_input = input("User: ") - conv.add("user", user_input) - if user_input.lower() == "quit": - break - task = ( - conv.return_history_as_string() - ) # Get the conversation history - out = llm(task) - conv.add("assistant", out) - print( - f"Assistant: {out}", - ) - conv.display_conversation() - conv.export_conversation("conversation.txt") - - -# Replace with your LLM instance -interactive_conversation(llm) diff --git a/playground/examples/example_worker.py b/playground/examples/example_worker.py deleted file mode 100644 index 8ae32984..00000000 --- a/playground/examples/example_worker.py +++ /dev/null @@ -1,35 +0,0 @@ -# Importing necessary modules -import os -from dotenv import load_dotenv -from swarms import Worker, OpenAIChat, tool - -# Loading environment variables from .env file -load_dotenv() - -# Retrieving the OpenAI API key from environment variables -api_key = os.getenv("OPENAI_API_KEY") - - -# Create a tool -@tool -def search_api(query: str): - pass - - -# Creating a Worker instance -worker = Worker( - name="My Worker", - role="Worker", - human_in_the_loop=False, - tools=[search_api], - temperature=0.5, - llm=OpenAIChat(openai_api_key=api_key), -) - -# Running the worker with a prompt -out = worker.run( - "Hello, how are you? Create an image of how your are doing!" -) - -# Printing the output -print(out) diff --git a/playground/examples/example_zeroscopetv.py b/playground/examples/example_zeroscopetv.py deleted file mode 100644 index e4fb8264..00000000 --- a/playground/examples/example_zeroscopetv.py +++ /dev/null @@ -1,12 +0,0 @@ -# Import the model -from swarms import ZeroscopeTTV - -# Initialize the model -zeroscope = ZeroscopeTTV() - -# Specify the task -task = "A person is walking on the street." - -# Generate the video! -video_path = zeroscope(task) -print(video_path) diff --git a/playground/memory/chromadb_example.py b/playground/memory/chromadb_example.py deleted file mode 100644 index 7dd1d008..00000000 --- a/playground/memory/chromadb_example.py +++ /dev/null @@ -1,186 +0,0 @@ -import logging -import os -import uuid -from typing import Optional - -import chromadb -from dotenv import load_dotenv - -from swarms.utils.data_to_text import data_to_text -from swarms.utils.markdown_message import display_markdown_message -from swarms.memory.base_vectordb import BaseVectorDatabase - -# Load environment variables -load_dotenv() - - -# Results storage using local ChromaDB -class ChromaDB(BaseVectorDatabase): - """ - - ChromaDB database - - Args: - metric (str): The similarity metric to use. - output (str): The name of the collection to store the results in. - limit_tokens (int, optional): The maximum number of tokens to use for the query. Defaults to 1000. - n_results (int, optional): The number of results to retrieve. Defaults to 2. - - Methods: - add: _description_ - query: _description_ - - Examples: - >>> chromadb = ChromaDB( - >>> metric="cosine", - >>> output="results", - >>> llm="gpt3", - >>> openai_api_key=OPENAI_API_KEY, - >>> ) - >>> chromadb.add(task, result, result_id) - """ - - def __init__( - self, - metric: str = "cosine", - output_dir: str = "swarms", - limit_tokens: Optional[int] = 1000, - n_results: int = 1, - docs_folder: str = None, - verbose: bool = False, - *args, - **kwargs, - ): - self.metric = metric - self.output_dir = output_dir - self.limit_tokens = limit_tokens - self.n_results = n_results - self.docs_folder = docs_folder - self.verbose = verbose - - # Disable ChromaDB logging - if verbose: - logging.getLogger("chromadb").setLevel(logging.INFO) - - # Create Chroma collection - chroma_persist_dir = "chroma" - chroma_client = chromadb.PersistentClient( - settings=chromadb.config.Settings( - persist_directory=chroma_persist_dir, - ), - *args, - **kwargs, - ) - - # Create ChromaDB client - self.client = chromadb.Client() - - # Create Chroma collection - self.collection = chroma_client.get_or_create_collection( - name=output_dir, - metadata={"hnsw:space": metric}, - *args, - **kwargs, - ) - display_markdown_message( - "ChromaDB collection created:" - f" {self.collection.name} with metric: {self.metric} and" - f" output directory: {self.output_dir}" - ) - - # If docs - if docs_folder: - display_markdown_message( - f"Traversing directory: {docs_folder}" - ) - self.traverse_directory() - - def add( - self, - document: str, - *args, - **kwargs, - ): - """ - Add a document to the ChromaDB collection. - - Args: - document (str): The document to be added. - condition (bool, optional): The condition to check before adding the document. Defaults to True. - - Returns: - str: The ID of the added document. - """ - try: - doc_id = str(uuid.uuid4()) - self.collection.add( - ids=[doc_id], - documents=[document], - *args, - **kwargs, - ) - print("-----------------") - print("Document added successfully") - print("-----------------") - return doc_id - except Exception as e: - raise Exception(f"Failed to add document: {str(e)}") - - def query( - self, - query_text: str, - *args, - **kwargs, - ) -> str: - """ - Query documents from the ChromaDB collection. - - Args: - query (str): The query string. - n_docs (int, optional): The number of documents to retrieve. Defaults to 1. - - Returns: - dict: The retrieved documents. - """ - try: - logging.info(f"Querying documents for: {query_text}") - docs = self.collection.query( - query_texts=[query_text], - n_results=self.n_results, - *args, - **kwargs, - )["documents"] - - # Convert into a string - out = "" - for doc in docs: - out += f"{doc}\n" - - # Display the retrieved document - display_markdown_message(f"Query: {query_text}") - display_markdown_message(f"Retrieved Document: {out}") - return out - - except Exception as e: - raise Exception(f"Failed to query documents: {str(e)}") - - def traverse_directory(self): - """ - Traverse through every file in the given directory and its subdirectories, - and return the paths of all files. - Parameters: - - directory_name (str): The name of the directory to traverse. - Returns: - - list: A list of paths to each file in the directory and its subdirectories. - """ - added_to_db = False - - for root, dirs, files in os.walk(self.docs_folder): - for file in files: - file_path = os.path.join(root, file) # Change this line - _, ext = os.path.splitext(file_path) - data = data_to_text(file_path) - added_to_db = self.add(str(data)) - print(f"{file_path} added to Database") - - return added_to_db diff --git a/playground/memory/mongodb.py b/playground/memory/mongodb.py deleted file mode 100644 index b37a5a2c..00000000 --- a/playground/memory/mongodb.py +++ /dev/null @@ -1,14 +0,0 @@ -from pymongo.mongo_client import MongoClient -from pymongo.server_api import ServerApi - -uri = "mongodb+srv://kye:Kgx7d2FeLN7AyGNh@cluster0.ndu3b6d.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" - -# Create a new client and connect to the server -client = MongoClient(uri, server_api=ServerApi("1")) - -# Send a ping to confirm a successful connection -try: - client.admin.command("ping") - print("Pinged your deployment. You successfully connected to MongoDB!") -except Exception as e: - print(e) diff --git a/playground/memory/pinecone.py b/playground/memory/pinecone.py index 54e6ea5b..32cb5a68 100644 --- a/playground/memory/pinecone.py +++ b/playground/memory/pinecone.py @@ -4,7 +4,7 @@ import pinecone from attr import define, field from swarms.memory.base_vectordb import BaseVectorDatabase -from swarms.utils.hash import str_to_hash +from swarms.utils import str_to_hash @define diff --git a/playground/memory/qdrant.py b/playground/memory/qdrant.py deleted file mode 100644 index 8004ae02..00000000 --- a/playground/memory/qdrant.py +++ /dev/null @@ -1,25 +0,0 @@ -from langchain.document_loaders import CSVLoader - -from swarms.memory import qdrant - -loader = CSVLoader( - file_path="../document_parsing/aipg/aipg.csv", - encoding="utf-8-sig", -) -docs = loader.load() - - -# Initialize the Qdrant instance -# See qdrant documentation on how to run locally -qdrant_client = qdrant.Qdrant( - host="https://697ea26c-2881-4e17-8af4-817fcb5862e8.europe-west3-0.gcp.cloud.qdrant.io", - collection_name="qdrant", -) -qdrant_client.add_vectors(docs) - -# Perform a search -search_query = "Who is jojo" -search_results = qdrant_client.search_vectors(search_query) -print("Search Results:") -for result in search_results: - print(result) diff --git a/playground/memory/weaviate_db.py b/playground/memory/weaviate_db.py deleted file mode 100644 index e9d7496d..00000000 --- a/playground/memory/weaviate_db.py +++ /dev/null @@ -1,180 +0,0 @@ -""" -Weaviate API Client -""" - -from typing import Any, Dict, List, Optional - -from swarms.memory.base_vectordb import BaseVectorDatabase - -try: - import weaviate -except ImportError: - print("pip install weaviate-client") - - -class WeaviateDB(BaseVectorDatabase): - """ - - Weaviate API Client - Interface to Weaviate, a vector database with a GraphQL API. - - Args: - http_host (str): The HTTP host of the Weaviate server. - http_port (str): The HTTP port of the Weaviate server. - http_secure (bool): Whether to use HTTPS. - grpc_host (Optional[str]): The gRPC host of the Weaviate server. - grpc_port (Optional[str]): The gRPC port of the Weaviate server. - grpc_secure (Optional[bool]): Whether to use gRPC over TLS. - auth_client_secret (Optional[Any]): The authentication client secret. - additional_headers (Optional[Dict[str, str]]): Additional headers to send with requests. - additional_config (Optional[weaviate.AdditionalConfig]): Additional configuration for the client. - - Methods: - create_collection: Create a new collection in Weaviate. - add: Add an object to a specified collection. - query: Query objects from a specified collection. - update: Update an object in a specified collection. - delete: Delete an object from a specified collection. - - Examples: - >>> from swarms.memory import WeaviateDB - """ - - def __init__( - self, - http_host: str, - http_port: str, - http_secure: bool, - grpc_host: Optional[str] = None, - grpc_port: Optional[str] = None, - grpc_secure: Optional[bool] = None, - auth_client_secret: Optional[Any] = None, - additional_headers: Optional[Dict[str, str]] = None, - additional_config: Optional[Any] = None, - connection_params: Dict[str, Any] = None, - *args, - **kwargs, - ): - super().__init__(*args, **kwargs) - self.http_host = http_host - self.http_port = http_port - self.http_secure = http_secure - self.grpc_host = grpc_host - self.grpc_port = grpc_port - self.grpc_secure = grpc_secure - self.auth_client_secret = auth_client_secret - self.additional_headers = additional_headers - self.additional_config = additional_config - self.connection_params = connection_params - - # If connection_params are provided, use them to initialize the client. - connection_params = weaviate.ConnectionParams.from_params( - http_host=http_host, - http_port=http_port, - http_secure=http_secure, - grpc_host=grpc_host, - grpc_port=grpc_port, - grpc_secure=grpc_secure, - ) - - # If additional headers are provided, add them to the connection params. - self.client = weaviate.WeaviateDB( - connection_params=connection_params, - auth_client_secret=auth_client_secret, - additional_headers=additional_headers, - additional_config=additional_config, - ) - - def create_collection( - self, - name: str, - properties: List[Dict[str, Any]], - vectorizer_config: Any = None, - ): - """Create a new collection in Weaviate. - - Args: - name (str): _description_ - properties (List[Dict[str, Any]]): _description_ - vectorizer_config (Any, optional): _description_. Defaults to None. - """ - try: - out = self.client.collections.create( - name=name, - vectorizer_config=vectorizer_config, - properties=properties, - ) - print(out) - except Exception as error: - print(f"Error creating collection: {error}") - raise - - def add(self, collection_name: str, properties: Dict[str, Any]): - """Add an object to a specified collection. - - Args: - collection_name (str): _description_ - properties (Dict[str, Any]): _description_ - - Returns: - _type_: _description_ - """ - try: - collection = self.client.collections.get(collection_name) - return collection.data.insert(properties) - except Exception as error: - print(f"Error adding object: {error}") - raise - - def query(self, collection_name: str, query: str, limit: int = 10): - """Query objects from a specified collection. - - Args: - collection_name (str): _description_ - query (str): _description_ - limit (int, optional): _description_. Defaults to 10. - - Returns: - _type_: _description_ - """ - try: - collection = self.client.collections.get(collection_name) - response = collection.query.bm25(query=query, limit=limit) - return [o.properties for o in response.objects] - except Exception as error: - print(f"Error querying objects: {error}") - raise - - def update( - self, - collection_name: str, - object_id: str, - properties: Dict[str, Any], - ): - """UPdate an object in a specified collection. - - Args: - collection_name (str): _description_ - object_id (str): _description_ - properties (Dict[str, Any]): _description_ - """ - try: - collection = self.client.collections.get(collection_name) - collection.data.update(object_id, properties) - except Exception as error: - print(f"Error updating object: {error}") - raise - - def delete(self, collection_name: str, object_id: str): - """Delete an object from a specified collection. - - Args: - collection_name (str): _description_ - object_id (str): _description_ - """ - try: - collection = self.client.collections.get(collection_name) - collection.data.delete_by_id(object_id) - except Exception as error: - print(f"Error deleting object: {error}") - raise diff --git a/playground/models/anthropic_example.py b/playground/models/anthropic_example.py index 0d8a7a4f..22dc6c00 100644 --- a/playground/models/anthropic_example.py +++ b/playground/models/anthropic_example.py @@ -1,6 +1,8 @@ +import os + from swarms.models import Anthropic -model = Anthropic(anthropic_api_key="") +model = Anthropic(anthropic_api_key=os.getenv("ANTHROPIC_API_KEY")) task = "What is quantum field theory? What are 3 books on the field?" diff --git a/playground/models/distilled_whiserpx_example.py b/playground/models/distilled_whiserpx_example.py deleted file mode 100644 index 1f6f0bc1..00000000 --- a/playground/models/distilled_whiserpx_example.py +++ /dev/null @@ -1,13 +0,0 @@ -import asyncio - -from swarms.models.distilled_whisperx import DistilWhisperModel - -model_wrapper = DistilWhisperModel() - -# Download mp3 of voice and place the path here -transcription = model_wrapper("path/to/audio.mp3") - -# For async usage -transcription = asyncio.run( - model_wrapper.async_transcribe("path/to/audio.mp3") -) diff --git a/playground/examples/example_anthropic.py b/playground/models/example_anthropic.py similarity index 100% rename from playground/examples/example_anthropic.py rename to playground/models/example_anthropic.py diff --git a/playground/examples/example_gpt4vison.py b/playground/models/example_gpt4vison.py similarity index 100% rename from playground/examples/example_gpt4vison.py rename to playground/models/example_gpt4vison.py diff --git a/playground/examples/example_idefics.py b/playground/models/example_idefics.py similarity index 100% rename from playground/examples/example_idefics.py rename to playground/models/example_idefics.py diff --git a/playground/examples/example_kosmos.py b/playground/models/example_kosmos.py similarity index 100% rename from playground/examples/example_kosmos.py rename to playground/models/example_kosmos.py diff --git a/playground/examples/example_qwenvlmultimodal.py b/playground/models/example_qwenvlmultimodal.py similarity index 100% rename from playground/examples/example_qwenvlmultimodal.py rename to playground/models/example_qwenvlmultimodal.py diff --git a/playground/models/miqu.py b/playground/models/miqu.py deleted file mode 100644 index a4c9430a..00000000 --- a/playground/models/miqu.py +++ /dev/null @@ -1,12 +0,0 @@ -from swarms import Mistral - -# Initialize the model -model = Mistral( - model_name="miqudev/miqu-1-70b", - max_length=500, - use_flash_attention=True, - load_in_4bit=True, -) - -# Run the model -result = model.run("What is the meaning of life?") diff --git a/playground/models/mistral_example.py b/playground/models/mistral_example.py deleted file mode 100644 index f1731aff..00000000 --- a/playground/models/mistral_example.py +++ /dev/null @@ -1,7 +0,0 @@ -from swarms.models import Mistral - -model = Mistral(device="cuda", use_flash_attention=True) - -prompt = "My favourite condiment is" -result = model.run(prompt) -print(result) diff --git a/playground/models/mpt_example.py b/playground/models/mpt_example.py deleted file mode 100644 index 8ffa30db..00000000 --- a/playground/models/mpt_example.py +++ /dev/null @@ -1,9 +0,0 @@ -from swarms.models.mpt import MPT - -mpt_instance = MPT( - "mosaicml/mpt-7b-storywriter", - "EleutherAI/gpt-neox-20b", - max_tokens=150, -) - -mpt_instance.generate("Once upon a time in a land far, far away...") diff --git a/playground/models/openai_example.py b/playground/models/openai_example.py deleted file mode 100644 index aacab66f..00000000 --- a/playground/models/openai_example.py +++ /dev/null @@ -1,7 +0,0 @@ -from swarms.models.openai_chat import OpenAIChat - -model = OpenAIChat() - -out = model("Hello, how are you?") - -print(out) diff --git a/playground/models/openai_model_example.py b/playground/models/openai_model_example.py index 3b9cb967..1a58770c 100644 --- a/playground/models/openai_model_example.py +++ b/playground/models/openai_model_example.py @@ -1,6 +1,10 @@ -from swarms.models.openai_models import OpenAIChat +import os +from swarms.models import OpenAIChat -openai = OpenAIChat(openai_api_key="", verbose=False) +# Load doten +openai = OpenAIChat( + openai_api_key=os.getenv("OPENAI_API_KEY"), verbose=False +) chat = openai("What are quantum fields?") print(chat) diff --git a/playground/structs/autoscaler_example.py b/playground/structs/autoscaler_example.py deleted file mode 100644 index aa7cf0c0..00000000 --- a/playground/structs/autoscaler_example.py +++ /dev/null @@ -1,45 +0,0 @@ -import os - -from dotenv import load_dotenv - -# Import the OpenAIChat model and the Agent struct -from swarms.models import OpenAIChat -from swarms.structs import Agent -from swarms.structs.autoscaler import AutoScaler - -# Load the environment variables -load_dotenv() - -# Get the API key from the environment -api_key = os.environ.get("OPENAI_API_KEY") - -# Initialize the language model -llm = OpenAIChat( - temperature=0.5, - openai_api_key=api_key, -) - - -## Initialize the workflow -agent = Agent(llm=llm, max_loops=1, dashboard=True) - - -# Load the autoscaler -autoscaler = AutoScaler( - initial_agents=2, - scale_up_factor=1, - idle_threshold=0.2, - busy_threshold=0.7, - agents=[agent], - autoscale=True, - min_agents=1, - max_agents=5, - custom_scale_strategy=None, -) -print(autoscaler) - -# Run the workflow on a task -out = autoscaler.run( - agent.id, "Generate a 10,000 word blog on health and wellness." -) -print(out) diff --git a/playground/examples/example_concurrentworkflow.py b/playground/structs/example_concurrentworkflow.py similarity index 100% rename from playground/examples/example_concurrentworkflow.py rename to playground/structs/example_concurrentworkflow.py diff --git a/playground/examples/example_recursiveworkflow.py b/playground/structs/example_recursiveworkflow.py similarity index 100% rename from playground/examples/example_recursiveworkflow.py rename to playground/structs/example_recursiveworkflow.py diff --git a/playground/examples/example_sequentialworkflow.py b/playground/structs/example_sequentialworkflow.py similarity index 100% rename from playground/examples/example_sequentialworkflow.py rename to playground/structs/example_sequentialworkflow.py diff --git a/playground/examples/example_swarmnetwork.py b/playground/structs/example_swarmnetwork.py similarity index 100% rename from playground/examples/example_swarmnetwork.py rename to playground/structs/example_swarmnetwork.py diff --git a/playground/swarms/auto_swarm_example.py b/playground/structs/swarms/auto_swarm_example.py similarity index 100% rename from playground/swarms/auto_swarm_example.py rename to playground/structs/swarms/auto_swarm_example.py diff --git a/playground/swarms/automate_docs.py b/playground/structs/swarms/automate_docs.py similarity index 100% rename from playground/swarms/automate_docs.py rename to playground/structs/swarms/automate_docs.py diff --git a/playground/swarms/build_a_swarm.py b/playground/structs/swarms/build_a_swarm.py similarity index 100% rename from playground/swarms/build_a_swarm.py rename to playground/structs/swarms/build_a_swarm.py diff --git a/playground/examples/example_logistics.py b/playground/structs/swarms/example_logistics.py similarity index 100% rename from playground/examples/example_logistics.py rename to playground/structs/swarms/example_logistics.py diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/Geo Finance Frag and.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Geo Finance Frag and.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/Geo Finance Frag and.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Geo Finance Frag and.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/Geo Frag costs.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Geo Frag costs.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/Geo Frag costs.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Geo Frag costs.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/GeoEconomic Literature IMF 21 June 23.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/GeoEconomic Literature IMF 21 June 23.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/GeoEconomic Literature IMF 21 June 23.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/GeoEconomic Literature IMF 21 June 23.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/Investment and FDI.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Investment and FDI.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/Investment and FDI.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/Investment and FDI.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/PIIE Econ war uk.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/PIIE Econ war uk.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/PIIE Econ war uk.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/PIIE Econ war uk.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/duplicate not needed.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/duplicate not needed.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/duplicate not needed.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/duplicate not needed.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2021069-print-pdf.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2021069-print-pdf.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2021069-print-pdf.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2021069-print-pdf.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2023073-print-pdf.pdf b/playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2023073-print-pdf.pdf similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2023073-print-pdf.pdf rename to playground/structs/swarms/geo_economic_forecast_docs/heinz_docs/wpiea2023073-print-pdf.pdf diff --git a/playground/swarms/geo_economic_forecast_docs/rag_doc_agent.py b/playground/structs/swarms/geo_economic_forecast_docs/rag_doc_agent.py similarity index 100% rename from playground/swarms/geo_economic_forecast_docs/rag_doc_agent.py rename to playground/structs/swarms/geo_economic_forecast_docs/rag_doc_agent.py diff --git a/playground/swarms/groupchat_example.py b/playground/structs/swarms/groupchat_example.py similarity index 100% rename from playground/swarms/groupchat_example.py rename to playground/structs/swarms/groupchat_example.py diff --git a/playground/swarms/hierarchical_swarm.py b/playground/structs/swarms/hierarchical_swarm.py similarity index 100% rename from playground/swarms/hierarchical_swarm.py rename to playground/structs/swarms/hierarchical_swarm.py diff --git a/playground/swarms/mixture_of_agents.py b/playground/structs/swarms/mixture_of_agents.py similarity index 100% rename from playground/swarms/mixture_of_agents.py rename to playground/structs/swarms/mixture_of_agents.py diff --git a/playground/swarms/movers_swarm.py b/playground/structs/swarms/movers_swarm.py similarity index 100% rename from playground/swarms/movers_swarm.py rename to playground/structs/swarms/movers_swarm.py diff --git a/playground/swarms/relocation_swarm b/playground/structs/swarms/relocation_swarm similarity index 100% rename from playground/swarms/relocation_swarm rename to playground/structs/swarms/relocation_swarm diff --git a/playground/swarms/swarm_example.py b/playground/structs/swarms/swarm_example.py similarity index 100% rename from playground/swarms/swarm_example.py rename to playground/structs/swarms/swarm_example.py diff --git a/playground/swarms_example.ipynb b/playground/swarms_example.ipynb index 83951984..c1b5c160 100644 --- a/playground/swarms_example.ipynb +++ b/playground/swarms_example.ipynb @@ -142,7 +142,9 @@ "metadata": {}, "outputs": [], "source": [ - "from swarms import Agent, ChromaDB, OpenAIChat, tool\n", + "# !pip install swarms-memory\n", + "from swarms import Agent, OpenAIChat, tool\n", + "from swarms_memory import ChromaDB\n", "\n", "# Making an instance of the ChromaDB class\n", "memory = ChromaDB(\n", diff --git a/playground/utils/pandas_to_str.py b/playground/utils/pandas_to_str.py deleted file mode 100644 index 1f599818..00000000 --- a/playground/utils/pandas_to_str.py +++ /dev/null @@ -1,14 +0,0 @@ -import pandas as pd - -from swarms import dataframe_to_text - -# # Example usage: -df = pd.DataFrame( - { - "A": [1, 2, 3], - "B": [4, 5, 6], - "C": [7, 8, 9], - } -) - -print(dataframe_to_text(df)) diff --git a/pyproject.toml b/pyproject.toml index 9014c64f..bbccb931 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms" -version = "5.3.3" +version = "5.3.4" description = "Swarms - Pytorch" license = "MIT" authors = ["Kye Gomez "] @@ -20,6 +20,11 @@ keywords = [ "Prompt Engineering", "swarms", "agents", + "llms", + "transformers", + "multi-agent", + "swarms of agents", + "chicken nuggets", ] classifiers = [ "Development Status :: 4 - Beta", diff --git a/scripts/cleanup/code_quality_cleanup.py b/scripts/cleanup/code_quality_cleanup.py new file mode 100644 index 00000000..a32d15e9 --- /dev/null +++ b/scripts/cleanup/code_quality_cleanup.py @@ -0,0 +1,7 @@ +""" +A script that runs ruff, black, autopep8, and all other formatters in one python script on a cron job. + +- Perhaps make a github workflow as well + + +""" diff --git a/swarms/models/__init__.py b/swarms/models/__init__.py index e7823eff..f221190c 100644 --- a/swarms/models/__init__.py +++ b/swarms/models/__init__.py @@ -40,6 +40,7 @@ from swarms.models.types import ( # noqa: E402 VideoModality, ) from swarms.models.vilt import Vilt # noqa: E402 +from swarms.models.popular_llms import FireWorksAI __all__ = [ "BaseEmbeddingModel", @@ -75,4 +76,5 @@ __all__ = [ "OpenAIEmbeddings", "llama3Hosted", "GPT4o", + "FireWorksAI", ] diff --git a/swarms/models/popular_llms.py b/swarms/models/popular_llms.py index 4c80caec..b600b4c6 100644 --- a/swarms/models/popular_llms.py +++ b/swarms/models/popular_llms.py @@ -80,3 +80,11 @@ class OctoAIChat(OctoAIEndpoint): def run(self, *args, **kwargs): return self.invoke(*args, **kwargs) + + +class FireWorksAI(Fireworks): + def __call__(self, *args, **kwargs): + return self.invoke(*args, **kwargs) + + def run(self, *args, **kwargs): + return self.invoke(*args, **kwargs) diff --git a/swarms/structs/Untitled-1.py b/swarms/structs/Untitled-1.py index 903e034f..7d664343 100644 --- a/swarms/structs/Untitled-1.py +++ b/swarms/structs/Untitled-1.py @@ -236,7 +236,7 @@ boss_agent_creator = Agent( def run_jamba_swarm(task: str = None): logger.info(f"Making plan for the task: {task}") - out = planning_agent.run(task) + planning_agent.run(task) memory = planning_agent.short_memory.return_history_as_string() diff --git a/tests/models/test_cohere.py b/tests/models/test_cohere.py index 131798e8..7dde385e 100644 --- a/tests/models/test_cohere.py +++ b/tests/models/test_cohere.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch import pytest from dotenv import load_dotenv -from swarms.models import BaseCohere, Cohere +from swarms import Cohere # Load the environment variables load_dotenv() @@ -154,7 +154,7 @@ def test_base_cohere_validate_environment(): "cohere_api_key": "my-api-key", "user_agent": "langchain", } - validated_values = BaseCohere.validate_environment(values) + validated_values = Cohere.validate_environment(values) assert "client" in validated_values assert "async_client" in validated_values @@ -166,7 +166,7 @@ def test_base_cohere_validate_environment_without_cohere(): } with patch.dict("sys.modules", {"cohere": None}): with pytest.raises(ImportError): - BaseCohere.validate_environment(values) + Cohere.validate_environment(values) # Test cases for benchmarking generations with various models From 78e87dd0f71148867fc4135f0917b8dad3492238 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 9 Jul 2024 17:02:01 -0700 Subject: [PATCH 16/19] [README] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97fba589..cab4d4ac 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ [![GitHub issues](https://img.shields.io/github/issues/kyegomez/swarms)](https://github.com/kyegomez/swarms/issues) [![GitHub forks](https://img.shields.io/github/forks/kyegomez/swarms)](https://github.com/kyegomez/swarms/network) [![GitHub stars](https://img.shields.io/github/stars/kyegomez/swarms)](https://github.com/kyegomez/swarms/stargazers) [![GitHub license](https://img.shields.io/github/license/kyegomez/swarms)](https://github.com/kyegomez/swarms/blob/main/LICENSE)[![GitHub star chart](https://img.shields.io/github/stars/kyegomez/swarms?style=social)](https://star-history.com/#kyegomez/swarms)[![Dependency Status](https://img.shields.io/librariesio/github/kyegomez/swarms)](https://libraries.io/github/kyegomez/swarms) [![Downloads](https://static.pepy.tech/badge/swarms/month)](https://pepy.tech/project/swarms) -![Share on Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Share%20%40kyegomez/swarms)](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20AI%20project:%20&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on Facebook](https://img.shields.io/badge/Share-%20facebook-blue)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on LinkedIn](https://img.shields.io/badge/Share-%20linkedin-blue)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=&summary=&source=) +[![Share on Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Share%20%40kyegomez/swarms)](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20AI%20project:%20&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on Facebook](https://img.shields.io/badge/Share-%20facebook-blue)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) [![Share on LinkedIn](https://img.shields.io/badge/Share-%20linkedin-blue)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=&summary=&source=) [![Share on Reddit](https://img.shields.io/badge/-Share%20on%20Reddit-orange)](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&title=Swarms%20-%20the%20future%20of%20AI) [![Share on Hacker News](https://img.shields.io/badge/-Share%20on%20Hacker%20News-orange)](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&t=Swarms%20-%20the%20future%20of%20AI) [![Share on Pinterest](https://img.shields.io/badge/-Share%20on%20Pinterest-red)](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms&media=https%3A%2F%2Fexample.com%2Fimage.jpg&description=Swarms%20-%20the%20future%20of%20AI) [![Share on WhatsApp](https://img.shields.io/badge/-Share%20on%20WhatsApp-green)](https://api.whatsapp.com/send?text=Check%20out%20Swarms%20-%20the%20future%20of%20AI%20%23swarms%20%23AI%0A%0Ahttps%3A%2F%2Fgithub.com%2Fkyegomez%2Fswarms) From 6861e1d9f05ef44cdb0ddf348dcd82d3c891e5a3 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Wed, 10 Jul 2024 16:08:13 -0700 Subject: [PATCH 17/19] [CLEANUP] --- README.md | 62 ++++++++++++++------- example.py | 29 ---------- playground/structs/agent_registry.py | 9 ++++ scripts/auto_tests_docs/docs.py | 3 +- swarms/structs/rearrange.py | 80 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index cab4d4ac..d1b604b4 100644 --- a/README.md +++ b/README.md @@ -75,29 +75,54 @@ Features: ```python import os +from swarms import Agent, Anthropic -from dotenv import load_dotenv - -# Import the OpenAIChat model and the Agent struct -from swarms import Agent, OpenAIChat - -# Load the environment variables -load_dotenv() - -# Get the API key from the environment -api_key = os.environ.get("OPENAI_API_KEY") -# Initialize the language model -llm = OpenAIChat( - temperature=0.5, openai_api_key=api_key, max_tokens=4000 +# Initialize the agent +agent = Agent( + agent_name="Accounting Assistant", + system_prompt="You're the accounting agent, your purpose is to generate a profit report for a company!", + agent_description="Generate a profit report for a company!", + llm=Anthropic( + anthropic_api_key = os.getenv("ANTHROPIC_API_KEY") + ), + max_loops="auto", + autosave=True, + # dynamic_temperature_enabled=True, + dashboard=False, + verbose=True, + streaming_on=True, + # interactive=True, # Set to False to disable interactive mode + saved_state_path="accounting_agent.json", + # tools=[ + # # calculate_profit, + # # generate_report, + # # search_knowledge_base, + # # write_memory_to_rag, + # # search_knowledge_base, + # # generate_speech, + # ], + stopping_token="Stop!", + interactive=True, + # docs_folder="docs", + # pdf_path="docs/accounting_agent.pdf", + # sop="Calculate the profit for a company.", + # sop_list=["Calculate the profit for a company."], + # user_name="User", + # # docs= + # # docs_folder="docs", + # retry_attempts=3, + # context_length=1000, + # tool_schema = dict + context_length=1000, + # agent_ops_on=True, + # long_term_memory=ChromaDB(docs_folder="artifacts"), ) +agent.run( + "Search the knowledge base for the swarms github framework and how it works" +) -## Initialize the workflow -agent = Agent(llm=llm, max_loops=1, autosave=True, dashboard=True) - -# Run the workflow on a task -agent.run("Generate a 10,000 word blog on health and wellness.") ``` @@ -131,6 +156,7 @@ llm = OpenAIChat( ## Initialize the workflow agent = Agent( llm=llm, + agent_name: str = "WellNess Agent", name = "Health and Wellness Blog", system_prompt="Generate a 10,000 word blog on health and wellness.", max_loops=4, diff --git a/example.py b/example.py index 24263e17..fc67a36e 100644 --- a/example.py +++ b/example.py @@ -1,34 +1,5 @@ from swarms import Agent, Anthropic - -def calculate_profit(revenue: float, expenses: float): - """ - Calculates the profit by subtracting expenses from revenue. - - Args: - revenue (float): The total revenue. - expenses (float): The total expenses. - - Returns: - float: The calculated profit. - """ - return revenue - expenses - - -def generate_report(company_name: str, profit: float): - """ - Generates a report for a company's profit. - - Args: - company_name (str): The name of the company. - profit (float): The calculated profit. - - Returns: - str: The report for the company's profit. - """ - return f"The profit for {company_name} is ${profit}." - - # Initialize the agent agent = Agent( agent_name="Accounting Assistant", diff --git a/playground/structs/agent_registry.py b/playground/structs/agent_registry.py index 79541665..8a798fd9 100644 --- a/playground/structs/agent_registry.py +++ b/playground/structs/agent_registry.py @@ -77,3 +77,12 @@ registry.add("Marketing Specialist", growth_agent1) registry.add("Sales Specialist", growth_agent2) registry.add("Product Development Specialist", growth_agent3) registry.add("Customer Service Specialist", growth_agent4) + + +# Query the agents +registry.get("Marketing Specialist") +registry.get("Sales Specialist") +registry.get("Product Development Specialist") + +# Get all the agents +registry.list_agents() \ No newline at end of file diff --git a/scripts/auto_tests_docs/docs.py b/scripts/auto_tests_docs/docs.py index 01df9d71..fd9bd276 100644 --- a/scripts/auto_tests_docs/docs.py +++ b/scripts/auto_tests_docs/docs.py @@ -110,7 +110,8 @@ def TEST_WRITER_SOP_PROMPT( TESTS_PROMPT = f""" Create 5,000 lines of extensive and thorough tests for the code below using the guide, do not worry about your limits you do not have any - just write the best tests possible, the module is {module}, the file path is {path} + just write the best tests possible, the module is {module}, the file path is {path} return all of the code in one file, make sure to test all the functions and methods in the code. + ######### TESTING GUIDE ############# diff --git a/swarms/structs/rearrange.py b/swarms/structs/rearrange.py index 2886bbe8..810537fd 100644 --- a/swarms/structs/rearrange.py +++ b/swarms/structs/rearrange.py @@ -50,6 +50,7 @@ class AgentRearrange(BaseSwarm): self.human_in_the_loop = human_in_the_loop self.custom_human_in_the_loop = custom_human_in_the_loop self.swarm_history = {agent.agent_name: [] for agent in agents} + self.sub_swarm = {} # Verbose is True if verbose is True: @@ -66,6 +67,14 @@ class AgentRearrange(BaseSwarm): ) ) + def add_sub_swarm(self, name: str, flow: str): + self.sub_swarm[name] = flow + logger.info(f"Sub-swarm {name} added with flow: {flow}") + + def set_custom_flow(self, flow: str): + self.flow = flow + logger.info(f"Custom flow set: {flow}") + def add_agent(self, agent: Agent): """ Adds an agent to the swarm. @@ -251,6 +260,77 @@ class AgentRearrange(BaseSwarm): logger.error(f"An error occurred: {e}") return e + def process_agent_or_swarm( + self, name: str, task: str, img: str, *args, **kwargs + ): + """ + + process_agent_or_swarm: Processes the agent or sub-swarm based on the given name. + + Args: + name (str): The name of the agent or sub-swarm to process. + task (str): The task to be executed. + img (str): The image to be processed by the agents. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + str: The result of the last executed task. + + """ + if name.startswith("Human"): + return self.human_intervention(task) + elif name in self.sub_swarm: + return self.run_sub_swarm(task, name, img, *args, **kwargs) + else: + agent = self.agents[name] + return agent.run(task, *args, **kwargs) + + def human_intervention(self, task: str) -> str: + if self.human_in_the_loop and self.custom_human_in_the_loop: + return self.custom_human_in_the_loop(task) + else: + return input( + "Human intervention required. Enter your response: " + ) + + def run_sub_swarm( + self, swarm_name: str, task: str, img: str, *args, **kwargs + ): + """ + Runs a sub-swarm by executing a sequence of tasks on a set of agents. + + Args: + swarm_name (str): The name of the sub-swarm to run. + task (str): The initial task to be executed. + img (str): The image to be processed by the agents. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + str: The result of the last executed task. + + """ + sub_flow = self.sub_swarm[swarm_name] + sub_tasks = sub_flow.split("->") + current_task = task + + for sub_task in sub_tasks: + agent_names = [name.strip() for name in sub_task.split(",")] + if len(agent_names) > 1: + results = [] + for agent_name in agent_names: + result = self.process_agent_or_swarm( + agent_name, current_task, img, *args, **kwargs + ) + results.append(result) + current_task = "; ".join(results) + else: + current_task = self.process_agent_or_swarm( + agent_names[0], current_task, img, *args, **kwargs + ) + return current_task + def rearrange( agents: List[Agent] = None, From e08d96b5ccbc93f2fee3f56d79f59f7f892fa900 Mon Sep 17 00:00:00 2001 From: Kye Gomez <98760976+kyegomez@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:31:16 -0700 Subject: [PATCH 18/19] Update README.md --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d1b604b4..f8b21e75 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ agent.run( ``` +----- ### `Agent` + Long Term Memory `Agent` equipped with quasi-infinite long term memory. Great for long document understanding, analysis, and retrieval. @@ -171,7 +172,7 @@ agent.run("Generate a 10,000 word blog on health and wellness.") ``` - +----- ### `Agent` ++ Long Term Memory ++ Tools! An LLM equipped with long term memory and tools, a full stack agent capable of automating all and any digital tasks given a good prompt. @@ -284,7 +285,7 @@ out = agent("Create a new file for a plan to take over the world.") print(out) ``` - +---- ### Devin Implementation of Devin in less than 90 lines of code with several tools: @@ -390,7 +391,7 @@ agent = Agent( out = agent("Create a new file for a plan to take over the world.") print(out) ``` - +--------------- ### `Agent`with Pydantic BaseModel as Output Type The following is an example of an agent that intakes a pydantic basemodel and outputs it at the same time: @@ -453,6 +454,7 @@ print(f"Generated data: {generated_data}") ``` +----- ### Multi Modal Autonomous Agent Run the agent with multiple modalities useful for various real-world tasks in manufacturing, logistics, and health. @@ -553,7 +555,7 @@ generated_data = agent.run(task) print(f"Generated data: {generated_data}") ``` - +---------------- ### `Task` For deeper control of your agent stack, `Task` is a simple structure for task execution with the `Agent`. Imagine zapier like LLM-based workflow automation. @@ -759,11 +761,6 @@ print(output) ## `HierarhicalSwarm` Coming soon... - -## `AgentLoadBalancer` -Coming soon... - - ## `GraphSwarm` Coming soon... From 0ad5a12f75b8089c40f19b0d28c2e4ae911daaa3 Mon Sep 17 00:00:00 2001 From: Kye Gomez <98760976+kyegomez@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:34:30 -0700 Subject: [PATCH 19/19] Delete swarms/structs/Untitled-1.py --- swarms/structs/Untitled-1.py | 250 ----------------------------------- 1 file changed, 250 deletions(-) delete mode 100644 swarms/structs/Untitled-1.py diff --git a/swarms/structs/Untitled-1.py b/swarms/structs/Untitled-1.py deleted file mode 100644 index 7d664343..00000000 --- a/swarms/structs/Untitled-1.py +++ /dev/null @@ -1,250 +0,0 @@ -# ! pip install ai21 -# ! pip install swarms -import os -from typing import List - -from ai21 import AI21Client -from ai21.models.chat import ChatMessage -from dotenv import load_dotenv - -from swarms import Agent, BaseLLM -from swarms.utils.loguru_logger import logger - -load_dotenv() - - -class Jamba(BaseLLM): - def __init__( - self, - api_key: str = os.getenv("AI21_API_KEY"), - temperature: int = 0.8, - max_tokens: int = 200, - ): - """ - Initializes the Jamba class with the provided API key. - - Args: - api_key (str): The API key for the AI21Client. - """ - os.environ["AI21_API_KEY"] = api_key - self.api_key = api_key - self.temperature = temperature - self.max_tokens = max_tokens - self.client = AI21Client() - - def run(self, prompt: str, *args, **kwargs) -> str: - """ - Generates a response for the given prompt using the AI21 model. - - Args: - prompt (str): The prompt for generating the response. - - Returns: - str: The generated response. - - Raises: - Exception: If there is an issue with the API request. - """ - try: - response = self.client.chat.completions.create( - model="jamba-instruct-preview", # Latest model - messages=[ChatMessage(role="user", content=prompt)], - temperature=self.temperature, - max_tokens=self.max_tokens, - *args, - **kwargs, - ) - return response.choices[0].message.content - except Exception as e: - print(f"Error: {e}") - raise - - -model = Jamba( - max_tokens=4000, -) - - -BOSS_PLANNER = """ -You're the swarm orchestrator agent - -**Objective:** Your task is to intake a business problem or activity and create a swarm of specialized LLM agents that can efficiently solve or automate the given problem. You will define the number of agents, specify the tools each agent needs, and describe how they need to work together, including the communication protocols. - -**Instructions:** - -1. **Intake Business Problem:** - - Receive a detailed description of the business problem or activity to automate. - - Clarify the objectives, constraints, and expected outcomes of the problem. - - Identify key components and sub-tasks within the problem. - -2. **Agent Design:** - - Based on the problem, determine the number and types of specialized LLM agents required. - - For each agent, specify: - - The specific task or role it will perform. - - The tools and resources it needs to perform its task. - - Any prerequisite knowledge or data it must have access to. - - Ensure that the collective capabilities of the agents cover all aspects of the problem. - -3. **Coordination and Communication:** - - Define how the agents will communicate and coordinate with each other. - - Choose the type of communication (e.g., synchronous, asynchronous, broadcast, direct messaging). - - Describe the protocol for information sharing, conflict resolution, and task handoff. - -4. **Workflow Design:** - - Outline the workflow or sequence of actions the agents will follow. - - Define the input and output for each agent. - - Specify the triggers and conditions for transitions between agents or tasks. - - Ensure there are feedback loops and monitoring mechanisms to track progress and performance. - -5. **Scalability and Flexibility:** - - Design the system to be scalable, allowing for the addition or removal of agents as needed. - - Ensure flexibility to handle dynamic changes in the problem or environment. - -6. **Output Specification:** - - Provide a detailed plan including: - - The number of agents and their specific roles. - - The tools and resources each agent will use. - - The communication and coordination strategy. - - The workflow and sequence of actions. - - Include a diagram or flowchart if necessary to visualize the system. - -**Example Structure:** - -**Business Problem:** Automate customer support for an e-commerce platform. - -**Agents and Roles:** -1. **Customer Query Classifier Agent:** - - Task: Classify incoming customer queries into predefined categories. - - Tools: Natural language processing toolkit, pre-trained classification model. - - Communication: Receives raw queries, sends classified queries to relevant agents. - -2. **Order Status Agent:** - - Task: Provide order status updates to customers. - - Tools: Access to order database, query processing toolkit. - - Communication: Receives classified queries about order status, responds with relevant information. - -3. **Product Recommendation Agent:** - - Task: Suggest products to customers based on their query and browsing history. - - Tools: Recommendation engine, access to product database. - - Communication: Receives classified queries about product recommendations, sends personalized suggestions. - -4. **Technical Support Agent:** - - Task: Assist customers with technical issues. - - Tools: Access to technical support database, troubleshooting toolkit. - - Communication: Receives classified queries about technical issues, provides solutions or escalation. - -**Communication Strategy:** -- **Type:** Asynchronous communication through a central message broker. -- **Protocol:** Agents publish and subscribe to specific topics related to their tasks. -- **Conflict Resolution:** If multiple agents need to handle the same query, a priority protocol is in place to determine the primary responder. - -**Workflow:** -1. Customer Query Classifier Agent receives and classifies the query. -2. Classified query is routed to the appropriate specialized agent. -3. Specialized agent processes the query and sends a response. -4. If needed, the response triggers further actions from other agents. - -**Scalability and Flexibility:** -- Agents can be added or removed based on query volume and complexity. -- System adapts to changes in query types and business needs. - -**Output Plan:** -- Diagram illustrating agent roles and communication flow. -- Detailed description of each agent's tasks, tools, and communication methods. -- Workflow sequence from query intake to resolution. - - -""" - - -# Initialize the agent -planning_agent = Agent( - agent_name="Boss Director", - system_prompt=BOSS_PLANNER, - agent_description="Generates a spec of agents for the problem at hand.", - llm=model, - max_loops=1, - autosave=True, - dynamic_temperature_enabled=True, - dashboard=False, - verbose=True, - streaming_on=True, - # interactive=True, # Set to False to disable interactive mode - saved_state_path="boss_planner.json", - # tools=[calculate_profit, generate_report], - # docs_folder="docs", - # pdf_path="docs/accounting_agent.pdf", - # tools=[browser_automation], -) - - -# Name, system prompt, -def create_worker_agent(name: str, system_prompt: str) -> List[Agent]: - """ - Creates a worker agent with the specified name, system prompt, and description. - - Args: - name (List[str]): The name of the worker agent. - system_prompt List(str): The system prompt for the worker agent. - - Returns: - List[Agent]: A list of worker agents created based on the input. - """ - # return agents - name = Agent( - agent_name=name, - system_prompt=system_prompt, - llm=model, - max_loops=1, - autosave=True, - dynamic_temperature_enabled=True, - dashboard=False, - verbose=True, - streaming_on=True, - # interactive=True, # Set to False to disable interactive mode - saved_state_path=f"{name.lower().replace(' ', '_')}_agent.json", - # tools=[calculate_profit, generate_report], - # docs_folder="docs", - # pdf_path="docs/accounting_agent.pdf", - # tools=[browser_automation], - ) - - out = name.run(system_prompt) - return out - - -# Boss Agent creator -boss_agent_creator = Agent( - agent_name="Boss Agent Creator", - system_prompt="Create the worker agents for the problem at hand using the specified names and system prompt tools provided.", - agent_description="Generates a spec of agents for the problem at hand.", - llm=model, - max_loops=1, - autosave=True, - dynamic_temperature_enabled=True, - dashboard=False, - verbose=True, - streaming_on=True, - # interactive=True, # Set to False to disable interactive mode - saved_state_path="boss_director_agent.json", - # tools=[calculate_profit, generate_report], - # docs_folder="docs", - # pdf_path="docs/accounting_agent.pdf", - tools=[create_worker_agent], -) - - -def run_jamba_swarm(task: str = None): - logger.info(f"Making plan for the task: {task}") - planning_agent.run(task) - - memory = planning_agent.short_memory.return_history_as_string() - - # Boss agent - return boss_agent_creator.run(memory) - - -# Example usage -run_jamba_swarm( - "Create a swarm of agents for automating customer support for an e-commerce platform." -)