From 2c63fb8547c675b6e0dd433881c934caa69eea92 Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:15:12 +0530 Subject: [PATCH 01/13] Add SQLite conversation example --- .../sqlite_conversation.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/communication_examples/sqlite_conversation.py diff --git a/examples/communication_examples/sqlite_conversation.py b/examples/communication_examples/sqlite_conversation.py new file mode 100644 index 00000000..a1e06b05 --- /dev/null +++ b/examples/communication_examples/sqlite_conversation.py @@ -0,0 +1,25 @@ +from swarms import Agent +from swarms.communication.sqlite_wrap import SQLiteConversation + +# Configure a conversation store backed by SQLite +conversation_store = SQLiteConversation( + db_path="agent_history.db", + table_name="messages", + enable_logging=True, +) + +# Create an agent that leverages the SQLite-based memory +agent = Agent( + agent_name="SupportAgent", + system_prompt="You are a helpful assistant.", + model_name="gpt-4o-mini", + long_term_memory=conversation_store, + max_loops=1, + autosave=True, +) + +response = agent.run("How do I reset my password?") +print(response) + +# Show the conversation as stored in SQLite +print(conversation_store.to_dict()) From b6b5c4df2bb02bc2e88973f361ac179af9eddde8 Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:45:04 +0530 Subject: [PATCH 02/13] Update SQLite conversation example --- examples/communication_examples/sqlite_conversation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/communication_examples/sqlite_conversation.py b/examples/communication_examples/sqlite_conversation.py index a1e06b05..312f99a9 100644 --- a/examples/communication_examples/sqlite_conversation.py +++ b/examples/communication_examples/sqlite_conversation.py @@ -1,3 +1,9 @@ +"""Persist agent dialogue using SQLiteConversation. + +Run `pip install -e .` in the repository root so the ``swarms`` package is +available before executing this script. +""" + from swarms import Agent from swarms.communication.sqlite_wrap import SQLiteConversation @@ -15,7 +21,9 @@ agent = Agent( model_name="gpt-4o-mini", long_term_memory=conversation_store, max_loops=1, - autosave=True, + # Autosave attempts to call `save()` on the memory object which is not + # implemented in SQLiteConversation, so we disable it for this example. + autosave=False, ) response = agent.run("How do I reset my password?") From b186ac3497a32071444cf977f59537bba85a4e28 Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:35:02 +0530 Subject: [PATCH 03/13] Add DuckDB conversation example --- .../communication_examples/duckdb_agent.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/communication_examples/duckdb_agent.py diff --git a/examples/communication_examples/duckdb_agent.py b/examples/communication_examples/duckdb_agent.py new file mode 100644 index 00000000..b10b3c6e --- /dev/null +++ b/examples/communication_examples/duckdb_agent.py @@ -0,0 +1,25 @@ +from swarms import Agent +from swarms.communication.duckdb_wrap import DuckDBConversation + +# Configure a DuckDB-backed conversation store +conversation_store = DuckDBConversation( + db_path="support_conversation.duckdb", + table_name="support_history", + enable_logging=True, +) + +# Create an agent that uses this persistent memory +agent = Agent( + agent_name="HelpdeskAgent", + system_prompt="You are a helpful assistant.", + model_name="gpt-4o-mini", + long_term_memory=conversation_store, + max_loops=1, + autosave=True, +) + +response = agent.run("What are your hours of operation?") +print(response) + +# View the conversation as stored in DuckDB +print(conversation_store.to_dict()) From 8e423c69fc1212d2b22f718994c5b1754abfab37 Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:10:37 +0530 Subject: [PATCH 04/13] Update duckdb_agent.py --- examples/communication_examples/duckdb_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/communication_examples/duckdb_agent.py b/examples/communication_examples/duckdb_agent.py index b10b3c6e..312b9346 100644 --- a/examples/communication_examples/duckdb_agent.py +++ b/examples/communication_examples/duckdb_agent.py @@ -15,7 +15,7 @@ agent = Agent( model_name="gpt-4o-mini", long_term_memory=conversation_store, max_loops=1, - autosave=True, + autosave=False, ) response = agent.run("What are your hours of operation?") From cb8e023a3348441accffbdaca90a1dc513badb98 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sat, 7 Jun 2025 16:33:09 -0700 Subject: [PATCH 05/13] [FEAT][Interactive Groupchat] + [DOCS][Agents as tools] --- agent_as_tools.py | 449 ++++++++++++++ docs/mkdocs.yml | 7 +- docs/swarms/examples/agent_output_types.md | 187 ++---- docs/swarms/examples/agents_as_tools.md | 586 ++++++++++++++++++ docs/swarms/examples/vision_processing.md | 150 +++++ docs/swarms/structs/image_batch_agent.md | 271 ++++++++ docs/swarms/structs/interactive_groupchat.md | 206 ++++++ example.py | 7 +- .../vision_examples/anthropic_vision_test.py | 26 + .../single_agent/vision_examples/image.jpg | Bin 0 -> 237149 bytes .../vision_examples/image_batch_example.py | 32 + .../vision_examples/vision_and_tools.py | 67 ++ .../vision_examples/vision_test.py | 27 + interactive_groupchat_example.py | 51 ++ ..._tool_use_demo.py => many_tool_use_demo.py | 6 +- swarms/communication/supabase_wrap.py | 6 +- swarms/schemas/llm_agent_schema.py | 180 +++--- swarms/structs/agent.py | 103 +-- swarms/structs/image_batch_processor.py | 261 ++++++++ swarms/structs/interactive_groupchat.py | 356 +++++++++++ swarms/utils/litellm_wrapper.py | 268 ++++++-- .../test_supabase_conversation.py | 18 +- v0_model.py | 79 +++ 23 files changed, 2980 insertions(+), 363 deletions(-) create mode 100644 agent_as_tools.py create mode 100644 docs/swarms/examples/agents_as_tools.md create mode 100644 docs/swarms/examples/vision_processing.md create mode 100644 docs/swarms/structs/image_batch_agent.md create mode 100644 docs/swarms/structs/interactive_groupchat.md create mode 100644 examples/single_agent/vision_examples/anthropic_vision_test.py create mode 100644 examples/single_agent/vision_examples/image.jpg create mode 100644 examples/single_agent/vision_examples/image_batch_example.py create mode 100644 examples/single_agent/vision_examples/vision_and_tools.py create mode 100644 examples/single_agent/vision_examples/vision_test.py create mode 100644 interactive_groupchat_example.py rename examples/tools/multii_tool_use/many_tool_use_demo.py => many_tool_use_demo.py (99%) create mode 100644 swarms/structs/image_batch_processor.py create mode 100644 swarms/structs/interactive_groupchat.py create mode 100644 v0_model.py diff --git a/agent_as_tools.py b/agent_as_tools.py new file mode 100644 index 00000000..6b529ff0 --- /dev/null +++ b/agent_as_tools.py @@ -0,0 +1,449 @@ +import json +import requests +from swarms import Agent + + +def create_python_file(code: str, filename: str) -> str: + """Create a Python file with the given code and execute it using Python 3.12. + + This function takes a string containing Python code, writes it to a file, and executes it + using Python 3.12 via subprocess. The file will be created in the current working directory. + If a file with the same name already exists, it will be overwritten. + + Args: + code (str): The Python code to write to the file. This should be valid Python 3.12 code. + filename (str): The name of the file to create and execute. + + Returns: + str: A detailed message indicating the file was created and the execution result. + + Raises: + IOError: If there are any issues writing to the file. + subprocess.SubprocessError: If there are any issues executing the file. + + Example: + >>> code = "print('Hello, World!')" + >>> result = create_python_file(code, "test.py") + >>> print(result) + 'Python file created successfully. Execution result: Hello, World!' + """ + import subprocess + import os + import datetime + + # Get current timestamp for logging + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Write the code to file + with open(filename, "w") as f: + f.write(code) + + # Get file size and permissions + file_stats = os.stat(filename) + file_size = file_stats.st_size + file_permissions = oct(file_stats.st_mode)[-3:] + + # Execute the file using Python 3.12 and capture output + try: + result = subprocess.run( + ["python3.12", filename], + capture_output=True, + text=True, + check=True, + ) + + # Create detailed response + response = f""" +File Creation Details: +---------------------- +Timestamp: {timestamp} +Filename: {filename} +File Size: {file_size} bytes +File Permissions: {file_permissions} +Location: {os.path.abspath(filename)} + +Execution Details: +----------------- +Exit Code: {result.returncode} +Execution Time: {result.returncode} seconds + +Output: +------- +{result.stdout} + +Error Output (if any): +-------------------- +{result.stderr} +""" + return response + except subprocess.CalledProcessError as e: + error_response = f""" +File Creation Details: +---------------------- +Timestamp: {timestamp} +Filename: {filename} +File Size: {file_size} bytes +File Permissions: {file_permissions} +Location: {os.path.abspath(filename)} + +Execution Error: +--------------- +Exit Code: {e.returncode} +Error Message: {e.stderr} + +Command Output: +------------- +{e.stdout} +""" + return error_response + + +def update_python_file(code: str, filename: str) -> str: + """Update an existing Python file with new code and execute it using Python 3.12. + + This function takes a string containing Python code and updates an existing Python file. + If the file doesn't exist, it will be created. The file will be executed using Python 3.12. + + Args: + code (str): The Python code to write to the file. This should be valid Python 3.12 code. + filename (str): The name of the file to update and execute. + + Returns: + str: A detailed message indicating the file was updated and the execution result. + + Raises: + IOError: If there are any issues writing to the file. + subprocess.SubprocessError: If there are any issues executing the file. + + Example: + >>> code = "print('Updated code!')" + >>> result = update_python_file(code, "my_script.py") + >>> print(result) + 'Python file updated successfully. Execution result: Updated code!' + """ + import subprocess + import os + import datetime + + # Get current timestamp for logging + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Check if file exists and get its stats + file_exists = os.path.exists(filename) + if file_exists: + old_stats = os.stat(filename) + old_size = old_stats.st_size + old_permissions = oct(old_stats.st_mode)[-3:] + + # Write the code to file + with open(filename, "w") as f: + f.write(code) + + # Get new file stats + new_stats = os.stat(filename) + new_size = new_stats.st_size + new_permissions = oct(new_stats.st_mode)[-3:] + + # Execute the file using Python 3.12 and capture output + try: + result = subprocess.run( + ["python3.12", filename], + capture_output=True, + text=True, + check=True, + ) + + # Create detailed response + response = f""" +File Update Details: +------------------- +Timestamp: {timestamp} +Filename: {filename} +Previous Status: {'Existed' if file_exists else 'Did not exist'} +Previous Size: {old_size if file_exists else 'N/A'} bytes +Previous Permissions: {old_permissions if file_exists else 'N/A'} +New Size: {new_size} bytes +New Permissions: {new_permissions} +Location: {os.path.abspath(filename)} + +Execution Details: +----------------- +Exit Code: {result.returncode} +Execution Time: {result.returncode} seconds + +Output: +------- +{result.stdout} + +Error Output (if any): +-------------------- +{result.stderr} +""" + return response + except subprocess.CalledProcessError as e: + error_response = f""" + File Update Details: + ------------------- + Timestamp: {timestamp} + Filename: {filename} + Previous Status: {'Existed' if file_exists else 'Did not exist'} + Previous Size: {old_size if file_exists else 'N/A'} bytes + Previous Permissions: {old_permissions if file_exists else 'N/A'} + New Size: {new_size} bytes + New Permissions: {new_permissions} + Location: {os.path.abspath(filename)} + + Execution Error: + --------------- + Exit Code: {e.returncode} + Error Message: {e.stderr} + + Command Output: + ------------- + {e.stdout} + """ + return error_response + + +def run_quant_trading_agent(task: str) -> str: + """Run a quantitative trading agent to analyze and execute trading strategies. + + This function initializes and runs a specialized quantitative trading agent that can: + - Develop and backtest trading strategies + - Analyze market data for alpha opportunities + - Implement risk management frameworks + - Optimize portfolio allocations + - Conduct quantitative research + - Monitor market microstructure + - Evaluate trading system performance + + Args: + task (str): The specific trading task or analysis to perform + + Returns: + str: The agent's response or analysis results + + Example: + >>> result = run_quant_trading_agent("Analyze SPY ETF for mean reversion opportunities") + >>> print(result) + """ + # Initialize the agent + agent = Agent( + agent_name="Quantitative-Trading-Agent", + agent_description="Advanced quantitative trading and algorithmic analysis agent", + system_prompt="""You are an expert quantitative trading agent with deep expertise in: + - Algorithmic trading strategies and implementation + - Statistical arbitrage and market making + - Risk management and portfolio optimization + - High-frequency trading systems + - Market microstructure analysis + - Quantitative research methodologies + - Financial mathematics and stochastic processes + - Machine learning applications in trading + + Your core responsibilities include: + 1. Developing and backtesting trading strategies + 2. Analyzing market data and identifying alpha opportunities + 3. Implementing risk management frameworks + 4. Optimizing portfolio allocations + 5. Conducting quantitative research + 6. Monitoring market microstructure + 7. Evaluating trading system performance + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + max_loops=2, + model_name="claude-3-5-sonnet-20240620", + tools=[ + create_python_file, + update_python_file, + backtest_summary, + ], + ) + + out = agent.run(task) + return out + + +def backtest_summary(report: str) -> str: + """Generate a summary of a backtest report, but only if the backtest was profitable. + + This function should only be used when the backtest results show a positive return. + Using this function for unprofitable backtests may lead to misleading conclusions. + + Args: + report (str): The backtest report containing performance metrics + + Returns: + str: A formatted summary of the backtest report + + Example: + >>> result = backtest_summary("Total Return: +15.2%, Sharpe: 1.8") + >>> print(result) + 'The backtest report is: Total Return: +15.2%, Sharpe: 1.8' + """ + return f"The backtest report is: {report}" + + +def get_coin_price(coin_id: str, vs_currency: str) -> str: + """ + Get the current price of a specific cryptocurrency. + + Args: + coin_id (str): The CoinGecko ID of the cryptocurrency (e.g., 'bitcoin', 'ethereum') + vs_currency (str, optional): The target currency. Defaults to "usd". + + Returns: + str: JSON formatted string containing the coin's current price and market data + + Raises: + requests.RequestException: If the API request fails + + Example: + >>> result = get_coin_price("bitcoin") + >>> print(result) + {"bitcoin": {"usd": 45000, "usd_market_cap": 850000000000, ...}} + """ + try: + url = "https://api.coingecko.com/api/v3/simple/price" + params = { + "ids": coin_id, + "vs_currencies": vs_currency, + "include_market_cap": True, + "include_24hr_vol": True, + "include_24hr_change": True, + "include_last_updated_at": True, + } + + response = requests.get(url, params=params, timeout=10) + response.raise_for_status() + + data = response.json() + return json.dumps(data, indent=2) + + except requests.RequestException as e: + return json.dumps( + { + "error": f"Failed to fetch price for {coin_id}: {str(e)}" + } + ) + except Exception as e: + return json.dumps({"error": f"Unexpected error: {str(e)}"}) + + +def run_crypto_quant_agent(task: str) -> str: + """ + Run a crypto quantitative trading agent with specialized tools for cryptocurrency market analysis. + + This function initializes and runs a quantitative trading agent specifically designed for + cryptocurrency markets. The agent is equipped with tools for price fetching and can perform + various quantitative analyses including algorithmic trading strategy development, risk management, + and market microstructure analysis. + + Args: + task (str): The task or query to be processed by the crypto quant agent. + + Returns: + str: The agent's response to the given task. + + Example: + >>> response = run_crypto_quant_agent("Analyze the current market conditions for Bitcoin") + >>> print(response) + "Based on current market analysis..." + """ + # Initialize the agent with expanded tools + quant_agent = Agent( + agent_name="Crypto-Quant-Agent", + agent_description="Advanced quantitative trading agent specializing in cryptocurrency markets with algorithmic analysis capabilities", + system_prompt="""You are an expert quantitative trading agent specializing in cryptocurrency markets. Your capabilities include: + - Algorithmic trading strategy development and backtesting + - Statistical arbitrage and market making for crypto assets + - Risk management and portfolio optimization for digital assets + - High-frequency trading system design for crypto markets + - Market microstructure analysis of crypto exchanges + - Quantitative research methodologies for crypto assets + - Financial mathematics and stochastic processes + - Machine learning applications in crypto trading + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + max_loops=1, + max_tokens=4096, + model_name="gpt-4.1-mini", + dynamic_temperature_enabled=True, + output_type="final", + tools=[ + get_coin_price, + ], + ) + + return quant_agent.run(task) + + +# Initialize the agent +agent = Agent( + agent_name="Director-Agent", + agent_description="Strategic director and project management agent", + system_prompt="""You are an expert Director Agent with comprehensive capabilities in: + - Strategic planning and decision making + - Project management and coordination + - Resource allocation and optimization + - Team leadership and delegation + - Risk assessment and mitigation + - Stakeholder management + - Process optimization + - Quality assurance + + Your core responsibilities include: + 1. Developing and executing strategic initiatives + 2. Coordinating cross-functional projects + 3. Managing resource allocation + 4. Setting and tracking KPIs + 5. Ensuring project deliverables + 6. Risk management and mitigation + 7. Stakeholder communication + + You maintain strict adherence to: + - Best practices in project management + - Data-driven decision making + - Clear communication protocols + - Quality standards + - Timeline management + - Budget constraints + - Regulatory compliance + + You communicate with clarity and authority while maintaining professionalism and ensuring all stakeholders are aligned.""", + max_loops=1, + model_name="gpt-4o-mini", + output_type="final", + interactive=False, + tools=[run_quant_trading_agent], +) + +out = agent.run( + """ + Please call the quantitative trading agent to generate Python code for an Bitcoin backtest using the CoinGecko API. + Provide a comprehensive description of the backtest methodology and trading strategy. + Consider the API limitations of CoinGecko and utilize only free, open-source libraries that don't require API keys. Use the requests library to fetch the data. Create a specialized strategy for the backtest focused on the orderbook and other data for price action. + The goal is to create a backtest that can predict the price action of the coin based on the orderbook and other data. + Maximize the profit of the backtest. Please use the OKX price API for the orderbook and other data. Be very explicit in your implementation. + Be very precise with the instructions you give to the agent and tell it to a 400 lines of good code. +""" +) +print(out) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 9e576f87..7fac02cd 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -251,6 +251,9 @@ nav: - Auto Agent Builder: "swarms/structs/auto_agent_builder.md" - Hybrid Hierarchical-Cluster Swarm: "swarms/structs/hhcs.md" - Auto Swarm Builder: "swarms/structs/auto_swarm_builder.md" + + - Multi-Agent Multi-Modal Structures: + - ImageAgentBatchProcessor: "swarms/structs/image_batch_agent.md" - Workflows: @@ -302,12 +305,13 @@ nav: - Swarms 5.9.2: "swarms/changelog/changelog_new.md" - Examples: - - Agent Examples: + - Customizing Agents: - Basic Agent: "swarms/examples/basic_agent.md" - Agents with Callable Tools: "swarms/examples/agent_with_tools.md" # - Agent With MCP Integration: "swarms/examples/agent_with_mcp.md" - Agent Output Types: "swarms/examples/agent_output_types.md" - Agent with Structured Outputs: "swarms/examples/agent_structured_outputs.md" + - Agents with Vision: "swarms/examples/vision_processing.md" - Various Model Providers: - OpenAI: "swarms/examples/openai_example.md" - Anthropic: "swarms/examples/claude.md" @@ -339,6 +343,7 @@ nav: - ConcurrentWorkflow Example: "swarms/examples/concurrent_workflow.md" - MixtureOfAgents Example: "swarms/examples/mixture_of_agents.md" - Unique Swarms: "swarms/examples/unique_swarms.md" + - Agents as Tools: "swarms/examples/agents_as_tools.md" - Applications: - Swarms DAO: "swarms/examples/swarms_dao.md" - Swarms of Browser Agents: "swarms/examples/swarms_of_browser_agents.md" diff --git a/docs/swarms/examples/agent_output_types.md b/docs/swarms/examples/agent_output_types.md index 917b9950..c9f149cd 100644 --- a/docs/swarms/examples/agent_output_types.md +++ b/docs/swarms/examples/agent_output_types.md @@ -1,11 +1,12 @@ -# Agent Output Types Examples +# Agent Output Types Examples with Vision Capabilities -This example demonstrates how to use different output types when working with Swarms agents. Each output type formats the agent's response in a specific way, making it easier to integrate with different parts of your application. +This example demonstrates how to use different output types when working with Swarms agents, including vision-enabled agents that can analyze images. Each output type formats the agent's response in a specific way, making it easier to integrate with different parts of your application. ## Prerequisites - Python 3.7+ - OpenAI API key +- Anthropic API key (optional, for Claude models) - Swarms library ## Installation @@ -18,171 +19,61 @@ pip3 install -U swarms ```plaintext WORKSPACE_DIR="agent_workspace" -OPENAI_API_KEY="" -ANTHROPIC_API_KEY="" +OPENAI_API_KEY="" # Required for GPT-4V vision capabilities +ANTHROPIC_API_KEY="" # Optional, for Claude models ``` -## Available Output Types - -The following output types are supported: - -| Output Type | Description | -|------------|-------------| -| `"list"` | Returns response as a JSON string containing a list | -| `"dict"` or `"dictionary"` | Returns response as a Python dictionary | -| `"string"` or `"str"` | Returns response as a plain string | -| `"final"` or `"last"` | Returns only the final response | -| `"json"` | Returns response as a JSON string | -| `"all"` | Returns all responses in the conversation | -| `"yaml"` | Returns response formatted as YAML | -| `"xml"` | Returns response formatted as XML | -| `"dict-all-except-first"` | Returns all responses except the first as a dictionary | -| `"str-all-except-first"` | Returns all responses except the first as a string | -| `"basemodel"` | Returns response as a Pydantic BaseModel | - ## Examples -### 1. String Output (Default) - -```python -from swarms import Agent - -# Initialize agent with string output -agent = Agent( - agent_name="String-Output-Agent", - agent_description="Demonstrates string output format", - system_prompt="You are a helpful assistant that provides clear text responses.", - output_type="str", # or "string" -) - -response = agent.run("What is the capital of France?") - -``` - -### 2. JSON Output - -```python -# Initialize agent with JSON output -agent = Agent( - agent_name="JSON-Output-Agent", - agent_description="Demonstrates JSON output format", - system_prompt="You are an assistant that provides structured data responses.", - output_type="json" -) - -response = agent.run("List the top 3 programming languages.") - -``` - -### 3. List Output - -```python -# Initialize agent with list output -agent = Agent( - agent_name="List-Output-Agent", - agent_description="Demonstrates list output format", - system_prompt="You are an assistant that provides list-based responses.", - output_type="list" -) - -response = agent.run("Name three primary colors.") - -``` - -### 4. Dictionary Output +### Vision-Enabled Quality Control Agent ```python -# Initialize agent with dictionary output -agent = Agent( - agent_name="Dict-Output-Agent", - agent_description="Demonstrates dictionary output format", - system_prompt="You are an assistant that provides dictionary-based responses.", - output_type="dict" # or "dictionary" +from swarms.structs import Agent +from swarms.prompts.logistics import ( + Quality_Control_Agent_Prompt, ) -response = agent.run("Provide information about a book.") - -``` +# Image for analysis +factory_image = "image.jpg" -### 5. YAML Output -```python -# Initialize agent with YAML output -agent = Agent( - agent_name="YAML-Output-Agent", - agent_description="Demonstrates YAML output format", - system_prompt="You are an assistant that provides YAML-formatted responses.", - output_type="yaml" -) - -response = agent.run("Describe a recipe.") -``` - -### 6. XML Output - -```python -# Initialize agent with XML output -agent = Agent( - agent_name="XML-Output-Agent", - agent_description="Demonstrates XML output format", - system_prompt="You are an assistant that provides XML-formatted responses.", - output_type="xml" +# Quality control agent +quality_control_agent = Agent( + agent_name="Quality Control Agent", + agent_description="A quality control agent that analyzes images and provides a detailed report on the quality of the product in the image.", + model_name="gpt-4.1-mini", + system_prompt=Quality_Control_Agent_Prompt, + multi_modal=True, + max_loops=2, + output_type="str-all-except-first", ) -response = agent.run("Provide user information.") -``` - -### 7. All Responses -```python -# Initialize agent to get all responses -agent = Agent( - agent_name="All-Output-Agent", - agent_description="Demonstrates getting all responses", - system_prompt="You are an assistant that provides multiple responses.", - output_type="all" +response = quality_control_agent.run( + task="what is in the image?", + img=factory_image, ) -response = agent.run("Tell me about climate change.") -``` - -### 8. Final Response Only +print(response) -```python -# Initialize agent to get only final response -agent = Agent( - agent_name="Final-Output-Agent", - agent_description="Demonstrates getting only final response", - system_prompt="You are an assistant that provides concise final answers.", - output_type="final" # or "last" -) - -response = agent.run("What's the meaning of life?") ``` +### Supported Image Formats -## Best Practices - -1. Choose the output type based on your application's needs: - - | Output Type | Use Case | - |------------|----------| - | `"str"` | Simple text responses | - | `"json"` or `"dict"` | Structured data | - | `"list"` | Array-like data | - | `"yaml"` | Configuration-like data | - | `"xml"` | XML-based integrations | - | `"basemodel"` | Type-safe data handling | - -2. Handle the output appropriately in your application: - - - Parse JSON/YAML responses when needed - - - Validate structured data - - - Handle potential formatting errors +The vision-enabled agents support various image formats including: -3. Consider using `try-except` blocks when working with structured output types to handle potential parsing errors. +| Format | Description | +|--------|-------------| +| JPEG/JPG | Standard image format with lossy compression | +| PNG | Lossless format supporting transparency | +| GIF | Animated format (only first frame used) | +| WebP | Modern format with both lossy and lossless compression | +### Best Practices for Vision Tasks -This comprehensive guide shows how to use all available output types in the Swarms framework, making it easier to integrate agent responses into your applications in the most suitable format for your needs. \ No newline at end of file +| Best Practice | Description | +|--------------|-------------| +| Image Quality | Ensure images are clear and well-lit for optimal analysis | +| Image Size | Keep images under 20MB and in supported formats | +| Task Specificity | Provide clear, specific instructions for image analysis | +| Model Selection | Use vision-capable models (e.g., GPT-4V) for image tasks | \ No newline at end of file diff --git a/docs/swarms/examples/agents_as_tools.md b/docs/swarms/examples/agents_as_tools.md new file mode 100644 index 00000000..f62521fe --- /dev/null +++ b/docs/swarms/examples/agents_as_tools.md @@ -0,0 +1,586 @@ +# Agents as Tools Tutorial + +This tutorial demonstrates how to create a powerful multi-agent system where agents can delegate tasks to specialized sub-agents. This pattern is particularly useful for complex tasks that require different types of expertise or capabilities. + +## Overview + +The Agents as Tools pattern allows you to: + +- Create specialized agents with specific capabilities + +- Have agents delegate tasks to other agents + +- Chain multiple agents together for complex workflows + +- Maintain separation of concerns between different agent roles + +## Prerequisites + +- Python 3.8 or higher + +- Basic understanding of Python programming + +- Familiarity with async/await concepts (optional) + + +## Installation + +Install the swarms package using pip: + +```bash +pip install -U swarms +``` + +## Basic Setup + +1. First, set up your environment variables: + +```python +WORKSPACE_DIR="agent_workspace" +ANTHROPIC_API_KEY="" +``` + +## Step-by-Step Guide + +1. **Define Your Tools** + + - Create functions that will serve as tools for your agents + + - Add proper type hints and detailed docstrings + + - Include error handling and logging + + - Example: + + ```python + def my_tool(param: str) -> str: + """Detailed description of what the tool does. + + Args: + param: Description of the parameter + + Returns: + Description of the return value + """ + # Tool implementation + return result + ``` + +2. **Create Specialized Agents** + + - Define agents with specific roles and capabilities + + - Configure each agent with appropriate settings + + - Assign relevant tools to each agent + + ```python + specialized_agent = Agent( + agent_name="Specialist", + agent_description="Expert in specific domain", + system_prompt="Detailed instructions for the agent", + tools=[tool1, tool2] + ) + ``` + +3. **Set Up the Director Agent** + + - Create a high-level agent that coordinates other agents + + - Give it access to specialized agents as tools + + - Define clear delegation rules + + ```python + director = Agent( + agent_name="Director", + agent_description="Coordinates other agents", + tools=[specialized_agent.run] + ) + ``` + +4. **Execute Multi-Agent Workflows** + + - Start with the director agent + + - Let it delegate tasks as needed + + - Handle responses and chain results + + ```python + result = director.run("Your high-level task description") + ``` + + + +## Code + +```python +import json +import requests +from swarms import Agent + +def create_python_file(code: str, filename: str) -> str: + """Create a Python file with the given code and execute it using Python 3.12. + + This function takes a string containing Python code, writes it to a file, and executes it + using Python 3.12 via subprocess. The file will be created in the current working directory. + If a file with the same name already exists, it will be overwritten. + + Args: + code (str): The Python code to write to the file. This should be valid Python 3.12 code. + filename (str): The name of the file to create and execute. + + Returns: + str: A detailed message indicating the file was created and the execution result. + + Raises: + IOError: If there are any issues writing to the file. + subprocess.SubprocessError: If there are any issues executing the file. + + Example: + >>> code = "print('Hello, World!')" + >>> result = create_python_file(code, "test.py") + >>> print(result) + 'Python file created successfully. Execution result: Hello, World!' + """ + import subprocess + import os + import datetime + + # Get current timestamp for logging + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Write the code to file + with open(filename, "w") as f: + f.write(code) + + # Get file size and permissions + file_stats = os.stat(filename) + file_size = file_stats.st_size + file_permissions = oct(file_stats.st_mode)[-3:] + + # Execute the file using Python 3.12 and capture output + try: + result = subprocess.run( + ["python3.12", filename], + capture_output=True, + text=True, + check=True + ) + + # Create detailed response + response = f""" +File Creation Details: +---------------------- +Timestamp: {timestamp} +Filename: {filename} +File Size: {file_size} bytes +File Permissions: {file_permissions} +Location: {os.path.abspath(filename)} + +Execution Details: +----------------- +Exit Code: {result.returncode} +Execution Time: {result.returncode} seconds + +Output: +------- +{result.stdout} + +Error Output (if any): +-------------------- +{result.stderr} +""" + return response + except subprocess.CalledProcessError as e: + error_response = f""" +File Creation Details: +---------------------- +Timestamp: {timestamp} +Filename: {filename} +File Size: {file_size} bytes +File Permissions: {file_permissions} +Location: {os.path.abspath(filename)} + +Execution Error: +--------------- +Exit Code: {e.returncode} +Error Message: {e.stderr} + +Command Output: +------------- +{e.stdout} +""" + return error_response + + + + + + +def update_python_file(code: str, filename: str) -> str: + """Update an existing Python file with new code and execute it using Python 3.12. + + This function takes a string containing Python code and updates an existing Python file. + If the file doesn't exist, it will be created. The file will be executed using Python 3.12. + + Args: + code (str): The Python code to write to the file. This should be valid Python 3.12 code. + filename (str): The name of the file to update and execute. + + Returns: + str: A detailed message indicating the file was updated and the execution result. + + Raises: + IOError: If there are any issues writing to the file. + subprocess.SubprocessError: If there are any issues executing the file. + + Example: + >>> code = "print('Updated code!')" + >>> result = update_python_file(code, "my_script.py") + >>> print(result) + 'Python file updated successfully. Execution result: Updated code!' + """ + import subprocess + import os + import datetime + + # Get current timestamp for logging + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Check if file exists and get its stats + file_exists = os.path.exists(filename) + if file_exists: + old_stats = os.stat(filename) + old_size = old_stats.st_size + old_permissions = oct(old_stats.st_mode)[-3:] + + # Write the code to file + with open(filename, "w") as f: + f.write(code) + + # Get new file stats + new_stats = os.stat(filename) + new_size = new_stats.st_size + new_permissions = oct(new_stats.st_mode)[-3:] + + # Execute the file using Python 3.12 and capture output + try: + result = subprocess.run( + ["python3.12", filename], + capture_output=True, + text=True, + check=True + ) + + # Create detailed response + response = f""" +File Update Details: +------------------- +Timestamp: {timestamp} +Filename: {filename} +Previous Status: {'Existed' if file_exists else 'Did not exist'} +Previous Size: {old_size if file_exists else 'N/A'} bytes +Previous Permissions: {old_permissions if file_exists else 'N/A'} +New Size: {new_size} bytes +New Permissions: {new_permissions} +Location: {os.path.abspath(filename)} + +Execution Details: +----------------- +Exit Code: {result.returncode} +Execution Time: {result.returncode} seconds + +Output: +------- +{result.stdout} + +Error Output (if any): +-------------------- +{result.stderr} +""" + return response + except subprocess.CalledProcessError as e: + error_response = f""" + File Update Details: + ------------------- + Timestamp: {timestamp} + Filename: {filename} + Previous Status: {'Existed' if file_exists else 'Did not exist'} + Previous Size: {old_size if file_exists else 'N/A'} bytes + Previous Permissions: {old_permissions if file_exists else 'N/A'} + New Size: {new_size} bytes + New Permissions: {new_permissions} + Location: {os.path.abspath(filename)} + + Execution Error: + --------------- + Exit Code: {e.returncode} + Error Message: {e.stderr} + + Command Output: + ------------- + {e.stdout} + """ + return error_response + + +def run_quant_trading_agent(task: str) -> str: + """Run a quantitative trading agent to analyze and execute trading strategies. + + This function initializes and runs a specialized quantitative trading agent that can: + - Develop and backtest trading strategies + - Analyze market data for alpha opportunities + - Implement risk management frameworks + - Optimize portfolio allocations + - Conduct quantitative research + - Monitor market microstructure + - Evaluate trading system performance + + Args: + task (str): The specific trading task or analysis to perform + + Returns: + str: The agent's response or analysis results + + Example: + >>> result = run_quant_trading_agent("Analyze SPY ETF for mean reversion opportunities") + >>> print(result) + """ + # Initialize the agent + agent = Agent( + agent_name="Quantitative-Trading-Agent", + agent_description="Advanced quantitative trading and algorithmic analysis agent", + system_prompt="""You are an expert quantitative trading agent with deep expertise in: + - Algorithmic trading strategies and implementation + - Statistical arbitrage and market making + - Risk management and portfolio optimization + - High-frequency trading systems + - Market microstructure analysis + - Quantitative research methodologies + - Financial mathematics and stochastic processes + - Machine learning applications in trading + + Your core responsibilities include: + 1. Developing and backtesting trading strategies + 2. Analyzing market data and identifying alpha opportunities + 3. Implementing risk management frameworks + 4. Optimizing portfolio allocations + 5. Conducting quantitative research + 6. Monitoring market microstructure + 7. Evaluating trading system performance + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + max_loops=2, + model_name="claude-3-5-sonnet-20240620", + tools=[create_python_file, update_python_file, backtest_summary], + ) + + out = agent.run(task) + return out + + + +def backtest_summary(report: str) -> str: + """Generate a summary of a backtest report, but only if the backtest was profitable. + + This function should only be used when the backtest results show a positive return. + Using this function for unprofitable backtests may lead to misleading conclusions. + + Args: + report (str): The backtest report containing performance metrics + + Returns: + str: A formatted summary of the backtest report + + Example: + >>> result = backtest_summary("Total Return: +15.2%, Sharpe: 1.8") + >>> print(result) + 'The backtest report is: Total Return: +15.2%, Sharpe: 1.8' + """ + return f"The backtest report is: {report}" + +def get_coin_price(coin_id: str, vs_currency: str) -> str: + """ + Get the current price of a specific cryptocurrency. + + Args: + coin_id (str): The CoinGecko ID of the cryptocurrency (e.g., 'bitcoin', 'ethereum') + vs_currency (str, optional): The target currency. Defaults to "usd". + + Returns: + str: JSON formatted string containing the coin's current price and market data + + Raises: + requests.RequestException: If the API request fails + + Example: + >>> result = get_coin_price("bitcoin") + >>> print(result) + {"bitcoin": {"usd": 45000, "usd_market_cap": 850000000000, ...}} + """ + try: + url = "https://api.coingecko.com/api/v3/simple/price" + params = { + "ids": coin_id, + "vs_currencies": vs_currency, + "include_market_cap": True, + "include_24hr_vol": True, + "include_24hr_change": True, + "include_last_updated_at": True, + } + + response = requests.get(url, params=params, timeout=10) + response.raise_for_status() + + data = response.json() + return json.dumps(data, indent=2) + + except requests.RequestException as e: + return json.dumps( + { + "error": f"Failed to fetch price for {coin_id}: {str(e)}" + } + ) + except Exception as e: + return json.dumps({"error": f"Unexpected error: {str(e)}"}) + + + +def run_crypto_quant_agent(task: str) -> str: + """ + Run a crypto quantitative trading agent with specialized tools for cryptocurrency market analysis. + + This function initializes and runs a quantitative trading agent specifically designed for + cryptocurrency markets. The agent is equipped with tools for price fetching and can perform + various quantitative analyses including algorithmic trading strategy development, risk management, + and market microstructure analysis. + + Args: + task (str): The task or query to be processed by the crypto quant agent. + + Returns: + str: The agent's response to the given task. + + Example: + >>> response = run_crypto_quant_agent("Analyze the current market conditions for Bitcoin") + >>> print(response) + "Based on current market analysis..." + """ + # Initialize the agent with expanded tools + quant_agent = Agent( + agent_name="Crypto-Quant-Agent", + agent_description="Advanced quantitative trading agent specializing in cryptocurrency markets with algorithmic analysis capabilities", + system_prompt="""You are an expert quantitative trading agent specializing in cryptocurrency markets. Your capabilities include: + - Algorithmic trading strategy development and backtesting + - Statistical arbitrage and market making for crypto assets + - Risk management and portfolio optimization for digital assets + - High-frequency trading system design for crypto markets + - Market microstructure analysis of crypto exchanges + - Quantitative research methodologies for crypto assets + - Financial mathematics and stochastic processes + - Machine learning applications in crypto trading + + You maintain strict adherence to: + - Mathematical rigor in all analyses + - Statistical significance in strategy development + - Risk-adjusted return optimization + - Market impact minimization + - Regulatory compliance + - Transaction cost analysis + - Performance attribution + + You communicate in precise, technical terms while maintaining clarity for stakeholders.""", + max_loops=1, + max_tokens=4096, + model_name="gpt-4.1-mini", + dynamic_temperature_enabled=True, + output_type="final", + tools=[ + get_coin_price, + ], + ) + + return quant_agent.run(task) + +# Initialize the agent +agent = Agent( + agent_name="Director-Agent", + agent_description="Strategic director and project management agent", + system_prompt="""You are an expert Director Agent with comprehensive capabilities in: + - Strategic planning and decision making + - Project management and coordination + - Resource allocation and optimization + - Team leadership and delegation + - Risk assessment and mitigation + - Stakeholder management + - Process optimization + - Quality assurance + + Your core responsibilities include: + 1. Developing and executing strategic initiatives + 2. Coordinating cross-functional projects + 3. Managing resource allocation + 4. Setting and tracking KPIs + 5. Ensuring project deliverables + 6. Risk management and mitigation + 7. Stakeholder communication + + You maintain strict adherence to: + - Best practices in project management + - Data-driven decision making + - Clear communication protocols + - Quality standards + - Timeline management + - Budget constraints + - Regulatory compliance + + You communicate with clarity and authority while maintaining professionalism and ensuring all stakeholders are aligned.""", + max_loops=1, + model_name="gpt-4o-mini", + output_type="final", + interactive=False, + tools=[run_quant_trading_agent], +) + +out = agent.run(""" + Please call the quantitative trading agent to generate Python code for an Bitcoin backtest using the CoinGecko API. + Provide a comprehensive description of the backtest methodology and trading strategy. + Consider the API limitations of CoinGecko and utilize only free, open-source libraries that don't require API keys. Use the requests library to fetch the data. Create a specialized strategy for the backtest focused on the orderbook and other data for price action. + The goal is to create a backtest that can predict the price action of the coin based on the orderbook and other data. + Maximize the profit of the backtest. Please use the OKX price API for the orderbook and other data. Be very explicit in your implementation. + Be very precise with the instructions you give to the agent and tell it to a 400 lines of good code. +""") +print(out) +``` + +## Best Practices + +| Category | Best Practice | Description | +|----------|---------------|-------------| +| **Tool Design** | Single Purpose | Keep tools focused and single-purpose | +| | Clear Naming | Use clear, descriptive names | +| | Error Handling | Include comprehensive error handling | +| | Documentation | Add detailed documentation | +| **Agent Configuration** | Clear Role | Give each agent a clear, specific role | +| | System Prompts | Provide detailed system prompts | +| | Model Parameters | Configure appropriate model and parameters | +| | Resource Limits | Set reasonable limits on iterations and tokens | +| **Error Handling** | Multi-level | Implement proper error handling at each level | +| | Logging | Include logging for debugging | +| | API Management | Handle API rate limits and timeouts | +| | Fallbacks | Provide fallback options when possible | +| **Performance Optimization** | Async Operations | Use async operations where appropriate | +| | Caching | Implement caching when possible | +| | Token Usage | Monitor and optimize token usage | +| | Batch Processing | Consider batch operations for efficiency | diff --git a/docs/swarms/examples/vision_processing.md b/docs/swarms/examples/vision_processing.md new file mode 100644 index 00000000..dd5bc481 --- /dev/null +++ b/docs/swarms/examples/vision_processing.md @@ -0,0 +1,150 @@ +# Vision Processing Examples + +This example demonstrates how to use vision-enabled agents in Swarms to analyze images and process visual information. You'll learn how to work with both OpenAI and Anthropic vision models for various use cases. + +## Prerequisites + +- Python 3.7+ + +- OpenAI API key (for GPT-4V) + +- Anthropic API key (for Claude 3) + +- Swarms library + +## Installation + +```bash +pip3 install -U swarms +``` + +## Environment Variables + +```plaintext +WORKSPACE_DIR="agent_workspace" +OPENAI_API_KEY="" # Required for GPT-4V +ANTHROPIC_API_KEY="" # Required for Claude 3 +``` + +## Working with Images + +### Supported Image Formats + +Vision-enabled agents support various image formats: + +| Format | Description | +|--------|-------------| +| JPEG/JPG | Standard image format with lossy compression | +| PNG | Lossless format supporting transparency | +| GIF | Animated format (only first frame used) | +| WebP | Modern format with both lossy and lossless compression | + +### Image Guidelines + +- Maximum file size: 20MB +- Recommended resolution: At least 512x512 pixels +- Image should be clear and well-lit +- Avoid heavily compressed or blurry images + +## Examples + +### 1. Quality Control with GPT-4V + +```python +from swarms.structs import Agent +from swarms.prompts.logistics import Quality_Control_Agent_Prompt + +# Load your image +factory_image = "path/to/your/image.jpg" # Local file path +# Or use a URL +# factory_image = "https://example.com/image.jpg" + +# Initialize quality control agent with GPT-4V +quality_control_agent = Agent( + agent_name="Quality Control Agent", + agent_description="A quality control agent that analyzes images and provides detailed quality reports.", + model_name="gpt-4.1-mini", + system_prompt=Quality_Control_Agent_Prompt, + multi_modal=True, + max_loops=1 +) + +# Run the analysis +response = quality_control_agent.run( + task="Analyze this image and provide a detailed quality control report", + img=factory_image +) + +print(response) +``` + +### 2. Visual Analysis with Claude 3 + +```python +from swarms.structs import Agent +from swarms.prompts.logistics import Visual_Analysis_Prompt + +# Load your image +product_image = "path/to/your/product.jpg" + +# Initialize visual analysis agent with Claude 3 +visual_analyst = Agent( + agent_name="Visual Analyst", + agent_description="An agent that performs detailed visual analysis of products and scenes.", + model_name="anthropic/claude-3-opus-20240229", + system_prompt=Visual_Analysis_Prompt, + multi_modal=True, + max_loops=1 +) + +# Run the analysis +response = visual_analyst.run( + task="Provide a comprehensive analysis of this product image", + img=product_image +) + +print(response) +``` + +### 3. Image Batch Processing + +```python +from swarms.structs import Agent +import os + +def process_image_batch(image_folder, agent): + """Process multiple images in a folder""" + results = [] + for image_file in os.listdir(image_folder): + if image_file.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')): + image_path = os.path.join(image_folder, image_file) + response = agent.run( + task="Analyze this image", + img=image_path + ) + results.append((image_file, response)) + return results + +# Example usage +image_folder = "path/to/image/folder" +batch_results = process_image_batch(image_folder, visual_analyst) +``` + +## Best Practices + +| Category | Best Practice | Description | +|----------|---------------|-------------| +| Image Preparation | Format Support | Ensure images are in supported formats (JPEG, PNG, GIF, WebP) | +| | Size & Quality | Optimize image size and quality for better processing | +| | Image Quality | Use clear, well-lit images for accurate analysis | +| Model Selection | GPT-4V Usage | Use for general vision tasks and detailed analysis | +| | Claude 3 Usage | Use for complex reasoning and longer outputs | +| | Batch Processing | Consider batch processing for multiple images | +| Error Handling | Path Validation | Always validate image paths before processing | +| | API Error Handling | Implement proper error handling for API calls | +| | Rate Monitoring | Monitor API rate limits and token usage | +| Performance Optimization | Result Caching | Cache results when processing the same images | +| | Batch Processing | Use batch processing for multiple images | +| | Parallel Processing | Implement parallel processing for large datasets | + + diff --git a/docs/swarms/structs/image_batch_agent.md b/docs/swarms/structs/image_batch_agent.md new file mode 100644 index 00000000..987c7c46 --- /dev/null +++ b/docs/swarms/structs/image_batch_agent.md @@ -0,0 +1,271 @@ +# ImageAgentBatchProcessor Documentation + +## Overview + +The `ImageAgentBatchProcessor` is a high-performance parallel image processing system designed for running AI agents on multiple images concurrently. It provides robust error handling, logging, and flexible configuration options. + +## Installation + +```bash +pip install swarms +``` + +## Class Arguments + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| agents | Union[Agent, List[Agent], Callable, List[Callable]] | Required | Single agent or list of agents to process images | +| max_workers | int | None | Maximum number of parallel workers (defaults to 95% of CPU cores) | +| supported_formats | List[str] | ['.jpg', '.jpeg', '.png'] | List of supported image file extensions | + +## Methods + +### run() + +**Description**: Main method for processing multiple images in parallel with configured agents. Can handle single images, multiple images, or entire directories. + +**Arguments**: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| image_paths | Union[str, List[str], Path] | Yes | Single image path, list of paths, or directory path | +| tasks | Union[str, List[str]] | Yes | Single task or list of tasks to perform on each image | + +**Returns**: List[Dict[str, Any]] - List of processing results for each image + +**Example**: + +```python +from swarms import Agent +from swarms.structs import ImageAgentBatchProcessor +from pathlib import Path + +# Initialize agent and processor +agent = Agent(api_key="your-api-key", model="gpt-4-vision") +processor = ImageAgentBatchProcessor(agents=agent) + +# Example 1: Process single image +results = processor.run( + image_paths="path/to/image.jpg", + tasks="Describe this image" +) + +# Example 2: Process multiple images +results = processor.run( + image_paths=["image1.jpg", "image2.jpg"], + tasks=["Describe objects", "Identify colors"] +) + +# Example 3: Process directory +results = processor.run( + image_paths=Path("./images"), + tasks="Analyze image content" +) +``` + +### _validate_image_path() + +**Description**: Internal method that validates if an image path exists and has a supported format. + +**Arguments**: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| image_path | Union[str, Path] | Yes | Path to the image file to validate | + +**Returns**: Path - Validated Path object + +**Example**: +```python + +from swarms.structs import ImageAgentBatchProcessor, ImageProcessingError +from pathlib import Path + +processor = ImageAgentBatchProcessor(agents=agent) + +try: + validated_path = processor._validate_image_path("image.jpg") + print(f"Valid image path: {validated_path}") +except ImageProcessingError as e: + print(f"Invalid image path: {e}") +``` + +### _process_single_image() + +**Description**: Internal method that processes a single image with one agent and one or more tasks. + +**Arguments**: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| image_path | Path | Yes | Path to the image to process | +| tasks | Union[str, List[str]] | Yes | Tasks to perform on the image | +| agent | Agent | Yes | Agent to use for processing | + +**Returns**: Dict[str, Any] - Processing results for the image + +**Example**: + +```python +from swarms import Agent +from swarms.structs import ImageAgentBatchProcessor +from pathlib import Path + +agent = Agent(api_key="your-api-key", model="gpt-4-vision") +processor = ImageAgentBatchProcessor(agents=agent) + +try: + result = processor._process_single_image( + image_path=Path("image.jpg"), + tasks=["Describe image", "Identify objects"], + agent=agent + ) + print(f"Processing results: {result}") +except Exception as e: + print(f"Processing failed: {e}") +``` + +### __call__() + +**Description**: Makes the ImageAgentBatchProcessor callable like a function. Redirects to the run() method. + +**Arguments**: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| *args | Any | No | Variable length argument list passed to run() | +| **kwargs | Any | No | Keyword arguments passed to run() | + +**Returns**: List[Dict[str, Any]] - Same as run() method + +**Example**: + +```python +from swarms import Agent +from swarms.structs import ImageAgentBatchProcessor + +# Initialize +agent = Agent(api_key="your-api-key", model="gpt-4-vision") +processor = ImageAgentBatchProcessor(agents=agent) + +# Using __call__ +results = processor( + image_paths=["image1.jpg", "image2.jpg"], + tasks="Describe the image" +) + +# This is equivalent to: +results = processor.run( + image_paths=["image1.jpg", "image2.jpg"], + tasks="Describe the image" +) +``` + +## Return Format + +The processor returns a list of dictionaries with the following structure: + +```python +{ + "image_path": str, # Path to the processed image + "results": { # Results for each task + "task_name": result, # Task-specific results + }, + "processing_time": float # Processing time in seconds +} +``` + +## Complete Usage Examples + +### 1. Basic Usage with Single Agent + +```python +from swarms import Agent +from swarms.structs import ImageAgentBatchProcessor + +# Initialize an agent +agent = Agent( + api_key="your-api-key", + model="gpt-4-vision" +) + +# Create processor +processor = ImageAgentBatchProcessor(agents=agent) + +# Process single image +results = processor.run( + image_paths="path/to/image.jpg", + tasks="Describe this image in detail" +) +``` + +### 2. Processing Multiple Images with Multiple Tasks + +```python +# Initialize with multiple agents +agent1 = Agent(api_key="key1", model="gpt-4-vision") +agent2 = Agent(api_key="key2", model="claude-3") + +processor = ImageAgentBatchProcessor( + agents=[agent1, agent2], + supported_formats=['.jpg', '.png', '.webp'] +) + +# Define multiple tasks +tasks = [ + "Describe the main objects in the image", + "What is the dominant color?", + "Identify any text in the image" +] + +# Process a directory of images +results = processor.run( + image_paths="path/to/image/directory", + tasks=tasks +) + +# Process results +for result in results: + print(f"Image: {result['image_path']}") + for task, output in result['results'].items(): + print(f"Task: {task}") + print(f"Result: {output}") + print(f"Processing time: {result['processing_time']:.2f} seconds") +``` + +### 3. Custom Error Handling + +```python +from swarms.structs import ImageAgentBatchProcessor, ImageProcessingError + +try: + processor = ImageAgentBatchProcessor(agents=agent) + results = processor.run( + image_paths=["image1.jpg", "image2.png", "invalid.txt"], + tasks="Analyze the image" + ) +except ImageProcessingError as e: + print(f"Image processing failed: {e}") +except InvalidAgentError as e: + print(f"Agent configuration error: {e}") +``` + +## Best Practices + +| Best Practice | Description | +|--------------|-------------| +| Resource Management | • The processor automatically uses 95% of available CPU cores
• For memory-intensive operations, consider reducing `max_workers` | +| Error Handling | • Always wrap processor calls in try-except blocks
• Check the results for any error keys | +| Task Design | • Keep tasks focused and specific
• Group related tasks together for efficiency | +| Performance Optimization | • Process images in batches for better throughput
• Use multiple agents for different types of analysis | + +## Limitations + +| Limitation | Description | +|------------|-------------| +| File Format Support | Only supports image file formats specified in `supported_formats` | +| Agent Requirements | Requires valid agent configurations | +| Resource Scaling | Memory usage scales with number of concurrent processes | + + +This documentation provides a comprehensive guide to using the `ImageAgentBatchProcessor`. The class is designed to be both powerful and flexible, allowing for various use cases from simple image analysis to complex multi-agent processing pipelines. diff --git a/docs/swarms/structs/interactive_groupchat.md b/docs/swarms/structs/interactive_groupchat.md new file mode 100644 index 00000000..c4705aee --- /dev/null +++ b/docs/swarms/structs/interactive_groupchat.md @@ -0,0 +1,206 @@ +# InteractiveGroupChat Documentation + +The InteractiveGroupChat is a sophisticated multi-agent system that enables interactive conversations between users and AI agents using @mentions. This system allows users to direct messages to specific agents and facilitates collaborative responses when multiple agents are mentioned. + +## Features + +- **@mentions Support**: Direct messages to specific agents using @agent_name syntax +- **Multi-Agent Collaboration**: Multiple mentioned agents can see and respond to each other's messages +- **Callable Function Support**: Supports both Agent instances and callable functions as chat participants +- **Comprehensive Error Handling**: Custom error classes for different scenarios +- **Conversation History**: Maintains a complete history of the conversation +- **Flexible Output Formatting**: Configurable output format for conversation history + +## Installation + +```bash +pip install swarms +``` + +## Basic Usage + +```python +from swarms import Agent, InteractiveGroupChat + +# Initialize agents +financial_advisor = Agent( + agent_name="FinancialAdvisor", + system_prompt="You are a financial advisor specializing in investment strategies.", + model_name="gpt-4o-mini" +) + +tax_expert = Agent( + agent_name="TaxExpert", + system_prompt="You are a tax expert providing tax-related guidance.", + model_name="gpt-4o-mini" +) + +# Create the interactive group chat +chat = InteractiveGroupChat( + name="Financial Team", + description="Financial advisory team", + agents=[financial_advisor, tax_expert] +) + +# Send a message to a single agent +response = chat.run("@FinancialAdvisor what are good investment strategies?") + +# Send a message to multiple agents +response = chat.run("@FinancialAdvisor and @TaxExpert, how can I optimize my investment taxes?") +``` + +## Advanced Usage + +### Using Callable Functions + +```python +def custom_agent(context: str) -> str: + """A custom callable function that can act as an agent""" + return "Custom response based on: " + context + +# Add both Agent instances and callable functions +agents = [financial_advisor, tax_expert, custom_agent] +chat = InteractiveGroupChat(agents=agents) + +# Interact with the callable function +response = chat.run("@custom_agent what do you think?") +``` + +## Configuration Options + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| name | str | "InteractiveGroupChat" | Name of the group chat | +| description | str | "An interactive group chat..." | Description of the chat's purpose | +| agents | List[Union[Agent, Callable]] | [] | List of agents or callable functions | +| max_loops | int | 1 | Maximum conversation turns | +| output_type | str | "string" | Output format type | + +## Error Handling + +The system includes several custom error classes: + +- **InteractiveGroupChatError**: Base exception class +- **AgentNotFoundError**: Raised when a mentioned agent doesn't exist +- **NoMentionedAgentsError**: Raised when no agents are mentioned +- **InvalidMessageFormatError**: Raised for invalid message formats + +Example error handling: + +```python +try: + response = chat.run("@NonExistentAgent hello!") +except AgentNotFoundError as e: + print(f"Agent not found: {e}") +except NoMentionedAgentsError as e: + print(f"No agents mentioned: {e}") +``` + +## Best Practices + +1. **Agent Naming**: Use clear, unique names for agents to avoid confusion +2. **Message Format**: Always use @mentions to direct messages to specific agents +3. **Error Handling**: Implement proper error handling for various scenarios +4. **Context Management**: Be aware that agents can see the full conversation history +5. **Resource Management**: Consider the number of agents and message length to optimize performance + +## Logging + +The system uses loguru for comprehensive logging: + +```python +from loguru import logger + +# Configure logging +logger.add("groupchat.log", rotation="500 MB") + +# Logs will include: +# - Agent responses +# - Error messages +# - System events +``` + +## Examples + +### Basic Interaction + +```python +# Single agent interaction +response = chat.run("@FinancialAdvisor what are the best investment strategies for 2024?") + +# Multiple agent collaboration +response = chat.run("@TaxExpert and @InvestmentAnalyst, how can we optimize investment taxes?") +``` + +### Error Handling + +```python +try: + # Invalid agent mention + response = chat.run("@NonExistentAgent hello!") +except AgentNotFoundError as e: + print(f"Error: {e}") + +try: + # No mentions + response = chat.run("Hello everyone!") +except NoMentionedAgentsError as e: + print(f"Error: {e}") +``` + +### Custom Callable Integration + +```python +def market_analyzer(context: str) -> str: + """Custom market analysis function""" + return "Market analysis based on: " + context + +agents = [financial_advisor, tax_expert, market_analyzer] +chat = InteractiveGroupChat(agents=agents) + +response = chat.run("@market_analyzer what's your analysis of the current market?") +``` + +## API Reference + +### InteractiveGroupChat Class + +```python +class InteractiveGroupChat: + def __init__( + self, + name: str = "InteractiveGroupChat", + description: str = "An interactive group chat for multiple agents", + agents: List[Union[Agent, Callable]] = [], + max_loops: int = 1, + output_type: str = "string", + ): + """Initialize the interactive group chat.""" + + def run(self, message: str) -> str: + """Process a message and get responses from mentioned agents.""" +``` + +### Custom Error Classes + +```python +class InteractiveGroupChatError(Exception): + """Base exception class for InteractiveGroupChat errors""" + +class AgentNotFoundError(InteractiveGroupChatError): + """Raised when a mentioned agent is not found""" + +class NoMentionedAgentsError(InteractiveGroupChatError): + """Raised when no agents are mentioned""" + +class InvalidMessageFormatError(InteractiveGroupChatError): + """Raised when the message format is invalid""" +``` + +## Contributing + +Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository. + +## License + +This project is licensed under the Apache License - see the LICENSE file for details. \ No newline at end of file diff --git a/example.py b/example.py index a99560dc..35ef36a4 100644 --- a/example.py +++ b/example.py @@ -1,6 +1,5 @@ import time from swarms import Agent -from swarms.schemas.conversation_schema import ConversationSchema # Initialize the agent agent = Agent( @@ -38,12 +37,8 @@ agent = Agent( max_loops=1, model_name="gpt-4o-mini", dynamic_temperature_enabled=True, - output_type="json", + output_type="all", safety_prompt_on=True, - conversation_schema=ConversationSchema( - time_enabled=True, - message_id_on=True, - ), ) out = agent.run("What are the best top 3 etfs for gold coverage?") diff --git a/examples/single_agent/vision_examples/anthropic_vision_test.py b/examples/single_agent/vision_examples/anthropic_vision_test.py new file mode 100644 index 00000000..6d24faeb --- /dev/null +++ b/examples/single_agent/vision_examples/anthropic_vision_test.py @@ -0,0 +1,26 @@ +from swarms.structs import Agent +from swarms.prompts.logistics import ( + Quality_Control_Agent_Prompt, +) + +# Image for analysis +factory_image = "image.jpg" + + +# Quality control agent +quality_control_agent = Agent( + agent_name="Quality Control Agent", + agent_description="A quality control agent that analyzes images and provides a detailed report on the quality of the product in the image.", + model_name="anthropic/claude-3-opus-20240229", + system_prompt=Quality_Control_Agent_Prompt, + multi_modal=True, + max_loops=1, + output_type="str-all-except-first", +) + +response = quality_control_agent.run( + task="Create a comprehensive report on the factory image and it's status", + img=factory_image, +) + +print(response) diff --git a/examples/single_agent/vision_examples/image.jpg b/examples/single_agent/vision_examples/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb7ae8190c67b7324b0fa485d233dc5185eb1902 GIT binary patch literal 237149 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%0DRywMQUDSeVWqMtu>CyXa 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�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 Nt!eIO{crcb|Jl^5KHLBR literal 0 HcmV?d00001 diff --git a/examples/single_agent/vision_examples/image_batch_example.py b/examples/single_agent/vision_examples/image_batch_example.py new file mode 100644 index 00000000..7ccd8c30 --- /dev/null +++ b/examples/single_agent/vision_examples/image_batch_example.py @@ -0,0 +1,32 @@ +from swarms import Agent +from swarms.structs.image_batch_processor import ( + ImageAgentBatchProcessor, +) +from pathlib import Path + +# Initialize agent and processor + +# Quality control agent +agent = Agent( + model_name="gpt-4.1-mini", + max_loops=1, +) + +# Create processor +processor = ImageAgentBatchProcessor(agents=agent) + +# Example 1: Process single image +results = processor.run( + image_paths="path/to/image.jpg", tasks="Describe this image" +) + +# Example 2: Process multiple images +results = processor.run( + image_paths=["image1.jpg", "image2.jpg"], + tasks=["Describe objects", "Identify colors"], +) + +# Example 3: Process directory +results = processor.run( + image_paths=Path("./images"), tasks="Analyze image content" +) diff --git a/examples/single_agent/vision_examples/vision_and_tools.py b/examples/single_agent/vision_examples/vision_and_tools.py new file mode 100644 index 00000000..7b0da0b5 --- /dev/null +++ b/examples/single_agent/vision_examples/vision_and_tools.py @@ -0,0 +1,67 @@ +import json +from swarms.structs import Agent +from swarms.prompts.logistics import ( + Quality_Control_Agent_Prompt, +) +from swarms import BaseTool +import litellm + +litellm._turn_on_debug() + +# Image for analysis +factory_image = "image.jpg" + + +def security_analysis(danger_level: str = None) -> str: + """ + Analyzes the security danger level and returns an appropriate response. + + Args: + danger_level (str, optional): The level of danger to analyze. + Can be "low", "medium", "high", or None. Defaults to None. + + Returns: + str: A string describing the danger level assessment. + - "No danger level provided" if danger_level is None + - "No danger" if danger_level is "low" + - "Medium danger" if danger_level is "medium" + - "High danger" if danger_level is "high" + - "Unknown danger level" for any other value + """ + if danger_level is None: + return "No danger level provided" + + if danger_level == "low": + return "No danger" + + if danger_level == "medium": + return "Medium danger" + + if danger_level == "high": + return "High danger" + + return "Unknown danger level" + + +schema = BaseTool().function_to_dict(security_analysis) +print(json.dumps(schema, indent=4)) + +# Quality control agent +quality_control_agent = Agent( + agent_name="Quality Control Agent", + agent_description="A quality control agent that analyzes images and provides a detailed report on the quality of the product in the image.", + model_name="anthropic/claude-3-opus-20240229", + system_prompt=Quality_Control_Agent_Prompt, + multi_modal=True, + max_loops=1, + output_type="str-all-except-first", + tools_list_dictionary=[schema], +) + + +response = quality_control_agent.run( + task="what is in the image?", + # img=factory_image, +) + +print(response) diff --git a/examples/single_agent/vision_examples/vision_test.py b/examples/single_agent/vision_examples/vision_test.py new file mode 100644 index 00000000..38525f37 --- /dev/null +++ b/examples/single_agent/vision_examples/vision_test.py @@ -0,0 +1,27 @@ +from swarms.structs import Agent +from swarms.prompts.logistics import ( + Quality_Control_Agent_Prompt, +) + +# Image for analysis +factory_image = "image.jpg" + + +# Quality control agent +quality_control_agent = Agent( + agent_name="Quality Control Agent", + agent_description="A quality control agent that analyzes images and provides a detailed report on the quality of the product in the image.", + model_name="gpt-4.1-mini", + system_prompt=Quality_Control_Agent_Prompt, + # multi_modal=True, + max_loops=1, + output_type="str-all-except-first", +) + + +response = quality_control_agent.run( + task="Create a comprehensive report on the image", + img=factory_image, +) + +print(response) diff --git a/interactive_groupchat_example.py b/interactive_groupchat_example.py new file mode 100644 index 00000000..bde71049 --- /dev/null +++ b/interactive_groupchat_example.py @@ -0,0 +1,51 @@ +from swarms import Agent +from swarms.structs.interactive_groupchat import InteractiveGroupChat + + +if __name__ == "__main__": + # Initialize agents + financial_advisor = Agent( + agent_name="FinancialAdvisor", + system_prompt="You are a financial advisor specializing in investment strategies and portfolio management.", + random_models_on=True, + output_type="final", + ) + + tax_expert = Agent( + agent_name="TaxExpert", + system_prompt="You are a tax expert who provides guidance on tax optimization and compliance.", + random_models_on=True, + output_type="final", + ) + + investment_analyst = Agent( + agent_name="InvestmentAnalyst", + system_prompt="You are an investment analyst focusing on market trends and investment opportunities.", + random_models_on=True, + output_type="final", + ) + + # Create list of agents including both Agent instances and callable + agents = [ + financial_advisor, + tax_expert, + investment_analyst, + ] + + # Initialize another chat instance in interactive mode + interactive_chat = InteractiveGroupChat( + name="Interactive Financial Advisory Team", + description="An interactive team of financial experts providing comprehensive financial advice", + agents=agents, + max_loops=1, + output_type="all", + interactive=True, + ) + + try: + # Start the interactive session + print("\nStarting interactive session...") + # interactive_chat.run("What is the best methodology to accumulate gold and silver commodities, what is the best long term strategy to accumulate them?") + interactive_chat.start_interactive_session() + except Exception as e: + print(f"An error occurred in interactive mode: {e}") diff --git a/examples/tools/multii_tool_use/many_tool_use_demo.py b/many_tool_use_demo.py similarity index 99% rename from examples/tools/multii_tool_use/many_tool_use_demo.py rename to many_tool_use_demo.py index f15369f0..6746dc61 100644 --- a/examples/tools/multii_tool_use/many_tool_use_demo.py +++ b/many_tool_use_demo.py @@ -423,7 +423,7 @@ agent = Agent( system_prompt="You are an advanced financial advisor agent with access to real-time cryptocurrency data from multiple sources including CoinGecko, Jupiter Protocol, and HTX. You can help users analyze market trends, check prices, find trading opportunities, perform swaps, and get detailed market insights. Always provide accurate, up-to-date information and explain market data in an easy-to-understand way.", max_loops=1, max_tokens=4096, - model_name="gpt-4o-mini", + model_name="gpt-4.1-mini", dynamic_temperature_enabled=True, output_type="all", tools=[ @@ -442,5 +442,7 @@ agent = Agent( ) # agent.run("Use defi stats to find the best defi project to invest in") -agent.run("Get the market sentiment for bitcoin") +agent.run( + "Get the market sentiment for bitcoin and fetch the price of ethereum" +) # Automatically executes any number and combination of tools you have uploaded to the tools parameter! diff --git a/swarms/communication/supabase_wrap.py b/swarms/communication/supabase_wrap.py index 321f084c..2a06cd34 100644 --- a/swarms/communication/supabase_wrap.py +++ b/swarms/communication/supabase_wrap.py @@ -223,10 +223,6 @@ class SupabaseConversation(BaseCommunication): """ # Try to create index as well - create_index_sql = f""" - CREATE INDEX IF NOT EXISTS idx_{self.table_name}_conversation_id - ON {self.table_name} (conversation_id); - """ # Attempt to create table using RPC function # Note: This requires a stored procedure to be created in Supabase @@ -322,7 +318,7 @@ class SupabaseConversation(BaseCommunication): if hasattr(self.client, "postgrest") and hasattr( self.client.postgrest, "rpc" ): - result = self.client.postgrest.rpc( + self.client.postgrest.rpc( "exec_sql", {"query": admin_sql} ).execute() if self.enable_logging: diff --git a/swarms/schemas/llm_agent_schema.py b/swarms/schemas/llm_agent_schema.py index ed310661..bf51f2bf 100644 --- a/swarms/schemas/llm_agent_schema.py +++ b/swarms/schemas/llm_agent_schema.py @@ -1,91 +1,109 @@ from pydantic import BaseModel, Field -from typing import List, Optional, Union, Any, Literal -from litellm.types import ( - ChatCompletionPredictionContentParam, -) +from typing import Optional +# from litellm.types import ( +# ChatCompletionPredictionContentParam, +# ) -class LLMCompletionRequest(BaseModel): - """Schema for LLM completion request parameters.""" - model: Optional[str] = Field( - default=None, - description="The name of the language model to use for text completion", - ) - temperature: Optional[float] = Field( - default=0.5, - description="Controls randomness of the output (0.0 to 1.0)", - ) - top_p: Optional[float] = Field( - default=None, - description="Controls diversity via nucleus sampling", - ) - n: Optional[int] = Field( - default=None, description="Number of completions to generate" - ) - stream: Optional[bool] = Field( - default=None, description="Whether to stream the response" - ) - stream_options: Optional[dict] = Field( - default=None, description="Options for streaming response" - ) - stop: Optional[Any] = Field( - default=None, - description="Up to 4 sequences where the API will stop generating", - ) - max_completion_tokens: Optional[int] = Field( - default=None, - description="Maximum tokens for completion including reasoning", - ) - max_tokens: Optional[int] = Field( - default=None, - description="Maximum tokens in generated completion", - ) - prediction: Optional[ChatCompletionPredictionContentParam] = ( - Field( - default=None, - description="Configuration for predicted output", - ) - ) - presence_penalty: Optional[float] = Field( - default=None, - description="Penalizes new tokens based on existence in text", - ) - frequency_penalty: Optional[float] = Field( - default=None, - description="Penalizes new tokens based on frequency in text", - ) - logit_bias: Optional[dict] = Field( - default=None, - description="Modifies probability of specific tokens", - ) - reasoning_effort: Optional[Literal["low", "medium", "high"]] = ( - Field( - default=None, - description="Level of reasoning effort for the model", - ) - ) - seed: Optional[int] = Field( - default=None, description="Random seed for reproducibility" - ) - tools: Optional[List] = Field( - default=None, - description="List of tools available to the model", - ) - tool_choice: Optional[Union[str, dict]] = Field( - default=None, description="Choice of tool to use" - ) - logprobs: Optional[bool] = Field( - default=None, - description="Whether to return log probabilities", - ) - top_logprobs: Optional[int] = Field( +# class LLMCompletionRequest(BaseModel): +# """Schema for LLM completion request parameters.""" + +# model: Optional[str] = Field( +# default=None, +# description="The name of the language model to use for text completion", +# ) +# temperature: Optional[float] = Field( +# default=0.5, +# description="Controls randomness of the output (0.0 to 1.0)", +# ) +# top_p: Optional[float] = Field( +# default=None, +# description="Controls diversity via nucleus sampling", +# ) +# n: Optional[int] = Field( +# default=None, description="Number of completions to generate" +# ) +# stream: Optional[bool] = Field( +# default=None, description="Whether to stream the response" +# ) +# stream_options: Optional[dict] = Field( +# default=None, description="Options for streaming response" +# ) +# stop: Optional[Any] = Field( +# default=None, +# description="Up to 4 sequences where the API will stop generating", +# ) +# max_completion_tokens: Optional[int] = Field( +# default=None, +# description="Maximum tokens for completion including reasoning", +# ) +# max_tokens: Optional[int] = Field( +# default=None, +# description="Maximum tokens in generated completion", +# ) +# prediction: Optional[ChatCompletionPredictionContentParam] = ( +# Field( +# default=None, +# description="Configuration for predicted output", +# ) +# ) +# presence_penalty: Optional[float] = Field( +# default=None, +# description="Penalizes new tokens based on existence in text", +# ) +# frequency_penalty: Optional[float] = Field( +# default=None, +# description="Penalizes new tokens based on frequency in text", +# ) +# logit_bias: Optional[dict] = Field( +# default=None, +# description="Modifies probability of specific tokens", +# ) +# reasoning_effort: Optional[Literal["low", "medium", "high"]] = ( +# Field( +# default=None, +# description="Level of reasoning effort for the model", +# ) +# ) +# seed: Optional[int] = Field( +# default=None, description="Random seed for reproducibility" +# ) +# tools: Optional[List] = Field( +# default=None, +# description="List of tools available to the model", +# ) +# tool_choice: Optional[Union[str, dict]] = Field( +# default=None, description="Choice of tool to use" +# ) +# logprobs: Optional[bool] = Field( +# default=None, +# description="Whether to return log probabilities", +# ) +# top_logprobs: Optional[int] = Field( +# default=None, +# description="Number of most likely tokens to return", +# ) +# parallel_tool_calls: Optional[bool] = Field( +# default=None, +# description="Whether to allow parallel tool calls", +# ) + +# class Config: +# allow_arbitrary_types = True + + +class ModelConfigOrigin(BaseModel): + """Schema for model configuration origin.""" + + model_url: Optional[str] = Field( default=None, - description="Number of most likely tokens to return", + description="The URL of the model to use for text completion", ) - parallel_tool_calls: Optional[bool] = Field( + + api_key: Optional[str] = Field( default=None, - description="Whether to allow parallel tool calls", + description="The API key to use for the model", ) class Config: diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 4064620b..fc0dc9cd 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -40,6 +40,7 @@ from swarms.schemas.base_schemas import ( ChatCompletionResponseChoice, ChatMessageResponse, ) +from swarms.schemas.llm_agent_schema import ModelConfigOrigin from swarms.structs.agent_roles import agent_roles from swarms.structs.conversation import Conversation from swarms.structs.safe_loading import ( @@ -407,6 +408,9 @@ class Agent: mcp_config: Optional[MCPConnection] = None, top_p: Optional[float] = 0.90, conversation_schema: Optional[ConversationSchema] = None, + aditional_llm_config: Optional[ModelConfigOrigin] = None, + llm_base_url: Optional[str] = None, + llm_api_key: Optional[str] = None, *args, **kwargs, ): @@ -534,10 +538,9 @@ class Agent: self.mcp_config = mcp_config self.top_p = top_p self.conversation_schema = conversation_schema - - self._cached_llm = ( - None # Add this line to cache the LLM instance - ) + self.aditional_llm_config = aditional_llm_config + self.llm_base_url = llm_base_url + self.llm_api_key = llm_api_key # self.short_memory = self.short_memory_init() @@ -547,6 +550,8 @@ class Agent: # self.init_handling() self.setup_config() + self.short_memory = self.short_memory_init() + if exists(self.docs_folder): self.get_docs_from_doc_folders() @@ -564,8 +569,6 @@ class Agent: if self.react_on is True: self.system_prompt += REACT_SYS_PROMPT - self.short_memory = self.short_memory_init() - # Run sequential operations after all concurrent tasks are done # self.agent_output = self.agent_output_model() log_agent_data(self.to_dict()) @@ -661,8 +664,8 @@ class Agent: def llm_handling(self): # Use cached instance if available - if self._cached_llm is not None: - return self._cached_llm + if self.llm is not None: + return self.llm if self.model_name is None: self.model_name = "gpt-4o-mini" @@ -682,11 +685,9 @@ class Agent: } if self.llm_args is not None: - self._cached_llm = LiteLLM( - **{**common_args, **self.llm_args} - ) + self.llm = LiteLLM(**{**common_args, **self.llm_args}) elif self.tools_list_dictionary is not None: - self._cached_llm = LiteLLM( + self.llm = LiteLLM( **common_args, tools_list_dictionary=self.tools_list_dictionary, tool_choice="auto", @@ -694,7 +695,7 @@ class Agent: ) elif self.mcp_url is not None: - self._cached_llm = LiteLLM( + self.llm = LiteLLM( **common_args, tools_list_dictionary=self.add_mcp_tools_to_memory(), tool_choice="auto", @@ -702,11 +703,14 @@ class Agent: mcp_call=True, ) else: - self._cached_llm = LiteLLM( - **common_args, stream=self.streaming_on + # common_args.update(self.aditional_llm_config.model_dump()) + + self.llm = LiteLLM( + **common_args, + stream=self.streaming_on, ) - return self._cached_llm + return self.llm except AgentLLMInitializationError as e: logger.error( f"Error in llm_handling: {e} Your current configuration is not supported. Please check the configuration and parameters." @@ -789,7 +793,7 @@ class Agent: "No agent details found. Using task as fallback for prompt generation." ) self.system_prompt = auto_generate_prompt( - task=task, model=self._cached_llm + task=task, model=self.llm ) else: # Combine all available components @@ -1012,16 +1016,20 @@ class Agent: ) self.memory_query(task_prompt) - # Generate response using LLM - response_args = ( - (task_prompt, *args) - if img is None - else (task_prompt, img, *args) - ) + # # Generate response using LLM + # response_args = ( + # (task_prompt, *args) + # if img is None + # else (task_prompt, img, *args) + # ) + + # # Call the LLM + # response = self.call_llm( + # *response_args, **kwargs + # ) - # Call the LLM response = self.call_llm( - *response_args, **kwargs + task=task_prompt, img=img, *args, **kwargs ) if exists(self.tools_list_dictionary): @@ -2388,7 +2396,9 @@ class Agent: return None - def call_llm(self, task: str, *args, **kwargs) -> str: + def call_llm( + self, task: str, img: str = None, *args, **kwargs + ) -> str: """ Calls the appropriate method on the `llm` object based on the given task. @@ -2407,17 +2417,9 @@ class Agent: TypeError: If task is not a string or llm object is None. ValueError: If task is empty. """ - # if not isinstance(task, str): - # task = any_to_str(task) - - # if img is not None: - # kwargs['img'] = img - - # if audio is not None: - # kwargs['audio'] = audio try: - out = self.llm.run(task=task, *args, **kwargs) + out = self.llm.run(task=task, img=img, *args, **kwargs) return out except AgentLLMError as e: @@ -2764,13 +2766,7 @@ class Agent: # Create a temporary LLM instance without tools for the follow-up call try: - temp_llm = LiteLLM( - model_name=self.model_name, - temperature=self.temperature, - max_tokens=self.max_tokens, - system_prompt=self.system_prompt, - stream=self.streaming_on, - ) + temp_llm = self.temp_llm_instance_for_tool_summary() summary = temp_llm.run( task=self.short_memory.get_str() @@ -2792,6 +2788,19 @@ class Agent: logger.error(f"Error in MCP tool: {e}") raise e + def temp_llm_instance_for_tool_summary(self): + return LiteLLM( + model_name=self.model_name, + temperature=self.temperature, + max_tokens=self.max_tokens, + system_prompt=self.system_prompt, + stream=self.streaming_on, + tools_list_dictionary=None, + parallel_tool_calls=False, + base_url=self.llm_base_url, + api_key=self.llm_api_key, + ) + def execute_tools(self, response: any, loop_count: int): output = ( @@ -2813,15 +2822,7 @@ class Agent: # Now run the LLM again without tools - create a temporary LLM instance # instead of modifying the cached one # Create a temporary LLM instance without tools for the follow-up call - temp_llm = LiteLLM( - model_name=self.model_name, - temperature=self.temperature, - max_tokens=self.max_tokens, - system_prompt=self.system_prompt, - stream=self.streaming_on, - tools_list_dictionary=None, - parallel_tool_calls=False, - ) + temp_llm = self.temp_llm_instance_for_tool_summary() tool_response = temp_llm.run( f""" diff --git a/swarms/structs/image_batch_processor.py b/swarms/structs/image_batch_processor.py new file mode 100644 index 00000000..fd9f86ff --- /dev/null +++ b/swarms/structs/image_batch_processor.py @@ -0,0 +1,261 @@ +import os +import time +from concurrent.futures import ThreadPoolExecutor, as_completed +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional, Union + +from loguru import logger + +from swarms.structs import Agent + + +class ImageProcessingError(Exception): + """Custom exception for image processing errors.""" + + pass + + +class InvalidAgentError(Exception): + """Custom exception for invalid agent configurations.""" + + pass + + +class ImageAgentBatchProcessor: + """ + A class for processing multiple images in parallel using one or more agents. + + This processor can: + - Handle multiple images from a directory + - Process images with single or multiple agents + - Execute tasks in parallel + - Provide detailed logging and error handling + + Attributes: + agents (List[Agent]): List of agents to process images + max_workers (int): Maximum number of parallel workers + supported_formats (set): Set of supported image formats + """ + + def __init__( + self, + agents: Union[Agent, List[Agent], Callable, List[Callable]], + max_workers: int = None, + supported_formats: Optional[List[str]] = None, + ): + """ + Initialize the ImageBatchProcessor. + + Args: + agents: Single agent or list of agents to process images + max_workers: Maximum number of parallel workers (default: 4) + supported_formats: List of supported image formats (default: ['.jpg', '.jpeg', '.png']) + + Raises: + InvalidAgentError: If agents parameter is invalid + """ + self.agents = agents + self.max_workers = max_workers + self.supported_formats = supported_formats + + self.agents = ( + [agents] if isinstance(agents, Agent) else agents + ) + if not self.agents: + raise InvalidAgentError( + "At least one agent must be provided" + ) + + # Get 95% of the total number of cores + self.max_workers = int(os.cpu_count() * 0.95) + + self.supported_formats = set( + supported_formats or [".jpg", ".jpeg", ".png"] + ) + + # Setup logging + logger.add( + "image_processor.log", + rotation="100 MB", + retention="10 days", + level="INFO", + ) + + def _validate_image_path( + self, image_path: Union[str, Path] + ) -> Path: + """ + Validate if the image path exists and has supported format. + + Args: + image_path: Path to the image file + + Returns: + Path: Validated Path object + + Raises: + ImageProcessingError: If path is invalid or format not supported + """ + path = Path(image_path) + if not path.exists(): + raise ImageProcessingError( + f"Image path does not exist: {path}" + ) + if path.suffix.lower() not in self.supported_formats: + raise ImageProcessingError( + f"Unsupported image format {path.suffix}. Supported formats: {self.supported_formats}" + ) + return path + + def _process_single_image( + self, + image_path: Path, + tasks: Union[str, List[str]], + agent: Agent, + ) -> Dict[str, Any]: + """ + Process a single image with one agent and one or more tasks. + + Args: + image_path: Path to the image + tasks: Single task or list of tasks to perform + agent: Agent to process the image + + Returns: + Dict containing results for each task + """ + try: + tasks_list = [tasks] if isinstance(tasks, str) else tasks + results = {} + + logger.info( + f"Processing image {image_path} with agent {agent.__class__.__name__}" + ) + start_time = time.time() + + for task in tasks_list: + try: + result = agent.run(task=task, img=str(image_path)) + results[task] = result + except Exception as e: + logger.error( + f"Error processing task '{task}' for image {image_path}: {str(e)}" + ) + results[task] = f"Error: {str(e)}" + + processing_time = time.time() - start_time + logger.info( + f"Completed processing {image_path} in {processing_time:.2f} seconds" + ) + + return { + "image_path": str(image_path), + "results": results, + "processing_time": processing_time, + } + + except Exception as e: + logger.error( + f"Failed to process image {image_path}: {str(e)}" + ) + raise ImageProcessingError( + f"Failed to process image {image_path}: {str(e)}" + ) + + def run( + self, + image_paths: Union[str, List[str], Path], + tasks: Union[str, List[str]], + ) -> List[Dict[str, Any]]: + """ + Process multiple images in parallel with the configured agents. + + Args: + image_paths: Single image path or list of image paths or directory path + tasks: Single task or list of tasks to perform on each image + + Returns: + List of dictionaries containing results for each image + + Raises: + ImageProcessingError: If any image processing fails + """ + # Handle directory input + if ( + isinstance(image_paths, (str, Path)) + and Path(image_paths).is_dir() + ): + image_paths = [ + os.path.join(image_paths, f) + for f in os.listdir(image_paths) + if Path(os.path.join(image_paths, f)).suffix.lower() + in self.supported_formats + ] + elif isinstance(image_paths, (str, Path)): + image_paths = [image_paths] + + # Validate all paths + validated_paths = [ + self._validate_image_path(path) for path in image_paths + ] + + if not validated_paths: + logger.warning("No valid images found to process") + return [] + + logger.info( + f"Starting batch processing of {len(validated_paths)} images" + ) + results = [] + + with ThreadPoolExecutor( + max_workers=self.max_workers + ) as executor: + future_to_path = {} + + # Submit all tasks + for path in validated_paths: + for agent in self.agents: + future = executor.submit( + self._process_single_image, path, tasks, agent + ) + future_to_path[future] = (path, agent) + + # Collect results as they complete + for future in as_completed(future_to_path): + path, agent = future_to_path[future] + try: + result = future.result() + results.append(result) + except Exception as e: + logger.error( + f"Failed to process {path} with {agent.__class__.__name__}: {str(e)}" + ) + results.append( + { + "image_path": str(path), + "error": str(e), + "agent": agent.__class__.__name__, + } + ) + + logger.info( + f"Completed batch processing of {len(validated_paths)} images" + ) + return results + + def __call__(self, *args, **kwargs): + """ + Make the ImageAgentBatchProcessor callable like a function. + + This allows the processor to be used directly as a function, which will + call the run() method with the provided arguments. + + Args: + *args: Variable length argument list to pass to run() + **kwargs: Arbitrary keyword arguments to pass to run() + + Returns: + The result of calling run() with the provided arguments + """ + return self.run(*args, **kwargs) diff --git a/swarms/structs/interactive_groupchat.py b/swarms/structs/interactive_groupchat.py new file mode 100644 index 00000000..4ffa5533 --- /dev/null +++ b/swarms/structs/interactive_groupchat.py @@ -0,0 +1,356 @@ +from typing import List, Union, Callable +import re +from loguru import logger +from swarms.structs.agent import Agent +from swarms.structs.conversation import Conversation +from swarms.utils.history_output_formatter import ( + history_output_formatter, +) +from swarms.utils.generate_keys import generate_api_key + + +class InteractiveGroupChatError(Exception): + """Base exception class for InteractiveGroupChat errors""" + + pass + + +class AgentNotFoundError(InteractiveGroupChatError): + """Raised when a mentioned agent is not found in the group""" + + pass + + +class NoMentionedAgentsError(InteractiveGroupChatError): + """Raised when no agents are mentioned in the message""" + + pass + + +class InvalidMessageFormatError(InteractiveGroupChatError): + """Raised when the message format is invalid""" + + pass + + +class InteractiveGroupChat: + """ + An interactive group chat system that enables conversations with multiple agents using @mentions. + + This class allows users to interact with multiple agents by mentioning them using @agent_name syntax. + When multiple agents are mentioned, they can see and respond to each other's messages. + + Attributes: + name (str): Name of the group chat + description (str): Description of the group chat's purpose + agents (List[Union[Agent, Callable]]): List of Agent instances or callable functions + max_loops (int): Maximum number of conversation turns + conversation (Conversation): Stores the chat history + agent_map (Dict[str, Union[Agent, Callable]]): Mapping of agent names to their instances + + Args: + name (str, optional): Name of the group chat. Defaults to "InteractiveGroupChat". + description (str, optional): Description of the chat. Defaults to "An interactive group chat for multiple agents". + agents (List[Union[Agent, Callable]], optional): List of participating agents or callables. Defaults to empty list. + max_loops (int, optional): Maximum conversation turns. Defaults to 1. + output_type (str, optional): Type of output format. Defaults to "string". + interactive (bool, optional): Whether to enable interactive terminal mode. Defaults to False. + + Raises: + ValueError: If invalid initialization parameters are provided + """ + + def __init__( + self, + id: str = generate_api_key(prefix="swarms-"), + name: str = "InteractiveGroupChat", + description: str = "An interactive group chat for multiple agents", + agents: List[Union[Agent, Callable]] = [], + max_loops: int = 1, + output_type: str = "string", + interactive: bool = False, + ): + self.id = id + self.name = name + self.description = description + self.agents = agents + self.max_loops = max_loops + self.output_type = output_type + self.interactive = interactive + + # Initialize conversation history + self.conversation = Conversation(time_enabled=True) + + # Create a mapping of agent names to agents for easy lookup + self.agent_map = {} + for agent in agents: + if isinstance(agent, Agent): + self.agent_map[agent.agent_name] = agent + elif callable(agent): + # For callable functions, use the function name as the agent name + self.agent_map[agent.__name__] = agent + + self._validate_initialization() + self._setup_conversation_context() + self._update_agent_prompts() + + def _validate_initialization(self) -> None: + """ + Validates the group chat configuration. + + Raises: + ValueError: If any required components are missing or invalid + """ + if len(self.agents) < 1: + raise ValueError( + "At least one agent is required for the group chat" + ) + + if self.max_loops <= 0: + raise ValueError("Max loops must be greater than 0") + + def _setup_conversation_context(self) -> None: + """Sets up the initial conversation context with group chat information.""" + agent_info = [] + for agent in self.agents: + if isinstance(agent, Agent): + agent_info.append( + f"- {agent.agent_name}: {agent.system_prompt}" + ) + elif callable(agent): + agent_info.append( + f"- {agent.__name__}: Custom callable function" + ) + + context = ( + f"Group Chat Name: {self.name}\n" + f"Description: {self.description}\n" + f"Available Agents:\n" + "\n".join(agent_info) + ) + self.conversation.add(role="System", content=context) + + def _update_agent_prompts(self) -> None: + """Updates each agent's system prompt with information about other agents and the group chat.""" + agent_info = [] + for agent in self.agents: + if isinstance(agent, Agent): + agent_info.append( + { + "name": agent.agent_name, + "description": agent.system_prompt, + } + ) + elif callable(agent): + agent_info.append( + { + "name": agent.__name__, + "description": "Custom callable function", + } + ) + + group_context = ( + f"\n\nYou are part of a group chat named '{self.name}' with the following description: {self.description}\n" + f"Other participants in this chat:\n" + ) + + for agent in self.agents: + if isinstance(agent, Agent): + # Create context excluding the current agent + other_agents = [ + info + for info in agent_info + if info["name"] != agent.agent_name + ] + agent_context = group_context + for other in other_agents: + agent_context += ( + f"- {other['name']}: {other['description']}\n" + ) + + # Update the agent's system prompt + agent.system_prompt = ( + agent.system_prompt + agent_context + ) + logger.info( + f"Updated system prompt for agent: {agent.agent_name}" + ) + + def _extract_mentions(self, message: str) -> List[str]: + """ + Extracts @mentions from the message. + + Args: + message (str): The input message + + Returns: + List[str]: List of mentioned agent names + + Raises: + InvalidMessageFormatError: If the message format is invalid + """ + try: + # Find all @mentions using regex + mentions = re.findall(r"@(\w+)", message) + return [ + mention + for mention in mentions + if mention in self.agent_map + ] + except Exception as e: + logger.error(f"Error extracting mentions: {e}") + raise InvalidMessageFormatError( + f"Invalid message format: {e}" + ) + + def start_interactive_session(self): + """ + Start an interactive terminal session for chatting with agents. + + This method creates a REPL (Read-Eval-Print Loop) that allows users to: + - Chat with agents using @mentions + - See available agents and their descriptions + - Exit the session using 'exit' or 'quit' + - Get help using 'help' or '?' + """ + if not self.interactive: + raise InteractiveGroupChatError( + "Interactive mode is not enabled. Initialize with interactive=True" + ) + + print(f"\nWelcome to {self.name}!") + print(f"Description: {self.description}") + print("\nAvailable agents:") + for name, agent in self.agent_map.items(): + if isinstance(agent, Agent): + print( + f"- @{name}: {agent.system_prompt.split('\n')[0]}" + ) + else: + print(f"- @{name}: Custom callable function") + + print("\nCommands:") + print("- Type 'help' or '?' for help") + print("- Type 'exit' or 'quit' to end the session") + print("- Use @agent_name to mention agents") + print("\nStart chatting:") + + while True: + try: + # Get user input + user_input = input("\nYou: ").strip() + + # Handle special commands + if user_input.lower() in ["exit", "quit"]: + print("Goodbye!") + break + + if user_input.lower() in ["help", "?"]: + print("\nHelp:") + print("1. Mention agents using @agent_name") + print( + "2. You can mention multiple agents in one message" + ) + print("3. Available agents:") + for name in self.agent_map: + print(f" - @{name}") + print( + "4. Type 'exit' or 'quit' to end the session" + ) + continue + + if not user_input: + continue + + # Process the message and get responses + try: + response = self.run(user_input) + print("\nChat:") + print(response) + + except NoMentionedAgentsError: + print( + "\nError: Please mention at least one agent using @agent_name" + ) + except AgentNotFoundError as e: + print(f"\nError: {str(e)}") + except Exception as e: + print(f"\nAn error occurred: {str(e)}") + + except KeyboardInterrupt: + print("\nSession terminated by user. Goodbye!") + break + except Exception as e: + print(f"\nAn unexpected error occurred: {str(e)}") + print( + "The session will continue. You can type 'exit' to end it." + ) + + def run(self, message: str) -> str: + """ + Process a message and get responses from mentioned agents. + If interactive mode is enabled, this will be called by start_interactive_session(). + Otherwise, it can be called directly for single message processing. + """ + try: + # Extract mentioned agents + mentioned_agents = self._extract_mentions(message) + + if not mentioned_agents: + raise NoMentionedAgentsError( + "No valid agents mentioned in the message" + ) + + # Add user message to conversation + self.conversation.add(role="User", content=message) + + # Get responses from mentioned agents + for agent_name in mentioned_agents: + agent = self.agent_map.get(agent_name) + if not agent: + raise AgentNotFoundError( + f"Agent '{agent_name}' not found" + ) + + try: + # Get the complete conversation history + context = ( + self.conversation.return_history_as_string() + ) + + # Get response from agent + if isinstance(agent, Agent): + response = agent.run( + task=f"{context}\nPlease respond to the latest message as {agent_name}." + ) + else: + # For callable functions + response = agent(context) + + # Add response to conversation + if response and not response.isspace(): + self.conversation.add( + role=agent_name, content=response + ) + logger.info(f"Agent {agent_name} responded") + + except Exception as e: + logger.error( + f"Error getting response from {agent_name}: {e}" + ) + self.conversation.add( + role=agent_name, + content=f"Error: Unable to generate response - {str(e)}", + ) + + return history_output_formatter( + self.conversation, self.output_type + ) + + except InteractiveGroupChatError as e: + logger.error(f"GroupChat error: {e}") + raise + except Exception as e: + logger.error(f"Unexpected error: {e}") + raise InteractiveGroupChatError( + f"Unexpected error occurred: {str(e)}" + ) diff --git a/swarms/utils/litellm_wrapper.py b/swarms/utils/litellm_wrapper.py index c3753ba7..6aa5c7d3 100644 --- a/swarms/utils/litellm_wrapper.py +++ b/swarms/utils/litellm_wrapper.py @@ -1,6 +1,8 @@ +import traceback from typing import Optional import base64 import requests +from pathlib import Path import asyncio from typing import List @@ -9,11 +11,7 @@ from loguru import logger import litellm from pydantic import BaseModel -from litellm import completion, acompletion - -litellm.set_verbose = True -litellm.ssl_verify = False -# litellm._turn_on_debug() +from litellm import completion, acompletion, supports_vision class LiteLLMException(Exception): @@ -53,6 +51,35 @@ def get_audio_base64(audio_source: str) -> str: return encoded_string +def get_image_base64(image_source: str) -> str: + """ + Convert image from a given source to a base64 encoded string. + Handles URLs, local file paths, and data URIs. + """ + # If already a data URI, return as is + if image_source.startswith("data:image"): + return image_source + + # Handle URL + if image_source.startswith(("http://", "https://")): + response = requests.get(image_source) + response.raise_for_status() + image_data = response.content + # Handle local file + else: + with open(image_source, "rb") as file: + image_data = file.read() + + # Get file extension for mime type + extension = Path(image_source).suffix.lower() + mime_type = ( + f"image/{extension[1:]}" if extension else "image/jpeg" + ) + + encoded_string = base64.b64encode(image_data).decode("utf-8") + return f"data:{mime_type};base64,{encoded_string}" + + class LiteLLM: """ This class represents a LiteLLM. @@ -72,12 +99,15 @@ class LiteLLM: tool_choice: str = "auto", parallel_tool_calls: bool = False, audio: str = None, - retries: int = 3, + retries: int = 0, verbose: bool = False, caching: bool = False, mcp_call: bool = False, top_p: float = 1.0, functions: List[dict] = None, + return_all: bool = False, + base_url: str = None, + api_key: str = None, *args, **kwargs, ): @@ -105,8 +135,11 @@ class LiteLLM: self.mcp_call = mcp_call self.top_p = top_p self.functions = functions + self.audio = audio + self.return_all = return_all + self.base_url = base_url + self.api_key = api_key self.modalities = [] - self._cached_messages = {} # Cache for prepared messages self.messages = [] # Initialize messages list # Configure litellm settings @@ -135,7 +168,11 @@ class LiteLLM: out = out.model_dump() return out - def _prepare_messages(self, task: str) -> list: + def _prepare_messages( + self, + task: str, + img: str = None, + ): """ Prepare the messages for the given task. @@ -145,91 +182,201 @@ class LiteLLM: Returns: list: A list of messages prepared for the task. """ - # Check cache first - cache_key = f"{self.system_prompt}:{task}" - if cache_key in self._cached_messages: - return self._cached_messages[cache_key].copy() + self.check_if_model_supports_vision(img=img) + # Initialize messages messages = [] - if self.system_prompt: + + # Add system prompt if present + if self.system_prompt is not None: messages.append( {"role": "system", "content": self.system_prompt} ) - messages.append({"role": "user", "content": task}) - # Cache the prepared messages - self._cached_messages[cache_key] = messages.copy() + # Handle vision case + if img is not None: + messages = self.vision_processing( + task=task, image=img, messages=messages + ) + else: + messages.append({"role": "user", "content": task}) + return messages - def audio_processing(self, task: str, audio: str): + def anthropic_vision_processing( + self, task: str, image: str, messages: list + ) -> list: """ - Process the audio for the given task. - - Args: - task (str): The task to be processed. - audio (str): The path or identifier for the audio file. + Process vision input specifically for Anthropic models. + Handles Anthropic's specific image format requirements. """ - self.modalities.append("audio") - - encoded_string = get_audio_base64(audio) + # Get base64 encoded image + image_url = get_image_base64(image) + + # Extract mime type from the data URI or use default + mime_type = "image/jpeg" # default + if "data:" in image_url and ";base64," in image_url: + mime_type = image_url.split(";base64,")[0].split("data:")[ + 1 + ] + + # Ensure mime type is one of the supported formats + supported_formats = [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + ] + if mime_type not in supported_formats: + mime_type = ( + "image/jpeg" # fallback to jpeg if unsupported + ) - # Append messages - self.messages.append( + # Construct Anthropic vision message + messages.append( { "role": "user", "content": [ {"type": "text", "text": task}, { - "type": "input_audio", - "input_audio": { - "data": encoded_string, - "format": "wav", + "type": "image_url", + "image_url": { + "url": image_url, + "format": mime_type, }, }, ], } ) - def vision_processing(self, task: str, image: str): + return messages + + def openai_vision_processing( + self, task: str, image: str, messages: list + ) -> list: + """ + Process vision input specifically for OpenAI models. + Handles OpenAI's specific image format requirements. + """ + # Get base64 encoded image with proper format + image_url = get_image_base64(image) + + # Prepare vision message + vision_message = { + "type": "image_url", + "image_url": {"url": image_url}, + } + + # Add format for specific models + extension = Path(image).suffix.lower() + mime_type = ( + f"image/{extension[1:]}" if extension else "image/jpeg" + ) + vision_message["image_url"]["format"] = mime_type + + # Append vision message + messages.append( + { + "role": "user", + "content": [ + {"type": "text", "text": task}, + vision_message, + ], + } + ) + + return messages + + def vision_processing( + self, task: str, image: str, messages: Optional[list] = None + ): """ Process the image for the given task. + Handles different image formats and model requirements. + """ + # # # Handle Anthropic models separately + # # if "anthropic" in self.model_name.lower() or "claude" in self.model_name.lower(): + # # messages = self.anthropic_vision_processing(task, image, messages) + # # return messages + + # # Get base64 encoded image with proper format + # image_url = get_image_base64(image) + + # # Prepare vision message + # vision_message = { + # "type": "image_url", + # "image_url": {"url": image_url}, + # } + + # # Add format for specific models + # extension = Path(image).suffix.lower() + # mime_type = f"image/{extension[1:]}" if extension else "image/jpeg" + # vision_message["image_url"]["format"] = mime_type + + # # Append vision message + # messages.append( + # { + # "role": "user", + # "content": [ + # {"type": "text", "text": task}, + # vision_message, + # ], + # } + # ) + + # return messages + if ( + "anthropic" in self.model_name.lower() + or "claude" in self.model_name.lower() + ): + messages = self.anthropic_vision_processing( + task, image, messages + ) + return messages + else: + messages = self.openai_vision_processing( + task, image, messages + ) + return messages + + def audio_processing(self, task: str, audio: str): """ - self.modalities.append("vision") + Process the audio for the given task. - # Append messages + Args: + task (str): The task to be processed. + audio (str): The path or identifier for the audio file. + """ + encoded_string = get_audio_base64(audio) + + # Append audio message self.messages.append( { "role": "user", "content": [ {"type": "text", "text": task}, { - "type": "image_url", - "image_url": { - "url": image, - # "detail": "high" - # "format": "image", + "type": "input_audio", + "input_audio": { + "data": encoded_string, + "format": "wav", }, }, ], } ) - def handle_modalities( - self, task: str, audio: str = None, img: str = None - ): + def check_if_model_supports_vision(self, img: str = None): """ - Handle the modalities for the given task. + Check if the model supports vision. """ - self.messages = [] # Reset messages - self.modalities.append("text") - - if audio is not None: - self.audio_processing(task=task, audio=audio) - self.modalities.append("audio") - if img is not None: - self.vision_processing(task=task, image=img) - self.modalities.append("vision") + out = supports_vision(model=self.model_name) + + if out is False: + raise ValueError( + f"Model {self.model_name} does not support vision" + ) def run( self, @@ -256,13 +403,7 @@ class LiteLLM: Exception: If there is an error in processing the request. """ try: - messages = self._prepare_messages(task) - - if audio is not None or img is not None: - self.handle_modalities( - task=task, audio=audio, img=img - ) - messages = self.messages + messages = self._prepare_messages(task=task, img=img) # Base completion parameters completion_params = { @@ -298,6 +439,9 @@ class LiteLLM: {"functions": self.functions} ) + if self.base_url is not None: + completion_params["base_url"] = self.base_url + # Add modalities if needed if self.modalities and len(self.modalities) >= 2: completion_params["modalities"] = self.modalities @@ -308,12 +452,16 @@ class LiteLLM: # Handle tool-based response if self.tools_list_dictionary is not None: return self.output_for_tools(response) + elif self.return_all is True: + return response.model_dump() else: # Return standard response content return response.choices[0].message.content except LiteLLMException as error: - logger.error(f"Error in LiteLLM run: {str(error)}") + logger.error( + f"Error in LiteLLM run: {str(error)} Traceback: {traceback.format_exc()}" + ) if "rate_limit" in str(error).lower(): logger.warning( "Rate limit hit, retrying with exponential backoff..." diff --git a/tests/communication/test_supabase_conversation.py b/tests/communication/test_supabase_conversation.py index 17f67745..5a3de2d8 100644 --- a/tests/communication/test_supabase_conversation.py +++ b/tests/communication/test_supabase_conversation.py @@ -294,7 +294,7 @@ def test_logging_configuration() -> bool: try: assert ( - conversation_with_logging.enable_logging == True + conversation_with_logging.enable_logging is True ), "Logging should be enabled" assert ( conversation_with_logging.logger is not None @@ -309,7 +309,7 @@ def test_logging_configuration() -> bool: ) assert ( - conversation_no_logging.enable_logging == False + conversation_no_logging.enable_logging is False ), "Logging should be disabled" print("✓ Logging configuration test passed") @@ -629,7 +629,7 @@ def test_update_message_method() -> bool: ) assert ( - success == True + success is True ), "update_message should return True on success" # Verify the update @@ -643,7 +643,7 @@ def test_update_message_method() -> bool: updated_msg["metadata"]["version"] == 2 ), "Metadata should be updated" assert ( - updated_msg["metadata"]["updated"] == True + updated_msg["metadata"]["updated"] is True ), "New metadata field should be added" # Test update_message with non-existent ID @@ -651,7 +651,7 @@ def test_update_message_method() -> bool: message_id=999999, content="This should fail" ) assert ( - failure == False + failure is False ), "update_message should return False for non-existent message" print("✓ Update message method test passed") @@ -1106,7 +1106,7 @@ def test_enhanced_error_handling() -> bool: # Test invalid credentials try: - invalid_conversation = SupabaseConversation( + SupabaseConversation( supabase_url="https://invalid-url.supabase.co", supabase_key="invalid_key", enable_logging=False, @@ -1139,7 +1139,7 @@ def test_enhanced_error_handling() -> bool: "999999", "user", "content" ) assert ( - update_result == False + update_result is False ), "_update_flexible should return False for invalid ID" # Test update_message with invalid ID @@ -1147,7 +1147,7 @@ def test_enhanced_error_handling() -> bool: 999999, "invalid content" ) assert ( - result == False + result is False ), "update_message should return False for invalid ID" # Test search with empty query @@ -1174,7 +1174,7 @@ def test_enhanced_error_handling() -> bool: "not_a_number", "user", "content" ) assert ( - invalid_update == False + invalid_update is False ), "Invalid ID should return False for update" print("✓ Enhanced error handling test passed") diff --git a/v0_model.py b/v0_model.py new file mode 100644 index 00000000..5546ffb9 --- /dev/null +++ b/v0_model.py @@ -0,0 +1,79 @@ +# 'v0-1.0-md' +# https://api.v0.dev/v1/chat/completions + +import time +from swarms import Agent +import os +from dotenv import load_dotenv + +load_dotenv() + +FRONT_END_DEVELOPMENT_PROMPT = """ + You are an expert full-stack development agent with comprehensive expertise in: + + Frontend Development: + - Modern React.js/Next.js architecture and best practices + - Advanced TypeScript implementation and type safety + - State-of-the-art UI/UX design patterns + - Responsive and accessible design principles + - Component-driven development with Storybook + - Modern CSS frameworks (Tailwind, Styled-Components) + - Performance optimization and lazy loading + + Backend Development: + - Scalable microservices architecture + - RESTful and GraphQL API design + - Database optimization and schema design + - Authentication and authorization systems + - Serverless architecture and cloud services + - CI/CD pipeline implementation + - Security best practices and OWASP guidelines + + Development Practices: + - Test-Driven Development (TDD) + - Clean Code principles + - Documentation (TSDoc/JSDoc) + - Git workflow and version control + - Performance monitoring and optimization + - Error handling and logging + - Code review best practices + + Your core responsibilities include: + 1. Developing production-grade TypeScript applications + 2. Implementing modern, accessible UI components + 3. Designing scalable backend architectures + 4. Writing comprehensive documentation + 5. Ensuring type safety across the stack + 6. Optimizing application performance + 7. Implementing security best practices + + You maintain strict adherence to: + - TypeScript strict mode and proper typing + - SOLID principles and clean architecture + - Accessibility standards (WCAG 2.1) + - Performance budgets and metrics + - Security best practices + - Comprehensive test coverage + - Modern design system principles +""" + +# Initialize the agent +agent = Agent( + agent_name="Quantitative-Trading-Agent", + agent_description="Advanced quantitative trading and algorithmic analysis agent", + system_prompt=FRONT_END_DEVELOPMENT_PROMPT, + max_loops=1, + model_name="v0-1.0-md", + dynamic_temperature_enabled=True, + output_type="all", + # safety_prompt_on=True, + llm_api_key=os.getenv("V0_API_KEY"), + llm_base_url="https://api.v0.dev/v1/chat/completions", +) + +out = agent.run( + "Build a simple web app that allows users to upload a file and then download it." +) + +time.sleep(10) +print(out) From 22875a6fc198c0ea2319356e9fd4e569ea1d04d7 Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:45:25 +0530 Subject: [PATCH 06/13] Add Pulsar conversation example --- .../pulsar_conversation.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/communication_examples/pulsar_conversation.py diff --git a/examples/communication_examples/pulsar_conversation.py b/examples/communication_examples/pulsar_conversation.py new file mode 100644 index 00000000..c74bc4d2 --- /dev/null +++ b/examples/communication_examples/pulsar_conversation.py @@ -0,0 +1,25 @@ +from swarms import Agent +from swarms.communication.pulsar_struct import PulsarConversation + +# Configure a Pulsar-backed conversation store +conversation_store = PulsarConversation( + pulsar_host="pulsar://localhost:6650", # adjust to your broker + topic="support_conversation", + token_count=False, +) + +# Create an agent that uses this persistent memory +agent = Agent( + agent_name="SupportAgent", + system_prompt="You are a helpful assistant.", + model_name="gpt-4o-mini", + long_term_memory=conversation_store, + max_loops=1, + autosave=True, +) + +response = agent.run("What time is check-out?") +print(response) + +# View the messages as stored in Pulsar +print(conversation_store.get_messages()) From c9e1497460370a9e16731e34f5d79a4b5683b4ee Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:35:20 +0000 Subject: [PATCH 07/13] Add Pulsar conversation example for persistent agent memory --- examples/communication_examples/pulsar_conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/communication_examples/pulsar_conversation.py b/examples/communication_examples/pulsar_conversation.py index c74bc4d2..b0ac00ee 100644 --- a/examples/communication_examples/pulsar_conversation.py +++ b/examples/communication_examples/pulsar_conversation.py @@ -15,7 +15,7 @@ agent = Agent( model_name="gpt-4o-mini", long_term_memory=conversation_store, max_loops=1, - autosave=True, + autosave=False, ) response = agent.run("What time is check-out?") From 0e0a46019d0b215f7168ada03e2e84ba97bcb91e Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 8 Jun 2025 19:09:36 -0700 Subject: [PATCH 08/13] [FEAT][InteractiveGroupChat] + [Swarms Examples][index.md] --- .gitignore | 1 + docs/examples/index.md | 108 +++++++ docs/mkdocs.yml | 2 + docs/swarms/structs/interactive_groupchat.md | 290 +++++++++++------- .../deep_research_example.py | 0 .../{ => utils}/unique_swarms_examples.py | 0 .../multii_tool_use/many_tool_use_demo.py | 0 swarms/structs/__init__.py | 2 + swarms/structs/interactive_groupchat.py | 50 +-- 9 files changed, 314 insertions(+), 139 deletions(-) create mode 100644 docs/examples/index.md rename examples/multi_agent/{ => deep_research_examples}/deep_research_example.py (100%) rename examples/multi_agent/{ => utils}/unique_swarms_examples.py (100%) rename many_tool_use_demo.py => examples/tools/multii_tool_use/many_tool_use_demo.py (100%) diff --git a/.gitignore b/.gitignore index 8ed30e17..6df4413d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Cargo.lock .pytest_cache static/generated conversations/ +next_swarms_update.txt runs Financial-Analysis-Agent_state.json conversations/ diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 00000000..4781c3d7 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,108 @@ +# Swarms Examples Index + +A comprehensive index of examples from the [Swarms Examples Repository](https://github.com/The-Swarm-Corporation/swarms-examples). + +Additionally, we have more comprehensive examples available in [The Swarms Cookbook](https://github.com/The-Swarm-Corporation/Cookbook). + +## Single Agent Examples + +### Core Agents +| Category | Example | Description | +|----------|---------|-------------| +| Basic | [Easy Example](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/easy_example.py) | Basic agent implementation demonstrating core functionality and setup | +| Settings | [Agent Settings](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/agent_settings.py) | Comprehensive configuration options for customizing agent behavior and capabilities | +| YAML | [Agents from YAML](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/agents_from_yaml_example.py) | Creating and configuring agents using YAML configuration files for easy deployment | +| Memory | [Agent with Long-term Memory](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/memory/agents_and_memory/agent_with_longterm_memory.py) | Implementation of persistent memory capabilities for maintaining context across sessions | + +### Model Integrations +| Category | Example | Description | +|----------|---------|-------------| +| Azure | [Azure OpenAI Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/settings/various_models/basic_agent_with_azure_openai.py) | Integration with Azure OpenAI services for enterprise-grade AI capabilities | +| Groq | [Groq Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/settings/various_models/groq_agent.py) | High-performance inference using Groq's accelerated computing platform | +| Custom | [Custom Model Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/settings/various_models/custom_model_with_agent.py) | Framework for integrating custom ML models into the agent architecture | + +### Tools and Function Calling +| Category | Example | Description | +|----------|---------|-------------| +| Basic Tools | [Tool Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/tool_agent.py) | Basic tool-using agent demonstrating external tool integration capabilities | +| Advanced Tools | [Agent with Many Tools](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/agent_with_many_tools.py) | Advanced agent utilizing multiple tools for complex task execution | +| OpenAI Functions | [OpenAI Function Caller](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/function_calling/openai_function_caller_example.py) | Integration with OpenAI's function calling API for structured outputs | +| Command Line | [Command Tool Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/tool_agent/command_r_tool_agent.py) | Command-line interface tool integration | +| Jamba | [Jamba Tool Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/tool_agent/jamba_tool_agent.py) | Integration with Jamba framework for enhanced tool capabilities | +| Pydantic | [Pydantic Tool Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/tools/tool_agent/tool_agent_pydantic.py) | Tool validation and schema enforcement using Pydantic | + +### Third-Party Integrations +| Category | Example | Description | +|----------|---------|-------------| +| Microsoft | [AutoGen Integration](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/3rd_party_agents/auto_gen.py) | Integration with Microsoft's AutoGen framework for autonomous agents | +| LangChain | [LangChain Integration](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/3rd_party_agents/langchain.py) | Combining LangChain's capabilities with Swarms for enhanced functionality | +| Browser | [Multion Integration](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/3rd_party_agents/multion_agent.py) | Web automation and browsing capabilities using Multion | +| Team AI | [Crew AI](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/3rd_party_agents/crew_ai.py) | Team-based AI collaboration using Crew AI framework | +| Development | [Griptape](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/3rd_party_agents/griptape.py) | Integration with Griptape for structured AI application development | + +### Industry-Specific Agents +| Category | Example | Description | +|----------|---------|-------------| +| Finance | [401k Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/finance/401k_agent.py) | Retirement planning assistant with investment strategy recommendations | +| Finance | [Estate Planning](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/finance/estate_planning_agent.py) | Comprehensive estate planning and wealth management assistant | +| Security | [Perimeter Defense](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/security/perimeter_defense_agent.py) | Security monitoring and threat detection system | +| Research | [Perplexity Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/research/perplexity_agent.py) | Advanced research automation using Perplexity AI integration | +| Legal | [Alberto Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/law/alberto_agent.py) | Legal research and document analysis assistant | +| Healthcare | [Pharma Agent](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/agents/use_cases/pharma/pharma_agent_two.py) | Pharmaceutical research and drug interaction analysis | + +## Multi-Agent Examples + +### Core Architectures +| Category | Example | Description | +|----------|---------|-------------| +| Basic | [Build a Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/base_swarm/build_a_swarm.py) | Foundation for creating custom swarm architectures with multiple agents | +| Auto Swarm | [Auto Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/auto_swarm/auto_swarm_example.py) | Self-organizing swarm with automatic task distribution and management | +| Concurrent | [Concurrent Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/concurrent_swarm/concurrent_swarm_example.py) | Parallel execution of tasks across multiple agents for improved performance | +| Star | [Star Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/different_architectures/star_swarm.py) | Centralized architecture with a hub agent coordinating peripheral agents | +| Circular | [Circular Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/different_architectures/circular_swarm.py) | Ring topology for cyclic information flow between agents | + +### Experimental Architectures +| Category | Example | Description | +|----------|---------|-------------| +| Monte Carlo | [Monte Carlo Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/monte_carlo_swarm.py) | Probabilistic decision-making using Monte Carlo simulation across agents | +| Federated | [Federated Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/federated_swarm.py) | Distributed learning system with privacy-preserving agent collaboration | +| Ant Colony | [Ant Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/ant_swarm.py) | Bio-inspired optimization using ant colony algorithms for agent coordination | +| Matrix | [Agent Matrix](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/agent_matrix.py) | Grid-based agent organization for complex problem-solving | +| DFS | [DFS Search Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/dfs_search_swarm.py) | Depth-first search swarm for complex problem exploration | +| Pulsar | [Pulsar Swarm](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/experimental/pulsar_swarm.py) | Pulsar-based coordination for synchronized agent behavior | + +### Collaboration Patterns +| Category | Example | Description | +|----------|---------|-------------| +| Delegation | [Agent Delegation](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/multi_agent_collaboration/agent_delegation.py) | Task delegation and management system | +| Communication | [Message Pool](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/multi_agent_collaboration/message_pool.py) | Shared communication system for efficient agent interaction | +| Scheduling | [Round Robin](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/multi_agent_collaboration/round_robin_example.py) | Round-robin task scheduling and execution | +| Load Balancing | [Load Balancer](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/multi_agent_collaboration/load_balancer_example.py) | Dynamic task distribution system for optimal resource utilization | +| Consensus | [Majority Voting](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/structs/swarms/multi_agent_collaboration/majority_voting.py) | Consensus-building system using democratic voting among agents | + +### Industry Applications +| Category | Example | Description | +|----------|---------|-------------| +| Finance | [Accountant Team](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/accountant_team/account_team2_example.py) | Multi-agent system for financial analysis, bookkeeping, and tax planning | +| Marketing | [Ad Generation](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/ad_gen/ad_gen_example.py) | Collaborative ad creation with copywriting and design agents | +| Aerospace | [Space Traffic Control](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/agentic_space_traffic_control/game.py) | Complex simulation of space traffic management with multiple coordinating agents | +| Agriculture | [Plant Biology](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/plant_biologist_swarm/agricultural_swarm.py) | Agricultural analysis and optimization using specialized biology agents | +| Urban Dev | [Urban Planning](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/urban_planning/urban_planning_example.py) | City development planning with multiple specialized urban development agents | +| Education | [Education System](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/education/education_example.py) | Personalized learning system with multiple teaching and assessment agents | +| Security | [Email Phishing Detection](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/email_phiser/email_swarm.py) | Multi-agent security analysis and threat detection | +| Fashion | [Personal Stylist](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/personal_stylist/personal_stylist_example.py) | Fashion recommendation system with style analysis and matching agents | +| Healthcare | [Healthcare Assistant](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/positive_med/positive_med_example.py) | Medical diagnosis and treatment planning with specialist consultation agents | +| Security Ops | [Security Team](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/security_team/security_team_example.py) | Comprehensive security operations with threat detection and response agents | +| Medical | [X-Ray Analysis](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/demos/xray/xray_example.py) | Multi-agent medical imaging analysis and diagnosis | +| Business | [Business Strategy](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/business_strategy/business_strategy_graph/growth_agent.py) | Strategic planning and business development swarm | +| Research | [Astronomy Research](https://github.com/The-Swarm-Corporation/swarms-examples/blob/main/examples/applications/astronomy/multiversal_detection/test.py) | Collaborative space research and astronomical analysis | + +## Additional Resources + +- [Github](https://github.com/kyegomez/swarms) + +- Discord (https://t.co/zlLe07AqUX) + +- Telegram (https://t.co/dSRy143zQv) + +- X Community (https://x.com/i/communities/1875452887414804745) \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7fac02cd..967a3cc7 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -242,6 +242,7 @@ nav: - MatrixSwarm: "swarms/structs/matrix_swarm.md" - ModelRouter: "swarms/structs/model_router.md" - MALT: "swarms/structs/malt.md" + - Interactive Group Chat: "swarms/structs/interactive_groupchat.md" - Various Execution Methods: "swarms/structs/various_execution_methods.md" - Deep Research Swarm: "swarms/structs/deep_research_swarm.md" - Swarm Matcher: "swarms/structs/swarm_matcher.md" @@ -305,6 +306,7 @@ nav: - Swarms 5.9.2: "swarms/changelog/changelog_new.md" - Examples: + - Overview: "examples/index.md" - Customizing Agents: - Basic Agent: "swarms/examples/basic_agent.md" - Agents with Callable Tools: "swarms/examples/agent_with_tools.md" diff --git a/docs/swarms/structs/interactive_groupchat.md b/docs/swarms/structs/interactive_groupchat.md index c4705aee..207e2a50 100644 --- a/docs/swarms/structs/interactive_groupchat.md +++ b/docs/swarms/structs/interactive_groupchat.md @@ -1,11 +1,11 @@ # InteractiveGroupChat Documentation -The InteractiveGroupChat is a sophisticated multi-agent system that enables interactive conversations between users and AI agents using @mentions. This system allows users to direct messages to specific agents and facilitates collaborative responses when multiple agents are mentioned. +The InteractiveGroupChat is a sophisticated multi-agent system that enables interactive conversations between users and AI agents using @mentions. This system allows users to direct tasks to specific agents and facilitates collaborative responses when multiple agents are mentioned. ## Features -- **@mentions Support**: Direct messages to specific agents using @agent_name syntax -- **Multi-Agent Collaboration**: Multiple mentioned agents can see and respond to each other's messages +- **@mentions Support**: Direct tasks to specific agents using @agent_name syntax +- **Multi-Agent Collaboration**: Multiple mentioned agents can see and respond to each other's tasks - **Callable Function Support**: Supports both Agent instances and callable functions as chat participants - **Comprehensive Error Handling**: Custom error classes for different scenarios - **Conversation History**: Maintains a complete history of the conversation @@ -17,186 +17,248 @@ The InteractiveGroupChat is a sophisticated multi-agent system that enables inte pip install swarms ``` -## Basic Usage +## Methods Reference +### Constructor (`__init__`) + +**Description:** +Initializes a new InteractiveGroupChat instance with the specified configuration. + +**Arguments:** +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +| `id` | str | Unique identifier for the chat | auto-generated key | +| `name` | str | Name of the group chat | "InteractiveGroupChat" | +| `description` | str | Description of the chat's purpose | generic description | +| `agents` | List[Union[Agent, Callable]] | List of participating agents | empty list | +| `max_loops` | int | Maximum conversation turns | 1 | +| `output_type` | str | Type of output format | "string" | +| `interactive` | bool | Whether to enable interactive mode | False | + +**Example:** ```python from swarms import Agent, InteractiveGroupChat -# Initialize agents +# Create agents financial_advisor = Agent( agent_name="FinancialAdvisor", system_prompt="You are a financial advisor specializing in investment strategies.", - model_name="gpt-4o-mini" + model_name="gpt-4" ) tax_expert = Agent( agent_name="TaxExpert", system_prompt="You are a tax expert providing tax-related guidance.", - model_name="gpt-4o-mini" + model_name="gpt-4" ) -# Create the interactive group chat +# Initialize group chat chat = InteractiveGroupChat( - name="Financial Team", - description="Financial advisory team", - agents=[financial_advisor, tax_expert] + id="finance-chat-001", + name="Financial Advisory Team", + description="Expert financial guidance team", + agents=[financial_advisor, tax_expert], + max_loops=3, + output_type="string", + interactive=True ) +``` -# Send a message to a single agent -response = chat.run("@FinancialAdvisor what are good investment strategies?") +### Run Method (`run`) -# Send a message to multiple agents -response = chat.run("@FinancialAdvisor and @TaxExpert, how can I optimize my investment taxes?") -``` +**Description:** +Processes a task and gets responses from mentioned agents. This is the main method for sending tasks in non-interactive mode. + +**Arguments:** -## Advanced Usage +- `task` (str): The input task containing @mentions to agents -### Using Callable Functions +**Returns:** +- str: Formatted conversation history including agent responses + +**Example:** ```python -def custom_agent(context: str) -> str: - """A custom callable function that can act as an agent""" - return "Custom response based on: " + context +# Single agent interaction +response = chat.run("@FinancialAdvisor what are the best ETFs for 2024?") +print(response) -# Add both Agent instances and callable functions -agents = [financial_advisor, tax_expert, custom_agent] -chat = InteractiveGroupChat(agents=agents) +# Multiple agent collaboration +response = chat.run("@FinancialAdvisor and @TaxExpert, how can I minimize taxes on my investments?") +print(response) +``` + +### Start Interactive Session (`start_interactive_session`) + +**Description:** +Starts an interactive terminal session for real-time chat with agents. This creates a REPL (Read-Eval-Print Loop) interface. + +**Arguments:** +None -# Interact with the callable function -response = chat.run("@custom_agent what do you think?") +**Example:** + +```python +# Initialize chat with interactive mode +chat = InteractiveGroupChat( + agents=[financial_advisor, tax_expert], + interactive=True +) + +# Start the interactive session +chat.start_interactive_session() ``` -## Configuration Options +### Extract Mentions (`_extract_mentions`) + +**Description:** -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| name | str | "InteractiveGroupChat" | Name of the group chat | -| description | str | "An interactive group chat..." | Description of the chat's purpose | -| agents | List[Union[Agent, Callable]] | [] | List of agents or callable functions | -| max_loops | int | 1 | Maximum conversation turns | -| output_type | str | "string" | Output format type | +Internal method that extracts @mentions from a task. Used by the run method to identify which agents should respond. -## Error Handling +**Arguments:** -The system includes several custom error classes: +- `task` (str): The input task to extract mentions from -- **InteractiveGroupChatError**: Base exception class -- **AgentNotFoundError**: Raised when a mentioned agent doesn't exist -- **NoMentionedAgentsError**: Raised when no agents are mentioned -- **InvalidMessageFormatError**: Raised for invalid message formats +**Returns:** -Example error handling: +- List[str]: List of mentioned agent names +**Example:** ```python -try: - response = chat.run("@NonExistentAgent hello!") -except AgentNotFoundError as e: - print(f"Agent not found: {e}") -except NoMentionedAgentsError as e: - print(f"No agents mentioned: {e}") +# Internal usage example (not typically called directly) +chat = InteractiveGroupChat(agents=[financial_advisor, tax_expert]) +mentions = chat._extract_mentions("@FinancialAdvisor and @TaxExpert, please help") +print(mentions) # ['FinancialAdvisor', 'TaxExpert'] ``` -## Best Practices +### Validate Initialization (`_validate_initialization`) + +**Description:** -1. **Agent Naming**: Use clear, unique names for agents to avoid confusion -2. **Message Format**: Always use @mentions to direct messages to specific agents -3. **Error Handling**: Implement proper error handling for various scenarios -4. **Context Management**: Be aware that agents can see the full conversation history -5. **Resource Management**: Consider the number of agents and message length to optimize performance +Internal method that validates the group chat configuration during initialization. -## Logging +**Arguments:** +None -The system uses loguru for comprehensive logging: +**Example:** ```python -from loguru import logger +# Internal validation happens automatically during initialization +chat = InteractiveGroupChat( + agents=[financial_advisor], # Valid: at least one agent + max_loops=5 # Valid: positive number +) +``` -# Configure logging -logger.add("groupchat.log", rotation="500 MB") +### Setup Conversation Context (`_setup_conversation_context`) -# Logs will include: -# - Agent responses -# - Error messages -# - System events -``` +**Description:** -## Examples +Internal method that sets up the initial conversation context with group chat information. -### Basic Interaction +**Arguments:** -```python -# Single agent interaction -response = chat.run("@FinancialAdvisor what are the best investment strategies for 2024?") +None -# Multiple agent collaboration -response = chat.run("@TaxExpert and @InvestmentAnalyst, how can we optimize investment taxes?") +**Example:** + +```python +# Context is automatically set up during initialization +chat = InteractiveGroupChat( + name="Investment Team", + description="Expert investment advice", + agents=[financial_advisor, tax_expert] +) +# The conversation context now includes chat name, description, and agent info ``` -### Error Handling +### Update Agent Prompts (`_update_agent_prompts`) -```python -try: - # Invalid agent mention - response = chat.run("@NonExistentAgent hello!") -except AgentNotFoundError as e: - print(f"Error: {e}") +**Description:** -try: - # No mentions - response = chat.run("Hello everyone!") -except NoMentionedAgentsError as e: - print(f"Error: {e}") -``` +Internal method that updates each agent's system prompt with information about other agents and the group chat. + +**Arguments:** -### Custom Callable Integration +None +**Example:** ```python -def market_analyzer(context: str) -> str: - """Custom market analysis function""" - return "Market analysis based on: " + context +# Agent prompts are automatically updated during initialization +chat = InteractiveGroupChat(agents=[financial_advisor, tax_expert]) +# Each agent now knows about the other participants in the chat +``` + +## Error Classes -agents = [financial_advisor, tax_expert, market_analyzer] -chat = InteractiveGroupChat(agents=agents) +### InteractiveGroupChatError -response = chat.run("@market_analyzer what's your analysis of the current market?") +**Description:** + +Base exception class for InteractiveGroupChat errors. + +**Example:** +```python +try: + # Some operation that might fail + chat.run("@InvalidAgent hello") +except InteractiveGroupChatError as e: + print(f"Chat error occurred: {e}") ``` -## API Reference +### AgentNotFoundError -### InteractiveGroupChat Class +**Description:** +Raised when a mentioned agent is not found in the group. + +**Example:** ```python -class InteractiveGroupChat: - def __init__( - self, - name: str = "InteractiveGroupChat", - description: str = "An interactive group chat for multiple agents", - agents: List[Union[Agent, Callable]] = [], - max_loops: int = 1, - output_type: str = "string", - ): - """Initialize the interactive group chat.""" - - def run(self, message: str) -> str: - """Process a message and get responses from mentioned agents.""" +try: + chat.run("@NonExistentAgent hello!") +except AgentNotFoundError as e: + print(f"Agent not found: {e}") ``` -### Custom Error Classes +### NoMentionedAgentsError + +**Description:** + +Raised when no agents are mentioned in the task. + +**Example:** ```python -class InteractiveGroupChatError(Exception): - """Base exception class for InteractiveGroupChat errors""" +try: + chat.run("Hello everyone!") # No @mentions +except NoMentionedAgentsError as e: + print(f"No agents mentioned: {e}") +``` -class AgentNotFoundError(InteractiveGroupChatError): - """Raised when a mentioned agent is not found""" +### InvalidtaskFormatError -class NoMentionedAgentsError(InteractiveGroupChatError): - """Raised when no agents are mentioned""" +**Description:** -class InvalidMessageFormatError(InteractiveGroupChatError): - """Raised when the message format is invalid""" +Raised when the task format is invalid. + +**Example:** +```python +try: + chat.run("@Invalid@Format") +except InvalidtaskFormatError as e: + print(f"Invalid task format: {e}") ``` +## Best Practices + +| Best Practice | Description | Example | +|--------------|-------------|---------| +| Agent Naming | Use clear, unique names for agents to avoid confusion | `financial_advisor`, `tax_expert` | +| task Format | Always use @mentions to direct tasks to specific agents | `@financial_advisor What's your investment advice?` | +| Error Handling | Implement proper error handling for various scenarios | `try/except` blocks for `AgentNotFoundError` | +| Context Management | Be aware that agents can see the full conversation history | Monitor conversation length and relevance | +| Resource Management | Consider the number of agents and task length to optimize performance | Limit max_loops and task size | + ## Contributing Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository. diff --git a/examples/multi_agent/deep_research_example.py b/examples/multi_agent/deep_research_examples/deep_research_example.py similarity index 100% rename from examples/multi_agent/deep_research_example.py rename to examples/multi_agent/deep_research_examples/deep_research_example.py diff --git a/examples/multi_agent/unique_swarms_examples.py b/examples/multi_agent/utils/unique_swarms_examples.py similarity index 100% rename from examples/multi_agent/unique_swarms_examples.py rename to examples/multi_agent/utils/unique_swarms_examples.py diff --git a/many_tool_use_demo.py b/examples/tools/multii_tool_use/many_tool_use_demo.py similarity index 100% rename from many_tool_use_demo.py rename to examples/tools/multii_tool_use/many_tool_use_demo.py diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index cc49a1fd..778a059a 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -84,6 +84,7 @@ from swarms.structs.swarming_architectures import ( staircase_swarm, star_swarm, ) +from swarms.structs.interactive_groupchat import InteractiveGroupChat __all__ = [ "Agent", @@ -156,4 +157,5 @@ __all__ = [ "aggregate", "find_agent_by_name", "run_agent", + "InteractiveGroupChat", ] diff --git a/swarms/structs/interactive_groupchat.py b/swarms/structs/interactive_groupchat.py index 4ffa5533..c8a9411b 100644 --- a/swarms/structs/interactive_groupchat.py +++ b/swarms/structs/interactive_groupchat.py @@ -1,12 +1,14 @@ -from typing import List, Union, Callable import re +from typing import Callable, List, Union + from loguru import logger + from swarms.structs.agent import Agent from swarms.structs.conversation import Conversation +from swarms.utils.generate_keys import generate_api_key from swarms.utils.history_output_formatter import ( history_output_formatter, ) -from swarms.utils.generate_keys import generate_api_key class InteractiveGroupChatError(Exception): @@ -22,13 +24,13 @@ class AgentNotFoundError(InteractiveGroupChatError): class NoMentionedAgentsError(InteractiveGroupChatError): - """Raised when no agents are mentioned in the message""" + """Raised when no agents are mentioned in the task""" pass -class InvalidMessageFormatError(InteractiveGroupChatError): - """Raised when the message format is invalid""" +class InvalidTaskFormatError(InteractiveGroupChatError): + """Raised when the task format is invalid""" pass @@ -38,7 +40,7 @@ class InteractiveGroupChat: An interactive group chat system that enables conversations with multiple agents using @mentions. This class allows users to interact with multiple agents by mentioning them using @agent_name syntax. - When multiple agents are mentioned, they can see and respond to each other's messages. + When multiple agents are mentioned, they can see and respond to each other's tasks. Attributes: name (str): Name of the group chat @@ -70,7 +72,7 @@ class InteractiveGroupChat: output_type: str = "string", interactive: bool = False, ): - self.id = id + self.id = id self.name = name self.description = description self.agents = agents @@ -175,22 +177,22 @@ class InteractiveGroupChat: f"Updated system prompt for agent: {agent.agent_name}" ) - def _extract_mentions(self, message: str) -> List[str]: + def _extract_mentions(self, task: str) -> List[str]: """ - Extracts @mentions from the message. + Extracts @mentions from the task. Args: - message (str): The input message + task (str): The input task Returns: List[str]: List of mentioned agent names Raises: - InvalidMessageFormatError: If the message format is invalid + InvalidtaskFormatError: If the task format is invalid """ try: # Find all @mentions using regex - mentions = re.findall(r"@(\w+)", message) + mentions = re.findall(r"@(\w+)", task) return [ mention for mention in mentions @@ -198,9 +200,7 @@ class InteractiveGroupChat: ] except Exception as e: logger.error(f"Error extracting mentions: {e}") - raise InvalidMessageFormatError( - f"Invalid message format: {e}" - ) + raise InvalidTaskFormatError(f"Invalid task format: {e}") def start_interactive_session(self): """ @@ -248,7 +248,7 @@ class InteractiveGroupChat: print("\nHelp:") print("1. Mention agents using @agent_name") print( - "2. You can mention multiple agents in one message" + "2. You can mention multiple agents in one task" ) print("3. Available agents:") for name in self.agent_map: @@ -261,7 +261,7 @@ class InteractiveGroupChat: if not user_input: continue - # Process the message and get responses + # Process the task and get responses try: response = self.run(user_input) print("\nChat:") @@ -285,23 +285,23 @@ class InteractiveGroupChat: "The session will continue. You can type 'exit' to end it." ) - def run(self, message: str) -> str: + def run(self, task: str) -> str: """ - Process a message and get responses from mentioned agents. + Process a task and get responses from mentioned agents. If interactive mode is enabled, this will be called by start_interactive_session(). - Otherwise, it can be called directly for single message processing. + Otherwise, it can be called directly for single task processing. """ try: # Extract mentioned agents - mentioned_agents = self._extract_mentions(message) + mentioned_agents = self._extract_mentions(task) if not mentioned_agents: raise NoMentionedAgentsError( - "No valid agents mentioned in the message" + "No valid agents mentioned in the task" ) - # Add user message to conversation - self.conversation.add(role="User", content=message) + # Add user task to conversation + self.conversation.add(role="User", content=task) # Get responses from mentioned agents for agent_name in mentioned_agents: @@ -320,7 +320,7 @@ class InteractiveGroupChat: # Get response from agent if isinstance(agent, Agent): response = agent.run( - task=f"{context}\nPlease respond to the latest message as {agent_name}." + task=f"{context}\nPlease respond to the latest task as {agent_name}." ) else: # For callable functions From 156f98a2c2375f1662a51beb3330edbab6e697e4 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Sun, 8 Jun 2025 23:01:50 -0700 Subject: [PATCH 09/13] cookbook index --- docs/examples/cookbook_index.md | 58 +++ docs/mkdocs.yml | 1 + swarms/schemas/agent_rag_schema.py | 7 + swarms/structs/agent.py | 51 +-- swarms/structs/agent_rag_handler.py | 682 ++++++++++++++++++++++++++++ 5 files changed, 766 insertions(+), 33 deletions(-) create mode 100644 docs/examples/cookbook_index.md create mode 100644 swarms/schemas/agent_rag_schema.py create mode 100644 swarms/structs/agent_rag_handler.py diff --git a/docs/examples/cookbook_index.md b/docs/examples/cookbook_index.md new file mode 100644 index 00000000..b16aee96 --- /dev/null +++ b/docs/examples/cookbook_index.md @@ -0,0 +1,58 @@ +# Swarms Cookbook Examples Index + +This index provides a categorized list of examples and tutorials for using the Swarms Framework across different industries. Each example demonstrates practical applications and implementations using the framework. + +## Finance & Trading + +| Name | Description | Link | +|------|-------------|------| +| Tickr-Agent | Financial analysis agent for stock market data using multithreaded processing and AI integration | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/finance/multi_agent/Swarms_Cookbook_Tickr_Agent.ipynb) | +| CryptoAgent | Real-time cryptocurrency data analysis and insights using CoinGecko integration | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/finance/multi_agent/Swarms_Cookbook_CryptoAgent.ipynb) | +| 10-K Analysis (Custom) | Detailed analysis of SEC 10-K reports using specialized agents | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/finance/multi_agent/swarms_finance_10k_analysis_custom.ipynb) | +| 10-K Analysis (AgentRearrange) | Mixed sequential and parallel analysis of 10-K reports | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/finance/multi_agent/swarms_finance_10k_analysis_agentrearrange.ipynb) | + +## Healthcare & Medical + +| Name | Description | Link | +|------|-------------|------| +| MedInsight Pro | Medical research summarization and analysis using AI-driven agents | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/medical/physical_therapy/Swarms_Cookbook_MedInsight_Pro.ipynb) | +| Athletics Diagnosis | Diagnosis and treatment system for extreme athletics using AgentRearrange | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/medical/physical_therapy/swarms_diagnosis_treatment_extreme_athletics.ipynb) | + +## Marketing & Content + +| Name | Description | Link | +|------|-------------|------| +| NewsAgent | Real-time news aggregation and summarization for business intelligence | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/marketing/news/Swarms_Cookbook_NewsAgent.ipynb) | +| Social Media Marketing | Spreadsheet-based content generation for multi-platform marketing | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/marketing/content_generation/swarms_spreadsheet_analysis_walkthrough.ipynb) | + +## Accounting & Finance Operations + +| Name | Description | Link | +|------|-------------|------| +| Accounting Agents | Multi-agent system for financial projections and risk assessment | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/enterprise/accounting/multi_agent/accounting_agents_for_moa.ipynb) | + +## Workshops & Tutorials + +| Name | Description | Link | +|------|-------------|------| +| GPTuesday Event | Example of creating promotional content for tech events | [View Example](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/cookbook/workshops/sep_6_workshop/gptuesday_swarm.py) | + +## Additional Resources + +| Platform | Link | Description | +|----------|------|-------------| +| 📚 Documentation | [docs.swarms.world](https://docs.swarms.world) | Official documentation and guides | +| 📝 Blog | [Medium](https://medium.com/@kyeg) | Latest updates and technical articles | +| 💬 Discord | [Join Discord](https://discord.gg/jM3Z6M9uMq) | Live chat and community support | +| 🐦 Twitter | [@kyegomez](https://twitter.com/kyegomez) | Latest news and announcements | +| 👥 LinkedIn | [The Swarm Corporation](https://www.linkedin.com/company/the-swarm-corporation) | Professional network and updates | +| 📺 YouTube | [Swarms Channel](https://www.youtube.com/channel/UC9yXyitkbU_WSy7bd_41SqQ) | Tutorials and demos | +| 🎫 Events | [Sign up here](https://lu.ma/5p2jnc2v) | Join our community events | + +## Contributing + +We welcome contributions! If you have an example or tutorial you'd like to add, please check our [contribution guidelines](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/CONTRIBUTING.md). + +## License + +This project is licensed under the MIT License - see the [LICENSE](https://github.com/The-Swarm-Corporation/Cookbook/blob/main/LICENSE) file for details. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 967a3cc7..d4e9725c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -307,6 +307,7 @@ nav: - Examples: - Overview: "examples/index.md" + - CookBook Index: "examples/cookbook_index.md" - Customizing Agents: - Basic Agent: "swarms/examples/basic_agent.md" - Agents with Callable Tools: "swarms/examples/agent_with_tools.md" diff --git a/swarms/schemas/agent_rag_schema.py b/swarms/schemas/agent_rag_schema.py new file mode 100644 index 00000000..817abc5a --- /dev/null +++ b/swarms/schemas/agent_rag_schema.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class AgentRAGConfig(BaseModel): + """ + Configuration for the AgentRAG class. + """ diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index fc0dc9cd..eae93084 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -41,6 +41,10 @@ from swarms.schemas.base_schemas import ( ChatMessageResponse, ) from swarms.schemas.llm_agent_schema import ModelConfigOrigin +from swarms.structs.agent_rag_handler import ( + RAGConfig, + AgentRAGHandler, +) from swarms.structs.agent_roles import agent_roles from swarms.structs.conversation import Conversation from swarms.structs.safe_loading import ( @@ -317,7 +321,7 @@ class Agent: pdf_path: Optional[str] = None, list_of_pdf: Optional[str] = None, tokenizer: Optional[Any] = None, - long_term_memory: Optional[Any] = None, + long_term_memory: Optional[Union[Callable, Any]] = None, preset_stopping_token: Optional[bool] = False, traceback: Optional[Any] = None, traceback_handlers: Optional[Any] = None, @@ -411,6 +415,7 @@ class Agent: aditional_llm_config: Optional[ModelConfigOrigin] = None, llm_base_url: Optional[str] = None, llm_api_key: Optional[str] = None, + rag_config: Optional[RAGConfig] = None, *args, **kwargs, ): @@ -541,6 +546,7 @@ class Agent: self.aditional_llm_config = aditional_llm_config self.llm_base_url = llm_base_url self.llm_api_key = llm_api_key + self.rag_config = rag_config # self.short_memory = self.short_memory_init() @@ -582,6 +588,17 @@ class Agent: if self.random_models_on is True: self.model_name = set_random_models_for_agents() + if self.long_term_memory is not None: + self.rag_handler = self.rag_setup_handling() + + def rag_setup_handling(self): + return AgentRAGHandler( + long_term_memory=self.long_term_memory, + config=self.rag_config, + agent_name=self.agent_name, + verbose=self.verbose, + ) + def tool_handling(self): self.tool_struct = BaseTool( @@ -1053,35 +1070,6 @@ class Agent: # Check and execute tools if exists(self.tools): - # out = self.parse_and_execute_tools( - # response - # ) - - # self.short_memory.add( - # role="Tool Executor", content=out - # ) - - # if self.no_print is False: - # agent_print( - # f"{self.agent_name} - Tool Executor", - # out, - # loop_count, - # self.streaming_on, - # ) - - # out = self.call_llm(task=out) - - # self.short_memory.add( - # role=self.agent_name, content=out - # ) - - # if self.no_print is False: - # agent_print( - # f"{self.agent_name} - Agent Analysis", - # out, - # loop_count, - # self.streaming_on, - # ) self.execute_tools( response=response, @@ -1166,9 +1154,6 @@ class Agent: log_agent_data(self.to_dict()) - if self.autosave: - self.save() - # Output formatting based on output_type return history_output_formatter( self.short_memory, type=self.output_type diff --git a/swarms/structs/agent_rag_handler.py b/swarms/structs/agent_rag_handler.py new file mode 100644 index 00000000..eb030f84 --- /dev/null +++ b/swarms/structs/agent_rag_handler.py @@ -0,0 +1,682 @@ +import time +from typing import Any, Dict, List, Optional + +from loguru import logger +from swarms.utils.litellm_tokenizer import count_tokens +from pydantic import BaseModel, Field, field_validator + + +class RAGConfig(BaseModel): + """Configuration class for RAG operations""" + + similarity_threshold: float = Field( + default=0.7, + ge=0.0, + le=1.0, + description="Similarity threshold for memory retrieval", + ) + max_results: int = Field( + default=5, + gt=0, + description="Maximum number of results to return from memory", + ) + context_window_tokens: int = Field( + default=2000, + gt=0, + description="Maximum number of tokens in the context window", + ) + auto_save_to_memory: bool = Field( + default=True, + description="Whether to automatically save responses to memory", + ) + save_every_n_loops: int = Field( + default=5, gt=0, description="Save to memory every N loops" + ) + min_content_length: int = Field( + default=50, + gt=0, + description="Minimum content length to save to memory", + ) + query_every_loop: bool = Field( + default=False, + description="Whether to query memory every loop", + ) + enable_conversation_summaries: bool = Field( + default=True, + description="Whether to enable conversation summaries", + ) + relevance_keywords: Optional[List[str]] = Field( + default=None, description="Keywords to check for relevance" + ) + + @field_validator("relevance_keywords", pre=True) + def set_default_keywords(cls, v): + if v is None: + return [ + "important", + "key", + "critical", + "summary", + "conclusion", + ] + return v + + class Config: + arbitrary_types_allowed = True + validate_assignment = True + json_schema_extra = { + "example": { + "similarity_threshold": 0.7, + "max_results": 5, + "context_window_tokens": 2000, + "auto_save_to_memory": True, + "save_every_n_loops": 5, + "min_content_length": 50, + "query_every_loop": False, + "enable_conversation_summaries": True, + "relevance_keywords": [ + "important", + "key", + "critical", + "summary", + "conclusion", + ], + } + } + + +class AgentRAGHandler: + """ + Handles all RAG (Retrieval-Augmented Generation) operations for agents. + Provides memory querying, storage, and context management capabilities. + """ + + def __init__( + self, + long_term_memory: Optional[Any] = None, + config: Optional[RAGConfig] = None, + agent_name: str = "Unknown", + max_context_length: int = 158_000, + verbose: bool = False, + ): + """ + Initialize the RAG handler. + + Args: + long_term_memory: The long-term memory store (must implement add() and query() methods) + config: RAG configuration settings + agent_name: Name of the agent using this handler + verbose: Enable verbose logging + """ + self.long_term_memory = long_term_memory + self.config = config or RAGConfig() + self.agent_name = agent_name + self.verbose = verbose + self.max_context_length = max_context_length + + self._loop_counter = 0 + self._conversation_history = [] + self._important_memories = [] + + # Validate memory interface + if ( + self.long_term_memory + and not self._validate_memory_interface() + ): + logger.warning( + "Long-term memory doesn't implement required interface" + ) + + def _validate_memory_interface(self) -> bool: + """Validate that the memory object has required methods""" + required_methods = ["add", "query"] + for method in required_methods: + if not hasattr(self.long_term_memory, method): + logger.error( + f"Memory object missing required method: {method}" + ) + return False + return True + + def is_enabled(self) -> bool: + """Check if RAG is enabled (has valid memory store)""" + return self.long_term_memory is not None + + def query_memory( + self, + query: str, + context_type: str = "general", + loop_count: Optional[int] = None, + ) -> str: + """ + Query the long-term memory and return formatted context. + + Args: + query: The query string to search for + context_type: Type of context being queried (for logging) + loop_count: Current loop number (for logging) + + Returns: + Formatted string of relevant memories, empty string if no results + """ + if not self.is_enabled(): + return "" + + try: + if self.verbose: + logger.info( + f"🔍 [{self.agent_name}] Querying RAG for {context_type}: {query[:100]}..." + ) + + # Query the memory store + results = self.long_term_memory.query( + query=query, + top_k=self.config.max_results, + similarity_threshold=self.config.similarity_threshold, + ) + + if not results: + if self.verbose: + logger.info( + f"No relevant memories found for query: {context_type}" + ) + return "" + + # Format results for context + formatted_context = self._format_memory_results( + results, context_type, loop_count + ) + + # Ensure context fits within token limits + if ( + count_tokens(formatted_context) + > self.config.context_window_tokens + ): + formatted_context = self._truncate_context( + formatted_context + ) + + if self.verbose: + logger.info( + f"✅ Retrieved {len(results)} relevant memories for {context_type}" + ) + + return formatted_context + + except Exception as e: + logger.error(f"Error querying long-term memory: {e}") + return "" + + def _format_memory_results( + self, + results: List[Any], + context_type: str, + loop_count: Optional[int] = None, + ) -> str: + """Format memory results into a structured context string""" + if not results: + return "" + + loop_info = f" (Loop {loop_count})" if loop_count else "" + header = ( + f"📚 Relevant Knowledge - {context_type.title()}{loop_info}:\n" + + "=" * 50 + + "\n" + ) + + formatted_sections = [header] + + for i, result in enumerate(results, 1): + content, score, source, metadata = ( + self._extract_result_fields(result) + ) + + section = f""" +[Memory {i}] Relevance: {score} | Source: {source} +{'-' * 40} +{content} +{'-' * 40} +""" + formatted_sections.append(section) + + formatted_sections.append(f"\n{'='*50}\n") + return "\n".join(formatted_sections) + + def _extract_result_fields(self, result: Any) -> tuple: + """Extract content, score, source, and metadata from a result object""" + if isinstance(result, dict): + content = result.get( + "content", result.get("text", str(result)) + ) + score = result.get( + "score", result.get("similarity", "N/A") + ) + metadata = result.get("metadata", {}) + source = metadata.get( + "source", result.get("source", "Unknown") + ) + else: + content = str(result) + score = "N/A" + source = "Unknown" + metadata = {} + + return content, score, source, metadata + + def _truncate_context(self, content: str) -> str: + """Truncate content to fit within token limits using smart truncation""" + max_chars = ( + self.config.context_window_tokens * 3 + ) # Rough token-to-char ratio + + if len(content) <= max_chars: + return content + + # Try to cut at section boundaries first + sections = content.split("=" * 50) + if len(sections) > 2: # Header + sections + footer + truncated_sections = [sections[0]] # Keep header + current_length = len(sections[0]) + + for section in sections[1:-1]: # Skip footer + if current_length + len(section) > max_chars * 0.9: + break + truncated_sections.append(section) + current_length += len(section) + + truncated_sections.append( + f"\n[... {len(sections) - len(truncated_sections)} more memories truncated for length ...]\n" + ) + truncated_sections.append(sections[-1]) # Keep footer + return "=" * (50).join(truncated_sections) + + # Fallback: simple truncation at sentence boundary + truncated = content[:max_chars] + last_period = truncated.rfind(".") + if last_period > max_chars * 0.8: + truncated = truncated[: last_period + 1] + + return ( + truncated + "\n\n[... content truncated for length ...]" + ) + + def should_save_response( + self, + response: str, + loop_count: int, + has_tool_usage: bool = False, + ) -> bool: + """ + Determine if a response should be saved to long-term memory. + + Args: + response: The response text to evaluate + loop_count: Current loop number + has_tool_usage: Whether tools were used in this response + + Returns: + Boolean indicating whether to save the response + """ + if ( + not self.is_enabled() + or not self.config.auto_save_to_memory + ): + return False + + # Content length check + if len(response.strip()) < self.config.min_content_length: + return False + + save_conditions = [ + # Substantial content + len(response) > 200, + # Contains important keywords + any( + keyword in response.lower() + for keyword in self.config.relevance_keywords + ), + # Periodic saves + loop_count % self.config.save_every_n_loops == 0, + # Tool usage indicates potentially important information + has_tool_usage, + # Complex responses (multiple sentences) + response.count(".") >= 3, + # Contains structured data or lists + any( + marker in response + for marker in ["- ", "1. ", "2. ", "* ", "```"] + ), + ] + + return any(save_conditions) + + def save_to_memory( + self, + content: str, + metadata: Optional[Dict] = None, + content_type: str = "response", + ) -> bool: + """ + Save content to long-term memory with metadata. + + Args: + content: The content to save + metadata: Additional metadata to store + content_type: Type of content being saved + + Returns: + Boolean indicating success + """ + if not self.is_enabled(): + return False + + if ( + not content + or len(content.strip()) < self.config.min_content_length + ): + return False + + try: + # Create default metadata + default_metadata = { + "timestamp": time.time(), + "agent_name": self.agent_name, + "content_type": content_type, + "loop_count": self._loop_counter, + "saved_at": time.strftime("%Y-%m-%d %H:%M:%S"), + } + + # Merge with provided metadata + if metadata: + default_metadata.update(metadata) + + if self.verbose: + logger.info( + f"💾 [{self.agent_name}] Saving to long-term memory: {content[:100]}..." + ) + + success = self.long_term_memory.add( + content, metadata=default_metadata + ) + + if success and self.verbose: + logger.info( + f"✅ Successfully saved {content_type} to long-term memory" + ) + + # Track important memories + if content_type in [ + "final_response", + "conversation_summary", + ]: + self._important_memories.append( + { + "content": content[:200], + "timestamp": time.time(), + "type": content_type, + } + ) + + return success + + except Exception as e: + logger.error(f"Error saving to long-term memory: {e}") + return False + + def create_conversation_summary( + self, + task: str, + final_response: str, + total_loops: int, + tools_used: List[str] = None, + ) -> str: + """Create a comprehensive summary of the conversation""" + tools_info = ( + f"Tools Used: {', '.join(tools_used)}" + if tools_used + else "Tools Used: None" + ) + + summary = f""" +CONVERSATION SUMMARY +==================== +Agent: {self.agent_name} +Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S')} + +ORIGINAL TASK: +{task} + +FINAL RESPONSE: +{final_response} + +EXECUTION DETAILS: +- Total Reasoning Loops: {total_loops} +- {tools_info} +- Memory Queries Made: {len(self._conversation_history)} + +KEY INSIGHTS: +{self._extract_key_insights(final_response)} +==================== +""" + return summary + + def _extract_key_insights(self, response: str) -> str: + """Extract key insights from the response for summary""" + # Simple keyword-based extraction + insights = [] + sentences = response.split(".") + + for sentence in sentences: + if any( + keyword in sentence.lower() + for keyword in self.config.relevance_keywords[:5] + ): + insights.append(sentence.strip()) + + if insights: + return "\n- " + "\n- ".join( + insights[:3] + ) # Top 3 insights + return "No specific insights extracted" + + def handle_loop_memory_operations( + self, + task: str, + response: str, + loop_count: int, + conversation_context: str = "", + has_tool_usage: bool = False, + ) -> str: + """ + Handle all memory operations for a single loop iteration. + + Args: + task: Original task + response: Current response + loop_count: Current loop number + conversation_context: Current conversation context + has_tool_usage: Whether tools were used + + Returns: + Retrieved context string (empty if no relevant memories) + """ + self._loop_counter = loop_count + retrieved_context = "" + + # 1. Query memory if enabled for this loop + if self.config.query_every_loop and loop_count > 1: + query_context = f"Task: {task}\nCurrent Context: {conversation_context[-500:]}" + retrieved_context = self.query_memory( + query_context, + context_type=f"loop_{loop_count}", + loop_count=loop_count, + ) + + # 2. Save response if criteria met + if self.should_save_response( + response, loop_count, has_tool_usage + ): + self.save_to_memory( + content=response, + metadata={ + "task_preview": task[:200], + "loop_count": loop_count, + "has_tool_usage": has_tool_usage, + }, + content_type="loop_response", + ) + + return retrieved_context + + def handle_initial_memory_query(self, task: str) -> str: + """Handle the initial memory query before reasoning loops begin""" + if not self.is_enabled(): + return "" + + return self.query_memory(task, context_type="initial_task") + + def handle_final_memory_consolidation( + self, + task: str, + final_response: str, + total_loops: int, + tools_used: List[str] = None, + ) -> bool: + """Handle final memory consolidation after all loops complete""" + if ( + not self.is_enabled() + or not self.config.enable_conversation_summaries + ): + return False + + # Create and save conversation summary + summary = self.create_conversation_summary( + task, final_response, total_loops, tools_used + ) + + return self.save_to_memory( + content=summary, + metadata={ + "task": task[:200], + "total_loops": total_loops, + "tools_used": tools_used or [], + }, + content_type="conversation_summary", + ) + + def search_memories( + self, + query: str, + top_k: int = None, + similarity_threshold: float = None, + ) -> List[Dict]: + """ + Search long-term memory and return raw results. + + Args: + query: Search query + top_k: Number of results to return (uses config default if None) + similarity_threshold: Similarity threshold (uses config default if None) + + Returns: + List of memory results + """ + if not self.is_enabled(): + return [] + + try: + results = self.long_term_memory.query( + query=query, + top_k=top_k or self.config.max_results, + similarity_threshold=similarity_threshold + or self.config.similarity_threshold, + ) + return results if results else [] + except Exception as e: + logger.error(f"Error searching memories: {e}") + return [] + + def get_memory_stats(self) -> Dict[str, Any]: + """Get statistics about memory usage and operations""" + return { + "is_enabled": self.is_enabled(), + "config": self.config.__dict__, + "loops_processed": self._loop_counter, + "important_memories_count": len(self._important_memories), + "last_important_memories": ( + self._important_memories[-3:] + if self._important_memories + else [] + ), + "memory_store_type": ( + type(self.long_term_memory).__name__ + if self.long_term_memory + else None + ), + } + + def clear_session_data(self): + """Clear session-specific data (not the long-term memory store)""" + self._loop_counter = 0 + self._conversation_history.clear() + self._important_memories.clear() + + if self.verbose: + logger.info(f"[{self.agent_name}] Session data cleared") + + def update_config(self, **kwargs): + """Update RAG configuration parameters""" + for key, value in kwargs.items(): + if hasattr(self.config, key): + setattr(self.config, key, value) + if self.verbose: + logger.info( + f"Updated RAG config: {key} = {value}" + ) + else: + logger.warning(f"Unknown config parameter: {key}") + + +# # Example memory interface that your RAG implementation should follow +# class ExampleMemoryInterface: +# """Example interface for long-term memory implementations""" + +# def add(self, content: str, metadata: Dict = None) -> bool: +# """ +# Add content to the memory store. + +# Args: +# content: Text content to store +# metadata: Additional metadata dictionary + +# Returns: +# Boolean indicating success +# """ +# # Your vector database implementation here +# return True + +# def query( +# self, +# query: str, +# top_k: int = 5, +# similarity_threshold: float = 0.7 +# ) -> List[Dict]: +# """ +# Query the memory store for relevant content. + +# Args: +# query: Search query string +# top_k: Maximum number of results to return +# similarity_threshold: Minimum similarity score + +# Returns: +# List of dictionaries with keys: 'content', 'score', 'metadata' +# """ +# # Your vector database query implementation here +# return [ +# { +# 'content': 'Example memory content', +# 'score': 0.85, +# 'metadata': {'source': 'example', 'timestamp': time.time()} +# } +# ] From bf26ade9316ef9feb1e3080706bdfa9661cb01e6 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Mon, 9 Jun 2025 23:13:08 +0530 Subject: [PATCH 10/13] [FEAT][DeepResearchSwarm] Enhance API search functionality and add result parsing/displaying features --- swarms/structs/deep_research_swarm.py | 265 ++++++++++++++++++++------ 1 file changed, 207 insertions(+), 58 deletions(-) diff --git a/swarms/structs/deep_research_swarm.py b/swarms/structs/deep_research_swarm.py index b5237ea1..a29f2153 100644 --- a/swarms/structs/deep_research_swarm.py +++ b/swarms/structs/deep_research_swarm.py @@ -1,12 +1,18 @@ import asyncio import concurrent.futures +import json import os from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from pathlib import Path from typing import Any, Dict, List, Tuple import aiohttp from dotenv import load_dotenv from rich.console import Console +from rich.panel import Panel +from rich.text import Text +from rich.tree import Tree from swarms.agents.reasoning_duo import ReasoningDuo from swarms.structs.agent import Agent @@ -116,21 +122,37 @@ async def _async_exa_search( ) -> Dict[str, Any]: """Asynchronous helper function for Exa.ai API requests""" api_url = "https://api.exa.ai/search" + + # Check if API key is available + api_key = os.getenv("EXA_API_KEY") + if not api_key: + return {"error": "EXA_API_KEY environment variable not set"} + headers = { - "x-api-key": os.getenv("EXA_API_KEY"), + "x-api-key": api_key, "Content-Type": "application/json", } + # Filter out None keys AND None values from kwargs + safe_kwargs = { + str(k): v for k, v in kwargs.items() + if k is not None and v is not None and str(k) != "None" + } + payload = { "query": query, "useAutoprompt": True, - "numResults": kwargs.get("num_results", 10), + "numResults": safe_kwargs.get("num_results", 10), "contents": { "text": True, "highlights": {"numSentences": 2}, }, - **kwargs, } + + # Only add safe_kwargs if they don't conflict with existing keys + for key, value in safe_kwargs.items(): + if key not in payload and key not in ["query", "useAutoprompt", "numResults", "contents"]: + payload[key] = value try: async with aiohttp.ClientSession() as session: @@ -370,24 +392,20 @@ class DeepResearchSwarm: return [] - def _process_query(self, query: str) -> Tuple[str, str]: + def _process_query(self, query: str) -> str: """ - Process a single query with search and reasoning. + Process a single query with search only. This function is designed to be run in a separate thread. Args: query (str): The query to process Returns: - Tuple[str, str]: A tuple containing (search_results, reasoning_output) + str: Search results """ - # Run the search + # Run the search only - no individual reasoning to avoid duplication results = exa_search(query) - - # Run the reasoning on the search results - reasoning_output = self.reasoning_duo.run(results) - - return (results, reasoning_output) + return results def step(self, query: str): """ @@ -399,54 +417,85 @@ class DeepResearchSwarm: Returns: Formatted conversation history """ - # Get all the queries to process - queries = self.get_queries(query) - - # Submit all queries for concurrent processing - # Using a list instead of generator for clearer debugging - futures = [] - for q in queries: - future = self.executor.submit(self._process_query, q) - futures.append((q, future)) + try: + # Get all the queries to process + queries = self.get_queries(query) + + if not queries: + error_msg = "No queries generated. Please check your input." + self.conversation.add(role="System", content=error_msg) + return history_output_formatter( + self.conversation, type=self.output_type + ) - # Process results as they complete (no waiting for slower queries) - for q, future in futures: + # Submit all queries for concurrent processing + futures = [] + for q in queries: + future = self.executor.submit(self._process_query, q) + futures.append((q, future)) + + # Process results as they complete + for q, future in futures: + try: + # Get search results only + results = future.result() + + # Add search results to conversation + self.conversation.add( + role="User", + content=f"Search results for {q}: \n {results}", + ) + + except Exception as e: + # Handle any errors in the thread + error_msg = f"Error processing query '{q}': {str(e)}" + console.print(f"[bold red]{error_msg}[/bold red]") + self.conversation.add( + role="System", + content=error_msg, + ) + + # Generate final comprehensive analysis after all searches are complete try: - # Get results (blocks until this specific future is done) - results, reasoning_output = future.result() - - # Add search results to conversation - self.conversation.add( - role="User", - content=f"Search results for {q}: \n {results}", + final_summary = self.reasoning_duo.run( + f"Generate an extensive report of the following content: {self.conversation.get_str()}" ) - # Add reasoning output to conversation self.conversation.add( role=self.reasoning_duo.agent_name, - content=reasoning_output, + content=final_summary, ) except Exception as e: - # Handle any errors in the thread + error_msg = f"Error generating final summary: {str(e)}" + console.print(f"[bold red]{error_msg}[/bold red]") self.conversation.add( role="System", - content=f"Error processing query '{q}': {str(e)}", + content=error_msg, ) - # Once all query processing is complete, generate the final summary - # This step runs after all queries to ensure it summarizes all results - final_summary = self.reasoning_duo.run( - f"Generate an extensive report of the following content: {self.conversation.get_str()}" - ) - - self.conversation.add( - role=self.reasoning_duo.agent_name, - content=final_summary, - ) - - return history_output_formatter( - self.conversation, type=self.output_type - ) + # Return formatted output + result = history_output_formatter( + self.conversation, type=self.output_type + ) + + # If output type is JSON, ensure it's properly formatted + if self.output_type.lower() == "json": + try: + import json + if isinstance(result, str): + # Try to parse and reformat for pretty printing + parsed = json.loads(result) + return json.dumps(parsed, indent=2, ensure_ascii=False) + except (json.JSONDecodeError, TypeError): + # If parsing fails, return as-is + pass + + return result + + except Exception as e: + error_msg = f"Critical error in step execution: {str(e)}" + console.print(f"[bold red]{error_msg}[/bold red]") + return {"error": error_msg} if self.output_type.lower() == "json" else error_msg def run(self, task: str): return self.step(task) @@ -466,14 +515,114 @@ class DeepResearchSwarm: future = self.executor.submit(self.step, task) futures.append((task, future)) + def parse_and_display_results(self, json_result: str, export_markdown: bool = True): + """ + Parse JSON results and display in rich format with optional markdown export. + + Args: + json_result (str): JSON string containing conversation results + export_markdown (bool): Whether to export to markdown file + """ + try: + # Parse JSON + data = json.loads(json_result) + + # Create rich display + console.print("\n" + "="*100, style="cyan") + console.print("🔬 DEEP RESEARCH RESULTS", style="bold cyan", justify="center") + console.print("="*100, style="cyan") + + # Create conversation tree + tree = Tree("🗣️ Research Conversation", style="bold blue") + markdown_content = ["# Deep Research Results\n", f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"] + + for i, entry in enumerate(data, 1): + if isinstance(entry, dict): + role = entry.get('role', 'Unknown') + content = entry.get('content', '') + timestamp = entry.get('timestamp', '') + + # Get role info for display + role_info = self._get_role_display_info(role) + + # Create tree branch + branch_text = f"{role_info['emoji']} {role}" + if timestamp: + time_part = timestamp.split()[-1] if ' ' in timestamp else timestamp[-8:] + branch_text += f" ({time_part})" + + branch = tree.add(branch_text, style=role_info['style']) + + # Add content preview to tree + content_preview = content[:150] + "..." if len(content) > 150 else content + content_preview = content_preview.replace('\n', ' ') + branch.add(content_preview, style="dim") + + # Add to markdown + markdown_content.append(f"\n## {i}. {role}") + if timestamp: + markdown_content.append(f"**Timestamp:** {timestamp}") + markdown_content.append(f"\n{content}\n") + + # Display full content for important entries + if role.lower() in ['reasoning-agent-01'] and len(content) > 300: + console.print(f"\n📋 {role} Full Response:", style="bold green") + console.print(Panel(content, border_style="green", title=f"{role} Analysis")) + + # Display the tree + console.print(tree) + + # Export to markdown if requested + if export_markdown: + # Create deepsearch_results directory + results_dir = Path("deepsearch_results") + results_dir.mkdir(exist_ok=True) + + # Generate filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = results_dir / f"research_results_{timestamp}.md" + + # Write markdown file + with open(filename, 'w', encoding='utf-8') as f: + f.write('\n'.join(markdown_content)) + + console.print(f"\n💾 Results exported to: {filename}", style="bold green") + + console.print("\n✅ Research analysis complete!", style="bold cyan") + + except json.JSONDecodeError as e: + console.print(f"❌ Error parsing JSON: {e}", style="red") + except Exception as e: + console.print(f"❌ Error displaying results: {e}", style="red") + + def _get_role_display_info(self, role: str) -> Dict[str, str]: + """Get display information for different conversation roles.""" + role_map = { + "user": {"emoji": "👤", "style": "cyan"}, + "deep-research-agent": {"emoji": "🔍", "style": "blue"}, + "reasoning-agent-01": {"emoji": "🧠", "style": "magenta"}, + "system": {"emoji": "⚙️", "style": "yellow"}, + } + + role_lower = role.lower() + return role_map.get(role_lower, {"emoji": "🤖", "style": "white"}) + -# # Example usage -# if __name__ == "__main__": -# swarm = DeepResearchSwarm( -# output_type="json", -# ) -# print( -# swarm.step( -# "What is the active tarrif situation with mexico? Only create 2 queries" -# ) -# ) +# Example usage + +if __name__ == "__main__": + try: + swarm = DeepResearchSwarm( + output_type="json", + ) + result = swarm.step( + "What is the active tariff situation with mexico? Only create 2 queries" + ) + + # Parse and display results in rich format with markdown export + swarm.parse_and_display_results(result, export_markdown=True) + + except Exception as e: + print(f"Error running deep research swarm: {str(e)}") + import traceback + traceback.print_exc() From 7a40494101ca22b2c82d54e07b5e6bac1010c028 Mon Sep 17 00:00:00 2001 From: harshalmore31 Date: Mon, 9 Jun 2025 23:15:35 +0530 Subject: [PATCH 11/13] refactor: example usage in deep_research_swarm.py --- swarms/structs/deep_research_swarm.py | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/swarms/structs/deep_research_swarm.py b/swarms/structs/deep_research_swarm.py index a29f2153..530dc2ff 100644 --- a/swarms/structs/deep_research_swarm.py +++ b/swarms/structs/deep_research_swarm.py @@ -609,20 +609,19 @@ class DeepResearchSwarm: # Example usage - -if __name__ == "__main__": - try: - swarm = DeepResearchSwarm( - output_type="json", - ) - result = swarm.step( - "What is the active tariff situation with mexico? Only create 2 queries" - ) +# if __name__ == "__main__": +# try: +# swarm = DeepResearchSwarm( +# output_type="json", +# ) +# result = swarm.step( +# "What is the active tariff situation with mexico? Only create 2 queries" +# ) - # Parse and display results in rich format with markdown export - swarm.parse_and_display_results(result, export_markdown=True) +# # Parse and display results in rich format with markdown export +# swarm.parse_and_display_results(result, export_markdown=True) - except Exception as e: - print(f"Error running deep research swarm: {str(e)}") - import traceback - traceback.print_exc() +# except Exception as e: +# print(f"Error running deep research swarm: {str(e)}") +# import traceback +# traceback.print_exc() From 8c315ca61e0775f7f2a3797102215b80cc0ef50a Mon Sep 17 00:00:00 2001 From: Pavan Kumar <66913595+ascender1729@users.noreply.github.com> Date: Tue, 10 Jun 2025 01:28:43 +0530 Subject: [PATCH 12/13] Remove empty documentation files --- docs/applications/compliance_swarm.md | 0 docs/applications/enterprise.md | 0 docs/swarms/examples/agent_with_mcp.md | 0 docs/swarms/models/langchain.md | 0 docs/swarms/prompts/8020.md | 0 docs/swarms/structs/various_swarm_architectures.md | 0 docs/swarms_platform/share_discover.md | 0 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/applications/compliance_swarm.md delete mode 100644 docs/applications/enterprise.md delete mode 100644 docs/swarms/examples/agent_with_mcp.md delete mode 100644 docs/swarms/models/langchain.md delete mode 100644 docs/swarms/prompts/8020.md delete mode 100644 docs/swarms/structs/various_swarm_architectures.md delete mode 100644 docs/swarms_platform/share_discover.md diff --git a/docs/applications/compliance_swarm.md b/docs/applications/compliance_swarm.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/applications/enterprise.md b/docs/applications/enterprise.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/swarms/examples/agent_with_mcp.md b/docs/swarms/examples/agent_with_mcp.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/swarms/models/langchain.md b/docs/swarms/models/langchain.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/swarms/prompts/8020.md b/docs/swarms/prompts/8020.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/swarms/structs/various_swarm_architectures.md b/docs/swarms/structs/various_swarm_architectures.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/swarms_platform/share_discover.md b/docs/swarms_platform/share_discover.md deleted file mode 100644 index e69de29b..00000000 From 1c36a1340e36c66c7c0c4fc46b65bdd42648f145 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Mon, 9 Jun 2025 13:00:16 -0700 Subject: [PATCH 13/13] [DOCS][FIX] [interactive group chat] --- docs/swarms/structs/interactive_groupchat.md | 2 + swarms/structs/deep_research_swarm.py | 188 +++++++++++++------ 2 files changed, 129 insertions(+), 61 deletions(-) diff --git a/docs/swarms/structs/interactive_groupchat.md b/docs/swarms/structs/interactive_groupchat.md index 207e2a50..2404af02 100644 --- a/docs/swarms/structs/interactive_groupchat.md +++ b/docs/swarms/structs/interactive_groupchat.md @@ -25,6 +25,7 @@ pip install swarms Initializes a new InteractiveGroupChat instance with the specified configuration. **Arguments:** + | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `id` | str | Unique identifier for the chat | auto-generated key | @@ -36,6 +37,7 @@ Initializes a new InteractiveGroupChat instance with the specified configuration | `interactive` | bool | Whether to enable interactive mode | False | **Example:** + ```python from swarms import Agent, InteractiveGroupChat diff --git a/swarms/structs/deep_research_swarm.py b/swarms/structs/deep_research_swarm.py index 530dc2ff..c2ae096b 100644 --- a/swarms/structs/deep_research_swarm.py +++ b/swarms/structs/deep_research_swarm.py @@ -5,13 +5,12 @@ import os from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List import aiohttp from dotenv import load_dotenv from rich.console import Console from rich.panel import Panel -from rich.text import Text from rich.tree import Tree from swarms.agents.reasoning_duo import ReasoningDuo @@ -122,12 +121,12 @@ async def _async_exa_search( ) -> Dict[str, Any]: """Asynchronous helper function for Exa.ai API requests""" api_url = "https://api.exa.ai/search" - + # Check if API key is available api_key = os.getenv("EXA_API_KEY") if not api_key: return {"error": "EXA_API_KEY environment variable not set"} - + headers = { "x-api-key": api_key, "Content-Type": "application/json", @@ -135,7 +134,8 @@ async def _async_exa_search( # Filter out None keys AND None values from kwargs safe_kwargs = { - str(k): v for k, v in kwargs.items() + str(k): v + for k, v in kwargs.items() if k is not None and v is not None and str(k) != "None" } @@ -148,10 +148,15 @@ async def _async_exa_search( "highlights": {"numSentences": 2}, }, } - + # Only add safe_kwargs if they don't conflict with existing keys for key, value in safe_kwargs.items(): - if key not in payload and key not in ["query", "useAutoprompt", "numResults", "contents"]: + if key not in payload and key not in [ + "query", + "useAutoprompt", + "numResults", + "contents", + ]: payload[key] = value try: @@ -420,10 +425,14 @@ class DeepResearchSwarm: try: # Get all the queries to process queries = self.get_queries(query) - + if not queries: - error_msg = "No queries generated. Please check your input." - self.conversation.add(role="System", content=error_msg) + error_msg = ( + "No queries generated. Please check your input." + ) + self.conversation.add( + role="System", content=error_msg + ) return history_output_formatter( self.conversation, type=self.output_type ) @@ -448,7 +457,9 @@ class DeepResearchSwarm: except Exception as e: # Handle any errors in the thread - error_msg = f"Error processing query '{q}': {str(e)}" + error_msg = ( + f"Error processing query '{q}': {str(e)}" + ) console.print(f"[bold red]{error_msg}[/bold red]") self.conversation.add( role="System", @@ -466,7 +477,9 @@ class DeepResearchSwarm: content=final_summary, ) except Exception as e: - error_msg = f"Error generating final summary: {str(e)}" + error_msg = ( + f"Error generating final summary: {str(e)}" + ) console.print(f"[bold red]{error_msg}[/bold red]") self.conversation.add( role="System", @@ -477,25 +490,32 @@ class DeepResearchSwarm: result = history_output_formatter( self.conversation, type=self.output_type ) - + # If output type is JSON, ensure it's properly formatted if self.output_type.lower() == "json": try: import json + if isinstance(result, str): # Try to parse and reformat for pretty printing parsed = json.loads(result) - return json.dumps(parsed, indent=2, ensure_ascii=False) + return json.dumps( + parsed, indent=2, ensure_ascii=False + ) except (json.JSONDecodeError, TypeError): # If parsing fails, return as-is pass - + return result - + except Exception as e: error_msg = f"Critical error in step execution: {str(e)}" console.print(f"[bold red]{error_msg}[/bold red]") - return {"error": error_msg} if self.output_type.lower() == "json" else error_msg + return ( + {"error": error_msg} + if self.output_type.lower() == "json" + else error_msg + ) def run(self, task: str): return self.step(task) @@ -515,10 +535,12 @@ class DeepResearchSwarm: future = self.executor.submit(self.step, task) futures.append((task, future)) - def parse_and_display_results(self, json_result: str, export_markdown: bool = True): + def parse_and_display_results( + self, json_result: str, export_markdown: bool = True + ): """ Parse JSON results and display in rich format with optional markdown export. - + Args: json_result (str): JSON string containing conversation results export_markdown (bool): Whether to export to markdown file @@ -526,86 +548,130 @@ class DeepResearchSwarm: try: # Parse JSON data = json.loads(json_result) - + # Create rich display - console.print("\n" + "="*100, style="cyan") - console.print("🔬 DEEP RESEARCH RESULTS", style="bold cyan", justify="center") - console.print("="*100, style="cyan") - + console.print("\n" + "=" * 100, style="cyan") + console.print( + "🔬 DEEP RESEARCH RESULTS", + style="bold cyan", + justify="center", + ) + console.print("=" * 100, style="cyan") + # Create conversation tree tree = Tree("🗣️ Research Conversation", style="bold blue") - markdown_content = ["# Deep Research Results\n", f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"] - + markdown_content = [ + "# Deep Research Results\n", + f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n", + ] + for i, entry in enumerate(data, 1): if isinstance(entry, dict): - role = entry.get('role', 'Unknown') - content = entry.get('content', '') - timestamp = entry.get('timestamp', '') - + role = entry.get("role", "Unknown") + content = entry.get("content", "") + timestamp = entry.get("timestamp", "") + # Get role info for display role_info = self._get_role_display_info(role) - + # Create tree branch branch_text = f"{role_info['emoji']} {role}" if timestamp: - time_part = timestamp.split()[-1] if ' ' in timestamp else timestamp[-8:] + time_part = ( + timestamp.split()[-1] + if " " in timestamp + else timestamp[-8:] + ) branch_text += f" ({time_part})" - - branch = tree.add(branch_text, style=role_info['style']) - + + branch = tree.add( + branch_text, style=role_info["style"] + ) + # Add content preview to tree - content_preview = content[:150] + "..." if len(content) > 150 else content - content_preview = content_preview.replace('\n', ' ') + content_preview = ( + content[:150] + "..." + if len(content) > 150 + else content + ) + content_preview = content_preview.replace( + "\n", " " + ) branch.add(content_preview, style="dim") - + # Add to markdown markdown_content.append(f"\n## {i}. {role}") if timestamp: - markdown_content.append(f"**Timestamp:** {timestamp}") + markdown_content.append( + f"**Timestamp:** {timestamp}" + ) markdown_content.append(f"\n{content}\n") - + # Display full content for important entries - if role.lower() in ['reasoning-agent-01'] and len(content) > 300: - console.print(f"\n📋 {role} Full Response:", style="bold green") - console.print(Panel(content, border_style="green", title=f"{role} Analysis")) - + if ( + role.lower() in ["reasoning-agent-01"] + and len(content) > 300 + ): + console.print( + f"\n📋 {role} Full Response:", + style="bold green", + ) + console.print( + Panel( + content, + border_style="green", + title=f"{role} Analysis", + ) + ) + # Display the tree console.print(tree) - + # Export to markdown if requested if export_markdown: # Create deepsearch_results directory results_dir = Path("deepsearch_results") results_dir.mkdir(exist_ok=True) - + # Generate filename with timestamp timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = results_dir / f"research_results_{timestamp}.md" - + filename = ( + results_dir / f"research_results_{timestamp}.md" + ) + # Write markdown file - with open(filename, 'w', encoding='utf-8') as f: - f.write('\n'.join(markdown_content)) - - console.print(f"\n💾 Results exported to: {filename}", style="bold green") - - console.print("\n✅ Research analysis complete!", style="bold cyan") - + with open(filename, "w", encoding="utf-8") as f: + f.write("\n".join(markdown_content)) + + console.print( + f"\n💾 Results exported to: {filename}", + style="bold green", + ) + + console.print( + "\n✅ Research analysis complete!", style="bold cyan" + ) + except json.JSONDecodeError as e: console.print(f"❌ Error parsing JSON: {e}", style="red") except Exception as e: - console.print(f"❌ Error displaying results: {e}", style="red") + console.print( + f"❌ Error displaying results: {e}", style="red" + ) def _get_role_display_info(self, role: str) -> Dict[str, str]: """Get display information for different conversation roles.""" role_map = { "user": {"emoji": "👤", "style": "cyan"}, - "deep-research-agent": {"emoji": "🔍", "style": "blue"}, + "deep-research-agent": {"emoji": "🔍", "style": "blue"}, "reasoning-agent-01": {"emoji": "🧠", "style": "magenta"}, "system": {"emoji": "⚙️", "style": "yellow"}, } - + role_lower = role.lower() - return role_map.get(role_lower, {"emoji": "🤖", "style": "white"}) + return role_map.get( + role_lower, {"emoji": "🤖", "style": "white"} + ) # Example usage @@ -617,10 +683,10 @@ class DeepResearchSwarm: # result = swarm.step( # "What is the active tariff situation with mexico? Only create 2 queries" # ) - + # # Parse and display results in rich format with markdown export # swarm.parse_and_display_results(result, export_markdown=True) - + # except Exception as e: # print(f"Error running deep research swarm: {str(e)}") # import traceback