From 76b9f5162ee59a0731767d52e8b0a988363e2ac2 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Wed, 2 Jul 2025 15:51:16 -0700 Subject: [PATCH] [feat][interactivegroupchat][random dynamic speaker functions --- .env.example | 2 + docs/mkdocs.yml | 1 - docs/swarms/structs/interactive_groupchat.md | 341 ++++++-- .../interactive_groupchat_speaker_example.py | 163 ++++ .../medical_panel_example.py | 162 ++++ .../speaker_function_examples.py | 8 +- .../stream_example.py | 6 +- pyproject.toml | 2 +- random_dynamic_speaker_example.py | 162 ++++ swarms/structs/__init__.py | 14 +- swarms/structs/agent.py | 11 +- swarms/structs/interactive_groupchat.py | 740 ++++++++++++++---- swarms/structs/ma_utils.py | 1 + swarms/structs/swarm_router.py | 3 + swarms/utils/formatter.py | 4 +- swarms/utils/litellm_wrapper.py | 2 + 16 files changed, 1364 insertions(+), 258 deletions(-) create mode 100644 examples/multi_agent/interactive_groupchat_examples/interactive_groupchat_speaker_example.py create mode 100644 examples/multi_agent/interactive_groupchat_examples/medical_panel_example.py rename speaker_function_examples.py => examples/multi_agent/interactive_groupchat_examples/speaker_function_examples.py (86%) rename stream_example.py => examples/multi_agent/interactive_groupchat_examples/stream_example.py (73%) create mode 100644 random_dynamic_speaker_example.py diff --git a/.env.example b/.env.example index b8ccaf86..a7bd6b36 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,8 @@ GEMINI_API_KEY="" ## Hugging Face HUGGINGFACE_TOKEN="" +GROQ_API_KEY="" + ## Perplexity AI PPLX_API_KEY="" diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ec3b0b1e..a1f9d9a2 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -260,7 +260,6 @@ nav: - Examples: - Overview: "examples/index.md" - CookBook Index: "examples/cookbook_index.md" - # - PreBuilt Templates: "examples/templates_index.md" - Basic Examples: - Individual Agents: - Basic Agent: "swarms/examples/basic_agent.md" diff --git a/docs/swarms/structs/interactive_groupchat.md b/docs/swarms/structs/interactive_groupchat.md index 7dbfae9c..d57df73b 100644 --- a/docs/swarms/structs/interactive_groupchat.md +++ b/docs/swarms/structs/interactive_groupchat.md @@ -14,6 +14,10 @@ The InteractiveGroupChat is a sophisticated multi-agent system that enables inte - **Dynamic Speaker Management**: Change speaker functions and priorities during runtime +- **Random Dynamic Speaker**: Advanced speaker function that follows @mentions in agent responses + +- **Parallel and Sequential Strategies**: Support for both parallel and sequential agent execution + - **Callable Function Support**: Supports both Agent instances and callable functions as chat participants - **Comprehensive Error Handling**: Custom error classes for different scenarios @@ -22,6 +26,8 @@ The InteractiveGroupChat is a sophisticated multi-agent system that enables inte - **Flexible Output Formatting**: Configurable output format for conversation history +- **Interactive Terminal Mode**: Full REPL interface for real-time chat with agents + ## Installation ```bash @@ -46,7 +52,7 @@ Initializes a new InteractiveGroupChat instance with the specified configuration | `max_loops` | int | Maximum conversation turns | 1 | | `output_type` | str | Type of output format | "string" | | `interactive` | bool | Whether to enable interactive mode | False | -| `speaker_function` | Callable | Function to determine speaking order | round_robin_speaker | +| `speaker_function` | Union[str, Callable] | Function to determine speaking order | round_robin_speaker | | `speaker_state` | dict | Initial state for speaker function | {"current_index": 0} | **Example:** @@ -90,6 +96,8 @@ Processes a task and gets responses from mentioned agents. This is the main meth **Arguments:** - `task` (str): The input task containing @mentions to agents +- `img` (Optional[str]): Optional image for the task +- `imgs` (Optional[List[str]]): Optional list of images for the task **Returns:** @@ -104,6 +112,10 @@ print(response) # Multiple agent collaboration response = chat.run("@FinancialAdvisor and @TaxExpert, how can I minimize taxes on my investments?") print(response) + +# With image input +response = chat.run("@FinancialAdvisor analyze this chart", img="chart.png") +print(response) ``` ### Start Interactive Session (`start_interactive_session`) @@ -114,6 +126,13 @@ Starts an interactive terminal session for real-time chat with agents. This crea **Arguments:** None +**Features:** +- Real-time chat with agents using @mentions +- View available agents and their descriptions +- Change speaker functions during the session +- Built-in help system +- Graceful exit with 'exit' or 'quit' commands + **Example:** ```python @@ -127,6 +146,119 @@ chat = InteractiveGroupChat( chat.start_interactive_session() ``` +**Interactive Session Commands:** +- `@agent_name message` - Mention specific agents +- `help` or `?` - Show help information +- `speaker` - Change speaker function +- `exit` or `quit` - End the session + +### Set Speaker Function (`set_speaker_function`) + +**Description:** + +Dynamically changes the speaker function and optional state during runtime. + +**Arguments:** + +- `speaker_function` (Union[str, Callable]): Function that determines speaking order + - String options: "round-robin-speaker", "random-speaker", "priority-speaker", "random-dynamic-speaker" + - Callable: Custom function that takes (agents: List[str], **kwargs) -> str +- `speaker_state` (dict, optional): State for the speaker function + +**Example:** +```python +from swarms.structs.interactive_groupchat import random_speaker, priority_speaker + +# Change to random speaker function +chat.set_speaker_function(random_speaker) + +# Change to priority speaker with custom priorities +chat.set_speaker_function(priority_speaker, {"financial_advisor": 3, "tax_expert": 2}) + +# Change to random dynamic speaker +chat.set_speaker_function("random-dynamic-speaker") +``` + +### Get Available Speaker Functions (`get_available_speaker_functions`) + +**Description:** + +Returns a list of all available built-in speaker function names. + +**Arguments:** +None + +**Returns:** + +- List[str]: List of available speaker function names + +**Example:** +```python +available_functions = chat.get_available_speaker_functions() +print(available_functions) +# Output: ['round-robin-speaker', 'random-speaker', 'priority-speaker', 'random-dynamic-speaker'] +``` + +### Get Current Speaker Function (`get_current_speaker_function`) + +**Description:** + +Returns the name of the currently active speaker function. + +**Arguments:** +None + +**Returns:** + +- str: Name of the current speaker function, or "custom" if it's a custom function + +**Example:** +```python +current_function = chat.get_current_speaker_function() +print(current_function) # Output: "round-robin-speaker" +``` + +### Set Priorities (`set_priorities`) + +**Description:** + +Sets agent priorities for priority-based speaking order. + +**Arguments:** + +- `priorities` (dict): Dictionary mapping agent names to priority weights + +**Example:** +```python +# Set agent priorities (higher numbers = higher priority) +chat.set_priorities({ + "financial_advisor": 5, + "tax_expert": 3, + "investment_analyst": 1 +}) +``` + +### Set Dynamic Strategy (`set_dynamic_strategy`) + +**Description:** + +Sets the strategy for the random-dynamic-speaker function. + +**Arguments:** + +- `strategy` (str): Either "sequential" or "parallel" + - "sequential": Process one agent at a time based on @mentions + - "parallel": Process all mentioned agents simultaneously + +**Example:** +```python +# Set to sequential strategy (one agent at a time) +chat.set_dynamic_strategy("sequential") + +# Set to parallel strategy (all mentioned agents respond simultaneously) +chat.set_dynamic_strategy("parallel") +``` + ### Extract Mentions (`_extract_mentions`) **Description:** @@ -207,49 +339,6 @@ chat = InteractiveGroupChat(agents=[financial_advisor, tax_expert]) # Each agent now knows about the other participants and how to collaborate effectively ``` -### Set Speaker Function (`set_speaker_function`) - -**Description:** - -Dynamically changes the speaker function and optional state during runtime. - -**Arguments:** - -- `speaker_function` (Callable): Function that determines speaking order - -- `speaker_state` (dict, optional): State for the speaker function - -**Example:** -```python -from swarms.structs.interactive_groupchat import random_speaker, priority_speaker - -# Change to random speaker function -chat.set_speaker_function(random_speaker) - -# Change to priority speaker with custom priorities -chat.set_speaker_function(priority_speaker, {"financial_advisor": 3, "tax_expert": 2}) -``` - -### Set Priorities (`set_priorities`) - -**Description:** - -Sets agent priorities for priority-based speaking order. - -**Arguments:** - -- `priorities` (dict): Dictionary mapping agent names to priority weights - -**Example:** -```python -# Set agent priorities (higher numbers = higher priority) -chat.set_priorities({ - "financial_advisor": 5, - "tax_expert": 3, - "investment_analyst": 1 -}) -``` - ### Get Speaking Order (`_get_speaking_order`) **Description:** @@ -345,6 +434,41 @@ chat = InteractiveGroupChat( - Good for hierarchical teams or expert-led discussions +#### Random Dynamic Speaker (`random_dynamic_speaker`) + +Advanced speaker function that follows @mentions in agent responses, enabling dynamic conversation flow. + +```python +from swarms.structs.interactive_groupchat import InteractiveGroupChat, random_dynamic_speaker + +chat = InteractiveGroupChat( + agents=agents, + speaker_function=random_dynamic_speaker, + speaker_state={"strategy": "parallel"}, # or "sequential" + interactive=False, +) +``` + +**Behavior:** + +- **First Call**: Randomly selects an agent to start the conversation +- **Subsequent Calls**: Extracts @mentions from the previous agent's response and selects the next speaker(s) +- **Two Strategies**: + - **Sequential**: Processes one agent at a time based on @mentions + - **Parallel**: Processes all mentioned agents simultaneously + +**Example Dynamic Flow:** +```python +# Agent A responds: "I think @AgentB should analyze this data and @AgentC should review the methodology" +# With sequential strategy: Agent B speaks next +# With parallel strategy: Both Agent B and Agent C speak simultaneously +``` + +**Use Cases:** +- Complex problem-solving where agents need to delegate to specific experts +- Dynamic workflows where the conversation flow depends on agent responses +- Collaborative decision-making processes + ### Custom Speaker Functions You can create your own speaker functions to implement custom logic: @@ -394,6 +518,10 @@ chat.set_speaker_function(random_speaker) # Change to priority with custom priorities chat.set_priorities({"financial_advisor": 5, "tax_expert": 3, "analyst": 1}) chat.set_speaker_function(priority_speaker) + +# Change to dynamic speaker with parallel strategy +chat.set_speaker_function("random-dynamic-speaker") +chat.set_dynamic_strategy("parallel") ``` ## Enhanced Collaborative Behavior @@ -518,6 +646,8 @@ except InvalidSpeakerFunctionError as e: | 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?` | | Speaker Functions | Choose appropriate speaker functions for your use case | Round robin for fairness, priority for expert-led discussions | +| Dynamic Speaker | Use random-dynamic-speaker for complex workflows with delegation | When agents need to call on specific experts | +| Strategy Selection | Choose sequential for focused discussions, parallel for brainstorming | Sequential for analysis, parallel for idea generation | | Collaborative Design | Design agents with complementary expertise for better collaboration | Analyst + Researcher + Strategist | | 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 | @@ -614,6 +744,53 @@ response = chat.run(task) print(response) ``` +### Dynamic Speaker Function with Delegation + +```python +from swarms.structs.interactive_groupchat import InteractiveGroupChat, random_dynamic_speaker + +# Create specialized medical agents +cardiologist = Agent( + agent_name="cardiologist", + system_prompt="You are a cardiologist specializing in heart conditions.", + llm="gpt-4", +) + +oncologist = Agent( + agent_name="oncologist", + system_prompt="You are an oncologist specializing in cancer treatment.", + llm="gpt-4", +) + +endocrinologist = Agent( + agent_name="endocrinologist", + system_prompt="You are an endocrinologist specializing in hormone disorders.", + llm="gpt-4", +) + +# Create dynamic group chat +chat = InteractiveGroupChat( + name="Medical Panel Discussion", + description="A collaborative panel of medical specialists", + agents=[cardiologist, oncologist, endocrinologist], + speaker_function=random_dynamic_speaker, + speaker_state={"strategy": "sequential"}, + interactive=False, +) + +# Complex medical case with dynamic delegation +case = """CASE PRESENTATION: +A 65-year-old male with Type 2 diabetes, hypertension, and recent diagnosis of +stage 3 colon cancer presents with chest pain and shortness of breath. +ECG shows ST-segment elevation. Recent blood work shows elevated blood glucose (280 mg/dL) +and signs of infection (WBC 15,000, CRP elevated). + +@cardiologist @oncologist @endocrinologist please provide your assessment and treatment recommendations.""" + +response = chat.run(case) +print(response) +``` + ### Dynamic Speaker Function Changes ```python @@ -621,7 +798,8 @@ from swarms.structs.interactive_groupchat import ( InteractiveGroupChat, round_robin_speaker, random_speaker, - priority_speaker + priority_speaker, + random_dynamic_speaker ) # Create brainstorming agents @@ -647,10 +825,16 @@ chat.set_speaker_function(priority_speaker) task2 = "Now let's analyze the feasibility of these ideas. @creative @analytical @practical" response2 = chat.run(task2) -# Phase 3: Implementation (round robin for equal input) -chat.set_speaker_function(round_robin_speaker) -task3 = "Finally, let's plan implementation. @creative @analytical @practical" +# Phase 3: Dynamic delegation (agents mention each other) +chat.set_speaker_function(random_dynamic_speaker) +chat.set_dynamic_strategy("sequential") +task3 = "Let's plan implementation with dynamic delegation. @creative @analytical @practical" response3 = chat.run(task3) + +# Phase 4: Final synthesis (round robin for equal input) +chat.set_speaker_function(round_robin_speaker) +task4 = "Finally, let's synthesize our findings. @creative @analytical @practical" +response4 = chat.run(task4) ``` ### Custom Speaker Function @@ -726,38 +910,47 @@ chat.start_interactive_session() 3. **Better Delegation**: Agents naturally delegate to appropriate experts 4. **Enhanced Problem Solving**: Complex problems are addressed systematically 5. **More Natural Interactions**: Agents respond like real team members +6. **Dynamic Workflows**: Conversation flow adapts based on agent responses +7. **Flexible Execution**: Support for both sequential and parallel processing ### Use Cases -| Use Case Category | Specific Use Case | Agent Team Composition | -|------------------|-------------------|----------------------| -| **Business Analysis and Strategy** | Data Analysis | Analyst + Researcher + Strategist | -| | Market Research | Multiple experts analyzing different aspects | -| | Strategic Planning | Expert-led discussions with collaborative input | -| **Product Development** | Requirements Gathering | Product Manager + Developer + Designer | -| | Technical Architecture | Senior + Junior developers with different expertise | -| | User Experience | UX Designer + Product Manager + Developer | -| **Research and Development** | Scientific Research | Multiple researchers with different specializations | -| | Literature Review | Different experts reviewing various aspects | -| | Experimental Design | Statistician + Domain Expert + Methodologist | -| **Creative Projects** | Content Creation | Writer + Editor + Designer | -| | Marketing Campaigns | Creative + Analyst + Strategist | -| | Design Projects | Designer + Developer + Product Manager | -| **Problem Solving** | Troubleshooting | Technical + Business + User perspective experts | -| | Crisis Management | Emergency + Communication + Technical teams | -| | Decision Making | Executive + Analyst + Specialist | +| Use Case Category | Specific Use Case | Agent Team Composition | Recommended Speaker Function | +|------------------|-------------------|----------------------|------------------------------| +| **Business Analysis and Strategy** | Data Analysis | Analyst + Researcher + Strategist | Round Robin | +| | Market Research | Multiple experts analyzing different aspects | Random Dynamic | +| | Strategic Planning | Expert-led discussions with collaborative input | Priority | +| **Product Development** | Requirements Gathering | Product Manager + Developer + Designer | Round Robin | +| | Technical Architecture | Senior + Junior developers with different expertise | Priority | +| | User Experience | UX Designer + Product Manager + Developer | Random Dynamic | +| **Research and Development** | Scientific Research | Multiple researchers with different specializations | Random Dynamic | +| | Literature Review | Different experts reviewing various aspects | Round Robin | +| | Experimental Design | Statistician + Domain Expert + Methodologist | Priority | +| **Creative Projects** | Content Creation | Writer + Editor + Designer | Random | +| | Marketing Campaigns | Creative + Analyst + Strategist | Random Dynamic | +| | Design Projects | Designer + Developer + Product Manager | Round Robin | +| **Problem Solving** | Troubleshooting | Technical + Business + User perspective experts | Priority | +| | Crisis Management | Emergency + Communication + Technical teams | Priority | +| | Decision Making | Executive + Analyst + Specialist | Priority | +| **Medical Consultation** | Complex Cases | Multiple specialists (Cardiologist + Oncologist + Endocrinologist) | Random Dynamic | +| | Treatment Planning | Senior + Junior doctors with different expertise | Priority | +| | Research Review | Multiple researchers reviewing different aspects | Round Robin | ### Speaker Function Selection Guide -| Use Case | Recommended Speaker Function | Reasoning | -|----------|------------------------------|-----------| -| Team Meetings | Round Robin | Ensures equal participation | -| Brainstorming | Random | Prevents bias and encourages creativity | -| Expert Consultation | Priority | Senior experts speak first | -| Problem Solving | Priority | Most relevant experts prioritize | -| Creative Sessions | Random | Encourages diverse perspectives | -| Decision Making | Priority | Decision makers speak first | -| Research Review | Round Robin | Equal contribution from all reviewers | +| Use Case | Recommended Speaker Function | Strategy | Reasoning | +|----------|------------------------------|----------|-----------| +| Team Meetings | Round Robin | N/A | Ensures equal participation | +| Brainstorming | Random | N/A | Prevents bias and encourages creativity | +| Expert Consultation | Priority | N/A | Senior experts speak first | +| Problem Solving | Priority | N/A | Most relevant experts prioritize | +| Creative Sessions | Random | N/A | Encourages diverse perspectives | +| Decision Making | Priority | N/A | Decision makers speak first | +| Research Review | Round Robin | N/A | Equal contribution from all reviewers | +| Complex Workflows | Random Dynamic | Sequential | Follows natural conversation flow | +| Parallel Analysis | Random Dynamic | Parallel | Multiple agents work simultaneously | +| Medical Panels | Random Dynamic | Sequential | Specialists delegate to relevant experts | +| Technical Architecture | Random Dynamic | Sequential | Senior architects guide the discussion | ## Contributing diff --git a/examples/multi_agent/interactive_groupchat_examples/interactive_groupchat_speaker_example.py b/examples/multi_agent/interactive_groupchat_examples/interactive_groupchat_speaker_example.py new file mode 100644 index 00000000..e2d1b9fc --- /dev/null +++ b/examples/multi_agent/interactive_groupchat_examples/interactive_groupchat_speaker_example.py @@ -0,0 +1,163 @@ +""" +Medical Panel Discussion Example + +This example demonstrates a panel of medical specialists discussing treatment solutions +for various diseases using InteractiveGroupChat with different speaker functions: +- Round Robin: Doctors speak in a fixed order +- Random: Doctors speak in random order +- Priority: Senior doctors speak first +- Custom: Disease-specific speaker function + +The panel includes specialists from different medical fields who can collaborate +on complex medical cases and treatment plans. +""" + +from swarms import Agent +from swarms.structs.interactive_groupchat import ( + InteractiveGroupChat, +) + + +def create_medical_panel(): + """Create a panel of medical specialists for discussion.""" + + # Cardiologist - Heart and cardiovascular system specialist + cardiologist = Agent( + agent_name="cardiologist", + system_prompt="""You are Dr. Sarah Chen, a board-certified cardiologist with 15 years of experience. + You specialize in cardiovascular diseases, heart failure, arrhythmias, and interventional cardiology. + You have expertise in: + - Coronary artery disease and heart attacks + - Heart failure and cardiomyopathy + - Arrhythmias and electrophysiology + - Hypertension and lipid disorders + - Cardiac imaging and diagnostic procedures + + When discussing cases, provide evidence-based treatment recommendations, + consider patient risk factors, and collaborate with other specialists for comprehensive care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Oncologist - Cancer specialist + oncologist = Agent( + agent_name="oncologist", + system_prompt="""You are Dr. Michael Rodriguez, a medical oncologist with 12 years of experience. + You specialize in the diagnosis and treatment of various types of cancer. + You have expertise in: + - Solid tumors (lung, breast, colon, prostate, etc.) + - Hematologic malignancies (leukemia, lymphoma, multiple myeloma) + - Targeted therapy and immunotherapy + - Clinical trials and novel treatments + - Palliative care and symptom management + + When discussing cases, consider the cancer type, stage, molecular profile, + patient performance status, and available treatment options including clinical trials.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Neurologist - Nervous system specialist + neurologist = Agent( + agent_name="neurologist", + system_prompt="""You are Dr. Emily Watson, a neurologist with 10 years of experience. + You specialize in disorders of the nervous system, brain, and spinal cord. + You have expertise in: + - Stroke and cerebrovascular disease + - Neurodegenerative disorders (Alzheimer's, Parkinson's, ALS) + - Multiple sclerosis and demyelinating diseases + - Epilepsy and seizure disorders + - Headache and migraine disorders + - Neuromuscular diseases + + When discussing cases, consider neurological symptoms, imaging findings, + and the impact of neurological conditions on overall patient care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Endocrinologist - Hormone and metabolism specialist + endocrinologist = Agent( + agent_name="endocrinologist", + system_prompt="""You are Dr. James Thompson, an endocrinologist with 8 years of experience. + You specialize in disorders of the endocrine system and metabolism. + You have expertise in: + - Diabetes mellitus (Type 1, Type 2, gestational) + - Thyroid disorders (hyperthyroidism, hypothyroidism, thyroid cancer) + - Adrenal disorders and Cushing's syndrome + - Pituitary disorders and growth hormone issues + - Osteoporosis and calcium metabolism + - Reproductive endocrinology + + When discussing cases, consider metabolic factors, hormone levels, + and how endocrine disorders may affect other organ systems.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Infectious Disease Specialist + infectious_disease = Agent( + agent_name="infectious_disease", + system_prompt="""You are Dr. Lisa Park, an infectious disease specialist with 11 years of experience. + You specialize in the diagnosis and treatment of infectious diseases. + You have expertise in: + - Bacterial, viral, fungal, and parasitic infections + - Antibiotic resistance and antimicrobial stewardship + - HIV/AIDS and opportunistic infections + - Travel medicine and tropical diseases + - Hospital-acquired infections + - Emerging infectious diseases + + When discussing cases, consider the infectious agent, antimicrobial susceptibility, + host factors, and infection control measures.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + return [ + cardiologist, + oncologist, + neurologist, + endocrinologist, + infectious_disease, + ] + + +def example_round_robin_panel(): + """Example with round robin speaking order.""" + print("=== ROUND ROBIN MEDICAL PANEL ===\n") + + agents = create_medical_panel() + + group_chat = InteractiveGroupChat( + name="Medical Panel Discussion", + description="A collaborative panel of medical specialists discussing complex cases", + agents=agents, + speaker_function="round-robin-speaker", + interactive=False, + ) + + print(group_chat.speaker_function) + + # Case 1: Complex patient with multiple conditions + case1 = """CASE PRESENTATION: + A 65-year-old male with Type 2 diabetes, hypertension, and recent diagnosis of + stage 3 colon cancer presents with chest pain and shortness of breath. + ECG shows ST-segment elevation. Recent blood work shows elevated blood glucose (280 mg/dL) + and signs of infection (WBC 15,000, CRP elevated). + + @cardiologist @oncologist @endocrinologist @infectious_disease please provide your + assessment and treatment recommendations for this complex case.""" + + response = group_chat.run(case1) + print(f"Response:\n{response}\n") + print("=" * 80 + "\n") + + +if __name__ == "__main__": + example_round_robin_panel() diff --git a/examples/multi_agent/interactive_groupchat_examples/medical_panel_example.py b/examples/multi_agent/interactive_groupchat_examples/medical_panel_example.py new file mode 100644 index 00000000..0e31c96e --- /dev/null +++ b/examples/multi_agent/interactive_groupchat_examples/medical_panel_example.py @@ -0,0 +1,162 @@ +""" +Medical Panel Discussion Example + +This example demonstrates a panel of medical specialists discussing treatment solutions +for various diseases using InteractiveGroupChat with different speaker functions: +- Round Robin: Doctors speak in a fixed order +- Random: Doctors speak in random order +- Priority: Senior doctors speak first +- Custom: Disease-specific speaker function + +The panel includes specialists from different medical fields who can collaborate +on complex medical cases and treatment plans. +""" + +from swarms import Agent +from swarms.structs.interactive_groupchat import ( + InteractiveGroupChat, + round_robin_speaker, +) + + +def create_medical_panel(): + """Create a panel of medical specialists for discussion.""" + + # Cardiologist - Heart and cardiovascular system specialist + cardiologist = Agent( + agent_name="cardiologist", + system_prompt="""You are Dr. Sarah Chen, a board-certified cardiologist with 15 years of experience. + You specialize in cardiovascular diseases, heart failure, arrhythmias, and interventional cardiology. + You have expertise in: + - Coronary artery disease and heart attacks + - Heart failure and cardiomyopathy + - Arrhythmias and electrophysiology + - Hypertension and lipid disorders + - Cardiac imaging and diagnostic procedures + + When discussing cases, provide evidence-based treatment recommendations, + consider patient risk factors, and collaborate with other specialists for comprehensive care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Oncologist - Cancer specialist + oncologist = Agent( + agent_name="oncologist", + system_prompt="""You are Dr. Michael Rodriguez, a medical oncologist with 12 years of experience. + You specialize in the diagnosis and treatment of various types of cancer. + You have expertise in: + - Solid tumors (lung, breast, colon, prostate, etc.) + - Hematologic malignancies (leukemia, lymphoma, multiple myeloma) + - Targeted therapy and immunotherapy + - Clinical trials and novel treatments + - Palliative care and symptom management + + When discussing cases, consider the cancer type, stage, molecular profile, + patient performance status, and available treatment options including clinical trials.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Neurologist - Nervous system specialist + neurologist = Agent( + agent_name="neurologist", + system_prompt="""You are Dr. Emily Watson, a neurologist with 10 years of experience. + You specialize in disorders of the nervous system, brain, and spinal cord. + You have expertise in: + - Stroke and cerebrovascular disease + - Neurodegenerative disorders (Alzheimer's, Parkinson's, ALS) + - Multiple sclerosis and demyelinating diseases + - Epilepsy and seizure disorders + - Headache and migraine disorders + - Neuromuscular diseases + + When discussing cases, consider neurological symptoms, imaging findings, + and the impact of neurological conditions on overall patient care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Endocrinologist - Hormone and metabolism specialist + endocrinologist = Agent( + agent_name="endocrinologist", + system_prompt="""You are Dr. James Thompson, an endocrinologist with 8 years of experience. + You specialize in disorders of the endocrine system and metabolism. + You have expertise in: + - Diabetes mellitus (Type 1, Type 2, gestational) + - Thyroid disorders (hyperthyroidism, hypothyroidism, thyroid cancer) + - Adrenal disorders and Cushing's syndrome + - Pituitary disorders and growth hormone issues + - Osteoporosis and calcium metabolism + - Reproductive endocrinology + + When discussing cases, consider metabolic factors, hormone levels, + and how endocrine disorders may affect other organ systems.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Infectious Disease Specialist + infectious_disease = Agent( + agent_name="infectious_disease", + system_prompt="""You are Dr. Lisa Park, an infectious disease specialist with 11 years of experience. + You specialize in the diagnosis and treatment of infectious diseases. + You have expertise in: + - Bacterial, viral, fungal, and parasitic infections + - Antibiotic resistance and antimicrobial stewardship + - HIV/AIDS and opportunistic infections + - Travel medicine and tropical diseases + - Hospital-acquired infections + - Emerging infectious diseases + + When discussing cases, consider the infectious agent, antimicrobial susceptibility, + host factors, and infection control measures.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + return [ + cardiologist, + oncologist, + neurologist, + endocrinologist, + infectious_disease, + ] + + +def example_round_robin_panel(): + """Example with round robin speaking order.""" + print("=== ROUND ROBIN MEDICAL PANEL ===\n") + + agents = create_medical_panel() + + group_chat = InteractiveGroupChat( + name="Medical Panel Discussion", + description="A collaborative panel of medical specialists discussing complex cases", + agents=agents, + speaker_function=round_robin_speaker, + interactive=False, + ) + + # Case 1: Complex patient with multiple conditions + case1 = """CASE PRESENTATION: + A 65-year-old male with Type 2 diabetes, hypertension, and recent diagnosis of + stage 3 colon cancer presents with chest pain and shortness of breath. + ECG shows ST-segment elevation. Recent blood work shows elevated blood glucose (280 mg/dL) + and signs of infection (WBC 15,000, CRP elevated). + + @cardiologist @oncologist @endocrinologist @infectious_disease please provide your + assessment and treatment recommendations for this complex case.""" + + response = group_chat.run(case1) + print(f"Response:\n{response}\n") + print("=" * 80 + "\n") + + +if __name__ == "__main__": + example_round_robin_panel() diff --git a/speaker_function_examples.py b/examples/multi_agent/interactive_groupchat_examples/speaker_function_examples.py similarity index 86% rename from speaker_function_examples.py rename to examples/multi_agent/interactive_groupchat_examples/speaker_function_examples.py index f9255831..49a4ac40 100644 --- a/speaker_function_examples.py +++ b/examples/multi_agent/interactive_groupchat_examples/speaker_function_examples.py @@ -24,7 +24,7 @@ def create_example_agents(): analyst = Agent( agent_name="analyst", system_prompt="You are a data analyst. You excel at analyzing data, creating charts, and providing insights.", - model_name="gpt-4.1", + model_name="claude-3-5-sonnet-20240620", streaming_on=True, print_on=True, ) @@ -32,7 +32,7 @@ def create_example_agents(): researcher = Agent( agent_name="researcher", system_prompt="You are a research specialist. You are great at gathering information, fact-checking, and providing detailed research.", - model_name="gpt-4.1", + model_name="claude-3-5-sonnet-20240620", streaming_on=True, print_on=True, ) @@ -40,7 +40,7 @@ def create_example_agents(): writer = Agent( agent_name="writer", system_prompt="You are a content writer. You excel at writing clear, engaging content and summarizing information.", - model_name="gpt-4.1", + model_name="claude-3-5-sonnet-20240620", streaming_on=True, print_on=True, ) @@ -61,7 +61,7 @@ def example_random(): ) # Test the random behavior - task = "Let's create a marketing strategy. @analyst @researcher @writer please contribute." + task = "Let's create a marketing strategy for a personal healthcare ai consumer assistant app. @analyst @researcher @writer please contribute." response = group_chat.run(task) print(f"Response:\n{response}\n") diff --git a/stream_example.py b/examples/multi_agent/interactive_groupchat_examples/stream_example.py similarity index 73% rename from stream_example.py rename to examples/multi_agent/interactive_groupchat_examples/stream_example.py index 3234fb60..8517ff57 100644 --- a/stream_example.py +++ b/examples/multi_agent/interactive_groupchat_examples/stream_example.py @@ -3,10 +3,12 @@ from swarms import Agent # Enable real-time streaming agent = Agent( agent_name="StoryAgent", - model_name="gpt-4o-mini", + # model_name="groq/llama-3.1-8b-instant", + model_name="claude-3-5-sonnet-20240620", + # system_prompt="", streaming_on=True, # 🔥 This enables real streaming! max_loops=1, - print_on=False, + print_on=True, output_type="all", ) diff --git a/pyproject.toml b/pyproject.toml index 85cfd7ee..963c2c2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "swarms" -version = "7.8.9" +version = "7.9.0" description = "Swarms - TGSC" license = "MIT" authors = ["Kye Gomez "] diff --git a/random_dynamic_speaker_example.py b/random_dynamic_speaker_example.py new file mode 100644 index 00000000..021c536f --- /dev/null +++ b/random_dynamic_speaker_example.py @@ -0,0 +1,162 @@ +""" +Medical Panel Discussion Example + +This example demonstrates a panel of medical specialists discussing treatment solutions +for various diseases using InteractiveGroupChat with different speaker functions: +- Round Robin: Doctors speak in a fixed order +- Random: Doctors speak in random order +- Priority: Senior doctors speak first +- Custom: Disease-specific speaker function + +The panel includes specialists from different medical fields who can collaborate +on complex medical cases and treatment plans. +""" + +from swarms import Agent +from swarms.structs.interactive_groupchat import ( + InteractiveGroupChat, +) + + +def create_medical_panel(): + """Create a panel of medical specialists for discussion.""" + + # Cardiologist - Heart and cardiovascular system specialist + cardiologist = Agent( + agent_name="cardiologist", + system_prompt="""You are Dr. Sarah Chen, a board-certified cardiologist with 15 years of experience. + You specialize in cardiovascular diseases, heart failure, arrhythmias, and interventional cardiology. + You have expertise in: + - Coronary artery disease and heart attacks + - Heart failure and cardiomyopathy + - Arrhythmias and electrophysiology + - Hypertension and lipid disorders + - Cardiac imaging and diagnostic procedures + + When discussing cases, provide evidence-based treatment recommendations, + consider patient risk factors, and collaborate with other specialists for comprehensive care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Oncologist - Cancer specialist + oncologist = Agent( + agent_name="oncologist", + system_prompt="""You are Dr. Michael Rodriguez, a medical oncologist with 12 years of experience. + You specialize in the diagnosis and treatment of various types of cancer. + You have expertise in: + - Solid tumors (lung, breast, colon, prostate, etc.) + - Hematologic malignancies (leukemia, lymphoma, multiple myeloma) + - Targeted therapy and immunotherapy + - Clinical trials and novel treatments + - Palliative care and symptom management + + When discussing cases, consider the cancer type, stage, molecular profile, + patient performance status, and available treatment options including clinical trials.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Neurologist - Nervous system specialist + neurologist = Agent( + agent_name="neurologist", + system_prompt="""You are Dr. Emily Watson, a neurologist with 10 years of experience. + You specialize in disorders of the nervous system, brain, and spinal cord. + You have expertise in: + - Stroke and cerebrovascular disease + - Neurodegenerative disorders (Alzheimer's, Parkinson's, ALS) + - Multiple sclerosis and demyelinating diseases + - Epilepsy and seizure disorders + - Headache and migraine disorders + - Neuromuscular diseases + + When discussing cases, consider neurological symptoms, imaging findings, + and the impact of neurological conditions on overall patient care.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Endocrinologist - Hormone and metabolism specialist + endocrinologist = Agent( + agent_name="endocrinologist", + system_prompt="""You are Dr. James Thompson, an endocrinologist with 8 years of experience. + You specialize in disorders of the endocrine system and metabolism. + You have expertise in: + - Diabetes mellitus (Type 1, Type 2, gestational) + - Thyroid disorders (hyperthyroidism, hypothyroidism, thyroid cancer) + - Adrenal disorders and Cushing's syndrome + - Pituitary disorders and growth hormone issues + - Osteoporosis and calcium metabolism + - Reproductive endocrinology + + When discussing cases, consider metabolic factors, hormone levels, + and how endocrine disorders may affect other organ systems.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + # Infectious Disease Specialist + infectious_disease = Agent( + agent_name="infectious_disease", + system_prompt="""You are Dr. Lisa Park, an infectious disease specialist with 11 years of experience. + You specialize in the diagnosis and treatment of infectious diseases. + You have expertise in: + - Bacterial, viral, fungal, and parasitic infections + - Antibiotic resistance and antimicrobial stewardship + - HIV/AIDS and opportunistic infections + - Travel medicine and tropical diseases + - Hospital-acquired infections + - Emerging infectious diseases + + When discussing cases, consider the infectious agent, antimicrobial susceptibility, + host factors, and infection control measures.""", + model_name="claude-3-5-sonnet-20240620", + streaming_on=True, + print_on=True, + ) + + return [ + cardiologist, + oncologist, + neurologist, + endocrinologist, + infectious_disease, + ] + + +def example_round_robin_panel(): + """Example with round robin speaking order.""" + print("=== ROUND ROBIN MEDICAL PANEL ===\n") + + agents = create_medical_panel() + + group_chat = InteractiveGroupChat( + name="Medical Panel Discussion", + description="A collaborative panel of medical specialists discussing complex cases", + agents=agents, + speaker_function="random-dynamic-speaker", + interactive=False, + ) + + print(group_chat.speaker_function) + print(group_chat.get_current_speaker_function()) + + # Case 1: Complex patient with multiple conditions + case1 = """CASE PRESENTATION: + A 65-year-old male with Type 2 diabetes, hypertension, and recent diagnosis of + stage 3 colon cancer presents with chest pain and shortness of breath. + ECG shows ST-segment elevation. Recent blood work shows elevated blood glucose (280 mg/dL) + and signs of infection (WBC 15,000, CRP elevated). + + @cardiologist @oncologist @endocrinologist @infectious_disease please provide your + assessment and treatment recommendations for this complex case.""" + + response = group_chat.run(case1) + + +if __name__ == "__main__": + example_round_robin_panel() diff --git a/swarms/structs/__init__.py b/swarms/structs/__init__.py index d47f6f67..bb005cc0 100644 --- a/swarms/structs/__init__.py +++ b/swarms/structs/__init__.py @@ -83,7 +83,14 @@ from swarms.structs.swarming_architectures import ( staircase_swarm, star_swarm, ) -from swarms.structs.interactive_groupchat import InteractiveGroupChat +from swarms.structs.interactive_groupchat import ( + InteractiveGroupChat, + speaker_function, + round_robin_speaker, + random_speaker, + priority_speaker, + random_dynamic_speaker, +) __all__ = [ "Agent", @@ -156,4 +163,9 @@ __all__ = [ "find_agent_by_name", "run_agent", "InteractiveGroupChat", + "speaker_function", + "round_robin_speaker", + "random_speaker", + "priority_speaker", + "random_dynamic_speaker", ] diff --git a/swarms/structs/agent.py b/swarms/structs/agent.py index 9a6227bf..d1c30116 100644 --- a/swarms/structs/agent.py +++ b/swarms/structs/agent.py @@ -1524,10 +1524,13 @@ class Agent: f"The model '{self.model_name}' does not support function calling. Please use a model that supports function calling." ) - if self.max_tokens > get_max_tokens(self.model_name): - raise AgentInitializationError( - f"Max tokens is set to {self.max_tokens}, but the model '{self.model_name}' only supports {get_max_tokens(self.model_name)} tokens. Please set max tokens to {get_max_tokens(self.model_name)} or less." - ) + try: + if self.max_tokens > get_max_tokens(self.model_name): + raise AgentInitializationError( + f"Max tokens is set to {self.max_tokens}, but the model '{self.model_name}' only supports {get_max_tokens(self.model_name)} tokens. Please set max tokens to {get_max_tokens(self.model_name)} or less." + ) + except Exception: + pass if self.model_name not in model_list: logger.warning( diff --git a/swarms/structs/interactive_groupchat.py b/swarms/structs/interactive_groupchat.py index 73e49b4b..9c0e8afe 100644 --- a/swarms/structs/interactive_groupchat.py +++ b/swarms/structs/interactive_groupchat.py @@ -121,6 +121,71 @@ def priority_speaker( return available_agents[-1] # Fallback +def random_dynamic_speaker( + agents: List[str], + response: str = "", + strategy: str = "parallel", + **kwargs, +) -> Union[str, List[str]]: + """ + Random dynamic speaker function that selects agents based on @mentions in responses. + + This function works in two phases: + 1. If no response is provided (first call), randomly selects an agent + 2. If a response is provided, extracts @mentions and returns agent(s) based on strategy + + Args: + agents: List of available agent names + response: The response from the previous agent (may contain @mentions) + strategy: How to handle multiple mentions - "sequential" or "parallel" + **kwargs: Additional arguments (ignored) + + Returns: + For sequential strategy: str (single agent name) + For parallel strategy: List[str] (list of agent names) + """ + if not agents: + raise ValueError( + "No agents provided for random dynamic selection" + ) + + # If no response provided, randomly select first agent + if not response: + return random.choice(agents) + + # Extract @mentions from the response + mentions = re.findall(r"@(\w+)", response) + + # Filter mentions to only include valid agents + valid_mentions = [ + mention for mention in mentions if mention in agents + ] + + if not valid_mentions: + # If no valid mentions, randomly select from all agents + return random.choice(agents) + + # Handle multiple mentions based on strategy + if strategy == "sequential": + # Return the first mentioned agent for sequential execution + return valid_mentions[0] + elif strategy == "parallel": + # Return all mentioned agents for parallel execution + return valid_mentions + else: + raise ValueError( + f"Invalid strategy: {strategy}. Must be 'sequential' or 'parallel'" + ) + + +speaker_functions = { + "round-robin-speaker": round_robin_speaker, + "random-speaker": random_speaker, + "priority-speaker": priority_speaker, + "random-dynamic-speaker": random_dynamic_speaker, +} + + class InteractiveGroupChat: """ An interactive group chat system that enables conversations with multiple agents using @mentions. @@ -145,11 +210,38 @@ class InteractiveGroupChat: 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. - speaker_function (Callable, optional): Function to determine speaking order. Defaults to round_robin_speaker. + speaker_function (Union[str, Callable], optional): Function to determine speaking order. Can be: + - A string name: "round-robin-speaker", "random-speaker", "priority-speaker", "random-dynamic-speaker" + - A custom callable function + - None (defaults to round_robin_speaker) speaker_state (dict, optional): Initial state for speaker function. Defaults to empty dict. Raises: ValueError: If invalid initialization parameters are provided + InvalidSpeakerFunctionError: If the speaker function is invalid + + Examples: + # Initialize with string-based speaker function + group_chat = InteractiveGroupChat( + agents=[agent1, agent2, agent3], + speaker_function="random-speaker" + ) + + # Initialize with priority speaker function + group_chat = InteractiveGroupChat( + agents=[agent1, agent2, agent3], + speaker_function="priority-speaker", + speaker_state={"priorities": {"agent1": 3, "agent2": 2, "agent3": 1}} + ) + + # Initialize with dynamic speaker function (agents mention each other) + group_chat = InteractiveGroupChat( + agents=[agent1, agent2, agent3], + speaker_function="random-dynamic-speaker" + ) + + # Change speaker function during runtime + group_chat.set_speaker_function("round-robin-speaker") """ def __init__( @@ -161,7 +253,7 @@ class InteractiveGroupChat: max_loops: int = 1, output_type: str = "string", interactive: bool = False, - speaker_function: Optional[Callable] = None, + speaker_function: Optional[Union[str, Callable]] = None, speaker_state: Optional[dict] = None, ): self.id = id @@ -173,9 +265,27 @@ class InteractiveGroupChat: self.interactive = interactive # Speaker function configuration - self.speaker_function = ( - speaker_function or round_robin_speaker - ) + if speaker_function is None: + self.speaker_function = round_robin_speaker + elif isinstance(speaker_function, str): + if speaker_function not in speaker_functions: + available_functions = ", ".join( + speaker_functions.keys() + ) + raise InvalidSpeakerFunctionError( + f"Invalid speaker function: '{speaker_function}'. " + f"Available functions: {available_functions}" + ) + self.speaker_function = speaker_functions[ + speaker_function + ] + elif callable(speaker_function): + self.speaker_function = speaker_function + else: + raise InvalidSpeakerFunctionError( + "Speaker function must be either a string, callable, or None" + ) + self.speaker_state = speaker_state or {"current_index": 0} # Validate speaker function @@ -197,6 +307,230 @@ class InteractiveGroupChat: self._setup_conversation_context() self._update_agent_prompts() + def set_speaker_function( + self, + speaker_function: Union[str, Callable], + speaker_state: Optional[dict] = None, + ) -> None: + """ + Set the speaker function using either a string name or a custom callable. + + Args: + speaker_function: Either a string name of a predefined function or a custom callable + String options: + - "round-robin-speaker": Cycles through agents in order + - "random-speaker": Selects agents randomly + - "priority-speaker": Selects based on priority weights + - "random-dynamic-speaker": Randomly selects first agent, then follows @mentions in responses + Callable: Custom function that takes (agents: List[str], **kwargs) -> str + speaker_state: Optional state for the speaker function + + Raises: + InvalidSpeakerFunctionError: If the speaker function is invalid + """ + if isinstance(speaker_function, str): + # Handle string-based speaker function + if speaker_function not in speaker_functions: + available_functions = ", ".join( + speaker_functions.keys() + ) + raise InvalidSpeakerFunctionError( + f"Invalid speaker function: '{speaker_function}'. " + f"Available functions: {available_functions}" + ) + self.speaker_function = speaker_functions[ + speaker_function + ] + logger.info( + f"Speaker function set to: {speaker_function}" + ) + elif callable(speaker_function): + # Handle callable speaker function + self.speaker_function = speaker_function + logger.info( + f"Custom speaker function set to: {speaker_function.__name__}" + ) + else: + raise InvalidSpeakerFunctionError( + "Speaker function must be either a string or a callable" + ) + + # Update speaker state if provided + if speaker_state: + self.speaker_state.update(speaker_state) + + # Validate the speaker function + self._validate_speaker_function() + + def set_priorities(self, priorities: dict) -> None: + """ + Set agent priorities for priority-based speaking order. + + Args: + priorities: Dictionary mapping agent names to priority weights + """ + self.speaker_state["priorities"] = priorities + logger.info(f"Agent priorities set: {priorities}") + + def get_available_speaker_functions(self) -> List[str]: + """ + Get a list of available speaker function names. + + Returns: + List[str]: List of available speaker function names + """ + return list(speaker_functions.keys()) + + def get_current_speaker_function(self) -> str: + """ + Get the name of the current speaker function. + + Returns: + str: Name of the current speaker function, or "custom" if it's a custom function + """ + for name, func in speaker_functions.items(): + if self.speaker_function == func: + return name + return "custom" + + 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( + f"Current speaker function: {self.get_current_speaker_function()}" + ) + print("\nAvailable agents:") + for name, agent in self.agent_map.items(): + if isinstance(agent, Agent): + print( + f"- @{name}: {agent.system_prompt.splitlines()[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("- Type 'speaker' to change speaker function") + 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 task" + ) + print("3. Available agents:") + for name in self.agent_map: + print(f" - @{name}") + print( + "4. Type 'speaker' to change speaker function" + ) + print( + "5. Type 'exit' or 'quit' to end the session" + ) + continue + + if user_input.lower() == "speaker": + print( + f"\nCurrent speaker function: {self.get_current_speaker_function()}" + ) + print("Available speaker functions:") + for i, func_name in enumerate( + self.get_available_speaker_functions(), 1 + ): + print(f" {i}. {func_name}") + + try: + choice = input( + "\nEnter the number or name of the speaker function: " + ).strip() + + # Try to parse as number first + try: + func_index = int(choice) - 1 + if ( + 0 + <= func_index + < len( + self.get_available_speaker_functions() + ) + ): + selected_func = self.get_available_speaker_functions()[ + func_index + ] + else: + print( + "Invalid number. Please try again." + ) + continue + except ValueError: + # Try to parse as name + selected_func = choice + + self.set_speaker_function(selected_func) + print( + f"Speaker function changed to: {self.get_current_speaker_function()}" + ) + + except InvalidSpeakerFunctionError as e: + print(f"Error: {e}") + except Exception as e: + print(f"An error occurred: {e}") + continue + + if not user_input: + continue + + # Process the task and get responses + try: + 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 _validate_speaker_function(self) -> None: """ Validates the speaker function. @@ -287,6 +621,7 @@ IMPORTANT: You are part of a collaborative group chat where you can interact wit 2. ACKNOWLEDGE: Reference and acknowledge what other agents have said 3. BUILD UPON: Add your perspective while building upon their insights 4. MENTION: Use @agent_name to call on other agents when needed +5. COMPLETE: Acknowledge when your part is done and what still needs to be done HOW TO MENTION OTHER AGENTS: - Use @agent_name to mention another agent in your response @@ -325,24 +660,33 @@ COLLABORATION GUIDELINES: - ASK CLARIFYING QUESTIONS if you need more information from other agents - DELEGATE appropriately: "Let me ask @expert_agent to verify this" or "@specialist, can you elaborate on this point?" +TASK COMPLETION GUIDELINES: +- ACKNOWLEDGE when you are done with your part of the task +- CLEARLY STATE what still needs to be done before the overall task is finished +- If you mention other agents, explain what specific input you need from them +- Use phrases like "I have completed [specific part]" or "The task still requires [specific actions]" +- Provide a clear status update: "My analysis is complete. The task now needs @writer to create content and @reviewer to validate the approach." + RESPONSE STRUCTURE: 1. ACKNOWLEDGE: "I've reviewed the responses from @agent1 and @agent2..." 2. BUILD: "Building on @agent1's analysis of the data..." 3. CONTRIBUTE: "From my perspective, I would add..." 4. COLLABORATE: "To get a complete picture, let me ask @agent3 to..." -5. SYNTHESIZE: "Combining our insights, the key findings are..." +5. COMPLETE: "I have completed [my part]. The task still requires [specific next steps]" +6. SYNTHESIZE: "Combining our insights, the key findings are..." EXAMPLES OF GOOD COLLABORATION: -- "I've reviewed @analyst's data analysis and @researcher's market insights. The data shows strong growth potential, and I agree with @researcher that we should focus on emerging markets. Let me add that from a content perspective, we should @writer to create targeted messaging for these markets." -- "Building on @researcher's findings about customer behavior, I can see that @analyst's data supports this trend. To get a complete understanding, let me ask @writer to help us craft messaging that addresses these specific customer needs." +- "I've reviewed @analyst's data analysis and @researcher's market insights. The data shows strong growth potential, and I agree with @researcher that we should focus on emerging markets. Let me add that from a content perspective, we should @writer to create targeted messaging for these markets. I have completed my market analysis. The task now requires @writer to develop content and @reviewer to validate our approach." +- "Building on @researcher's findings about customer behavior, I can see that @analyst's data supports this trend. To get a complete understanding, let me ask @writer to help us craft messaging that addresses these specific customer needs. My data analysis is complete. The task still needs @writer to create messaging and @reviewer to approve the final strategy." AVOID: - Ignoring other agents' responses - Repeating what others have already said - Making assumptions without consulting relevant experts - Responding in isolation without considering the group's collective knowledge +- Not acknowledging task completion status -Remember: You are part of a team. Your response should reflect that you've read, understood, and built upon the contributions of others. +Remember: You are part of a team. Your response should reflect that you've read, understood, and are building upon the contributions of others, and clearly communicate your task completion status. """ # Update the agent's system prompt @@ -438,6 +782,12 @@ Remember: You are part of a team. Your response should reflect that you've read, ) return sorted_agents + elif self.speaker_function == random_dynamic_speaker: + # For dynamic speaker, we need to handle it differently + # The dynamic speaker will be called during the run method + # For now, just return the original order + return mentioned_agents + else: # Custom speaker function # For custom functions, we'll use the first agent returned @@ -460,120 +810,12 @@ Remember: You are part of a team. Your response should reflect that you've read, # Fallback to original order return mentioned_agents - def set_speaker_function( + def run( self, - speaker_function: Callable, - speaker_state: Optional[dict] = None, - ) -> None: - """ - Set a custom speaker function and optional state. - - Args: - speaker_function: Function that determines speaking order - speaker_state: Optional state for the speaker function - """ - self.speaker_function = speaker_function - if speaker_state: - self.speaker_state.update(speaker_state) - self._validate_speaker_function() - logger.info( - f"Speaker function updated to: {speaker_function.__name__}" - ) - - def set_priorities(self, priorities: dict) -> None: - """ - Set agent priorities for priority-based speaking order. - - Args: - priorities: Dictionary mapping agent names to priority weights - """ - self.speaker_state["priorities"] = priorities - logger.info(f"Agent priorities set: {priorities}") - - 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.splitlines()[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 task" - ) - 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 task and get responses - try: - 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, task: str) -> str: + task: str, + img: Optional[str] = None, + imgs: Optional[List[str]] = None, + ) -> str: """ Process a task and get responses from mentioned agents. If interactive mode is enabled, this will be called by start_interactive_session(). @@ -591,31 +833,171 @@ Remember: You are part of a team. Your response should reflect that you've read, # Add user task to conversation self.conversation.add(role="User", content=task) - # Determine speaking order using speaker function - speaking_order = self._get_speaking_order( - mentioned_agents - ) - logger.info( - f"Speaking order determined: {speaking_order}" - ) + # Handle dynamic speaker function differently + if self.speaker_function == random_dynamic_speaker: + # Get strategy from speaker state (default to sequential) + strategy = self.speaker_state.get( + "strategy", "sequential" + ) - # Get responses from mentioned agents in the determined order - for agent_name in speaking_order: - agent = self.agent_map.get(agent_name) - if not agent: - raise AgentNotFoundError( - f"Agent '{agent_name}' not found" + # For dynamic speaker, we'll determine the next speaker after each response + # Track which agents have spoken to ensure all get a chance + spoken_agents = set() + last_response = "" + max_iterations = ( + len(mentioned_agents) * 3 + ) # Allow more iterations for parallel + iteration = 0 + + while iteration < max_iterations and len( + spoken_agents + ) < len(mentioned_agents): + # Determine next speaker(s) using dynamic function + next_speakers = self.speaker_function( + mentioned_agents, # Use all mentioned agents, not remaining_agents + last_response, + strategy=strategy, + **self.speaker_state, ) - try: - # Get the complete conversation history - context = ( - self.conversation.return_history_as_string() + # Handle both single agent and multiple agents + if isinstance(next_speakers, str): + next_speakers = [next_speakers] + + # Filter out invalid agents + valid_next_speakers = [ + agent + for agent in next_speakers + if agent in mentioned_agents + ] + + if not valid_next_speakers: + # If no valid mentions found, randomly select from unspoken agents + unspoken_agents = [ + agent + for agent in mentioned_agents + if agent not in spoken_agents + ] + if unspoken_agents: + valid_next_speakers = [ + random.choice(unspoken_agents) + ] + else: + # All agents have spoken, break the loop + break + + # Process agents based on strategy + if strategy == "sequential": + # Process one agent at a time + for next_speaker in valid_next_speakers: + if next_speaker in spoken_agents: + continue # Skip if already spoken + + response = self._get_agent_response( + next_speaker, img, imgs + ) + if response: + last_response = response + spoken_agents.add(next_speaker) + break # Only process one agent in sequential mode + + elif strategy == "parallel": + # Process all mentioned agents in parallel + import concurrent.futures + + # Get responses from all valid agents + responses = [] + with concurrent.futures.ThreadPoolExecutor() as executor: + future_to_agent = { + executor.submit( + self._get_agent_response, + agent, + img, + imgs, + ): agent + for agent in valid_next_speakers + if agent not in spoken_agents + } + + for ( + future + ) in concurrent.futures.as_completed( + future_to_agent + ): + agent = future_to_agent[future] + try: + response = future.result() + if response: + responses.append(response) + spoken_agents.add(agent) + except Exception as e: + logger.error( + f"Error getting response from {agent}: {e}" + ) + + # Combine responses for next iteration + if responses: + last_response = "\n\n".join(responses) + + iteration += 1 + else: + # For non-dynamic speaker functions, use the original logic + speaking_order = self._get_speaking_order( + mentioned_agents + ) + logger.info( + f"Speaking order determined: {speaking_order}" + ) + + # Get responses from mentioned agents in the determined order + for agent_name in speaking_order: + response = self._get_agent_response( + agent_name, img, imgs ) - # Get response from agent - if isinstance(agent, Agent): - collaborative_task = f"""{context} + 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)}" + ) + + def _get_agent_response( + self, + agent_name: str, + img: Optional[str] = None, + imgs: Optional[List[str]] = None, + ) -> Optional[str]: + """ + Get response from a specific agent. + + Args: + agent_name: Name of the agent to get response from + img: Optional image for the task + imgs: Optional list of images for the task + + Returns: + The agent's response or None if error + """ + 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): + collaborative_task = f"""{context} COLLABORATIVE TASK: Please respond to the latest task as {agent_name}. @@ -626,38 +1008,56 @@ IMPORTANT INSTRUCTIONS: 4. If you need input from other agents, mention them using @agent_name 5. Provide your unique expertise while showing you understand the group's collective knowledge -Remember: You are part of a collaborative team. Your response should demonstrate that you've read, understood, and are building upon the contributions of others.""" +TASK COMPLETION GUIDELINES: +- Acknowledge when you are done with your part of the task +- Clearly state what still needs to be done before the overall task is finished +- If you mention other agents, explain what specific input you need from them +- Use phrases like "I have completed [specific part]" or "The task still requires [specific actions]" - response = agent.run(task=collaborative_task) - else: - # For callable functions - response = agent(context) +Remember: You are part of a collaborative team. Your response should demonstrate that you've read, understood, and are building upon the contributions of others.""" - # 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") + response = agent.run( + task=collaborative_task, + img=img, + imgs=imgs, + ) + else: + # For callable functions + response = agent(context) - 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)}", - ) + # 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") + return response - return history_output_formatter( - self.conversation, self.output_type + 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 f"Error: Unable to generate response - {str(e)}" - 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)}" + return None + + def set_dynamic_strategy(self, strategy: str) -> None: + """ + Set the strategy for the random-dynamic-speaker function. + + Args: + strategy: Either "sequential" or "parallel" + - "sequential": Process one agent at a time based on @mentions + - "parallel": Process all mentioned agents simultaneously + """ + if strategy not in ["sequential", "parallel"]: + raise ValueError( + "Strategy must be either 'sequential' or 'parallel'" ) + + self.speaker_state["strategy"] = strategy + logger.info(f"Dynamic speaker strategy set to: {strategy}") diff --git a/swarms/structs/ma_utils.py b/swarms/structs/ma_utils.py index b47080b8..1cf1e0fb 100644 --- a/swarms/structs/ma_utils.py +++ b/swarms/structs/ma_utils.py @@ -86,6 +86,7 @@ models = [ "o4-mini", "o3", "gpt-4.1", + "groq/llama-3.1-8b-instant", "gpt-4.1-nano", ] diff --git a/swarms/structs/swarm_router.py b/swarms/structs/swarm_router.py index 4cfe119e..3452343b 100644 --- a/swarms/structs/swarm_router.py +++ b/swarms/structs/swarm_router.py @@ -182,6 +182,7 @@ class SwarmRouter: list_all_agents: bool = False, conversation: Any = None, agents_config: Optional[Dict[Any, Any]] = None, + speaker_function: str = None, *args, **kwargs, ): @@ -208,6 +209,7 @@ class SwarmRouter: self.list_all_agents = list_all_agents self.conversation = conversation self.agents_config = agents_config + self.speaker_function = speaker_function # Reliability check self.reliability_check() @@ -358,6 +360,7 @@ class SwarmRouter: agents=self.agents, max_loops=self.max_loops, output_type=self.output_type, + speaker_function=self.speaker_function, ) elif self.swarm_type == "DeepResearchSwarm": diff --git a/swarms/utils/formatter.py b/swarms/utils/formatter.py index ccf5eaca..34aa5eb8 100644 --- a/swarms/utils/formatter.py +++ b/swarms/utils/formatter.py @@ -179,7 +179,9 @@ class Formatter: panel_style = ( f"bold {random_color}" if style is None else style ) - text_style = random_color + text_style = ( + "white" # Make text white instead of random color + ) def create_streaming_panel(text_obj, is_complete=False): """Create panel with proper text wrapping using Rich's built-in capabilities""" diff --git a/swarms/utils/litellm_wrapper.py b/swarms/utils/litellm_wrapper.py index 01392b34..063e6ce3 100644 --- a/swarms/utils/litellm_wrapper.py +++ b/swarms/utils/litellm_wrapper.py @@ -151,6 +151,8 @@ class LiteLLM: retries # Add retries for better reliability ) + litellm.drop_params = True + def output_for_tools(self, response: any): if self.mcp_call is True: out = response.choices[0].message.tool_calls[0].function