From 4a57c20aa89ed4e7595ccae99f73fc75fe372cc5 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 19 Oct 2025 14:36:19 -0700 Subject: [PATCH] [Improvement][ForestSwarm][Improve format and structure] [Improvement][Agent][Add more verbose logging for mcp] [Docs][ForestSwarm] --- agent_mcp.py | 23 ++ docs/swarms/structs/forest_swarm.md | 103 ++++++-- examples/aop_examples/server.py | 6 +- examples/mcp/mcp_agent_tool.py | 36 --- .../mcp/mcp_utils/mcp_multiple_tool_test.py | 10 + examples/mcp/{ => mcp_utils}/utils.py | 0 examples/mcp/multi_mcp_example.py | 17 -- examples/mcp/multi_mcp_guide/agent_mcp.py | 12 +- .../multi_agent/tree_swarm_new_updates.py | 43 ++++ swarms/structs/agent.py | 40 +-- swarms/structs/cron_job.py | 3 - swarms/structs/tree_swarm.py | 235 +++++++++++------- swarms/tools/mcp_client_tools.py | 12 +- 13 files changed, 344 insertions(+), 196 deletions(-) create mode 100644 agent_mcp.py delete mode 100644 examples/mcp/mcp_agent_tool.py create mode 100644 examples/mcp/mcp_utils/mcp_multiple_tool_test.py rename examples/mcp/{ => mcp_utils}/utils.py (100%) create mode 100644 examples/multi_agent/tree_swarm_new_updates.py diff --git a/agent_mcp.py b/agent_mcp.py new file mode 100644 index 00000000..89de8697 --- /dev/null +++ b/agent_mcp.py @@ -0,0 +1,23 @@ +from swarms import Agent +from swarms.prompts.finance_agent_sys_prompt import ( + FINANCIAL_AGENT_SYS_PROMPT, +) + +agent = Agent( + agent_name="Financial-Analysis-Agent", # Name of the agent + agent_description="Personal finance advisor agent", # Description of the agent's role + system_prompt=FINANCIAL_AGENT_SYS_PROMPT, # System prompt for financial tasks + max_loops=1, + mcp_urls=[ + "http://0.0.0.0:5932/mcp", + ], + model_name="gpt-4o-mini", + output_type="all", +) + +out = agent.run( + "Use the discover agent tools to find what agents are available and provide a summary" +) + +# Print the output from the agent's run method. +print(out) diff --git a/docs/swarms/structs/forest_swarm.md b/docs/swarms/structs/forest_swarm.md index 519aed8b..b9a12799 100644 --- a/docs/swarms/structs/forest_swarm.md +++ b/docs/swarms/structs/forest_swarm.md @@ -2,7 +2,7 @@ This documentation describes the **ForestSwarm** that organizes agents into trees. Each agent specializes in processing specific tasks. Trees are collections of agents, each assigned based on their relevance to a task through keyword extraction and **litellm-based embedding similarity**. -The architecture allows for efficient task assignment by selecting the most relevant agent from a set of trees. Tasks are processed asynchronously, with agents selected based on task relevance, calculated by the similarity of system prompts and task keywords using **litellm embeddings** and cosine similarity calculations. +The architecture allows for efficient task assignment by selecting the most relevant agent from a set of trees. Tasks are processed with agents selected based on task relevance, calculated by the similarity of system prompts and task keywords using **litellm embeddings** and cosine similarity calculations. ## Module Path: `swarms.structs.tree_swarm` @@ -11,24 +11,30 @@ The architecture allows for efficient task assignment by selecting the most rele ### Utility Functions #### `extract_keywords(prompt: str, top_n: int = 5) -> List[str]` + Extracts relevant keywords from a text prompt using basic word splitting and frequency counting. **Parameters:** + - `prompt` (str): The text to extract keywords from - `top_n` (int): Maximum number of keywords to return **Returns:** + - `List[str]`: List of extracted keywords sorted by frequency #### `cosine_similarity(vec1: List[float], vec2: List[float]) -> float` + Calculates the cosine similarity between two embedding vectors. **Parameters:** + - `vec1` (List[float]): First embedding vector - `vec2` (List[float]): Second embedding vector **Returns:** -- `float`: Cosine similarity score between -1 and 1 + +- `float`: Cosine similarity score between 0 and 1 --- @@ -36,25 +42,29 @@ Calculates the cosine similarity between two embedding vectors. `TreeAgent` represents an individual agent responsible for handling a specific task. Agents are initialized with a **system prompt** and use **litellm embeddings** to dynamically determine their relevance to a given task. -#### Attributes +#### TreeAgent Attributes | **Attribute** | **Type** | **Description** | |--------------------------|------------------|---------------------------------------------------------------------------------| +| `name` | `str` | Name of the agent | +| `description` | `str` | Description of the agent | | `system_prompt` | `str` | A string that defines the agent's area of expertise and task-handling capability.| -| `llm` | `callable` | The language model (LLM) used to process tasks (e.g., GPT-4). | +| `model_name` | `str` | Name of the language model to use (default: "gpt-4.1") | | `agent_name` | `str` | The name of the agent. | | `system_prompt_embedding`| `List[float]` | **litellm-generated embedding** of the system prompt for similarity-based task matching.| | `relevant_keywords` | `List[str]` | Keywords dynamically extracted from the system prompt to assist in task matching.| | `distance` | `Optional[float]`| The computed distance between agents based on embedding similarity. | | `embedding_model_name` | `str` | **Name of the litellm embedding model** (default: "text-embedding-ada-002"). | +| `verbose` | `bool` | Whether to enable verbose logging | -#### Methods +#### TreeAgent Methods | **Method** | **Input** | **Output** | **Description** | |--------------------|---------------------------------|--------------------|---------------------------------------------------------------------------------| +| `__init__(name, description, system_prompt, model_name, agent_name, embedding_model_name, verbose, *args, **kwargs)` | Various initialization parameters | `None` | Initializes a TreeAgent with litellm embedding capabilities | | `_get_embedding(text: str)` | `text: str` | `List[float]` | **Internal method to generate embeddings using litellm.** | | `calculate_distance(other_agent: TreeAgent)` | `other_agent: TreeAgent` | `float` | Calculates the **cosine similarity distance** between this agent and another agent.| -| `run_task(task: str)` | `task: str` | `Any` | Executes the task, logs the input/output, and returns the result. | +| `run_task(task: str, img: str = None, *args, **kwargs)` | `task: str, img: str, *args, **kwargs` | `Any` | Executes the task, logs the input/output, and returns the result. | | `is_relevant_for_task(task: str, threshold: float = 0.7)` | `task: str, threshold: float` | `bool` | Checks if the agent is relevant for the task using **keyword matching and litellm embedding similarity**.| --- @@ -63,28 +73,30 @@ Calculates the cosine similarity between two embedding vectors. `Tree` organizes multiple agents into a hierarchical structure, where agents are sorted based on their relevance to tasks using **litellm embeddings**. -#### Attributes +#### Tree Attributes | **Attribute** | **Type** | **Description** | |--------------------------|------------------|---------------------------------------------------------------------------------| | `tree_name` | `str` | The name of the tree (represents a domain of agents, e.g., "Financial Tree"). | | `agents` | `List[TreeAgent]`| List of agents belonging to this tree, **sorted by embedding-based distance**. | +| `verbose` | `bool` | Whether to enable verbose logging | -#### Methods +#### Tree Methods | **Method** | **Input** | **Output** | **Description** | |--------------------|---------------------------------|--------------------|---------------------------------------------------------------------------------| +| `__init__(tree_name: str, agents: List[TreeAgent], verbose: bool = False)` | `tree_name: str, agents: List[TreeAgent], verbose: bool` | `None` | Initializes a tree of agents | | `calculate_agent_distances()` | `None` | `None` | **Calculates and assigns distances between agents based on litellm embedding similarity of prompts.** | | `find_relevant_agent(task: str)` | `task: str` | `Optional[TreeAgent]` | **Finds the most relevant agent for a task based on keyword and litellm embedding similarity.** | | `log_tree_execution(task: str, selected_agent: TreeAgent, result: Any)` | `task: str, selected_agent: TreeAgent, result: Any` | `None` | Logs details of the task execution by the selected agent. | --- -### Class: `ForestSwarm` +### Class: `ForestSwarm` `ForestSwarm` is the main class responsible for managing multiple trees. It oversees task delegation by finding the most relevant tree and agent for a given task using **litellm embeddings**. -#### Attributes +#### ForestSwarm Attributes | **Attribute** | **Type** | **Description** | |--------------------------|------------------|---------------------------------------------------------------------------------| @@ -92,42 +104,51 @@ Calculates the cosine similarity between two embedding vectors. | `description` | `str` | Description of the forest swarm. | | `trees` | `List[Tree]` | List of trees containing agents organized by domain. | | `shared_memory` | `Any` | Shared memory object for inter-tree communication. | -| `rules` | `str` | Rules governing the forest swarm behavior. | +| `verbose` | `bool` | Whether to enable verbose logging | +| `save_file_path` | `str` | File path for saving conversation logs | | `conversation` | `Conversation` | Conversation object for tracking interactions. | -#### Methods +#### ForestSwarm Methods | **Method** | **Input** | **Output** | **Description** | |--------------------|---------------------------------|--------------------|---------------------------------------------------------------------------------| +| `__init__(name, description, trees, shared_memory, rules, verbose, *args, **kwargs)` | Various initialization parameters | `None` | Initialize a ForestSwarm with multiple trees of agents | | `find_relevant_tree(task: str)` | `task: str` | `Optional[Tree]` | **Searches across all trees to find the most relevant tree based on litellm embedding similarity.**| | `run(task: str, img: str = None, *args, **kwargs)` | `task: str, img: str, *args, **kwargs` | `Any` | **Executes the task by finding the most relevant agent from the relevant tree using litellm embeddings.**| +| `batched_run(tasks: List[str], *args, **kwargs)` | `tasks: List[str], *args, **kwargs` | `List[Any]` | **Executes multiple tasks by finding the most relevant agent for each task.**| --- ### Pydantic Models for Logging #### `AgentLogInput` + Input log model for tracking agent task execution. **Fields:** + - `log_id` (str): Unique identifier for the log entry - `agent_name` (str): Name of the agent executing the task - `task` (str): Description of the task being executed - `timestamp` (datetime): When the task was started #### `AgentLogOutput` + Output log model for tracking agent task completion. **Fields:** + - `log_id` (str): Unique identifier for the log entry - `agent_name` (str): Name of the agent that completed the task - `result` (Any): Result/output from the task execution - `timestamp` (datetime): When the task was completed #### `TreeLog` + Tree execution log model for tracking tree-level operations. **Fields:** + - `log_id` (str): Unique identifier for the log entry - `tree_name` (str): Name of the tree that executed the task - `task` (str): Description of the task that was executed @@ -145,49 +166,72 @@ from swarms.structs.tree_swarm import TreeAgent, Tree, ForestSwarm # Create agents with varying system prompts and dynamically generated distances/keywords agents_tree1 = [ TreeAgent( + name="Financial Advisor", system_prompt="I am a financial advisor specializing in investment planning, retirement strategies, and tax optimization for individuals and businesses.", agent_name="Financial Advisor", + verbose=True ), TreeAgent( + name="Tax Expert", system_prompt="I am a tax expert with deep knowledge of corporate taxation, Delaware incorporation benefits, and free tax filing options for businesses.", agent_name="Tax Expert", + verbose=True ), TreeAgent( + name="Retirement Planner", system_prompt="I am a retirement planning specialist who helps individuals and businesses create comprehensive retirement strategies and investment plans.", agent_name="Retirement Planner", + verbose=True ), ] agents_tree2 = [ TreeAgent( + name="Stock Analyst", system_prompt="I am a stock market analyst who provides insights on market trends, stock recommendations, and portfolio optimization strategies.", agent_name="Stock Analyst", + verbose=True ), TreeAgent( + name="Investment Strategist", system_prompt="I am an investment strategist specializing in portfolio diversification, risk management, and market analysis.", agent_name="Investment Strategist", + verbose=True ), TreeAgent( + name="ROTH IRA Specialist", system_prompt="I am a ROTH IRA specialist who helps individuals optimize their retirement accounts and tax advantages.", agent_name="ROTH IRA Specialist", + verbose=True ), ] # Create trees -tree1 = Tree(tree_name="Financial Services Tree", agents=agents_tree1) -tree2 = Tree(tree_name="Investment & Trading Tree", agents=agents_tree2) +tree1 = Tree(tree_name="Financial Services Tree", agents=agents_tree1, verbose=True) +tree2 = Tree(tree_name="Investment & Trading Tree", agents=agents_tree2, verbose=True) # Create the ForestSwarm forest_swarm = ForestSwarm( name="Financial Services Forest", description="A comprehensive financial services multi-agent system", - trees=[tree1, tree2] + trees=[tree1, tree2], + verbose=True ) # Run a task task = "Our company is incorporated in Delaware, how do we do our taxes for free?" output = forest_swarm.run(task) print(output) + +# Run multiple tasks +tasks = [ + "What are the best investment strategies for retirement?", + "How do I file taxes for my Delaware corporation?", + "What's the current market outlook for tech stocks?" +] +results = forest_swarm.batched_run(tasks) +for i, result in enumerate(results): + print(f"Task {i+1} result: {result}") ``` --- @@ -203,12 +247,14 @@ print(output) - Searches through all trees using **cosine similarity** - Finds the most relevant agent based on **embedding similarity and keyword matching** 6. **Task Execution**: The selected agent processes the task, and the result is returned and logged. +7. **Batched Processing**: Multiple tasks can be processed using the `batched_run` method for efficient batch processing. ```plaintext Task: "Our company is incorporated in Delaware, how do we do our taxes for free?" ``` -**Process**: +**Process:** + - The system generates **litellm embeddings** for the task - Searches through the `Financial Services Tree` and `Investment & Trading Tree` - Uses **cosine similarity** to find the most relevant agent (likely the "Tax Expert") @@ -219,20 +265,29 @@ Task: "Our company is incorporated in Delaware, how do we do our taxes for free? ## Key Features ### **litellm Integration** + - **Embedding Generation**: Uses litellm's `embedding()` function for generating high-quality embeddings - **Model Flexibility**: Supports various embedding models (default: "text-embedding-ada-002") - **Error Handling**: Robust fallback mechanisms for embedding failures ### **Semantic Similarity** + - **Cosine Similarity**: Implements efficient cosine similarity calculations for vector comparisons - **Threshold-based Selection**: Configurable similarity thresholds for agent selection - **Hybrid Matching**: Combines keyword matching with semantic similarity for optimal results ### **Dynamic Agent Organization** + - **Automatic Distance Calculation**: Agents are automatically organized by semantic similarity - **Real-time Relevance**: Task relevance is calculated dynamically using current embeddings - **Scalable Architecture**: Easy to add/remove agents and trees without manual configuration +### **Batch Processing** + +- **Batched Execution**: Process multiple tasks efficiently using `batched_run` method +- **Parallel Processing**: Each task is processed independently with the most relevant agent +- **Result Aggregation**: All results are returned as a list for easy processing + --- ## Analysis of the Swarm Architecture @@ -243,7 +298,7 @@ The **ForestSwarm Architecture** leverages a hierarchical structure (forest) com - **Task Specialization**: Each agent is specialized, which ensures that tasks are matched with the most appropriate agent based on **litellm embedding similarity** and expertise. - **Dynamic Matching**: The architecture uses both keyword-based and **litellm embedding-based matching** to assign tasks, ensuring a high level of accuracy in agent selection. - **Logging and Accountability**: Each task execution is logged in detail, providing transparency and an audit trail of which agent handled which task and the results produced. -- **Asynchronous Task Execution**: The architecture can be adapted for asynchronous task processing, making it scalable and suitable for large-scale task handling in real-time systems. +- **Batch Processing**: The architecture supports efficient batch processing of multiple tasks simultaneously. --- @@ -274,6 +329,13 @@ graph TD P --> Q[Execute Task] Q --> R[Log Results] end + + subgraph Batch Processing + S[Multiple Tasks] --> T[Process Each Task] + T --> U[Find Relevant Agent per Task] + U --> V[Execute All Tasks] + V --> W[Return Results List] + end ``` ### Explanation of the Diagram @@ -283,6 +345,7 @@ graph TD - **Agents**: Each agent within the tree is responsible for handling tasks in its area of expertise. Agents within a tree are organized based on their **litellm embedding similarity** (distance). - **Embedding Process**: Shows how **litellm embeddings** are used for similarity calculations and agent selection. - **Task Processing**: Illustrates the complete workflow from task input to result logging. +- **Batch Processing**: Shows how multiple tasks can be processed efficiently using the `batched_run` method. --- @@ -295,6 +358,7 @@ python test_forest_swarm.py ``` The test suite covers: + - **Utility Functions**: `extract_keywords`, `cosine_similarity` - **Pydantic Models**: `AgentLogInput`, `AgentLogOutput`, `TreeLog` - **Core Classes**: `TreeAgent`, `Tree`, `ForestSwarm` @@ -308,8 +372,11 @@ The test suite covers: This **ForestSwarm Architecture** provides an efficient, scalable, and accurate architecture for delegating and executing tasks based on domain-specific expertise. The combination of hierarchical organization, **litellm-based semantic similarity**, dynamic task matching, and comprehensive logging ensures reliability, performance, and transparency in task execution. **Key Advantages:** + - **High Accuracy**: litellm embeddings provide superior semantic understanding - **Scalability**: Easy to add new agents, trees, and domains - **Flexibility**: Configurable similarity thresholds and embedding models - **Robustness**: Comprehensive error handling and fallback mechanisms -- **Transparency**: Detailed logging and audit trails for all operations \ No newline at end of file +- **Transparency**: Detailed logging and audit trails for all operations +- **Batch Processing**: Efficient processing of multiple tasks simultaneously +- **Verbose Logging**: Comprehensive logging at all levels for debugging and monitoring \ No newline at end of file diff --git a/examples/aop_examples/server.py b/examples/aop_examples/server.py index 89f13ac5..89420fed 100644 --- a/examples/aop_examples/server.py +++ b/examples/aop_examples/server.py @@ -79,14 +79,16 @@ financial_agent = Agent( max_loops=1, top_p=None, dynamic_temperature_enabled=True, - system_prompt="""You are a financial specialist. Your role is to: + system_prompt=""" + You are a financial specialist. Your role is to: 1. Analyze financial data and markets 2. Provide investment insights 3. Assess risk and opportunities 4. Create financial reports 5. Explain complex financial concepts - Always provide accurate, well-reasoned financial analysis.""", + Always provide accurate, well-reasoned financial analysis. + """, ) # Basic usage - individual agent addition diff --git a/examples/mcp/mcp_agent_tool.py b/examples/mcp/mcp_agent_tool.py deleted file mode 100644 index a0489cb8..00000000 --- a/examples/mcp/mcp_agent_tool.py +++ /dev/null @@ -1,36 +0,0 @@ -from mcp.server.fastmcp import FastMCP - -from swarms import Agent - -mcp = FastMCP("MCPAgentTool") - - -@mcp.tool( - name="create_agent", - description="Create an agent with the specified name, system prompt, and model, then run a task.", -) -def create_agent( - agent_name: str, system_prompt: str, model_name: str, task: str -) -> str: - """ - Create an agent with the given parameters and execute the specified task. - - Args: - agent_name (str): The name of the agent to create. - system_prompt (str): The system prompt to initialize the agent with. - model_name (str): The model name to use for the agent. - task (str): The task for the agent to perform. - - Returns: - str: The result of the agent running the given task. - """ - agent = Agent( - agent_name=agent_name, - system_prompt=system_prompt, - model_name=model_name, - ) - return agent.run(task) - - -if __name__ == "__main__": - mcp.run() diff --git a/examples/mcp/mcp_utils/mcp_multiple_tool_test.py b/examples/mcp/mcp_utils/mcp_multiple_tool_test.py new file mode 100644 index 00000000..72b62d9b --- /dev/null +++ b/examples/mcp/mcp_utils/mcp_multiple_tool_test.py @@ -0,0 +1,10 @@ +from swarms.tools.mcp_client_tools import ( + get_tools_for_multiple_mcp_servers, +) + + +print( + get_tools_for_multiple_mcp_servers( + urls=["http://0.0.0.0:5932/mcp"] + ) +) diff --git a/examples/mcp/utils.py b/examples/mcp/mcp_utils/utils.py similarity index 100% rename from examples/mcp/utils.py rename to examples/mcp/mcp_utils/utils.py diff --git a/examples/mcp/multi_mcp_example.py b/examples/mcp/multi_mcp_example.py index 1636da92..22c2ebb2 100644 --- a/examples/mcp/multi_mcp_example.py +++ b/examples/mcp/multi_mcp_example.py @@ -1,20 +1,3 @@ -#!/usr/bin/env python3 -""" -Multi-MCP Agent Example - -This example demonstrates how to use multiple MCP (Model Context Protocol) servers -with a single Swarms agent. The agent can access tools from different MCP servers -simultaneously, enabling powerful cross-server functionality. - -Prerequisites: -1. Start the OKX crypto server: python multi_mcp_guide/okx_crypto_server.py -2. Start the agent tools server: python multi_mcp_guide/mcp_agent_tool.py -3. Install required dependencies: pip install swarms mcp fastmcp requests - -Usage: - python examples/multi_agent/multi_mcp_example.py -""" - from swarms import Agent from swarms.prompts.finance_agent_sys_prompt import ( FINANCIAL_AGENT_SYS_PROMPT, diff --git a/examples/mcp/multi_mcp_guide/agent_mcp.py b/examples/mcp/multi_mcp_guide/agent_mcp.py index 858502e4..b4444272 100644 --- a/examples/mcp/multi_mcp_guide/agent_mcp.py +++ b/examples/mcp/multi_mcp_guide/agent_mcp.py @@ -3,26 +3,18 @@ from swarms.prompts.finance_agent_sys_prompt import ( FINANCIAL_AGENT_SYS_PROMPT, ) -# Initialize the financial analysis agent with a system prompt and configuration. agent = Agent( agent_name="Financial-Analysis-Agent", # Name of the agent agent_description="Personal finance advisor agent", # Description of the agent's role system_prompt=FINANCIAL_AGENT_SYS_PROMPT, # System prompt for financial tasks max_loops=1, - mcp_urls=[ - "http://0.0.0.0:8001/mcp", # URL for the OKX crypto price MCP server - "http://0.0.0.0:8000/mcp", # URL for the agent creation MCP server - ], + mcp_url="http://0.0.0.0:8001/mcp", # URL for the OKX crypto price MCP server model_name="gpt-4o-mini", output_type="all", ) -# Run the agent with a specific instruction to use the create_agent tool. -# The agent is asked to create a new agent specialized for accounting rules in crypto. out = agent.run( - # Example alternative prompt: - # "Use the get_okx_crypto_price to get the price of solana just put the name of the coin", - "Use the create_agent tool that is specialized in creating agents and create an agent speecialized for accounting rules in crypto" + "Use the get_okx_crypto_price to get the price of solana just put the name of the coin", ) # Print the output from the agent's run method. diff --git a/examples/multi_agent/tree_swarm_new_updates.py b/examples/multi_agent/tree_swarm_new_updates.py new file mode 100644 index 00000000..455f79f6 --- /dev/null +++ b/examples/multi_agent/tree_swarm_new_updates.py @@ -0,0 +1,43 @@ +from swarms.structs.tree_swarm import TreeAgent, Tree, ForestSwarm + +# Create agents with varying system prompts and dynamically generated distances/keywords +agents_tree1 = [ + TreeAgent( + system_prompt="Stock Analysis Agent", + agent_name="Stock Analysis Agent", + ), + TreeAgent( + system_prompt="Financial Planning Agent", + agent_name="Financial Planning Agent", + ), + TreeAgent( + agent_name="Retirement Strategy Agent", + system_prompt="Retirement Strategy Agent", + ), +] + +agents_tree2 = [ + TreeAgent( + system_prompt="Tax Filing Agent", + agent_name="Tax Filing Agent", + ), + TreeAgent( + system_prompt="Investment Strategy Agent", + agent_name="Investment Strategy Agent", + ), + TreeAgent( + system_prompt="ROTH IRA Agent", agent_name="ROTH IRA Agent" + ), +] + +# Create trees +tree1 = Tree(tree_name="Financial Tree", agents=agents_tree1) +tree2 = Tree(tree_name="Investment Tree", agents=agents_tree2) + +# Create the ForestSwarm +multi_agent_structure = ForestSwarm(trees=[tree1, tree2]) + +# Run a task +task = "Our company is incorporated in delaware, how do we do our taxes for free?" +output = multi_agent_structure.run(task) +print(output) diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 78381692..3bbde210 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -822,7 +822,17 @@ class Agent: tools_list.extend(self.tools_list_dictionary) if exists(self.mcp_url) or exists(self.mcp_urls): - tools_list.extend(self.add_mcp_tools_to_memory()) + if self.verbose: + logger.info( + f"Adding MCP tools to memory for {self.agent_name}" + ) + # tools_list.extend(self.add_mcp_tools_to_memory()) + mcp_tools = self.add_mcp_tools_to_memory() + + if self.verbose: + logger.info(f"MCP tools: {mcp_tools}") + + tools_list.extend(mcp_tools) # Additional arguments for LiteLLM initialization additional_args = {} @@ -888,37 +898,37 @@ class Agent: Exception: If there's an error accessing the MCP tools """ try: + # Determine which MCP configuration to use if exists(self.mcp_url): tools = get_mcp_tools_sync(server_path=self.mcp_url) elif exists(self.mcp_config): tools = get_mcp_tools_sync(connection=self.mcp_config) - # logger.info(f"Tools: {tools}") elif exists(self.mcp_urls): + logger.info( + f"Getting MCP tools for multiple MCP servers for {self.agent_name}" + ) tools = get_tools_for_multiple_mcp_servers( urls=self.mcp_urls, - output_type="str", ) - # print(f"Tools: {tools} for {self.mcp_urls}") + + if self.verbose: + logger.info(f"MCP tools: {tools}") else: raise AgentMCPConnectionError( "mcp_url must be either a string URL or MCPConnection object" ) - if ( - exists(self.mcp_url) - or exists(self.mcp_urls) - or exists(self.mcp_config) - ): - if self.print_on is True: - self.pretty_print( - f"✨ [SYSTEM] Successfully integrated {len(tools)} MCP tools into agent: {self.agent_name} | Status: ONLINE | Time: {time.strftime('%H:%M:%S')} ✨", - loop_count=0, - ) + # Print success message if any MCP configuration exists + if self.print_on: + self.pretty_print( + f"✨ [SYSTEM] Successfully integrated {len(tools)} MCP tools into agent: {self.agent_name} | Status: ONLINE | Time: {time.strftime('%H:%M:%S')} ✨", + loop_count=0, + ) return tools except AgentMCPConnectionError as e: logger.error( - f"Error in MCP connection: {e} Traceback: {traceback.format_exc()}" + f"Error Adding MCP Tools to Agent: {self.agent_name} Error: {e} Traceback: {traceback.format_exc()}" ) raise e diff --git a/swarms/structs/cron_job.py b/swarms/structs/cron_job.py index 0e858a11..eff7472e 100644 --- a/swarms/structs/cron_job.py +++ b/swarms/structs/cron_job.py @@ -7,9 +7,6 @@ import schedule from loguru import logger -# from swarms import Agent - - class CronJobError(Exception): """Base exception class for CronJob errors.""" diff --git a/swarms/structs/tree_swarm.py b/swarms/structs/tree_swarm.py index c1b21eb1..7607fe0b 100644 --- a/swarms/structs/tree_swarm.py +++ b/swarms/structs/tree_swarm.py @@ -1,9 +1,8 @@ import uuid from collections import Counter -from datetime import datetime +from datetime import datetime, timezone from typing import Any, List, Optional -import numpy as np from litellm import embedding from pydantic import BaseModel, Field @@ -14,6 +13,47 @@ from swarms.utils.loguru_logger import initialize_logger logger = initialize_logger(log_folder="tree_swarm") +def extract_keywords(prompt: str, top_n: int = 5) -> List[str]: + """ + A simplified keyword extraction function using basic word splitting instead of NLTK tokenization. + + Args: + prompt (str): The text prompt to extract keywords from + top_n (int): Maximum number of keywords to return + + Returns: + List[str]: List of extracted keywords + """ + words = prompt.lower().split() + filtered_words = [word for word in words if word.isalnum()] + word_counts = Counter(filtered_words) + return [word for word, _ in word_counts.most_common(top_n)] + + +def cosine_similarity(vec1: List[float], vec2: List[float]) -> float: + """ + Calculate cosine similarity between two vectors. + + Args: + vec1 (List[float]): First vector + vec2 (List[float]): Second vector + + Returns: + float: Cosine similarity score between 0 and 1 + """ + # Calculate dot product + dot_product = sum(a * b for a, b in zip(vec1, vec2)) + + # Calculate norms + norm1 = sum(a * a for a in vec1) ** 0.5 + norm2 = sum(b * b for b in vec2) ** 0.5 + + if norm1 == 0 or norm2 == 0: + return 0.0 + + return dot_product / (norm1 * norm2) + + # Pydantic Models for Logging class AgentLogInput(BaseModel): """ @@ -32,7 +72,7 @@ class AgentLogInput(BaseModel): agent_name: str task: str timestamp: datetime = Field( - default_factory=lambda: datetime.now(datetime.UTC) + default_factory=lambda: datetime.now(timezone.utc) ) @@ -53,7 +93,7 @@ class AgentLogOutput(BaseModel): agent_name: str result: Any timestamp: datetime = Field( - default_factory=lambda: datetime.now(datetime.UTC) + default_factory=lambda: datetime.now(timezone.utc) ) @@ -77,52 +117,11 @@ class TreeLog(BaseModel): task: str selected_agent: str timestamp: datetime = Field( - default_factory=lambda: datetime.now(datetime.UTC) + default_factory=lambda: datetime.now(timezone.utc) ) result: Any -def extract_keywords(prompt: str, top_n: int = 5) -> List[str]: - """ - A simplified keyword extraction function using basic word splitting instead of NLTK tokenization. - - Args: - prompt (str): The text prompt to extract keywords from - top_n (int): Maximum number of keywords to return - - Returns: - List[str]: List of extracted keywords - """ - words = prompt.lower().split() - filtered_words = [word for word in words if word.isalnum()] - word_counts = Counter(filtered_words) - return [word for word, _ in word_counts.most_common(top_n)] - - -def cosine_similarity(vec1: List[float], vec2: List[float]) -> float: - """ - Calculate cosine similarity between two vectors. - - Args: - vec1 (List[float]): First vector - vec2 (List[float]): Second vector - - Returns: - float: Cosine similarity score between 0 and 1 - """ - vec1 = np.array(vec1) - vec2 = np.array(vec2) - - dot_product = np.dot(vec1, vec2) - norm1 = np.linalg.norm(vec1) - norm2 = np.linalg.norm(vec2) - - if norm1 == 0 or norm2 == 0: - return 0.0 - - return dot_product / (norm1 * norm2) - - class TreeAgent(Agent): """ A specialized Agent class that contains information about the system prompt's @@ -137,6 +136,7 @@ class TreeAgent(Agent): model_name: str = "gpt-4.1", agent_name: Optional[str] = None, embedding_model_name: str = "text-embedding-ada-002", + verbose: bool = False, *args, **kwargs, ): @@ -150,6 +150,7 @@ class TreeAgent(Agent): model_name (str): Name of the language model to use agent_name (Optional[str]): Alternative name for the agent embedding_model_name (str): Name of the embedding model to use + verbose (bool): Whether to enable verbose logging *args: Additional positional arguments **kwargs: Additional keyword arguments """ @@ -164,6 +165,7 @@ class TreeAgent(Agent): **kwargs, ) self.embedding_model_name = embedding_model_name + self.verbose = verbose # Generate system prompt embedding using litellm if system_prompt: @@ -195,7 +197,8 @@ class TreeAgent(Agent): response = embedding( model=self.embedding_model_name, input=[text] ) - logger.info(f"Embedding type: {type(response)}") + if self.verbose: + logger.info(f"Embedding type: {type(response)}") # print(response) # Handle different response structures from litellm if hasattr(response, "data") and response.data: @@ -207,17 +210,20 @@ class TreeAgent(Agent): ): return response.data[0]["embedding"] else: - logger.error( - f"Unexpected response structure: {response.data[0]}" - ) + if self.verbose: + logger.error( + f"Unexpected response structure: {response.data[0]}" + ) return [0.0] * 1536 else: - logger.error( - f"Unexpected response structure: {response}" - ) + if self.verbose: + logger.error( + f"Unexpected response structure: {response}" + ) return [0.0] * 1536 except Exception as e: - logger.error(f"Error getting embedding: {e}") + if self.verbose: + logger.error(f"Error getting embedding: {e}") # Return a zero vector as fallback return [0.0] * 1536 # Default OpenAI embedding dimension @@ -264,20 +270,24 @@ class TreeAgent(Agent): input_log = AgentLogInput( agent_name=self.agent_name, task=task, - timestamp=datetime.now(), + timestamp=datetime.now(timezone.utc), ) - logger.info(f"Running task on {self.agent_name}: {task}") - logger.debug(f"Input Log: {input_log.json()}") + if self.verbose: + logger.info(f"Running task on {self.agent_name}: {task}") + logger.debug(f"Input Log: {input_log.json()}") result = self.run(task=task, img=img, *args, **kwargs) output_log = AgentLogOutput( agent_name=self.agent_name, result=result, - timestamp=datetime.now(), + timestamp=datetime.now(timezone.utc), ) - logger.info(f"Task result from {self.agent_name}: {result}") - logger.debug(f"Output Log: {output_log.json()}") + if self.verbose: + logger.info( + f"Task result from {self.agent_name}: {result}" + ) + logger.debug(f"Output Log: {output_log.json()}") return result @@ -306,25 +316,36 @@ class TreeAgent(Agent): similarity = cosine_similarity( self.system_prompt_embedding, task_embedding ) - logger.info( - f"Semantic similarity between task and {self.agent_name}: {similarity:.2f}" - ) + if self.verbose: + logger.info( + f"Semantic similarity between task and {self.agent_name}: {similarity:.2f}" + ) return similarity >= threshold return True # Return True if keyword match is found class Tree: - def __init__(self, tree_name: str, agents: List[TreeAgent]): + def __init__( + self, + tree_name: str, + agents: List[TreeAgent], + verbose: bool = False, + ): """ Initializes a tree of agents. Args: tree_name (str): The name of the tree. agents (List[TreeAgent]): A list of agents in the tree. + verbose (bool): Whether to enable verbose logging """ self.tree_name = tree_name self.agents = agents + self.verbose = verbose + # Pass verbose to all agents + for agent in self.agents: + agent.verbose = verbose self.calculate_agent_distances() def calculate_agent_distances(self): @@ -334,9 +355,10 @@ class Tree: This method computes the semantic distance between consecutive agents using their system prompt embeddings and sorts the agents by distance for optimal task routing. """ - logger.info( - f"Calculating distances between agents in tree '{self.tree_name}'" - ) + if self.verbose: + logger.info( + f"Calculating distances between agents in tree '{self.tree_name}'" + ) for i, agent in enumerate(self.agents): if i > 0: agent.distance = agent.calculate_distance( @@ -359,15 +381,17 @@ class Tree: Returns: Optional[TreeAgent]: The most relevant agent, or None if no match found. """ - logger.info( - f"Searching relevant agent in tree '{self.tree_name}' for task: {task}" - ) + if self.verbose: + logger.info( + f"Searching relevant agent in tree '{self.tree_name}' for task: {task}" + ) for agent in self.agents: if agent.is_relevant_for_task(task): return agent - logger.warning( - f"No relevant agent found in tree '{self.tree_name}' for task: {task}" - ) + if self.verbose: + logger.warning( + f"No relevant agent found in tree '{self.tree_name}' for task: {task}" + ) return None def log_tree_execution( @@ -380,13 +404,14 @@ class Tree: tree_name=self.tree_name, task=task, selected_agent=selected_agent.agent_name, - timestamp=datetime.now(), + timestamp=datetime.now(timezone.utc), result=result, ) - logger.info( - f"Tree '{self.tree_name}' executed task with agent '{selected_agent.agent_name}'" - ) - logger.debug(f"Tree Log: {tree_log.json()}") + if self.verbose: + logger.info( + f"Tree '{self.tree_name}' executed task with agent '{selected_agent.agent_name}'" + ) + logger.debug(f"Tree Log: {tree_log.json()}") class ForestSwarm: @@ -397,6 +422,7 @@ class ForestSwarm: trees: List[Tree] = [], shared_memory: Any = None, rules: str = None, + verbose: bool = False, *args, **kwargs, ): @@ -409,6 +435,7 @@ class ForestSwarm: trees (List[Tree]): A list of trees in the structure shared_memory (Any): Shared memory object for inter-tree communication rules (str): Rules governing the forest swarm behavior + verbose (bool): Whether to enable verbose logging *args: Additional positional arguments **kwargs: Additional keyword arguments """ @@ -416,10 +443,13 @@ class ForestSwarm: self.description = description self.trees = trees self.shared_memory = shared_memory + self.verbose = verbose + # Pass verbose to all trees + for tree in self.trees: + tree.verbose = verbose self.save_file_path = f"forest_swarm_{uuid.uuid4().hex}.json" self.conversation = Conversation( time_enabled=False, - auto_save=True, save_filepath=self.save_file_path, rules=rules, ) @@ -434,13 +464,15 @@ class ForestSwarm: Returns: Optional[Tree]: The most relevant tree, or None if no match found """ - logger.info( - f"Searching for the most relevant tree for task: {task}" - ) + if self.verbose: + logger.info( + f"Searching for the most relevant tree for task: {task}" + ) for tree in self.trees: if tree.find_relevant_agent(task): return tree - logger.warning(f"No relevant tree found for task: {task}") + if self.verbose: + logger.warning(f"No relevant tree found for task: {task}") return None def run(self, task: str, img: str = None, *args, **kwargs) -> Any: @@ -457,9 +489,10 @@ class ForestSwarm: Any: The result of the task after it has been processed by the agents """ try: - logger.info( - f"Running task across MultiAgentTreeStructure: {task}" - ) + if self.verbose: + logger.info( + f"Running task across MultiAgentTreeStructure: {task}" + ) relevant_tree = self.find_relevant_tree(task) if relevant_tree: agent = relevant_tree.find_relevant_agent(task) @@ -472,14 +505,32 @@ class ForestSwarm: ) return result else: - logger.error( - "Task could not be completed: No relevant agent or tree found." - ) + if self.verbose: + logger.error( + "Task could not be completed: No relevant agent or tree found." + ) return "No relevant agent found to handle this task." except Exception as error: - logger.error( - f"Error detected in the ForestSwarm, check your inputs and try again ;) {error}" - ) + if self.verbose: + logger.error( + f"Error detected in the ForestSwarm, check your inputs and try again ;) {error}" + ) + + def batched_run( + self, + tasks: List[str], + *args, + **kwargs, + ) -> List[Any]: + """ + Execute the given tasks by finding the most relevant tree and agent within that tree. + + Args: + tasks: List[str]: The tasks to be executed + *args: Additional positional arguments + **kwargs: Additional keyword arguments + """ + return [self.run(task, *args, **kwargs) for task in tasks] # # Example Usage: diff --git a/swarms/tools/mcp_client_tools.py b/swarms/tools/mcp_client_tools.py index 77886f4e..c59f6332 100644 --- a/swarms/tools/mcp_client_tools.py +++ b/swarms/tools/mcp_client_tools.py @@ -64,6 +64,7 @@ class MCPExecutionError(MCPError): ######################################################## def transform_mcp_tool_to_openai_tool( mcp_tool: MCPTool, + verbose: bool = False, ) -> ChatCompletionToolParam: """ Convert an MCP tool to an OpenAI tool. @@ -72,9 +73,11 @@ def transform_mcp_tool_to_openai_tool( Returns: ChatCompletionToolParam: The OpenAI-compatible tool parameter. """ - logger.info( - f"Transforming MCP tool '{mcp_tool.name}' to OpenAI tool format." - ) + if verbose: + logger.info( + f"Transforming MCP tool '{mcp_tool.name}' to OpenAI tool format." + ) + return ChatCompletionToolParam( type="function", function=FunctionDefinition( @@ -529,12 +532,15 @@ def get_tools_for_multiple_mcp_servers( logger.info( f"get_tools_for_multiple_mcp_servers called for {len(urls)} urls." ) + tools = [] + ( min(32, os.cpu_count() + 4) if max_workers is None else max_workers ) + with ThreadPoolExecutor(max_workers=max_workers) as executor: if exists(connections): future_to_url = {