diff --git a/README.md b/README.md index ccfac708..43407822 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@
-Swarms is a modular framework that enables reliable and useful multi-agent collaboration at scale to automate real-world tasks. - +A modular framework that enables you to Build, Deploy, and Scale Reliable Autonomous Agents. Get started now below. [![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) @@ -17,7 +16,7 @@ Swarms is a modular framework that enables reliable and useful multi-agent colla ---- ## Installation -`pip3 install --upgrade swarms` +`pip3 install -U swarms` --- @@ -180,8 +179,8 @@ for task in workflow.tasks: ```python -import os -from dotenv import load_dotenv +import os +from dotenv import load_dotenv from swarms import OpenAIChat, Task, ConcurrentWorkflow, Agent # Load environment variables from .env file @@ -200,9 +199,7 @@ 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(task1) -workflow.add(task2) -workflow.add(task3) +workflow.add(tasks=[task1, task2, task3]) # Run the workflow workflow.run() @@ -413,9 +410,10 @@ print(out) ```python import os -from swarms import Task, Agent, OpenAIChat + from dotenv import load_dotenv +from swarms.structs import Agent, OpenAIChat, Task # Load the environment variables load_dotenv() @@ -440,7 +438,13 @@ agent = Agent( ) # Create a task -task = Task(agent, "Create a strategy to cut business costs by 40% this month") +task = Task( + description=( + "Generate a report on the top 3 biggest expenses for small" + " businesses and how businesses can save 20%" + ), + agent=agent, +) # Set the action and condition task.set_action(my_action) @@ -906,6 +910,31 @@ cog_agent = CogAgent() # Run the model on the tests cog_agent.run("Describe this scene", "images/1.jpg") +``` + + +### `QwenVLMultiModal` +A radically simple interface for QwenVLMultiModal comes complete with Quantization to turn it on just set quantize to true! + +```python +from swarms import QwenVLMultiModal + +# Instantiate the QwenVLMultiModal model +model = QwenVLMultiModal( + model_name="Qwen/Qwen-VL-Chat", + device="cuda", + quantize=True, +) + +# Run the model +response = model( + "Hello, how are you?", "https://example.com/image.jpg" +) + +# Print the response +print(response) + + ``` ---- @@ -1038,10 +1067,13 @@ Help us accelerate our backlog by supporting us financially! Note, we're an open ## Swarm Newsletter 🤖 🤖 🤖 📧 -Sign up to the Swarm newsletter to receieve updates on the latest Autonomous agent research papers, step by step guides on creating multi-agent app, and much more Swarmie goodiness 😊 +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) # License Apache License + + diff --git a/docs/corporate/data_room.md b/docs/corporate/data_room.md new file mode 100644 index 00000000..550e8f94 --- /dev/null +++ b/docs/corporate/data_room.md @@ -0,0 +1,102 @@ +# Swarms Data Room + +## Table of Contents + +**Introduction** + +- Overview of the Company + +- Vision and Mission Statement + +- Executive Summary + +**Corporate Documents** + +- Articles of Incorporation + +- Bylaws + +- Shareholder Agreements + +- Board Meeting Minutes + +- Company Structure and Org Chart + +**Financial Information** + +- Historical Financial Statements + + - Income Statements + + - Balance Sheets + + - Cash Flow Statements + +- Financial Projections and Forecasts + +- Cap Table + +- Funding History and Use of Funds + +**Products and Services** + +- Detailed Descriptions of Products/Services + +- Product Development Roadmap + +- User Manuals and Technical Specifications + +- Case Studies and Use Cases + + +## **Introdution** +Swarms provides automation-as-a-service through swarms of autonomous agents that work together as a team. We enable our customers to build, deploy, and scale production-grade multi-agent applications to automate real-world tasks. + + +### **Vision** +Our vision for 2024 is to provide the most reliable infrastructure for deploying autonomous agents into the real world through the Swarm Cloud, our premier cloud platform for the scalable deployment of Multi-Modal Autonomous Agents. The platform focuses on delivering maximum value to users by only taking a small fee when utilizing the agents for the hosted compute power needed to host the agents. + +### **Executive Summary** +The Swarm Corporation aims to enable AI models to automate complex workflows and operations, not just singular low-value tasks. We believe collaboration between multiple agents can overcome limitations of individual agents for reasoning, planning, etc. This will allow automation of processes in mission-critical industries like security, logistics, and manufacturing where AI adoption is currently low. + +We provide an open source framework to deploy production-grade multi-modal agents in just a few lines of code. This builds our user base, recruits talent, gets customer feedback to improve products, gains awareness and trust. + +Our business model focuses on customer satisfaction, openness, integration with other tools/platforms, and production-grade reliability. + +Go-to-market strategy is to get the framework to product-market fit with over 50K weekly recurring users, then secure high-value contracts in target industries. Long-term monetization via microtransactions, usage-based pricing, subscriptions. + +The team has thousands of hours building and optimizing autonomous agents. Leadership includes AI engineers, product experts, open source contributors and community builders. + +Key milestones: get 80K framework users in January 2024, start contracts in target verticals, introduce commercial products in 2025 with various pricing models. + + +### **The Swarm Corporation Memo** +To learn more about our mission, vision, plans for GTM, and much more please refer to the [Swarm Memo here](https://docs.google.com/document/d/1hS_nv_lFjCqLfnJBoF6ULY9roTbSgSuCkvXvSUSc7Lo/edit?usp=sharing) + + +## **Product** +Swarms is an open source framework for developers in python to enable seamless, reliable, and scalable multi-agent orchestration through modularity, customization, and precision. + +[Here is the official Swarms Github Page:](https://github.com/kyegomez/swarms) + +### Product Growth Metrics + +- Total Downloads of all time: [![GitHub issues](https://img.shields.io/github/issues/kyegomez/swarms)](https://github.com/kyegomez/swarms/issues) + +- Click here for Downloads this month: [![Downloads](https://static.pepy.tech/badge/swarms/month)](https://pepy.tech/project/swarms) + +- Total Downloads this week: [![GitHub issues](https://img.shields.io/github/issues/kyegomez/swarms)](https://github.com/kyegomez/swarms/issues) + +- Click here for Forks which represent the number of times a user has copied the entire codebase for optimization, contribution, or usage. [![GitHub forks](https://img.shields.io/github/forks/kyegomez/swarms)](https://github.com/kyegomez/swarms/network) + +- Stars are the number of people that have liked our project, click here for more: [![GitHub stars](https://img.shields.io/github/stars/kyegomez/swarms)](https://github.com/kyegomez/swarms/stargazers) + +- Various Project Statistics such as watchers, number of contributors, date repository was created and much more. [CLICK HERE](https://libraries.io/github/kyegomez/swarms) + +- Contribution Based Statistics such as number of contributors, number of lines of code changed, and much more [HERE](https://github.com/kyegomez/swarms/graphs/contributors) + +- [Github Community insights](https://github.com/kyegomez/swarms/graphs/community) + +- [Github Traffic Metrics](https://github.com/kyegomez/swarms/graphs/traffic) + +- Issues with the framework or Github Issues: [![GitHub issues](https://img.shields.io/github/issues/kyegomez/swarms)](https://github.com/kyegomez/swarms/issues) \ No newline at end of file diff --git a/docs/swarms/agents/abstract_agent.md b/docs/swarms/agents/abstract_agent.md deleted file mode 100644 index 4201eef2..00000000 --- a/docs/swarms/agents/abstract_agent.md +++ /dev/null @@ -1,90 +0,0 @@ -`AbsractAgent` Class: A Deep Dive -======================== - -The `AbstractAgent` class is a fundamental building block in the design of AI systems. It encapsulates the behavior of an AI entity, allowing it to interact with other agents and perform actions. The class is designed to be flexible and extensible, enabling the creation of agents with diverse behaviors. - -## Architecture ------------- - -The architecture of the `AbstractAgent` class is centered around three main components: the agent's name, tools, and memory. - -- The `name` is a string that uniquely identifies the agent. This is crucial for communication between agents and for tracking their actions. - -- The `tools` are a list of `Tool` objects that the agent uses to perform its tasks. These could include various AI models, data processing utilities, or any other resources that the agent needs to function. The `tools` method is used to initialize these tools. - -- The `memory` is a `Memory` object that the agent uses to store and retrieve information. This could be used, for example, to remember past actions or to store the state of the environment. The `memory` method is used to initialize the memory. - -The `AbstractAgent` class also includes several methods that define the agent's behavior. These methods are designed to be overridden in subclasses to implement specific behaviors. - -## Methods -------- - -### `reset` - -The `reset` method is used to reset the agent's state. This could involve clearing the agent's memory, resetting its tools, or any other actions necessary to bring the agent back to its initial state. This method is abstract and must be overridden in subclasses. - -### `run` and `_arun` - -The `run` method is used to execute a task. The task is represented as a string, which could be a command, a query, or any other form of instruction that the agent can interpret. The `_arun` method is the asynchronous version of `run`, allowing tasks to be executed concurrently. - -### `chat` and `_achat` - -The `chat` method is used for communication between agents. It takes a list of messages as input, where each message is a dictionary. The `_achat` method is the asynchronous version of `chat`, allowing messages to be sent and received concurrently. - -### `step` and `_astep` - -The `step` method is used to advance the agent's state by one step in response to a message. The `_astep` method is the asynchronous version of `step`, allowing the agent's state to be updated concurrently. - -## Usage E#xamples --------------- - -### Example 1: Creating an Agent - -``` -from swarms.agents.base import AbtractAgent - -agent = Agent(name="Agent1") -print(agent.name) # Output: Agent1 -``` - - -In this example, we create an instance of `AbstractAgent` named "Agent1" and print its name. - -### Example 2: Initializing Tools and Memory - -``` -from swarms.agents.base import AbtractAgent - -agent = Agent(name="Agent1") -tools = [Tool1(), Tool2(), Tool3()] -memory_store = Memory() - -agent.tools(tools) -agent.memory(memory_store) -``` - - -In this example, we initialize the tools and memory of "Agent1". The tools are a list of `Tool` instances, and the memory is a `Memory` instance. - -### Example 3: Running an Agent - -``` -from swarms.agents.base import AbtractAgent - -agent = Agent(name="Agent1") -task = "Task1" - -agent.run(task) -``` - - -In this example, we run "Agent1" with a task named "Task1". - -Notes ------ - -- The `AbstractAgent` class is an abstract class, which means it cannot be instantiated directly. Instead, it should be subclassed, and at least the `reset`, `run`, `chat`, and `step` methods should be overridden. -- The `run`, `chat`, and `step` methods are designed to be flexible and can be adapted to a wide range of tasks and behaviors. For example, the `run` method could be used to execute a machine learning model, the `chat` method could be used to send and receive messages in a chatbot, and the `step` method could be used to update the agent's state in a reinforcement learning environment. -- The `_arun`, `_achat`, and `_astep` methods are asynchronous versions of the `run`, `chat`, and `step` methods, respectively. They return a coroutine that can be awaited using the `await` keyword. This allows multiple tasks to be executed concurrently, improving the efficiency of the agent. -- The `tools` and `memory` methods are used to initialize the agent's tools and memory, respectively. These methods can be overridden in subclasses to initialize specific tools and memory structures. -- The `reset` method is used to reset the agent's state. This method can be overridden in subclasses to define specific reset behaviors. For example, in a reinforcement learning agent, the \ No newline at end of file diff --git a/docs/swarms/agents/abstractagent.md b/docs/swarms/agents/abstractagent.md new file mode 100644 index 00000000..cdd06715 --- /dev/null +++ b/docs/swarms/agents/abstractagent.md @@ -0,0 +1,124 @@ +# swarms.agents + +## 1. Introduction + +`AbstractAgent` is an abstract class that serves as a foundation for implementing AI agents. An agent is an entity that can communicate with other agents and perform actions. The `AbstractAgent` class allows for customization in the implementation of the `receive` method, enabling different agents to define unique actions for receiving and processing messages. + +`AbstractAgent` provides capabilities for managing tools and accessing memory, and has methods for running, chatting, and stepping through communication with other agents. + +## 2. Class Definition + +```python +class AbstractAgent: + """An abstract class for AI agent. + + An agent can communicate with other agents and perform actions. + Different agents can differ in what actions they perform in the `receive` method. + + Agents are full and completed: + + Agents = llm + tools + memory + """ + + def __init__(self, name: str): + """ + Args: + name (str): name of the agent. + """ + self._name = name + + @property + def name(self): + """Get the name of the agent.""" + return self._name + + def tools(self, tools): + """init tools""" + + def memory(self, memory_store): + """init memory""" + pass + + def reset(self): + """(Abstract method) Reset the agent.""" + + def run(self, task: str): + """Run the agent once""" + + def _arun(self, taks: str): + """Run Async run""" + + def chat(self, messages: List[Dict]): + """Chat with the agent""" + + def _achat(self, messages: List[Dict]): + """Asynchronous Chat""" + + def step(self, message: str): + """Step through the agent""" + + def _astep(self, message: str): + """Asynchronous step""" +``` + +## 3. Functionality and Usage + +The `AbstractAgent` class represents a generic AI agent and provides a set of methods to interact with it. + +To create an instance of an agent, the `name` of the agent should be specified. + +### Core Methods + +#### 1. `reset` + +The `reset` method allows the agent to be reset to its initial state. + +```python +agent.reset() +``` + +#### 2. `run` + +The `run` method allows the agent to perform a specific task. + +```python +agent.run('some_task') +``` + +#### 3. `chat` + +The `chat` method enables communication with the agent through a series of messages. + +```python +messages = [{'id': 1, 'text': 'Hello, agent!'}, {'id': 2, 'text': 'How are you?'}] +agent.chat(messages) +``` + +#### 4. `step` + +The `step` method allows the agent to process a single message. + +```python +agent.step('Hello, agent!') +``` + +### Asynchronous Methods + +The class also provides asynchronous variants of the core methods. + +### Additional Functionality + +Additional functionalities for agent initialization and management of tools and memory are also provided. + +```python +agent.tools(some_tools) +agent.memory(some_memory_store) +``` + +## 4. Additional Information and Tips + +When implementing a new agent using the `AbstractAgent` class, ensure that the `receive` method is overridden to define the specific behavior of the agent upon receiving messages. + +## 5. References and Resources + +For further exploration and understanding of AI agents and agent communication, refer to the relevant literature and research on this topic. diff --git a/docs/swarms/agents/message.md b/docs/swarms/agents/message.md new file mode 100644 index 00000000..87794ebc --- /dev/null +++ b/docs/swarms/agents/message.md @@ -0,0 +1,120 @@ +# The Module/Class Name: Message + +In the swarms.agents framework, the class `Message` is used to represent a message with timestamp and optional metadata. + +## Overview and Introduction + +The `Message` class is a fundamental component that enables the representation of messages within an agent system. Messages contain essential information such as the sender, content, timestamp, and optional metadata. + +## Class Definition + +### Constructor: `__init__` + +The constructor of the `Message` class takes three parameters: + +1. `sender` (str): The sender of the message. +2. `content` (str): The content of the message. +3. `metadata` (dict or None): Optional metadata associated with the message. + +### Methods + +1. `__repr__(self)`: Returns a string representation of the `Message` object, including the timestamp, sender, and content. + +```python +class Message: + """ + Represents a message with timestamp and optional metadata. + + Usage + -------------- + mes = Message( + sender = "Kye", + content = "message" + ) + + print(mes) + """ + + def __init__(self, sender, content, metadata=None): + self.timestamp = datetime.datetime.now() + self.sender = sender + self.content = content + self.metadata = metadata or {} + + def __repr__(self): + """ + __repr__ represents the string representation of the Message object. + + Returns: + (str) A string containing the timestamp, sender, and content of the message. + """ + return f"{self.timestamp} - {self.sender}: {self.content}" +``` + +## Functionality and Usage + +The `Message` class represents a message in the agent system. Upon initialization, the `timestamp` is set to the current date and time, and the `metadata` is set to an empty dictionary if no metadata is provided. + +### Usage Example 1 + +Creating a `Message` object and displaying its string representation. + +```python +mes = Message( + sender = "Kye", + content = "Hello! How are you?" +) + +print(mes) +``` + +Output: +``` +2023-09-20 13:45:00 - Kye: Hello! How are you? +``` + +### Usage Example 2 + +Creating a `Message` object with metadata. + +```python +metadata = {"priority": "high", "category": "urgent"} +mes_with_metadata = Message( + sender = "Alice", + content = "Important update", + metadata = metadata +) + +print(mes_with_metadata) +``` + +Output: +``` +2023-09-20 13:46:00 - Alice: Important update +``` + +### Usage Example 3 + +Creating a `Message` object without providing metadata. + +```python +mes_no_metadata = Message( + sender = "Bob", + content = "Reminder: Meeting at 2PM" +) + +print(mes_no_metadata) +``` + +Output: +``` +2023-09-20 13:47:00 - Bob: Reminder: Meeting at 2PM +``` + +## Additional Information and Tips + +When creating a new `Message` object, ensure that the required parameters `sender` and `content` are provided. The `timestamp` will automatically be assigned the current date and time. Optional `metadata` can be included to provide additional context or information associated with the message. + +## References and Resources + +For further information on the `Message` class and its usage, refer to the official swarms.agents documentation and relevant tutorials related to message handling and communication within the agent system. diff --git a/docs/swarms/agents/omnimodalagent.md b/docs/swarms/agents/omnimodalagent.md new file mode 100644 index 00000000..841a39f0 --- /dev/null +++ b/docs/swarms/agents/omnimodalagent.md @@ -0,0 +1,79 @@ +# Module/Class Name: OmniModalAgent + +The `OmniModalAgent` class is a module that operates based on the Language Model (LLM) aka Language Understanding Model, Plans, Tasks, and Tools. It is designed to be a multi-modal chatbot which uses various AI-based capabilities for fulfilling user requests. + +It has the following architecture: +1. Language Model (LLM). +2. Chat Planner - Plans +3. Task Executor - Tasks +4. Tools - Tools + +![OmniModalAgent](https://source.unsplash.com/random) + +--- + +### Usage + from swarms import OmniModalAgent, OpenAIChat + + llm = OpenAIChat() + agent = OmniModalAgent(llm) + response = agent.run("Hello, how are you? Create an image of how your are doing!") + +--- + +--- + +### Initialization + +The constructor of `OmniModalAgent` class takes two main parameters: +- `llm`: A `BaseLanguageModel` that represents the language model +- `tools`: A List of `BaseTool` instances that are used by the agent for fulfilling different requests. + +```python +def __init__( + self, + llm: BaseLanguageModel, + # tools: List[BaseTool] +): +``` + +--- + +### Methods + +The class has two main methods: +1. `run`: This method takes an input string and executes various plans and tasks using the provided tools. Ultimately, it generates a response based on the user's input and returns it. + - Parameters: + - `input`: A string representing the user's input text. + - Returns: + - A string representing the response. + + Usage: + ```python + response = agent.run("Hello, how are you? Create an image of how your are doing!") + ``` + +2. `chat`: This method is used to simulate a chat dialog with the agent. It can take user's messages and return the response (or stream the response word-by-word if required). + - Parameters: + - `msg` (optional): A string representing the message to send to the agent. + - `streaming` (optional): A boolean specifying whether to stream the response. + - Returns: + - A string representing the response from the agent. + + Usage: + ```python + response = agent.chat("Hello") + ``` + +--- + +### Streaming Response + +The class provides a method `_stream_response` that can be used to get the response token by token (i.e. word by word). It yields individual tokens from the response. + +Usage: +```python +for token in _stream_response(response): + print(token) +``` + diff --git a/docs/swarms/agents/toolagent.md b/docs/swarms/agents/toolagent.md new file mode 100644 index 00000000..ebb00623 --- /dev/null +++ b/docs/swarms/agents/toolagent.md @@ -0,0 +1,113 @@ +# ToolAgent Documentation + + +### Overview and Introduction + +The `ToolAgent` class represents an intelligent agent capable of performing a specific task using a pre-trained model and tokenizer. It leverages the Transformer models of the Hugging Face `transformers` library to generate outputs that adhere to a specific JSON schema. This provides developers with a flexible tool for creating bots, text generators, and conversational AI agents. The `ToolAgent` operates based on a JSON schema provided by you, the user. Using the schema, the agent applies the provided model and tokenizer to generate structured text data that matches the specified format. + +The primary objective of the `ToolAgent` class is to amplify the efficiency of developers and AI practitioners by simplifying the process of generating meaningful outputs that navigate the complexities of the model and tokenizer. + +### Class Definition + +The `ToolAgent` class has the following definition: + +```python +class ToolAgent(AbstractLLM): + def __init__( + self, + name: str, + description: str, + model: Any, + tokenizer: Any, + json_schema: Any, + *args, + **kwargs, + ) + def run(self, task: str, *args, **kwargs) + def __call__(self, task: str, *args, **kwargs) +``` + +### Arguments + +The `ToolAgent` class takes the following arguments: + +| Argument | Type | Description | +| --- | --- | --- | +| name | str | The name of the tool agent. +| description | str | A description of the tool agent. +| model | Any | The model used by the tool agent (e.g., `transformers.AutoModelForCausalLM`). +| tokenizer | Any | The tokenizer used by the tool agent (e.g., `transformers.AutoTokenizer`). +| json_schema | Any | The JSON schema used by the tool agent. +| *args | - | Variable-length arguments. +| **kwargs | - | Keyword arguments. + +### Methods + +`ToolAgent` exposes the following methods: + +#### `run(self, task: str, *args, **kwargs) -> Any` + +- Description: Runs the tool agent for a specific task. +- Parameters: + - `task` (str): The task to be performed by the tool agent. + - `*args`: Variable-length argument list. + - `**kwargs`: Arbitrary keyword arguments. +- Returns: The output of the tool agent. +- Raises: Exception if an error occurs during the execution of the tool agent. + + +#### `__call__(self, task: str, *args, **kwargs) -> Any` + +- Description: Calls the tool agent to perform a specific task. +- Parameters: + - `task` (str): The task to be performed by the tool agent. + - `*args`: Variable-length argument list. + - `**kwargs`: Arbitrary keyword arguments. +- Returns: The output of the tool agent. + +### Usage Example + +```python +from transformers import AutoModelForCausalLM, AutoTokenizer +from swarms import ToolAgent + +# Creating a model and tokenizer +model = AutoModelForCausalLM.from_pretrained("databricks/dolly-v2-12b") +tokenizer = AutoTokenizer.from_pretrained("databricks/dolly-v2-12b") + +# Defining a JSON schema +json_schema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "number"}, + "is_student": {"type": "boolean"}, + "courses": { + "type": "array", + "items": {"type": "string"} + } + } +} + +# Defining a task +task = "Generate a person's information based on the following schema:" + +# Creating the ToolAgent instance +agent = ToolAgent(model=model, tokenizer=tokenizer, json_schema=json_schema) + +# Running the tool agent +generated_data = agent.run(task) + +# Accessing and printing the generated data +print(generated_data) +``` + +### Additional Information and Tips + +When using the `ToolAgent`, it is important to ensure compatibility between the provided model, tokenizer, and the JSON schema. Additionally, any errors encountered during the execution of the tool agent are propagated as exceptions. Handling such exceptions appropriately can improve the robustness of the tool agent usage. + +### References and Resources + +For further exploration and understanding of the underlying Transformer-based models and tokenizers, refer to the Hugging Face `transformers` library documentation and examples. Additionally, for JSON schema modeling, you can refer to the official JSON Schema specification and examples. + +This documentation provides a comprehensive guide on using the `ToolAgent` class from `swarms` library, and it is recommended to refer back to this document when utilizing the `ToolAgent` for developing your custom conversational agents or text generation tools. diff --git a/docs/swarms/agents/workeragent.md b/docs/swarms/agents/workeragent.md new file mode 100644 index 00000000..e46ec1af --- /dev/null +++ b/docs/swarms/agents/workeragent.md @@ -0,0 +1,78 @@ +# WorkerClass Documentation + +## Overview + +The Worker class represents an autonomous agent that can perform tasks through function calls or by running a chat. It can be used to create applications that demand effective user interactions like search engines, human-like conversational bots, or digital assistants. + +The `Worker` class is part of the `swarms.agents` codebase. This module is largely used in Natural Language Processing (NLP) projects where the agent undertakes conversations and other language-specific operations. + +## Class Definition + +The class `Worker` has the following arguments: + +| Argument | Type | Default Value | Description | +|-----------------------|---------------|----------------------------------|----------------------------------------------------| +| name | str | "Worker" | Name of the agent. | +| role | str | "Worker in a swarm" | Role of the agent. | +| external_tools | list | None | List of external tools available to the agent. | +| human_in_the_loop | bool | False | Determines whether human interaction is required. | +| temperature | float | 0.5 | Temperature for the autonomous agent. | +| llm | None | None | Language model. | +| openai_api_key | str | None | OpenAI API key. | +| tools | List[Any] | None | List of tools available to the agent. | +| embedding_size | int | 1536 | Size of the word embeddings. | +| search_kwargs | dict | {"k": 8} | Search parameters. | +| args | Multiple | | Additional arguments that can be passed. | +| kwargs | Multiple | | Additional keyword arguments that can be passed. | +## Usage + +#### Example 1: Creating and Running an Agent + +```python +from swarms import Worker + +worker = Worker( + name="My Worker", + role="Worker", + external_tools=[MyTool1(), MyTool2()], + human_in_the_loop=False, + temperature=0.5, + llm=some_language_model, + openai_api_key="my_key" +) +worker.run("What's the weather in Miami?") +``` + +#### Example 2: Receiving and Sending Messages + +```python +worker.receieve("User", "Hello there!") +worker.receieve("User", "Can you tell me something about history?") +worker.send() +``` + +#### Example 3: Setting up Tools + +```python +external_tools = [MyTool1(), MyTool2()] +worker = Worker( +name="My Worker", +role="Worker", +external_tools=external_tools, +human_in_the_loop=False, +temperature=0.5, +) +``` + +## Additional Information and Tips + +- The class allows the setting up of tools for the worker to operate effectively. It provides setup facilities for essential computing infrastructure, such as the agent's memory and language model. +- By setting the `human_in_the_loop` parameter to True, interactions with the worker can be made more user-centric. +- The `openai_api_key` argument can be provided for leveraging the OpenAI infrastructure and services. +- A qualified language model can be passed as an instance of the `llm` object, which can be useful when integrating with state-of-the-art text generation engines. + +## References and Resources + +- [OpenAI APIs](https://openai.com) +- [Models and Languages at HuggingFace](https://huggingface.co/models) +- [Deep Learning and Language Modeling at the Allen Institute for AI](https://allenai.org) diff --git a/mkdocs.yml b/mkdocs.yml index b3adedb9..033ac5f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,12 +60,12 @@ nav: - Contributing: "contributing.md" - Swarms: - Overview: "swarms/index.md" - - swarms.workers: - - Overview: "swarms/workers/index.md" - - AbstractWorker: "swarms/workers/abstract_worker.md" - swarms.agents: - - AbstractAgent: "swarms/agents/abstract_agent.md" - - OmniModalAgent: "swarms/agents/omni_agent.md" + - Agents: + - WorkerAgent: "swarms/agents/workeragent.md" + - OmniAgent: "swarms/agents/omni_agent.md" + - AbstractAgent: "swarms/agents/abstractagent.md" + - ToolAgent: "swarms/agents/toolagent.md" - swarms.models: - Language: - BaseLLM: "swarms/models/base_llm.md" @@ -96,41 +96,46 @@ nav: - Gemini: "swarms/models/gemini.md" - ZeroscopeTTV: "swarms/models/zeroscope.md" - swarms.structs: - - agent: "swarms/structs/agent.md" - - basestructure: "swarms/structs/basestructure.md" - - artifactupload: "swarms/structs/artifactupload.md" - - sequential_workflow: "swarms/structs/sequential_workflow.md" - - taskinput: "swarms/structs/taskinput.md" - - concurrentworkflow: "swarms/structs/concurrentworkflow.md" - - nonlinearworkflow: "swarms/structs/nonlinearworkflow.md" - - stepinput: "swarms/structs/stepinput.md" - - workflow: "swarms/structs/workflow.md" - - artifact: "swarms/structs/artifact.md" - - recursiveworkflow: "swarms/structs/recursiveworkflow.md" - - swarmnetwork: "swarms/structs/swarmnetwork.md" - - task: "swarms/structs/task.md" - - groupchatmanager: "swarms/structs/groupchatmanager.md" - - baseworkflow: "swarms/structs/baseworkflow.md" - - conversation: "swarms/structs/conversation.md" - - groupchat: "swarms/structs/groupchat.md" + - Foundational Structures: + - agent: "swarms/structs/agent.md" + - basestructure: "swarms/structs/basestructure.md" + - artifactupload: "swarms/structs/artifactupload.md" + - taskinput: "swarms/structs/taskinput.md" + - stepinput: "swarms/structs/stepinput.md" + - artifact: "swarms/structs/artifact.md" + - task: "swarms/structs/task.md" + - Workflows: + - recursiveworkflow: "swarms/structs/recursiveworkflow.md" + - concurrentworkflow: "swarms/structs/concurrentworkflow.md" + - nonlinearworkflow: "swarms/structs/nonlinearworkflow.md" + - sequential_workflow: "swarms/structs/sequential_workflow.md" + - workflow: "swarms/structs/workflow.md" + - baseworkflow: "swarms/structs/baseworkflow.md" + - Multi Agent Architectures: + - conversation: "swarms/structs/conversation.md" + - groupchat: "swarms/structs/groupchat.md" + - swarmnetwork: "swarms/structs/swarmnetwork.md" + - groupchatmanager: "swarms/structs/groupchatmanager.md" - swarms.memory: - - Weaviate: "swarms/memory/weaviate.md" - - PineconeDB: "swarms/memory/pinecone.md" - - PGVectorStore: "swarms/memory/pg.md" + - Vector Databases: + - Weaviate: "swarms/memory/weaviate.md" + - PineconeDB: "swarms/memory/pinecone.md" + - PGVectorStore: "swarms/memory/pg.md" - ShortTermMemory: "swarms/memory/short_term_memory.md" - swarms.utils: - - pdf_to_text: "swarms/utils/pdf_to_text.md" - - load_model_torch: "swarms/utils/load_model_torch.md" - - metrics_decorator: "swarms/utils/metrics_decorator.md" - - prep_torch_inference: "swarms/utils/prep_torch_inference.md" - - find_image_path: "swarms/utils/find_image_path.md" - - print_class_parameters: "swarms/utils/print_class_parameters.md" - - extract_code_from_markdown: "swarms/utils/extract_code_from_markdown.md" - - check_device: "swarms/utils/check_device.md" - - display_markdown_message: "swarms/utils/display_markdown_message.md" - - phoenix_tracer: "swarms/utils/phoenix_tracer.md" - - limit_tokens_from_string: "swarms/utils/limit_tokens_from_string.md" - - math_eval: "swarms/utils/math_eval.md" + - Misc: + - pdf_to_text: "swarms/utils/pdf_to_text.md" + - load_model_torch: "swarms/utils/load_model_torch.md" + - metrics_decorator: "swarms/utils/metrics_decorator.md" + - prep_torch_inference: "swarms/utils/prep_torch_inference.md" + - find_image_path: "swarms/utils/find_image_path.md" + - print_class_parameters: "swarms/utils/print_class_parameters.md" + - extract_code_from_markdown: "swarms/utils/extract_code_from_markdown.md" + - check_device: "swarms/utils/check_device.md" + - display_markdown_message: "swarms/utils/display_markdown_message.md" + - phoenix_tracer: "swarms/utils/phoenix_tracer.md" + - limit_tokens_from_string: "swarms/utils/limit_tokens_from_string.md" + - math_eval: "swarms/utils/math_eval.md" - Guides: - Overview: "examples/index.md" - Agents: @@ -160,4 +165,5 @@ nav: - Checklist: "corporate/checklist.md" - Hiring: "corporate/hiring.md" - SwarmCloud: "corporate/swarm_cloud.md" - - SwarmMemo: "corporate/swarm_memo.md" \ No newline at end of file + - SwarmMemo: "corporate/swarm_memo.md" + - Data Room: "corporate/data_room.md" diff --git a/playground/models/llava.py b/playground/models/llava.py new file mode 100644 index 00000000..561b6f88 --- /dev/null +++ b/playground/models/llava.py @@ -0,0 +1,16 @@ +from swarms import QwenVLMultiModal + +# Instantiate the QwenVLMultiModal model +model = QwenVLMultiModal( + model_name="Qwen/Qwen-VL-Chat", + device="cuda", + quantize=True, +) + +# Run the model +response = model( + "Hello, how are you?", "https://example.com/image.jpg" +) + +# Print the response +print(response) diff --git a/playground/structs/company_example.py b/playground/structs/company_example.py new file mode 100644 index 00000000..72396c61 --- /dev/null +++ b/playground/structs/company_example.py @@ -0,0 +1,38 @@ +# Example + +import os + +from dotenv import load_dotenv + +from swarms import Agent, OpenAIChat +from swarms.structs.company import Company + +load_dotenv() + +llm = OpenAIChat( + openai_api_key=os.getenv("OPENAI_API_KEY"), max_tokens=4000 +) + +ceo = Agent(llm=llm, ai_name="CEO") +dev = Agent(llm=llm, ai_name="Developer") +va = Agent(llm=llm, ai_name="VA") + +# Create a company +company = Company( + org_chart=[[dev, va]], + shared_instructions="Do your best", + ceo=ceo, +) + +# Add agents to the company +hr = Agent(llm=llm, name="HR") +company.add(hr) + +# Get an agent from the company +hr = company.get("CEO") + +# Remove an agent from the company +company.remove(hr) + +# Run the company +company.run() diff --git a/playground/structs/concurrent_workflow.py b/playground/structs/concurrent_workflow.py index a228d247..98531388 100644 --- a/playground/structs/concurrent_workflow.py +++ b/playground/structs/concurrent_workflow.py @@ -12,15 +12,18 @@ agent = Agent(llm=llm, max_loops=1) # Create a workflow workflow = ConcurrentWorkflow(max_workers=5) +task = ( + "Generate a report on how small businesses spend money and how" + " can they cut 40 percent of their costs" +) + # 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") +task1 = Task(agent, task) +task2 = Task(agent, task) +task3 = Task(agent, task) # Add tasks to the workflow -workflow.add(task1) -workflow.add(task2) -workflow.add(task3) +workflow.add(tasks=[task1, task2, task3]) # Run the workflow workflow.run() diff --git a/playground/structs/sequential_workflow.py b/playground/structs/sequential_workflow.py index fa7ca16a..7fa110bc 100644 --- a/playground/structs/sequential_workflow.py +++ b/playground/structs/sequential_workflow.py @@ -1,6 +1,4 @@ -from swarms.models import OpenAIChat -from swarms.structs import Agent -from swarms.structs.sequential_workflow import SequentialWorkflow +from swarms import OpenAIChat, Agent, Task, SequentialWorkflow # Example usage llm = OpenAIChat( @@ -9,25 +7,42 @@ llm = OpenAIChat( ) # Initialize the Agent with the language agent -flow1 = Agent(llm=llm, max_loops=1, dashboard=False) +agent1 = Agent( + agent_name="John the writer", + llm=llm, + max_loops=0, + dashboard=False, +) +task1 = Task( + agent=agent1, + description="Write a 1000 word blog about the future of AI", +) # Create another Agent for a different task -flow2 = Agent(llm=llm, max_loops=1, dashboard=False) +agent2 = Agent("Summarizer", llm=llm, max_loops=1, dashboard=False) +task2 = Task( + agent=agent2, + description="Summarize the generated blog", +) # Create the workflow -workflow = SequentialWorkflow(max_loops=1) - -# Add tasks to the workflow -workflow.add( - "Generate a 10,000 word blog on health and wellness.", flow1 +workflow = SequentialWorkflow( + name="Blog Generation Workflow", + description=( + "A workflow to generate and summarize a blog about the future" + " of AI" + ), + max_loops=1, + autosave=True, + dashboard=False, ) -# Suppose the next task takes the output of the first task as input -workflow.add("Summarize the generated blog", flow2) +# Add tasks to the workflow +workflow.add(tasks=[task1, task2]) # Run the workflow workflow.run() -# Output the results -for task in workflow.tasks: - print(f"Task: {task.description}, Result: {task.result}") +# # Output the results +# for task in workflow.tasks: +# print(f"Task: {task.description}, Result: {task.result}") diff --git a/playground/structs/task.py b/playground/structs/task.py index 089cb263..c2ade96a 100644 --- a/playground/structs/task.py +++ b/playground/structs/task.py @@ -1,8 +1,8 @@ -from swarms.structs import Task, Agent -from swarms.models import OpenAIChat -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from swarms.structs import Agent, OpenAIChat, Task # Load the environment variables load_dotenv() @@ -27,7 +27,13 @@ agent = Agent( ) # Create a task -task = Task(description="What's the weather in miami", agent=agent) +task = Task( + description=( + "Generate a report on the top 3 biggest expenses for small" + " businesses and how businesses can save 20%" + ), + agent=agent, +) # Set the action and condition task.set_action(my_action) diff --git a/pyproject.toml b/pyproject.toml index 22f09946..fdd127c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms" -version = "3.7.5" +version = "3.7.9" description = "Swarms - Pytorch" license = "MIT" authors = ["Kye Gomez "] @@ -73,6 +73,7 @@ psutil = "*" ultralytics = "*" timm = "*" supervision = "*" +scikit-image = "*" diff --git a/requirements.txt b/requirements.txt index d7befb85..ab78bb36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,4 +61,5 @@ pre-commit==3.2.2 peft psutil ultralytics -supervision \ No newline at end of file +supervision +scikit-image \ No newline at end of file diff --git a/scripts/auto_tests_docs/auto_docs.py b/scripts/auto_tests_docs/auto_docs.py index 8d441ff3..020684ff 100644 --- a/scripts/auto_tests_docs/auto_docs.py +++ b/scripts/auto_tests_docs/auto_docs.py @@ -9,20 +9,11 @@ from scripts.auto_tests_docs.docs import DOCUMENTATION_WRITER_SOP from swarms import OpenAIChat ########## -from swarms.structs.task import Task -from swarms.structs.swarm_net import SwarmNetwork -from swarms.structs.nonlinear_workflow import NonlinearWorkflow -from swarms.structs.recursive_workflow import RecursiveWorkflow -from swarms.structs.groupchat import GroupChat, GroupChatManager -from swarms.structs.base_workflow import BaseWorkflow -from swarms.structs.concurrent_workflow import ConcurrentWorkflow -from swarms.structs.base import BaseStructure -from swarms.structs.schemas import ( - Artifact, - ArtifactUpload, - StepInput, - TaskInput, -) +from swarms.agents.base import AbstractAgent +from swarms.structs.message import Message +from swarms.agents.omni_modal_agent import OmniModalAgent +from swarms.agents.tool_agent import ToolAgent +from swarms.agents.worker_agent import WorkerAgent #################### load_dotenv() @@ -49,14 +40,14 @@ def process_documentation(cls): # Process with OpenAI model (assuming the model's __call__ method takes this input and returns processed content) processed_content = model( - DOCUMENTATION_WRITER_SOP(input_content, "swarms.structs") + DOCUMENTATION_WRITER_SOP(input_content, "swarms.agents") ) # doc_content = f"# {cls.__name__}\n\n{processed_content}\n" doc_content = f"{processed_content}\n" # Create the directory if it doesn't exist - dir_path = "docs/swarms/structs" + dir_path = "docs/swarms/agents" os.makedirs(dir_path, exist_ok=True) # Write the processed documentation to a Markdown file @@ -69,19 +60,11 @@ def process_documentation(cls): def main(): classes = [ - Task, - SwarmNetwork, - NonlinearWorkflow, - RecursiveWorkflow, - GroupChat, - GroupChatManager, - BaseWorkflow, - ConcurrentWorkflow, - BaseStructure, - Artifact, - ArtifactUpload, - StepInput, - TaskInput, + AbstractAgent, + Message, + OmniModalAgent, + ToolAgent, + WorkerAgent, ] threads = [] for cls in classes: @@ -95,7 +78,7 @@ def main(): for thread in threads: thread.join() - print("Documentation generated in 'swarms.structs' directory.") + print("Documentation generated in 'swarms.agents' directory.") if __name__ == "__main__": diff --git a/scripts/auto_tests_docs/mkdocs_handler.py b/scripts/auto_tests_docs/mkdocs_handler.py index 8b1dc0a0..f5c4044f 100644 --- a/scripts/auto_tests_docs/mkdocs_handler.py +++ b/scripts/auto_tests_docs/mkdocs_handler.py @@ -28,4 +28,4 @@ def generate_file_list(directory, output_file): # Use the function to generate the file list -generate_file_list("docs/swarms/structs", "file_list.txt") +generate_file_list("docs/swarms/agents", "file_list.txt") diff --git a/swarms/__init__.py b/swarms/__init__.py index 4e6785cb..54a60596 100644 --- a/swarms/__init__.py +++ b/swarms/__init__.py @@ -1,3 +1,4 @@ +# from swarms.telemetry.main import Telemetry # noqa: E402, F403 from swarms.telemetry.bootup import bootup # noqa: E402, F403 bootup() @@ -8,3 +9,14 @@ from swarms.models import * # noqa: E402, F403 from swarms.telemetry import * # noqa: E402, F403 from swarms.utils import * # noqa: E402, F403 from swarms.prompts import * # noqa: E402, F403 + + +# telemetry = Telemetry('mongodb://localhost:27017/', 'mydatabase') + +# telemetry.log_import('swarms.telemetry.bootup') +# telemetry.log_import('swarms.agents') +# telemetry.log_import('swarms.structs') +# telemetry.log_import('swarms.models') +# telemetry.log_import('swarms.telemetry') +# telemetry.log_import('swarms.utils') +# telemetry.log_import('swarms.prompts') diff --git a/swarms/agents/__init__.py b/swarms/agents/__init__.py index 876a4d27..4b786bf4 100644 --- a/swarms/agents/__init__.py +++ b/swarms/agents/__init__.py @@ -1,13 +1,35 @@ -from swarms.agents.message import Message from swarms.agents.base import AbstractAgent -from swarms.agents.tool_agent import ToolAgent -from swarms.agents.simple_agent import SimpleAgent from swarms.agents.omni_modal_agent import OmniModalAgent +from swarms.agents.simple_agent import SimpleAgent +from swarms.agents.stopping_conditions import ( + check_cancelled, + check_complete, + check_done, + check_end, + check_error, + check_exit, + check_failure, + check_finished, + check_stopped, + check_success, +) +from swarms.agents.tool_agent import ToolAgent +from swarms.agents.worker_agent import Worker __all__ = [ - "Message", "AbstractAgent", "ToolAgent", "SimpleAgent", "OmniModalAgent", + "check_done", + "check_finished", + "check_complete", + "check_success", + "check_failure", + "check_error", + "check_stopped", + "check_cancelled", + "check_exit", + "check_end", + "Worker", ] diff --git a/swarms/agents/omni_modal_agent.py b/swarms/agents/omni_modal_agent.py index fae6ab4e..113ec461 100644 --- a/swarms/agents/omni_modal_agent.py +++ b/swarms/agents/omni_modal_agent.py @@ -10,7 +10,7 @@ from langchain_experimental.autonomous_agents.hugginggpt.task_planner import ( ) from transformers import load_tool -from swarms.agents.message import Message +from swarms.structs.message import Message class OmniModalAgent: diff --git a/swarms/agents/stopping_conditions.py b/swarms/agents/stopping_conditions.py new file mode 100644 index 00000000..85acbf94 --- /dev/null +++ b/swarms/agents/stopping_conditions.py @@ -0,0 +1,38 @@ +def check_done(s): + return "" in s + + +def check_finished(s): + return "finished" in s + + +def check_complete(s): + return "complete" in s + + +def check_success(s): + return "success" in s + + +def check_failure(s): + return "failure" in s + + +def check_error(s): + return "error" in s + + +def check_stopped(s): + return "stopped" in s + + +def check_cancelled(s): + return "cancelled" in s + + +def check_exit(s): + return "exit" in s + + +def check_end(s): + return "end" in s diff --git a/swarms/agents/tool_agent.py b/swarms/agents/tool_agent.py index bc34a476..b2a22ba0 100644 --- a/swarms/agents/tool_agent.py +++ b/swarms/agents/tool_agent.py @@ -1,7 +1,3 @@ -""" -Tool Agent - -""" from swarms.tools.format_tools import Jsonformer from typing import Any from swarms.models.base_llm import AbstractLLM diff --git a/swarms/models/__init__.py b/swarms/models/__init__.py index 364d1d7f..635124a6 100644 --- a/swarms/models/__init__.py +++ b/swarms/models/__init__.py @@ -44,7 +44,9 @@ from swarms.models.timm import TimmModel # noqa: E402 from swarms.models.ultralytics_model import ( UltralyticsModel, ) # noqa: E402 - +from swarms.models.vip_llava import VipLlavaMultiModal # noqa: E402 +from swarms.models.llava import LavaMultiModal # noqa: E402 +from swarms.models.qwen import QwenVLMultiModal # noqa: E402 # from swarms.models.dalle3 import Dalle3 # from swarms.models.distilled_whisperx import DistilWhisperModel # noqa: E402 @@ -105,4 +107,7 @@ __all__ = [ "TogetherLLM", "TimmModel", "UltralyticsModel", + "VipLlavaMultiModal", + "LavaMultiModal", + "QwenVLMultiModal", ] diff --git a/swarms/models/llava.py b/swarms/models/llava.py index 605904c3..bcc1b09f 100644 --- a/swarms/models/llava.py +++ b/swarms/models/llava.py @@ -1,82 +1,82 @@ -from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline +import requests +from PIL import Image +from transformers import AutoProcessor, LlavaForConditionalGeneration +from typing import Tuple, Union +from io import BytesIO +from swarms.models.base_multimodal_model import BaseMultiModalModel -class MultiModalLlava: +class LavaMultiModal(BaseMultiModalModel): """ - LLava Model + A class to handle multi-modal inputs (text and image) using the Llava model for conditional generation. + + Attributes: + model_name (str): The name or path of the pre-trained model. + max_length (int): The maximum length of the generated sequence. Args: - model_name_or_path: The model name or path to the model - revision: The revision of the model to use - device: The device to run the model on - max_new_tokens: The maximum number of tokens to generate - do_sample: Whether or not to use sampling - temperature: The temperature of the sampling - top_p: The top p value for sampling - top_k: The top k value for sampling - repetition_penalty: The repetition penalty for sampling - device_map: The device map to use + model_name (str): The name of the pre-trained model. + max_length (int): The maximum length of the generated sequence. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. - Methods: - __call__: Call the model - chat: Interactive chat in terminal + Examples: + >>> model = LavaMultiModal() + >>> model.run("A cat", "https://example.com/cat.jpg") - Example: - >>> from swarms.models.llava import LlavaModel - >>> model = LlavaModel(device="cpu") - >>> model("Hello, I am a robot.") """ def __init__( self, - model_name_or_path="TheBloke/llava-v1.5-13B-GPTQ", - revision="main", - device="cuda", - max_new_tokens=512, - do_sample=True, - temperature=0.7, - top_p=0.95, - top_k=40, - repetition_penalty=1.1, - device_map: str = "auto", - ): - self.device = device - self.model = AutoModelForCausalLM.from_pretrained( - model_name_or_path, - device_map=device_map, - trust_remote_code=False, - revision=revision, - ).to(self.device) + model_name: str = "llava-hf/llava-1.5-7b-hf", + max_length: int = 30, + *args, + **kwargs, + ) -> None: + super().__init__(*args, **kwargs) + self.model_name = model_name + self.max_length = max_length - self.tokenizer = AutoTokenizer.from_pretrained( - model_name_or_path, use_fast=True - ) - self.pipe = pipeline( - "text-generation", - model=self.model, - tokenizer=self.tokenizer, - max_new_tokens=max_new_tokens, - do_sample=do_sample, - temperature=temperature, - top_p=top_p, - top_k=top_k, - repetition_penalty=repetition_penalty, - device=0 if self.device == "cuda" else -1, + self.model = LlavaForConditionalGeneration.from_pretrained( + model_name, *args, **kwargs ) + self.processor = AutoProcessor.from_pretrained(model_name) - def __call__(self, prompt): - """Call the model""" - return self.pipe(prompt)[0]["generated_text"] + def run( + self, text: str, img: str, *args, **kwargs + ) -> Union[str, Tuple[None, str]]: + """ + Processes the input text and image, and generates a response. - def chat(self): - """Interactive chat in terminal""" - print( - "Starting chat with LlavaModel. Type 'exit' to end the" - " session." - ) - while True: - user_input = input("You: ") - if user_input.lower() == "exit": - break - response = self(user_input) - print(f"Model: {response}") + Args: + text (str): The input text for the model. + img (str): The URL of the image to process. + max_length (int): The maximum length of the generated sequence. + + Returns: + Union[str, Tuple[None, str]]: The generated response string or a tuple (None, error message) in case of an error. + """ + try: + response = requests.get(img, stream=True) + response.raise_for_status() + image = Image.open(BytesIO(response.content)) + + inputs = self.processor( + text=text, images=image, return_tensors="pt" + ) + + # Generate + generate_ids = self.model.generate( + **inputs, max_length=self.max_length, **kwargs + ) + return self.processor.batch_decode( + generate_ids, + skip_special_tokens=True, + clean_up_tokenization_spaces=False, + *args, + )[0] + + except requests.RequestException as e: + return None, f"Error fetching image: {str(e)}" + except Exception as e: + return None, f"Error during model processing: {str(e)}" diff --git a/swarms/models/medical_sam.py b/swarms/models/medical_sam.py new file mode 100644 index 00000000..8d096ba5 --- /dev/null +++ b/swarms/models/medical_sam.py @@ -0,0 +1,144 @@ +import os +from dataclasses import dataclass +from typing import Tuple + +import numpy as np +import requests +import torch +import torch.nn.functional as F +from skimage import transform +from torch import Tensor + + +def sam_model_registry(): + pass + + +@dataclass +class MedicalSAM: + """ + MedicalSAM class for performing semantic segmentation on medical images using the SAM model. + + Attributes: + model_path (str): The file path to the model weights. + device (str): The device to run the model on (default is "cuda:0"). + model_weights_url (str): The URL to download the model weights from. + + Methods: + __post_init__(): Initializes the MedicalSAM object. + download_model_weights(model_path: str): Downloads the model weights from the specified URL and saves them to the given file path. + preprocess(img): Preprocesses the input image. + run(img, box): Runs the semantic segmentation on the input image within the specified bounding box. + + """ + + model_path: str + device: str = "cuda:0" + model_weights_url: str = "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth" + + def __post_init__(self): + if not os.path.exists(self.model_path): + self.download_model_weights(self.model_path) + + self.model = sam_model_registry["vit_b"]( + checkpoint=self.model_path + ) + self.model = self.model.to(self.device) + self.model.eval() + + def download_model_weights(self, model_path: str): + """ + Downloads the model weights from the specified URL and saves them to the given file path. + + Args: + model_path (str): The file path where the model weights will be saved. + + Raises: + Exception: If the model weights fail to download. + """ + response = requests.get(self.model_weights_url, stream=True) + if response.status_code == 200: + with open(model_path, "wb") as f: + f.write(response.content) + else: + raise Exception("Failed to download model weights.") + + def preprocess(self, img: np.ndarray) -> Tuple[Tensor, int, int]: + """ + Preprocesses the input image. + + Args: + img: The input image. + + Returns: + img_tensor: The preprocessed image tensor. + H: The original height of the image. + W: The original width of the image. + """ + if len(img.shape) == 2: + img = np.repeat(img[:, :, None], 3, axis=-1) + H, W, _ = img.shape + img = transform.resize( + img, + (1024, 1024), + order=3, + preserve_range=True, + anti_aliasing=True, + ).astype(np.uint8) + img = img - img.min() / np.clip( + img.max() - img.min(), a_min=1e-8, a_max=None + ) + img = torch.tensor(img).float().permute(2, 0, 1).unsqueeze(0) + return img, H, W + + @torch.no_grad() + def run(self, img: np.ndarray, box: np.ndarray) -> np.ndarray: + """ + Runs the semantic segmentation on the input image within the specified bounding box. + + Args: + img: The input image. + box: The bounding box coordinates (x1, y1, x2, y2). + + Returns: + medsam_seg: The segmented image. + """ + img_tensor, H, W = self.preprocess(img) + img_tensor = img_tensor.to(self.device) + box_1024 = box / np.array([W, H, W, H]) * 1024 + img = self.model.image_encoder(img_tensor) + + box_torch = torch.as_tensor( + box_1024, dtype=torch.float, device=img_tensor.device + ) + + if len(box_torch.shape) == 2: + box_torch = box_torch[:, None, :] + + sparse_embeddings, dense_embeddings = ( + self.model.prompt_encoder( + points=None, + boxes=box_torch, + masks=None, + ) + ) + + low_res_logits, _ = self.model.mask_decoder( + image_embeddings=img, + image_pe=self.model.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=False, + ) + + low_res_pred = torch.sigmoid(low_res_logits) + low_res_pred = F.interpolate( + low_res_pred, + size=(H, W), + mode="bilinear", + align_corners=False, + ) + low_res_pred = low_res_pred.squeeze().cpu().numpy() + medsam_seg = (low_res_pred > 0.5).astype(np.uint8) + + return medsam_seg diff --git a/swarms/models/mistral.py b/swarms/models/mistral.py index 297ecf12..aeeb37a8 100644 --- a/swarms/models/mistral.py +++ b/swarms/models/mistral.py @@ -1,7 +1,7 @@ import torch from transformers import AutoModelForCausalLM, AutoTokenizer -from swarms.agents.message import Message +from swarms.structs.message import Message class Mistral: diff --git a/swarms/models/odin.py b/swarms/models/odin.py index 1ab09893..27cb1710 100644 --- a/swarms/models/odin.py +++ b/swarms/models/odin.py @@ -1,47 +1,56 @@ +import os import supervision as sv -from ultraanalytics import YOLO +from ultralytics import YOLO from tqdm import tqdm from swarms.models.base_llm import AbstractLLM +from swarms.utils.download_weights_from_url import ( + download_weights_from_url, +) class Odin(AbstractLLM): """ Odin class represents an object detection and tracking model. - Args: - source_weights_path (str): Path to the weights file for the object detection model. - source_video_path (str): Path to the source video file. - target_video_path (str): Path to save the output video file. - confidence_threshold (float): Confidence threshold for object detection. - iou_threshold (float): Intersection over Union (IoU) threshold for object detection. - Attributes: - source_weights_path (str): Path to the weights file for the object detection model. - source_video_path (str): Path to the source video file. - target_video_path (str): Path to save the output video file. - confidence_threshold (float): Confidence threshold for object detection. - iou_threshold (float): Intersection over Union (IoU) threshold for object detection. + source_weights_path (str): The file path to the YOLO model weights. + confidence_threshold (float): The confidence threshold for object detection. + iou_threshold (float): The intersection over union (IOU) threshold for object detection. + + Example: + >>> odin = Odin( + ... source_weights_path="yolo.weights", + ... confidence_threshold=0.3, + ... iou_threshold=0.7, + ... ) + >>> odin.run(video="input.mp4") + + """ def __init__( self, - source_weights_path: str = None, - target_video_path: str = None, + source_weights_path: str = "yolo.weights", confidence_threshold: float = 0.3, iou_threshold: float = 0.7, ): super(Odin, self).__init__() self.source_weights_path = source_weights_path - self.target_video_path = target_video_path self.confidence_threshold = confidence_threshold self.iou_threshold = iou_threshold - def run(self, video_path: str, *args, **kwargs): + if not os.path.exists(self.source_weights_path): + download_weights_from_url( + url=source_weights_path, + save_path=self.source_weights_path, + ) + + def run(self, video: str, *args, **kwargs): """ Runs the object detection and tracking algorithm on the specified video. Args: - video_path (str): The path to the input video file. + video (str): The path to the input video file. *args: Additional positional arguments. **kwargs: Additional keyword arguments. @@ -53,14 +62,12 @@ class Odin(AbstractLLM): tracker = sv.ByteTrack() box_annotator = sv.BoxAnnotator() frame_generator = sv.get_video_frames_generator( - source_path=self.source_video_path - ) - video_info = sv.VideoInfo.from_video_path( - video_path=video_path + source_path=self.source_video ) + video_info = sv.VideoInfo.from_video(video=video) with sv.VideoSink( - target_path=self.target_video_path, video_info=video_info + target_path=self.target_video, video_info=video_info ) as sink: for frame in tqdm( frame_generator, total=video_info.total_frames diff --git a/swarms/models/qwen.py b/swarms/models/qwen.py new file mode 100644 index 00000000..1533b117 --- /dev/null +++ b/swarms/models/qwen.py @@ -0,0 +1,108 @@ +from dataclasses import dataclass, field +from typing import Optional, Tuple + +from PIL import Image +from transformers import AutoModelForCausalLM, AutoTokenizer + +from swarms.models.base_multimodal_model import BaseMultiModalModel + + +@dataclass +class QwenVLMultiModal(BaseMultiModalModel): + """ + QwenVLMultiModal is a class that represents a multi-modal model for Qwen chatbot. + It inherits from the BaseMultiModalModel class. + + Examples: + >>> model = QwenVLMultiModal() + >>> model.run("Hello, how are you?", "https://example.com/image.jpg") + + """ + + model_name: str = "Qwen/Qwen-VL-Chat" + device: str = "cuda" + args: tuple = field(default_factory=tuple) + kwargs: dict = field(default_factory=dict) + quantize: bool = False + + def __post_init__(self): + """ + Initializes the QwenVLMultiModal object. + It initializes the tokenizer and the model for the Qwen chatbot. + """ + + if self.quantize: + self.model_name = "Qwen/Qwen-VL-Chat-Int4" + + self.tokenizer = AutoTokenizer.from_pretrained( + self.model_name, trust_remote_code=True + ) + self.model = AutoModelForCausalLM.from_pretrained( + self.model_name, + device_map=self.device, + trust_remote_code=True, + ).eval() + + def run( + self, text: str, img: str, *args, **kwargs + ) -> Tuple[Optional[str], Optional[Image.Image]]: + """ + Runs the Qwen chatbot model on the given text and image inputs. + + Args: + text (str): The input text for the chatbot. + img (str): The input image for the chatbot. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + Tuple[Optional[str], Optional[Image.Image]]: A tuple containing the response generated by the chatbot + and the image associated with the response (if any). + """ + try: + query = self.tokenizer.from_list_format( + [ + {"image": img, "text": text}, + ] + ) + + inputs = self.tokenizer(query, return_tensors="pt") + inputs = inputs.to(self.model.device) + pred = self.model.generate(**inputs) + response = self.tokenizer.decode( + pred.cpu()[0], skip_special_tokens=False + ) + return response + except Exception as error: + print(f"[ERROR]: [QwenVLMultiModal]: {error}") + + def chat( + self, text: str, img: str, *args, **kwargs + ) -> tuple[str, list]: + """ + Chat with the model using text and image inputs. + + Args: + text (str): The text input for the chat. + img (str): The image input for the chat. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + tuple[str, list]: A tuple containing the response and chat history. + + Raises: + Exception: If an error occurs during the chat. + + """ + try: + response, history = self.model.chat( + self.tokenizer, + query=f"{img}这是什么", + history=None, + ) + return response, history + except Exception as e: + raise Exception( + "An error occurred during the chat." + ) from e diff --git a/swarms/models/vip_llava.py b/swarms/models/vip_llava.py new file mode 100644 index 00000000..db532913 --- /dev/null +++ b/swarms/models/vip_llava.py @@ -0,0 +1,94 @@ +from io import BytesIO + +import requests +import torch +from PIL import Image +from transformers import ( + AutoProcessor, + VipLlavaForConditionalGeneration, +) + +from swarms.models.base_multimodal_model import BaseMultiModalModel + + +class VipLlavaMultiModal(BaseMultiModalModel): + """ + A multi-modal model for VIP-LLAVA. + + Args: + model_name (str): The name or path of the pre-trained model. + max_new_tokens (int): The maximum number of new tokens to generate. + device_map (str): The device mapping for the model. + torch_dtype: The torch data type for the model. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + """ + + def __init__( + self, + model_name: str = "llava-hf/vip-llava-7b-hf", + max_new_tokens: int = 500, + device_map: str = "auto", + torch_dtype=torch.float16, + *args, + **kwargs, + ): + super().__init__(*args, **kwargs) + self.model_name = model_name + self.max_new_tokens = max_new_tokens + self.device_map = device_map + self.torch_dtype = torch_dtype + + self.model = VipLlavaForConditionalGeneration.from_pretrained( + model_name, + device_map=device_map, + torch_dtype=torch_dtype, + *args, + **kwargs, + ) + self.processor = AutoProcessor.from_pretrained( + model_name, *args, **kwargs + ) + + def run(self, text: str, img: str, *args, **kwargs): + """ + Run the VIP-LLAVA model. + + Args: + text (str): The input text. + img (str): The URL of the input image. + *args: Additional positional arguments. + **kwargs: Additional keyword arguments. + + Returns: + str: The generated output text. + tuple: A tuple containing None and the error message if an error occurs. + """ + try: + response = requests.get(img, stream=True) + response.raise_for_status() + image = Image.open(BytesIO(response.content)) + + inputs = self.processor( + text=text, + images=image, + return_tensors="pt", + *args, + **kwargs, + ).to(0, self.torch_dtype) + + # Generate + generate_ids = self.model.generate( + **inputs, max_new_tokens=self.max_new_tokens, **kwargs + ) + + return self.processor.decode( + generate_ids[0][len(inputs["input_ids"][0]) :], + skip_special_tokens=True, + ) + + except requests.RequestException as error: + return None, f"Error fetching image: {error}" + + except Exception as error: + return None, f"Error during model inference: {error}" diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index e7ba49ca..11e9e523 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -3,12 +3,16 @@ from swarms.structs.autoscaler import AutoScaler from swarms.structs.base import BaseStructure from swarms.structs.base_swarm import AbstractSwarm from swarms.structs.base_workflow import BaseWorkflow +from swarms.structs.block_wrapper import block from swarms.structs.concurrent_workflow import ConcurrentWorkflow from swarms.structs.conversation import Conversation +from swarms.structs.graph_workflow import GraphWorkflow from swarms.structs.groupchat import GroupChat, GroupChatManager +from swarms.structs.message import Message from swarms.structs.model_parallizer import ModelParallelizer from swarms.structs.multi_agent_collab import MultiAgentCollaboration from swarms.structs.nonlinear_workflow import NonlinearWorkflow +from swarms.structs.plan import Plan from swarms.structs.recursive_workflow import RecursiveWorkflow from swarms.structs.schemas import ( Artifact, @@ -17,22 +21,38 @@ from swarms.structs.schemas import ( TaskInput, ) from swarms.structs.sequential_workflow import SequentialWorkflow +from swarms.structs.step import Step from swarms.structs.swarm_net import SwarmNetwork +from swarms.structs.swarming_architectures import ( + broadcast, + circular_swarm, + exponential_swarm, + fibonacci_swarm, + geometric_swarm, + grid_swarm, + harmonic_swarm, + linear_swarm, + log_swarm, + mesh_swarm, + one_to_one, + one_to_three, + power_swarm, + prime_swarm, + pyramid_swarm, + sigmoid_swarm, + staircase_swarm, + star_swarm, +) +from swarms.structs.task import Task from swarms.structs.utils import ( + detect_markdown, distribute_tasks, extract_key_from_json, extract_tokens_from_text, find_agent_by_id, find_token_in_text, parse_tasks, - detect_markdown, ) -from swarms.structs.task import Task -from swarms.structs.block_wrapper import block -from swarms.structs.graph_workflow import GraphWorkflow -from swarms.structs.step import Step -from swarms.structs.plan import Plan - __all__ = [ "Agent", @@ -66,4 +86,23 @@ __all__ = [ "GraphWorkflow", "Step", "Plan", + "Message", + "broadcast", + "circular_swarm", + "exponential_swarm", + "fibonacci_swarm", + "geometric_swarm", + "grid_swarm", + "harmonic_swarm", + "linear_swarm", + "log_swarm", + "mesh_swarm", + "one_to_one", + "one_to_three", + "power_swarm", + "prime_swarm", + "pyramid_swarm", + "sigmoid_swarm", + "staircase_swarm", + "star_swarm", ] diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 1b137988..dace1ff3 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -1,9 +1,8 @@ import asyncio -import inspect import json import logging +import os import random -import re import time import uuid from typing import Any, Callable, Dict, List, Optional, Tuple @@ -13,23 +12,19 @@ from termcolor import colored from swarms.memory.base_vectordb import VectorDatabase from swarms.prompts.agent_system_prompts import ( AGENT_SYSTEM_PROMPT_3, - agent_system_prompt_2, ) from swarms.prompts.multi_modal_autonomous_instruction_prompt import ( MULTI_MODAL_AUTO_AGENT_SYSTEM_PROMPT_1, ) -from swarms.prompts.tools import ( - SCENARIOS, -) from swarms.tools.tool import BaseTool -from swarms.tools.tool_func_doc_scraper import scrape_tool_func_docs from swarms.utils.code_interpreter import SubprocessCodeInterpreter +from swarms.utils.data_to_text import data_to_text +from swarms.utils.logger import logger from swarms.utils.parse_code import ( extract_code_from_markdown, ) from swarms.utils.pdf_to_text import pdf_to_text from swarms.utils.token_count_tiktoken import limit_tokens_from_string -from swarms.utils.data_to_text import data_to_text # Utils @@ -112,26 +107,17 @@ class Agent: filtered_run: Run the agent with filtered responses interactive_run: Run the agent in interactive mode streamed_generation: Stream the generation of the response - get_llm_params: Get the llm parameters save_state: Save the state load_state: Load the state - get_llm_init_params: Get the llm init parameters - get_tool_description: Get the tool description - find_tool_by_name: Find a tool by name - extract_tool_commands: Extract the tool commands - execute_tools: Execute the tools - parse_and_execute_tools: Parse and execute the tools truncate_history: Truncate the history add_task_to_memory: Add the task to the memory add_message_to_memory: Add the message to the memory add_message_to_memory_and_truncate: Add the message to the memory and truncate - parse_tool_docs: Parse the tool docs print_dashboard: Print the dashboard loop_count_print: Print the loop count streaming: Stream the content _history: Generate the history _dynamic_prompt_setup: Setup the dynamic prompt - agent_system_prompt_2: Agent system prompt 2 run_async: Run the agent asynchronously run_async_concurrent: Run the agent asynchronously and concurrently run_async_concurrent: Run the agent asynchronously and concurrently @@ -182,12 +168,13 @@ class Agent: pdf_path: Optional[str] = None, list_of_pdf: Optional[str] = None, tokenizer: Optional[Any] = None, - memory: Optional[VectorDatabase] = None, + long_term_memory: Optional[VectorDatabase] = None, preset_stopping_token: Optional[bool] = False, traceback: Any = None, traceback_handlers: Any = None, streaming_on: Optional[bool] = False, docs: List[str] = None, + docs_folder: str = None, *args, **kwargs: Any, ): @@ -210,9 +197,7 @@ class Agent: self.context_length = context_length self.sop = sop self.sop_list = sop_list - self.sop_list = [] - self.tools = tools or [] - self.tool_docs = [] + self.tools = tools self.system_prompt = system_prompt self.agent_name = agent_name self.agent_description = agent_description @@ -226,12 +211,13 @@ class Agent: self.pdf_path = pdf_path self.list_of_pdf = list_of_pdf self.tokenizer = tokenizer - self.memory = memory + self.long_term_memory = long_term_memory self.preset_stopping_token = preset_stopping_token self.traceback = traceback self.traceback_handlers = traceback_handlers self.streaming_on = streaming_on self.docs = docs + self.docs_folder = docs_folder # The max_loops will be set dynamically if the dynamic_loop if self.dynamic_loops: @@ -256,18 +242,15 @@ class Agent: if preset_stopping_token: self.stopping_token = "" - # If tools exist then add the tool docs usage to the sop - if self.tools: - self.sop_list.append( - self.tools_prompt_prep(self.tool_docs, SCENARIOS) - ) - - # self.short_memory_test = Conversation(time_enabled=True) + # self.short_memory = Conversation(time_enabled=True) # If the docs exist then ingest the docs if self.docs: self.ingest_docs(self.docs) + if self.docs_folder: + self.get_docs_from_doc_folders() + def set_system_prompt(self, system_prompt: str): """Set the system prompt""" self.system_prompt = system_prompt @@ -316,93 +299,6 @@ class Agent: """Format the template with the provided kwargs using f-string interpolation.""" return template.format(**kwargs) - def get_llm_init_params(self) -> str: - """Get LLM init params""" - init_signature = inspect.signature(self.llm.__init__) - params = init_signature.parameters - params_str_list = [] - - for name, param in params.items(): - if name == "self": - continue - if hasattr(self.llm, name): - value = getattr(self.llm, name) - else: - value = self.llm.__dict__.get(name, "Unknown") - - params_str_list.append( - f" {name.capitalize().replace('_', ' ')}: {value}" - ) - - return "\n".join(params_str_list) - - def get_tool_description(self): - """Get the tool description""" - if self.tools: - try: - tool_descriptions = [] - for tool in self.tools: - description = f"{tool.name}: {tool.description}" - tool_descriptions.append(description) - return "\n".join(tool_descriptions) - except Exception as error: - print( - f"Error getting tool description: {error} try" - " adding a description to the tool or removing" - " the tool" - ) - else: - return "No tools available" - - def find_tool_by_name(self, name: str): - """Find a tool by name""" - for tool in self.tools: - if tool.name == name: - return tool - - def extract_tool_commands(self, text: str): - """ - Extract the tool commands from the text - - Example: - ```json - { - "tool": "tool_name", - "params": { - "tool1": "inputs", - "param2": "value2" - } - } - ``` - - """ - # Regex to find JSON like strings - pattern = r"```json(.+?)```" - matches = re.findall(pattern, text, re.DOTALL) - json_commands = [] - for match in matches: - try: - json_commands = json.loads(match) - json_commands.append(json_commands) - except Exception as error: - print(f"Error parsing JSON command: {error}") - - def execute_tools(self, tool_name, params): - """Execute the tool with the provided params""" - tool = self.tool_find_by_name(tool_name) - if tool: - # Execute the tool with the provided parameters - tool_result = tool.run(**params) - print(tool_result) - - def parse_and_execute_tools(self, response: str): - """Parse and execute the tools""" - json_commands = self.extract_tool_commands(response) - for command in json_commands: - tool_name = command.get("tool") - params = command.get("parmas", {}) - self.execute_tools(tool_name, params) - def truncate_history(self): """ Take the history and truncate it to fit into the model context length @@ -446,12 +342,6 @@ class Agent: self.short_memory[-1].append(message) self.truncate_history() - def parse_tool_docs(self): - """Parse the tool docs""" - for tool in self.tools: - docs = self.tool_docs.append(scrape_tool_func_docs(tool)) - return str(docs) - def print_dashboard(self, task: str): """Print dashboard""" model_config = self.get_llm_init_params() @@ -578,14 +468,11 @@ class Agent: combined_prompt = f"{dynamic_prompt}\n{task}" return combined_prompt - def agent_system_prompt_2(self): - """Agent system prompt 2""" - return agent_system_prompt_2(self.agent_name) - def run( self, task: Optional[str] = None, img: Optional[str] = None, + *args, **kwargs, ): """ @@ -638,9 +525,7 @@ class Agent: self.dynamic_temperature() # Preparing the prompt - task = self.agent_history_prompt( - AGENT_SYSTEM_PROMPT_3, response - ) + task = self.agent_history_prompt(history=response) attempt = 0 while attempt < self.retry_attempts: @@ -663,10 +548,6 @@ class Agent: if self.code_interpreter: self.run_code(response) - # If there are any tools then parse and execute them - if self.tools: - self.parse_and_execute_tools(response) - # If interactive mode is enabled then print the response and get user input if self.interactive: print(f"AI: {response}") @@ -712,7 +593,7 @@ class Agent: return response except Exception as error: - print(f"Error running agent: {error}") + logger.error(f"Error running agent: {error}") raise def __call__(self, task: str, img: str = None, *args, **kwargs): @@ -740,8 +621,7 @@ class Agent: def agent_history_prompt( self, - system_prompt: str = AGENT_SYSTEM_PROMPT_3, - history=None, + history: str = None, ): """ Generate the agent history prompt @@ -754,7 +634,7 @@ class Agent: str: The agent history prompt """ if self.sop: - system_prompt = system_prompt or self.system_prompt + system_prompt = self.system_prompt agent_history_prompt = f""" SYSTEM_PROMPT: {system_prompt} @@ -767,7 +647,7 @@ class Agent: """ return agent_history_prompt else: - system_prompt = system_prompt or self.system_prompt + system_prompt = self.system_prompt agent_history_prompt = f""" SYSTEM_PROMPT: {system_prompt} @@ -777,7 +657,7 @@ class Agent: """ return agent_history_prompt - def agent_memory_prompt(self, query, prompt): + def long_term_memory_prompt(self, query: str, prompt: str): """ Generate the agent long term memory prompt @@ -788,16 +668,12 @@ class Agent: Returns: str: The agent history prompt """ - context_injected_prompt = prompt - if self.memory: - ltr = self.memory.query(query) + ltr = self.long_term_memory.query(query) - context_injected_prompt = f"""{prompt} - ################ CONTEXT #################### - {ltr} - """ - - return context_injected_prompt + return f"""{prompt} + ################ CONTEXT #################### + {ltr} + """ async def run_concurrent(self, tasks: List[str], **kwargs): """ @@ -1045,45 +921,6 @@ class Agent: print() return response - def get_llm_params(self): - """ - Extracts and returns the parameters of the llm object for serialization. - It assumes that the llm object has an __init__ method - with parameters that can be used to recreate it. - """ - if not hasattr(self.llm, "__init__"): - return None - - init_signature = inspect.signature(self.llm.__init__) - params = init_signature.parameters - llm_params = {} - - for name, param in params.items(): - if name == "self": - continue - if hasattr(self.llm, name): - value = getattr(self.llm, name) - if isinstance( - value, - ( - str, - int, - float, - bool, - list, - dict, - tuple, - type(None), - ), - ): - llm_params[name] = value - else: - llm_params[name] = str( - value - ) # For non-serializable objects, save their string representation. - - return llm_params - def save_state(self, file_path: str) -> None: """ Saves the current state of the agent to a JSON file, including the llm parameters. @@ -1277,85 +1114,51 @@ class Agent: text = limit_tokens_from_string(text, num_limits) return text - def tools_prompt_prep( - self, docs: str = None, scenarios: str = SCENARIOS - ): - """ - Tools prompt prep + def ingest_docs(self, docs: List[str], *args, **kwargs): + """Ingest the docs into the memory Args: - docs (str, optional): _description_. Defaults to None. - scenarios (str, optional): _description_. Defaults to None. + docs (List[str]): _description_ Returns: _type_: _description_ """ - PROMPT = f""" - # Task - You will be provided with a list of APIs. These APIs will have a - description and a list of parameters and return types for each tool. Your - task involves creating varied, complex, and detailed user scenarios - that require to call API calls. You must select what api to call based on - the context of the task and the scenario. - - For instance, given the APIs: SearchHotels, BookHotel, CancelBooking, - GetNFLNews. Given that GetNFLNews is explicitly provided, your scenario - should articulate something akin to: - - "The user wants to see if the Broncos won their last game (GetNFLNews). - They then want to see if that qualifies them for the playoffs and who - they will be playing against (GetNFLNews). The Broncos did make it into - the playoffs, so the user wants watch the game in person. They want to - look for hotels where the playoffs are occurring (GetNBANews + - SearchHotels). After looking at the options, the user chooses to book a - 3-day stay at the cheapest 4-star option (BookHotel)." - 13 - - This scenario exemplifies a scenario using 5 API calls. The scenario is - complex, detailed, and concise as desired. The scenario also includes two - APIs used in tandem, the required API, GetNBANews to search for the - playoffs location and SearchHotels to find hotels based on the returned - location. Usage of multiple APIs in tandem is highly desirable and will - receive a higher score. Ideally each scenario should contain one or more - instances of multiple APIs being used in tandem. - - Note that this scenario does not use all the APIs given and re-uses the " - GetNBANews" API. Re-using APIs is allowed, but each scenario should - involve as many different APIs as the user demands. Note that API usage is also included - in the scenario, but exact parameters ar necessary. You must use a - different combination of APIs for each scenario. All APIs must be used in - at least one scenario. You can only use the APIs provided in the APIs - section. - - Note that API calls are not explicitly mentioned and their uses are - included in parentheses. This behaviour should be mimicked in your - response. - - Output the tool usage in a strict json format with the function name and input to - the function. For example, Deliver your response in this format: - - ‘‘‘ - {scenarios} - ‘‘‘ - # APIs - ‘‘‘ - {docs} - ‘‘‘ - # Response - ‘‘‘ - """ - return PROMPT + for doc in docs: + data = data_to_text(doc) - def ingest_docs(self, docs: List[str], *args, **kwargs): - """Ingest the docs into the memory + return self.short_memory.append(data) + + def ingest_pdf(self, pdf: str): + """Ingest the pdf into the memory Args: - docs (List[str]): _description_ + pdf (str): _description_ Returns: _type_: _description_ """ - for doc in docs: - data = data_to_text(doc) + text = pdf_to_text(pdf) + return self.short_memory.append(text) - return self.short_memory.append(data) + def receieve_mesage(self, name: str, message: str): + """Receieve a message""" + message = f"{name}: {message}" + return self.short_memory.append(message) + + def send_agent_message( + self, agent_name: str, message: str, *args, **kwargs + ): + """Send a message to the agent""" + message = f"{agent_name}: {message}" + return self.run(message, *args, **kwargs) + + def get_docs_from_doc_folders(self): + """Get the docs from the files""" + # Get the list of files then extract them and add them to the memory + files = os.listdir(self.docs_folder) + + # Extract the text from the files + for file in files: + text = data_to_text(file) + + return self.short_memory.append(text) diff --git a/swarms/structs/async_workflow.py b/swarms/structs/async_workflow.py new file mode 100644 index 00000000..b46061b2 --- /dev/null +++ b/swarms/structs/async_workflow.py @@ -0,0 +1,103 @@ +import asyncio +from dataclasses import dataclass, field +from typing import Any, Callable, List, Optional +from swarms.structs.task import Task +from swarms.utils.logger import logger + + +@dataclass +class AsyncWorkflow: + """ + Represents an asynchronous workflow to run tasks. + + Attributes: + name (str): The name of the workflow. + description (str): The description of the workflow. + max_loops (int): The maximum number of loops to run the workflow. + autosave (bool): Flag indicating whether to autosave the results. + dashboard (bool): Flag indicating whether to display a dashboard. + task_pool (List[Any]): The list of tasks in the workflow. + results (List[Any]): The list of results from running the tasks. + loop (Optional[asyncio.AbstractEventLoop]): The event loop to use. + stopping_condition (Optional[Callable]): The stopping condition for the workflow. + + Methods: + add(tasks: List[Any]) -> None: + Add tasks to the workflow. + + delete(task: Task = None, tasks: List[Task] = None) -> None: + Delete a task from the workflow. + + run() -> List[Any]: + Run the workflow and return the results. + """ + + name: str = "Async Workflow" + description: str = "A workflow to run asynchronous tasks" + max_loops: int = 1 + autosave: bool = True + dashboard: bool = False + task_pool: List[Any] = field(default_factory=list) + results: List[Any] = field(default_factory=list) + loop: Optional[asyncio.AbstractEventLoop] = None + stopping_condition: Optional[Callable] = None + + async def add(self, task: Any, tasks: List[Any]): + """Add tasks to the workflow""" + try: + if tasks: + for task in tasks: + self.task_pool.extend(tasks) + elif task: + self.task_pool.append(task) + + else: + if task and tasks: + # Add the task and tasks to the task pool + self.task_pool.append(task) + self.task_pool.extend(tasks) + else: + raise ValueError( + "Either task or tasks must be provided" + ) + + except Exception as error: + logger.error(f"[ERROR][AsyncWorkflow] {error}") + + async def delete( + self, task: Any = None, tasks: List[Task] = None + ): + """Delete a task from the workflow""" + try: + if task: + self.task_pool.remove(task) + elif tasks: + for task in tasks: + self.task_pool.remove(task) + except Exception as error: + logger.error(f"[ERROR][AsyncWorkflow] {error}") + + async def run(self): + """Run the workflow""" + if self.loop is None: + self.loop = asyncio.get_event_loop() + for i in range(self.max_loops): + logger.info( + f"[INFO][AsyncWorkflow] Loop {i + 1}/{self.max_loops}" + ) + futures = [ + asyncio.ensure_future(task.execute()) + for task in self.task_pool + ] + self.results = await asyncio.gather(*futures) + # if self.autosave: + # self.save() + # if self.dashboard: + # self.display() + + # Add a stopping condition to stop the workflow, if provided but stopping_condition takes in a parameter s for string + if self.stopping_condition: + if self.stopping_condition(self.results): + break + + return self.results diff --git a/swarms/structs/company.py b/swarms/structs/company.py new file mode 100644 index 00000000..11b6d61f --- /dev/null +++ b/swarms/structs/company.py @@ -0,0 +1,175 @@ +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Union + +from swarms.structs.agent import Agent +from swarms.utils.logger import logger +from swarms.structs.conversation import Conversation + + +@dataclass +class Company: + """ + Represents a company with a hierarchical organizational structure. + """ + + org_chart: List[List[Agent]] + shared_instructions: str = None + ceo: Optional[Agent] = None + agents: List[Agent] = field(default_factory=list) + agent_interactions: Dict[str, List[str]] = field( + default_factory=dict + ) + history: Conversation = field(default_factory=Conversation) + + def __post_init__(self): + self._parse_org_chart(self.org_chart) + + def add(self, agent: Agent) -> None: + """ + Adds an agent to the company. + + Args: + agent (Agent): The agent to be added. + + Raises: + ValueError: If an agent with the same ID already exists in the company. + """ + try: + if any( + existing_agent.id == agent.id + for existing_agent in self.agents + ): + raise ValueError( + f"Agent with id {agent.id} already exists in the" + " company." + ) + self.agents.append(agent) + + except Exception as error: + logger.error( + f"[ERROR][CLASS: Company][METHOD: add] {error}" + ) + raise error + + def get(self, agent_name: str) -> Agent: + """ + Retrieves an agent from the company by name. + + Args: + agent_name (str): The name of the agent to retrieve. + + Returns: + Agent: The retrieved agent. + + Raises: + ValueError: If an agent with the specified name does not exist in the company. + """ + try: + for agent in self.agents: + if agent.name == agent_name: + return agent + raise ValueError( + f"Agent with name {agent_name} does not exist in the" + " company." + ) + except Exception as error: + logger.error( + f"[ERROR][CLASS: Company][METHOD: get] {error}" + ) + raise error + + def remove(self, agent: Agent) -> None: + """ + Removes an agent from the company. + + Args: + agent (Agent): The agent to be removed. + """ + try: + self.agents.remove(agent) + except Exception as error: + logger.error( + f"[ERROR][CLASS: Company][METHOD: remove] {error}" + ) + raise error + + def _parse_org_chart( + self, org_chart: Union[List[Agent], List[List[Agent]]] + ) -> None: + """ + Parses the organization chart and adds agents to the company. + + Args: + org_chart (Union[List[Agent], List[List[Agent]]]): The organization chart + representing the hierarchy of agents. + + Raises: + ValueError: If more than one CEO is found in the org chart or if an invalid + agent is encountered. + """ + try: + for node in org_chart: + if isinstance(node, Agent): + if self.ceo: + raise ValueError("1 CEO is only allowed") + self.ceo = node + self.add(node) + + elif isinstance(node, list): + for agent in node: + if not isinstance(agent, Agent): + raise ValueError( + "Invalid agent in org chart" + ) + self.add(agent) + + for i, agent in enumerate(node): + if i == len(node) - 1: + continue + + for other_agent in node[i + 1]: + self.__init_task(agent, other_agent) + except Exception as error: + logger.error( + "[ERROR][CLASS: Company][METHOD: _parse_org_chart]" + f" {error}" + ) + raise error + + def _init_interaction( + self, + agent1: Agent, + agent2: Agent, + ) -> None: + """ + Initializes the interaction between two agents. + + Args: + agent1 (Agent): The first agent involved in the interaction. + agent2 (Agent): The second agent involved in the interaction. + + Returns: + None + """ + if agent1.ai_name not in self.agents_interactions: + self.agents_interactions[agent1.ai_name] = [] + self.agents_interactions[agent1.ai_name].append( + agent2.ai_name + ) + + def run(self): + """ + Run the company + """ + for ( + agent_name, + interaction_agents, + ) in self.agents_interactions.items(): + agent = self.get(agent_name) + for interaction_agent in interaction_agents: + task_description = ( + f"Task for {agent_name} to interact with" + f" {interaction_agent}" + ) + print(f"{task_description} is being executed") + agent.run(task_description) diff --git a/swarms/structs/concurrent_workflow.py b/swarms/structs/concurrent_workflow.py index dd3fc518..8aa5399b 100644 --- a/swarms/structs/concurrent_workflow.py +++ b/swarms/structs/concurrent_workflow.py @@ -1,6 +1,6 @@ import concurrent.futures from dataclasses import dataclass, field -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Callable from swarms.structs.base import BaseStructure from swarms.structs.task import Task @@ -33,6 +33,7 @@ class ConcurrentWorkflow(BaseStructure): """ task_pool: List[Dict] = field(default_factory=list) + max_loops: int = 1 max_workers: int = 5 autosave: bool = False saved_state_filepath: Optional[str] = ( @@ -41,6 +42,7 @@ class ConcurrentWorkflow(BaseStructure): print_results: bool = False return_results: bool = False use_processes: bool = False + stopping_condition: Optional[Callable] = None def add(self, task: Task = None, tasks: List[Task] = None): """Adds a task to the workflow. @@ -66,7 +68,7 @@ class ConcurrentWorkflow(BaseStructure): logger.warning(f"[ERROR][ConcurrentWorkflow] {error}") raise error - def run(self): + def run(self, *args, **kwargs): """ Executes the tasks in parallel using a ThreadPoolExecutor. @@ -77,27 +79,37 @@ class ConcurrentWorkflow(BaseStructure): Returns: List[Any]: A list of the results of each task, if return_results is True. Otherwise, returns None. """ - with concurrent.futures.ThreadPoolExecutor( - max_workers=self.max_workers - ) as executor: - futures = { - executor.submit(task.execute): task - for task in self.task_pool - } - results = [] - - for future in concurrent.futures.as_completed(futures): - task = futures[future] - try: - result = future.result() - if self.print_results: - logger.info(f"Task {task}: {result}") - if self.return_results: - results.append(result) - except Exception as e: - logger.error( - f"Task {task} generated an exception: {e}" - ) + loop_count = 0 + while loop_count < self.max_loops: + with concurrent.futures.ThreadPoolExecutor( + max_workers=self.max_workers + ) as executor: + futures = { + executor.submit(task.execute): task + for task in self.task_pool + } + results = [] + + for future in concurrent.futures.as_completed( + futures + ): + task = futures[future] + try: + result = future.result() + if self.print_results: + logger.info(f"Task {task}: {result}") + if self.return_results: + results.append(result) + except Exception as e: + logger.error( + f"Task {task} generated an exception: {e}" + ) + + loop_count += 1 + if self.stopping_condition and self.stopping_condition( + results + ): + break return results if self.return_results else None diff --git a/swarms/agents/message.py b/swarms/structs/message.py similarity index 100% rename from swarms/agents/message.py rename to swarms/structs/message.py diff --git a/swarms/structs/recursive_workflow.py b/swarms/structs/recursive_workflow.py index 64b27406..afeb91b7 100644 --- a/swarms/structs/recursive_workflow.py +++ b/swarms/structs/recursive_workflow.py @@ -31,7 +31,11 @@ class RecursiveWorkflow(BaseStructure): >>> workflow.run() """ - def __init__(self, stop_token: str = ""): + def __init__( + self, + stop_token: str = "", + stopping_conditions: callable = None, + ): self.stop_token = stop_token self.task_pool = [] diff --git a/swarms/structs/sequential_workflow.py b/swarms/structs/sequential_workflow.py index 15e2c0f8..6b1d7c06 100644 --- a/swarms/structs/sequential_workflow.py +++ b/swarms/structs/sequential_workflow.py @@ -1,11 +1,9 @@ -import concurrent.futures import json from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional from termcolor import colored -from swarms.structs.agent import Agent from swarms.structs.task import Task from swarms.utils.logger import logger @@ -14,7 +12,7 @@ from swarms.utils.logger import logger @dataclass class SequentialWorkflow: """ - SequentialWorkflow class for running a sequence of tasks using N number of autonomous agents. + SequentialWorkflow class for running a sequence of task_pool using N number of autonomous agents. Args: max_loops (int): The maximum number of times to run the workflow. @@ -22,7 +20,7 @@ class SequentialWorkflow: Attributes: - tasks (List[Task]): The list of tasks to execute. + task_pool (List[Task]): The list of task_pool to execute. max_loops (int): The maximum number of times to run the workflow. dashboard (bool): Whether to display the dashboard for the workflow. @@ -35,13 +33,13 @@ class SequentialWorkflow: >>> workflow.add("What's the weather in miami", llm) >>> workflow.add("Create a report on these metrics", llm) >>> workflow.run() - >>> workflow.tasks + >>> workflow.task_pool """ name: str = None description: str = None - tasks: List[Task] = field(default_factory=list) + task_pool: List[Task] = field(default_factory=list) max_loops: int = 1 autosave: bool = False saved_state_filepath: Optional[str] = ( @@ -52,9 +50,8 @@ class SequentialWorkflow: def add( self, - agent: Union[Callable, Agent], - task: Optional[str] = None, - tasks: Optional[List[str]] = None, + task: Optional[Task] = None, + tasks: Optional[List[Task]] = None, *args, **kwargs, ) -> None: @@ -69,26 +66,28 @@ class SequentialWorkflow: **kwargs: Additional keyword arguments to pass to the task execution. """ try: - # If the agent is a Agent instance, we include the task in kwargs for Agent.run() - if isinstance(agent, Agent): - kwargs["task"] = ( - task # Set the task as a keyword argument for Agent + # If the agent is a Task instance, we include the task in kwargs for Agent.run() + # Append the task to the task_pool list + if task: + self.task_pool.append(task) + logger.info( + f"[INFO][SequentialWorkflow] Added task {task} to" + " workflow" ) + elif tasks: + for task in tasks: + self.task_pool.append(task) + logger.info( + "[INFO][SequentialWorkflow] Added task" + f" {task} to workflow" + ) + else: + if task and tasks is not None: + # Add the task and list of tasks to the task_pool at the same time + self.task_pool.append(task) + for task in tasks: + self.task_pool.append(task) - # Append the task to the tasks list - self.tasks.append( - Task( - description=task, - agent=agent, - args=list(args), - kwargs=kwargs, - ) - ) - - logger.info( - f"[INFO][SequentialWorkflow] Added task {task} to" - " workflow" - ) except Exception as error: logger.error( colored( @@ -99,8 +98,12 @@ class SequentialWorkflow: def reset_workflow(self) -> None: """Resets the workflow by clearing the results of each task.""" try: - for task in self.tasks: + for task in self.task_pool: task.result = None + logger.info( + f"[INFO][SequentialWorkflow] Reset task {task} in" + " workflow" + ) except Exception as error: logger.error( colored(f"Error resetting workflow: {error}", "red"), @@ -115,7 +118,8 @@ class SequentialWorkflow: """ try: return { - task.description: task.result for task in self.tasks + task.description: task.result + for task in self.task_pool } except Exception as error: logger.error( @@ -124,14 +128,10 @@ class SequentialWorkflow: ), ) - def remove_task(self, task: str) -> None: - """Remove tasks from sequential workflow""" + def remove_task(self, task: Task) -> None: + """Remove task_pool from sequential workflow""" try: - self.tasks = [ - task - for task in self.tasks - if task.description != task - ] + self.task_pool.remove(task) logger.info( f"[INFO][SequentialWorkflow] Removed task {task} from" " workflow" @@ -144,129 +144,6 @@ class SequentialWorkflow: ), ) - def update_task(self, task: str, **updates) -> None: - """ - Updates the arguments of a task in the workflow. - - Args: - task (str): The description of the task to update. - **updates: The updates to apply to the task. - - Raises: - ValueError: If the task is not found in the workflow. - - Examples: - >>> from swarms.models import OpenAIChat - >>> from swarms.structs import SequentialWorkflow - >>> llm = OpenAIChat(openai_api_key="") - >>> workflow = SequentialWorkflow(max_loops=1) - >>> workflow.add("What's the weather in miami", llm) - >>> workflow.add("Create a report on these metrics", llm) - >>> workflow.update_task("What's the weather in miami", max_tokens=1000) - >>> workflow.tasks[0].kwargs - {'max_tokens': 1000} - - """ - try: - for task in self.tasks: - if task.description == task: - task.kwargs.update(updates) - break - else: - raise ValueError( - f"Task {task} not found in workflow." - ) - - print( - f"[INFO][SequentialWorkflow] Updated task {task} in" - " workflow" - ) - except Exception as error: - logger.error( - colored( - f"Error updating task in workflow: {error}", "red" - ), - ) - - def delete_task(self, task: str) -> None: - """ - Delete a task from the workflow. - - Args: - task (str): The description of the task to delete. - - Raises: - ValueError: If the task is not found in the workflow. - - Examples: - >>> from swarms.models import OpenAIChat - >>> from swarms.structs import SequentialWorkflow - >>> llm = OpenAIChat(openai_api_key="") - >>> workflow = SequentialWorkflow(max_loops=1) - >>> workflow.add("What's the weather in miami", llm) - >>> workflow.add("Create a report on these metrics", llm) - >>> workflow.delete_task("What's the weather in miami") - >>> workflow.tasks - [Task(description='Create a report on these metrics', agent=Agent(llm=OpenAIChat(openai_api_key=''), max_loops=1, dashboard=False), args=[], kwargs={}, result=None, history=[])] - """ - try: - for task in self.tasks: - if task.description == task: - self.tasks.remove(task) - break - else: - raise ValueError( - f"Task {task} not found in workflow." - ) - - print( - f"[INFO][SequentialWorkflow] Deleted task {task} from" - " workflow" - ) - except Exception as error: - logger.error( - colored( - f"Error deleting task from workflow: {error}", - "red", - ), - ) - - def concurrent_run(self): - """ - Concurrently run the workflow using a pool of workers. - - Examples: - >>> from swarms.models import OpenAIChat - >>> from swarms.structs import SequentialWorkflow - >>> llm = OpenAIChat(openai_api_key="") - >>> workflow = SequentialWorkflow(max_loops=1) - - """ - try: - with concurrent.futures.ThreadPoolExecutor() as executor: - futures_to_task = { - executor.submit(task.run): task - for task in self.tasks - } - results = [] - for future in concurrent.futures.as_completed( - futures_to_task - ): - task = futures_to_task[future] - - try: - result = future.result() - except Exception as error: - print(f"Error running workflow: {error}") - else: - results.append(result) - print( - f"Task {task} completed successfully with" - f" result: {result}" - ) - except Exception as error: - print(colored(f"Error running workflow: {error}", "red")) - def save_workflow_state( self, filepath: Optional[str] = "sequential_workflow_state.json", @@ -293,7 +170,7 @@ class SequentialWorkflow: with open(filepath, "w") as f: # Saving the state as a json for simplicuty state = { - "tasks": [ + "task_pool": [ { "description": task.description, "args": task.args, @@ -301,13 +178,13 @@ class SequentialWorkflow: "result": task.result, "history": task.history, } - for task in self.tasks + for task in self.task_pool ], "max_loops": self.max_loops, } json.dump(state, f, indent=4) - print( + logger.info( "[INFO][SequentialWorkflow] Saved workflow state to" f" {filepath}" ) @@ -357,7 +234,7 @@ class SequentialWorkflow: -------------------------------- Name: {self.name} Description: {self.description} - Tasks: {len(self.tasks)} + task_pool: {len(self.task_pool)} Max Loops: {self.max_loops} Autosave: {self.autosave} Autosave Filepath: {self.saved_state_filepath} @@ -382,38 +259,6 @@ class SequentialWorkflow: ) ) - def add_objective_to_workflow(self, task: str, **kwargs) -> None: - """Adds an objective to the workflow.""" - try: - print( - colored( - """ - Adding Objective to Workflow...""", - "green", - attrs=["bold", "underline"], - ) - ) - - task = Task( - description=task, - agent=kwargs["agent"], - args=list(kwargs["args"]), - kwargs=kwargs["kwargs"], - ) - self.tasks.append(task) - - print( - f"[INFO][SequentialWorkflow] Added task {task} to" - " workflow" - ) - except Exception as error: - logger.error( - colored( - f"Error adding objective to workflow: {error}", - "red", - ) - ) - def load_workflow_state( self, filepath: str = None, **kwargs ) -> None: @@ -440,8 +285,8 @@ class SequentialWorkflow: with open(filepath, "r") as f: state = json.load(f) self.max_loops = state["max_loops"] - self.tasks = [] - for task_state in state["tasks"]: + self.task_pool = [] + for task_state in state["task_pool"]: task = Task( description=task_state["description"], agent=task_state["agent"], @@ -450,7 +295,7 @@ class SequentialWorkflow: result=task_state["result"], history=task_state["history"], ) - self.tasks.append(task) + self.task_pool.append(task) print( "[INFO][SequentialWorkflow] Loaded workflow state" @@ -474,114 +319,35 @@ class SequentialWorkflow: """ try: self.workflow_bootup() - for _ in range(self.max_loops): - for task in self.tasks: + loops = 0 + while loops < self.max_loops: + for i in range(len(self.task_pool)): + task = self.task_pool[i] # Check if the current task can be executed if task.result is None: - # Check if the agent is a Agent and a 'task' argument is needed - if isinstance(task.agent, Agent): - # Ensure that 'task' is provided in the kwargs - if "task" not in task.kwargs: - raise ValueError( - "The 'task' argument is required" - " for the Agent agent execution" - f" in '{task.description}'" - ) - # Separate the 'task' argument from other kwargs - flow_task_arg = task.kwargs.pop("task") - task.result = task.agent.run( - flow_task_arg, - *task.args, - **task.kwargs, - ) - else: - # If it's not a Agent instance, call the agent directly - task.result = task.agent( - *task.args, **task.kwargs - ) + # Get the inputs for the current task + task.context(task) - # Pass the result as an argument to the next task if it exists - next_task_index = self.tasks.index(task) + 1 - if next_task_index < len(self.tasks): - next_task = self.tasks[next_task_index] - if isinstance(next_task.agent, Agent): - # For Agent flows, 'task' should be a keyword argument - next_task.kwargs["task"] = task.result - else: - # For other callable flows, the result is added to args - next_task.args.insert(0, task.result) + result = task.execute() - # Autosave the workflow state - if self.autosave: - self.save_workflow_state( - "sequential_workflow_state.json" - ) - except Exception as e: - logger.error( - colored( - ( - "Error initializing the Sequential workflow:" - f" {e} try optimizing your inputs like the" - " agent class and task description" - ), - "red", - attrs=["bold", "underline"], - ) - ) - - async def arun(self) -> None: - """ - Asynchronously run the workflow. - - Raises: - ValueError: If a Agent instance is used as a task and the 'task' argument is not provided. - - """ - try: - for _ in range(self.max_loops): - for task in self.tasks: - # Check if the current task can be executed - if task.result is None: - # Check if the agent is a Agent and a 'task' argument is needed - if isinstance(task.agent, Agent): - # Ensure that 'task' is provided in the kwargs - if "task" not in task.kwargs: - raise ValueError( - "The 'task' argument is required" - " for the Agent agent execution" - f" in '{task.description}'" - ) - # Separate the 'task' argument from other kwargs - flow_task_arg = task.kwargs.pop("task") - task.result = await task.agent.arun( - flow_task_arg, - *task.args, - **task.kwargs, - ) - else: - # If it's not a Agent instance, call the agent directly - task.result = await task.agent( - *task.args, **task.kwargs - ) + # Pass the inputs to the next task + if i < len(self.task_pool) - 1: + next_task = self.task_pool[i + 1] + next_task.description = result - # Pass the result as an argument to the next task if it exists - next_task_index = self.tasks.index(task) + 1 - if next_task_index < len(self.tasks): - next_task = self.tasks[next_task_index] - if isinstance(next_task.agent, Agent): - # For Agent flows, 'task' should be a keyword argument - next_task.kwargs["task"] = task.result - else: - # For other callable flows, the result is added to args - next_task.args.insert(0, task.result) + # Execute the current task + task.execute() # Autosave the workflow state if self.autosave: self.save_workflow_state( "sequential_workflow_state.json" ) + + self.workflow_shutdown() + loops += 1 except Exception as e: - print( + logger.error( colored( ( "Error initializing the Sequential workflow:" diff --git a/swarms/structs/swarming_architectures.py b/swarms/structs/swarming_architectures.py index a6ccdf9b..ad3ad4ed 100644 --- a/swarms/structs/swarming_architectures.py +++ b/swarms/structs/swarming_architectures.py @@ -1,6 +1,8 @@ import math from typing import List from swarms.structs.agent import Agent +import asyncio +from swarms.utils.logger import logger def circular_swarm(agents: List[Agent], tasks: List[str]): @@ -159,3 +161,95 @@ def sinusoidal_swarm(agents: List[Agent], task: str): for i in range(len(agents)): index = int((math.sin(i) + 1) / 2 * len(agents)) agents[index].run(task) + + +async def one_to_three(sender: Agent, agents: List[Agent], task: str): + """ + Sends a message from the sender agent to three other agents. + + Args: + sender (Agent): The agent sending the message. + agents (List[Agent]): The list of agents to receive the message. + task (str): The message to be sent. + + Raises: + Exception: If there is an error while sending the message. + + Returns: + None + """ + try: + receive_tasks = [] + for agent in agents: + receive_tasks.append( + agent.receive_message(sender.ai_name, task) + ) + + await asyncio.gather(*receive_tasks) + except Exception as error: + logger.error( + f"[ERROR][CLASS: Agent][METHOD: one_to_three] {error}" + ) + raise error + + +async def broadcast( + sender: Agent, + agents: List[Agent], + task: str, +): + """ + Broadcasts a message from the sender agent to a list of agents. + + Args: + sender (Agent): The agent sending the message. + agents (List[Agent]): The list of agents to receive the message. + task (str): The message to be broadcasted. + + Raises: + Exception: If an error occurs during the broadcast. + + Returns: + None + """ + try: + receive_tasks = [] + for agent in agents: + receive_tasks.append( + agent.receive_message(sender.ai_name, task) + ) + + await asyncio.gather(*receive_tasks) + except Exception as error: + logger.error( + f"[ERROR][CLASS: Agent][METHOD: broadcast] {error}" + ) + raise error + + +async def one_to_one( + sender: Agent, + receiver: Agent, + task: str, +): + """ + Sends a message from the sender agent to the receiver agent. + + Args: + sender (Agent): The agent sending the message. + receiver (Agent): The agent to receive the message. + task (str): The message to be sent. + + Raises: + Exception: If an error occurs during the message sending. + + Returns: + None + """ + try: + await receiver.receive_message(sender.ai_name, task) + except Exception as error: + logger.error( + f"[ERROR][CLASS: Agent][METHOD: one_to_one] {error}" + ) + raise error diff --git a/swarms/structs/task.py b/swarms/structs/task.py index 68a30951..a794506f 100644 --- a/swarms/structs/task.py +++ b/swarms/structs/task.py @@ -12,6 +12,7 @@ from typing import ( from swarms.structs.agent import Agent from swarms.utils.logger import logger +from swarms.structs.conversation import Conversation @dataclass @@ -57,9 +58,7 @@ class Task: """ agent: Union[Callable, Agent] - description: str - args: List[Any] = field(default_factory=list) - kwargs: Dict[str, Any] = field(default_factory=dict) + description: str = None result: Any = None history: List[Any] = field(default_factory=list) schedule_time: datetime = None @@ -69,8 +68,10 @@ class Task: condition: Callable = None priority: int = 0 dependencies: List["Task"] = field(default_factory=list) + args: List[Any] = field(default_factory=list) + kwargs: Dict[str, Any] = field(default_factory=dict) - def execute(self, task: str, img: str = None, *args, **kwargs): + def execute(self, *args, **kwargs): """ Execute the task by calling the agent or model with the arguments and keyword arguments. You can add images to the agent by passing the @@ -86,8 +87,10 @@ class Task: >>> task.result """ - logger.info(f"[INFO][Task] Executing task: {task}") - task = self.description or task + logger.info( + f"[INFO][Task] Executing task: {self.description}" + ) + task = self.description try: if isinstance(self.agent, Agent): if self.condition is None or self.condition(): @@ -109,11 +112,11 @@ class Task: except Exception as error: logger.error(f"[ERROR][Task] {error}") - def run(self, task: str, *args, **kwargs): - self.execute(task, *args, **kwargs) + def run(self, *args, **kwargs): + self.execute(*args, **kwargs) - def __call__(self, task: str, *args, **kwargs): - self.execute(task, *args, **kwargs) + def __call__(self, *args, **kwargs): + self.execute(*args, **kwargs) def handle_scheduled_task(self): """ @@ -206,3 +209,51 @@ class Task: logger.error( f"[ERROR][Task][check_dependency_completion] {error}" ) + + def context( + self, + task: "Task" = None, + context: List["Task"] = None, + *args, + **kwargs, + ): + """ + Set the context for the task. + + Args: + context (str): The context to set. + """ + # For sequential workflow, sequentially add the context of the previous task in the list + new_context = Conversation(time_enabled=True, *args, **kwargs) + + if context: + for task in context: + description = ( + task.description + if task.description is not None + else "" + ) + result = ( + task.result if task.result is not None else "" + ) + + # Add the context of the task to the conversation + new_context.add( + task.agent.agent_name, f"{description} {result}" + ) + + elif task: + description = ( + task.description + if task.description is not None + else "" + ) + result = task.result if task.result is not None else "" + new_context.add( + task.agent.agent_name, f"{description} {result}" + ) + + prompt = new_context.return_history_as_string() + + # Add to history + return self.history.append(prompt) diff --git a/swarms/telemetry/__init__.py b/swarms/telemetry/__init__.py index 0a16ca28..d829b724 100644 --- a/swarms/telemetry/__init__.py +++ b/swarms/telemetry/__init__.py @@ -1,13 +1,12 @@ from swarms.telemetry.log_all import log_all_calls, log_calls from swarms.telemetry.sys_info import ( get_cpu_info, - get_oi_version, + get_swarms_verison, get_os_version, get_package_mismatches, get_pip_version, get_python_version, get_ram_info, - interpreter_info, system_info, ) from swarms.telemetry.user_utils import ( @@ -15,6 +14,7 @@ from swarms.telemetry.user_utils import ( generate_user_id, get_machine_id, get_system_info, + get_user_device_data, ) __all__ = [ @@ -26,11 +26,11 @@ __all__ = [ "generate_unique_identifier", "get_python_version", "get_pip_version", - "get_oi_version", + "get_swarms_verison", "get_os_version", "get_cpu_info", "get_ram_info", "get_package_mismatches", - "interpreter_info", "system_info", + "get_user_device_data", ] diff --git a/swarms/telemetry/main.py b/swarms/telemetry/main.py new file mode 100644 index 00000000..fe00fecf --- /dev/null +++ b/swarms/telemetry/main.py @@ -0,0 +1,58 @@ +import logging +import pymongo +import platform +import datetime + + +class Telemetry: + def __init__(self, db_url, db_name): + self.logger = self.setup_logging() + self.db = self.setup_db(db_url, db_name) + + def setup_logging(self): + logger = logging.getLogger("telemetry") + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + ) + logger.addHandler(handler) + return logger + + def setup_db(self, db_url, db_name): + client = pymongo.MongoClient(db_url) + return client[db_name] + + def capture_device_data(self): + data = { + "system": platform.system(), + "node": platform.node(), + "release": platform.release(), + "version": platform.version(), + "machine": platform.machine(), + "processor": platform.processor(), + "time": datetime.datetime.now(), + } + return data + + def send_to_db(self, collection_name, data): + collection = self.db[collection_name] + collection.insert_one(data) + + def log_and_capture(self, message, level, collection_name): + if level == "info": + self.logger.info(message) + elif level == "error": + self.logger.error(message) + data = self.capture_device_data() + data["log"] = message + self.send_to_db(collection_name, data) + + def log_import(self, module_name): + self.logger.info(f"Importing module {module_name}") + module = __import__(module_name, fromlist=["*"]) + for k in dir(module): + if not k.startswith("__"): + self.logger.info(f"Imported {k} from {module_name}") diff --git a/swarms/telemetry/sys_info.py b/swarms/telemetry/sys_info.py index 08ad1db3..d2841585 100644 --- a/swarms/telemetry/sys_info.py +++ b/swarms/telemetry/sys_info.py @@ -22,20 +22,20 @@ def get_pip_version(): return pip_version -def get_oi_version(): +def get_swarms_verison(): try: - oi_version_cmd = ( - subprocess.check_output(["interpreter", "--version"]) + swarms_verison_cmd = ( + subprocess.check_output(["swarms", "--version"]) .decode() .split()[1] ) except Exception as e: - oi_version_cmd = str(e) - oi_version_pkg = pkg_resources.get_distribution( - "open-interpreter" + swarms_verison_cmd = str(e) + swarms_verison_pkg = pkg_resources.get_distribution( + "swarms" ).version - oi_version = oi_version_cmd, oi_version_pkg - return oi_version + swarms_verison = swarms_verison_cmd, swarms_verison_pkg + return swarms_verison def get_os_version(): @@ -89,70 +89,15 @@ def get_package_mismatches(file_path="pyproject.toml"): return "\n" + "\n".join(mismatches) -def interpreter_info(interpreter): - try: - if interpreter.offline and interpreter.llm.api_base: - try: - curl = subprocess.check_output( - f"curl {interpreter.llm.api_base}" - ) - except Exception as e: - curl = str(e) - else: - curl = "Not local" - - messages_to_display = [] - for message in interpreter.messages: - message = message.copy() - try: - if len(message["content"]) > 600: - message["content"] = ( - message["content"][:300] - + "..." - + message["content"][-300:] - ) - except Exception as e: - print(str(e), "for message:", message) - messages_to_display.append(message) - - return f""" - - # Interpreter Info - - Vision: {interpreter.llm.supports_vision} - Model: {interpreter.llm.model} - Function calling: {interpreter.llm.supports_functions} - Context window: {interpreter.llm.context_window} - Max tokens: {interpreter.llm.max_tokens} - - Auto run: {interpreter.auto_run} - API base: {interpreter.llm.api_base} - Offline: {interpreter.offline} - - Curl output: {curl} - - # Messages - - System Message: {interpreter.system_message} - - """ + "\n\n".join([str(m) for m in messages_to_display]) - except: - return "Error, couldn't get interpreter info" - - -def system_info(interpreter): - oi_version = get_oi_version() - print(f""" - Python Version: {get_python_version()} - Pip Version: {get_pip_version()} - Open-interpreter Version: cmd:{oi_version[0]}, pkg: {oi_version[1]} - OS Version and Architecture: {get_os_version()} - CPU Info: {get_cpu_info()} - RAM Info: {get_ram_info()} - {interpreter_info(interpreter)} - """) - - # Removed the following, as it causes `FileNotFoundError: [Errno 2] No such file or directory: 'pyproject.toml'`` on prod - # (i think it works on dev, but on prod the pyproject.toml will not be in the cwd. might not be accessible at all) - # Package Version Mismatches: - # {get_package_mismatches()} + +def system_info(): + swarms_verison = get_swarms_verison() + return { + "Python Version": get_python_version(), + "Pip Version": get_pip_version(), + "Swarms Version": swarms_verison, + "OS Version and Architecture": get_os_version(), + "CPU Info": get_cpu_info(), + "RAM Info": get_ram_info(), + } + diff --git a/swarms/telemetry/user_utils.py b/swarms/telemetry/user_utils.py index 74667326..9369bc26 100644 --- a/swarms/telemetry/user_utils.py +++ b/swarms/telemetry/user_utils.py @@ -2,7 +2,8 @@ import hashlib import platform import uuid import socket - +from swarms.telemetry.sys_info import system_info +from swarms.telemetry.check_update import check_for_package # Helper functions def generate_user_id(): @@ -47,6 +48,7 @@ def get_system_info(): ), "processor": platform.processor(), "python_version": platform.python_version(), + "Misc": system_info(), } return info @@ -61,3 +63,27 @@ def generate_unique_identifier(): system_info = get_system_info() unique_id = uuid.uuid5(uuid.NAMESPACE_DNS, str(system_info)) return str(unique_id) + + +def get_local_ip(): + """Get local ip + + Returns: + str: local ip + + """ + return socket.gethostbyname(socket.gethostname()) + + + +def get_user_device_data(): + data = { + "ID": generate_user_id(), + "Machine ID": get_machine_id(), + "System Info": get_system_info(), + "UniqueID": generate_unique_identifier(), + "Swarms [Version]": check_for_package("swarms"), + } + return data + +# \ No newline at end of file diff --git a/swarms/tools/tool_utils.py b/swarms/tools/tool_utils.py index da13e223..a5a4e47c 100644 --- a/swarms/tools/tool_utils.py +++ b/swarms/tools/tool_utils.py @@ -1,6 +1,12 @@ -import re import json -from typing import List, Any +import re +from typing import Any, List + +from swarms.prompts.tools import ( + SCENARIOS, +) +from swarms.tools.tool import BaseTool +from swarms.tools.tool_func_doc_scraper import scrape_tool_func_docs def tool_find_by_name(tool_name: str, tools: List[Any]): @@ -55,3 +61,79 @@ def execute_tools(tool_name, params): # Execute the tool with the provided parameters tool_result = tool.run(**params) print(tool_result) + + +def parse_tool_docs(tools: List[BaseTool]): + """Parse the tool docs""" + tool_docs = [] + for tool in tools: + docs = tool_docs.append(scrape_tool_func_docs(tool)) + return str(docs) + + +def tools_prompt_prep(docs: str = None, scenarios: str = SCENARIOS): + """ + Tools prompt prep + + Args: + docs (str, optional): _description_. Defaults to None. + scenarios (str, optional): _description_. Defaults to None. + + Returns: + _type_: _description_ + """ + PROMPT = f""" + # Task + You will be provided with a list of APIs. These APIs will have a + description and a list of parameters and return types for each tool. Your + task involves creating varied, complex, and detailed user scenarios + that require to call API calls. You must select what api to call based on + the context of the task and the scenario. + + For instance, given the APIs: SearchHotels, BookHotel, CancelBooking, + GetNFLNews. Given that GetNFLNews is explicitly provided, your scenario + should articulate something akin to: + + "The user wants to see if the Broncos won their last game (GetNFLNews). + They then want to see if that qualifies them for the playoffs and who + they will be playing against (GetNFLNews). The Broncos did make it into + the playoffs, so the user wants watch the game in person. They want to + look for hotels where the playoffs are occurring (GetNBANews + + SearchHotels). After looking at the options, the user chooses to book a + 3-day stay at the cheapest 4-star option (BookHotel)." + 13 + + This scenario exemplifies a scenario using 5 API calls. The scenario is + complex, detailed, and concise as desired. The scenario also includes two + APIs used in tandem, the required API, GetNBANews to search for the + playoffs location and SearchHotels to find hotels based on the returned + location. Usage of multiple APIs in tandem is highly desirable and will + receive a higher score. Ideally each scenario should contain one or more + instances of multiple APIs being used in tandem. + + Note that this scenario does not use all the APIs given and re-uses the " + GetNBANews" API. Re-using APIs is allowed, but each scenario should + involve as many different APIs as the user demands. Note that API usage is also included + in the scenario, but exact parameters ar necessary. You must use a + different combination of APIs for each scenario. All APIs must be used in + at least one scenario. You can only use the APIs provided in the APIs + section. + + Note that API calls are not explicitly mentioned and their uses are + included in parentheses. This behaviour should be mimicked in your + response. + + Output the tool usage in a strict json format with the function name and input to + the function. For example, Deliver your response in this format: + + ‘‘‘ + {scenarios} + ‘‘‘ + # APIs + ‘‘‘ + {docs} + ‘‘‘ + # Response + ‘‘‘ + """ + return PROMPT diff --git a/swarms/utils/__init__.py b/swarms/utils/__init__.py index e265a1c8..df9fe6ca 100644 --- a/swarms/utils/__init__.py +++ b/swarms/utils/__init__.py @@ -19,6 +19,10 @@ from swarms.utils.data_to_text import ( data_to_text, ) from swarms.utils.try_except_wrapper import try_except_wrapper +from swarms.utils.download_weights_from_url import ( + download_weights_from_url, +) +from swarms.utils.save_logs import parse_log_file __all__ = [ @@ -39,4 +43,6 @@ __all__ = [ "txt_to_text", "data_to_text", "try_except_wrapper", + "download_weights_from_url", + "parse_log_file", ] diff --git a/swarms/utils/download_weights_from_url.py b/swarms/utils/download_weights_from_url.py new file mode 100644 index 00000000..b5fa1633 --- /dev/null +++ b/swarms/utils/download_weights_from_url.py @@ -0,0 +1,22 @@ +import requests + + +def download_weights_from_url( + url: str, save_path: str = "models/weights.pth" +): + """ + Downloads model weights from the given URL and saves them to the specified path. + + Args: + url (str): The URL from which to download the model weights. + save_path (str, optional): The path where the downloaded weights should be saved. + Defaults to "models/weights.pth". + """ + response = requests.get(url, stream=True) + response.raise_for_status() + + with open(save_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + print(f"Model weights downloaded and saved to {save_path}") diff --git a/swarms/utils/fetch_init_params.py b/swarms/utils/fetch_init_params.py new file mode 100644 index 00000000..5798eb14 --- /dev/null +++ b/swarms/utils/fetch_init_params.py @@ -0,0 +1,33 @@ +import inspect + + +def get_cls_init_params(cls) -> str: + """ + Get the initialization parameters of a class. + + Args: + cls: The class to retrieve the initialization parameters from. + + Returns: + str: A string representation of the initialization parameters. + + """ + init_signature = inspect.signature(cls.__init__) + params = init_signature.parameters + params_str_list = [] + + for name, param in params.items(): + if name == "self": + continue + if name == "kwargs": + value = "Any keyword arguments" + elif hasattr(cls, name): + value = getattr(cls, name) + else: + value = cls.__dict__.get(name, "Unknown") + + params_str_list.append( + f" {name.capitalize().replace('_', ' ')}: {value}" + ) + + return "\n".join(params_str_list) diff --git a/swarms/utils/file_extension_seach.py b/swarms/utils/file_extension_seach.py new file mode 100644 index 00000000..a29cb505 --- /dev/null +++ b/swarms/utils/file_extension_seach.py @@ -0,0 +1,21 @@ +import re + + +def get_file_extension(s): + """ + Get the file extension from a given string. + + Args: + s (str): The input string. + + Returns: + str or None: The file extension if found, or None if not found. + + Raises: + ValueError: If the input is not a string. + """ + if not isinstance(s, str): + raise ValueError("Input must be a string") + + match = re.search(r"\.(pdf|csv|txt|docx|xlsx)$", s, re.IGNORECASE) + return match.group()[1:] if match else None diff --git a/swarms/utils/save_logs.py b/swarms/utils/save_logs.py new file mode 100644 index 00000000..c8193905 --- /dev/null +++ b/swarms/utils/save_logs.py @@ -0,0 +1,46 @@ +import os + + +def parse_log_file(filename: str): + """ + Parse a log file and return a list of log entries. + + Each log entry is a dictionary with keys for the timestamp, name, level, and message. + + Args: + filename (str): The name of the log file. + + Returns: + list: A list of log entries. + + Raises: + FileNotFoundError: If the log file does not exist. + ValueError: If a log entry does not have the correct format. + """ + # Check if the file exists + if not os.path.exists(filename): + raise FileNotFoundError( + f"The file {filename} does not exist." + ) + + log_entries = [] + + with open(filename, "r") as file: + for line in file: + parts = line.split(" - ") + # Check if the log entry has the correct format + if len(parts) != 4: + raise ValueError( + f"The log entry '{line}' does not have the" + " correct format." + ) + timestamp, name, level, message = parts + log_entry = { + "timestamp": timestamp, + "name": name, + "level": level, + "message": message.rstrip("\n"), + } + log_entries.append(log_entry) + + return log_entries diff --git a/tests/models/test_qwen.py b/tests/models/test_qwen.py new file mode 100644 index 00000000..28178fc0 --- /dev/null +++ b/tests/models/test_qwen.py @@ -0,0 +1,60 @@ +from unittest.mock import Mock, patch +from swarms.models.qwen import QwenVLMultiModal + + +def test_post_init(): + with patch( + "swarms.models.qwen.AutoTokenizer.from_pretrained" + ) as mock_tokenizer, patch( + "swarms.models.qwen.AutoModelForCausalLM.from_pretrained" + ) as mock_model: + mock_tokenizer.return_value = Mock() + mock_model.return_value = Mock() + + model = QwenVLMultiModal() + mock_tokenizer.assert_called_once_with( + model.model_name, trust_remote_code=True + ) + mock_model.assert_called_once_with( + model.model_name, + device_map=model.device, + trust_remote_code=True, + ) + + +def test_run(): + with patch( + "swarms.models.qwen.AutoTokenizer.from_list_format" + ) as mock_format, patch( + "swarms.models.qwen.AutoTokenizer.__call__" + ) as mock_call, patch( + "swarms.models.qwen.AutoModelForCausalLM.generate" + ) as mock_generate, patch( + "swarms.models.qwen.AutoTokenizer.decode" + ) as mock_decode: + mock_format.return_value = Mock() + mock_call.return_value = Mock() + mock_generate.return_value = Mock() + mock_decode.return_value = "response" + + model = QwenVLMultiModal() + response = model.run( + "Hello, how are you?", "https://example.com/image.jpg" + ) + + assert response == "response" + + +def test_chat(): + with patch( + "swarms.models.qwen.AutoModelForCausalLM.chat" + ) as mock_chat: + mock_chat.return_value = ("response", ["history"]) + + model = QwenVLMultiModal() + response, history = model.chat( + "Hello, how are you?", "https://example.com/image.jpg" + ) + + assert response == "response" + assert history == ["history"] diff --git a/tests/structs/test_company.py b/tests/structs/test_company.py new file mode 100644 index 00000000..0b1ec105 --- /dev/null +++ b/tests/structs/test_company.py @@ -0,0 +1,70 @@ +import pytest +from swarms.structs.agent import Agent +from swarms.structs.company import Company +from swarms import OpenAIChat + +# Mock OpenAIChat instance +llm = OpenAIChat(openai_api_key="test_key", max_tokens=4000) + +# Mock Agents +ceo = Agent(llm=llm, name="CEO") +dev = Agent(llm=llm, name="Developer") +va = Agent(llm=llm, name="VA") +hr = Agent(llm=llm, name="HR") +shared_instructions = "Listen to your boss" + + +def test_add_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + company.add(hr) + assert hr in company.agents + + +def test_get_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + company.add(hr) + assert company.get("HR") == hr + + +def test_remove_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + company.add(hr) + company.remove(hr) + assert hr not in company.agents + + +def test_add_existing_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + company.add(hr) + with pytest.raises(ValueError): + company.add(hr) + + +def test_get_nonexistent_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + with pytest.raises(ValueError): + company.get("Nonexistent") + + +def test_remove_nonexistent_agent(): + company = Company( + org_chart=[[ceo, [dev, va]]], + shared_instructions=shared_instructions, + ) + with pytest.raises(ValueError): + company.remove(hr)