From 20da1748f51d98255500dceb2d200ec69506a6cf Mon Sep 17 00:00:00 2001 From: Wasim Date: Wed, 2 Sep 2026 10:41:01 +0530 Subject: [PATCH 1/3] fix: validate participants type in BaseGroupChat constructor Previously, passing None, a non-sequence, or non-agent objects as participants would raise internal AttributeError/TypeError. Now raises clear TypeError with descriptive message. Fixes #7580 --- .../teams/_group_chat/_base_group_chat.py | 11 +++++++++ .../tests/test_group_chat.py | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index 60f222912387..1a882a663ab0 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -78,8 +78,19 @@ def __init__( ): self._name = name self._description = description + if participants is None: + raise TypeError("participants must be a non-empty sequence of ChatAgent or Team instances, got None") + if not isinstance(participants, (list, tuple)): + raise TypeError( + f"participants must be a non-empty sequence of ChatAgent or Team instances, got {type(participants).__name__}" + ) if len(participants) == 0: raise ValueError("At least one participant is required.") + for i, participant in enumerate(participants): + if not isinstance(participant, (ChatAgent, Team)): + raise TypeError( + f"participants[{i}] must be a ChatAgent or Team instance, got {type(participant).__name__}" + ) if len(participants) != len(set(participant.name for participant in participants)): raise ValueError("The participant names must be unique.") self._participants = participants diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 3ded2e0c2e60..3b84ce4708a8 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1944,3 +1944,26 @@ async def test_selector_group_chat_streaming(runtime: AgentRuntime | None) -> No # Content-based verification instead of index-based # Note: The streaming test verifies the streaming behavior, not the final result content + + +@pytest.mark.asyncio +async def test_round_robin_group_chat_validates_participants_none() -> None: + """Test that participants=None raises a clear TypeError.""" + with pytest.raises(TypeError, match="participants must be a non-empty sequence"): + RoundRobinGroupChat(participants=None) # type: ignore + + +@pytest.mark.asyncio +async def test_round_robin_group_chat_validates_participants_not_sequence() -> None: + """Test that non-sequence participants raises a clear TypeError.""" + with pytest.raises(TypeError, match="participants must be a non-empty sequence"): + RoundRobinGroupChat(participants="not a list") # type: ignore + + +@pytest.mark.asyncio +async def test_round_robin_group_chat_validates_participants_invalid_type() -> None: + """Test that participants containing non-agent objects raises a clear TypeError.""" + from autogen_agentchat.agents import UserProxyAgent + + with pytest.raises(TypeError, match=r"participants\[1\] must be a ChatAgent or Team"): + RoundRobinGroupChat(participants=[UserProxyAgent(name="valid"), "invalid"]) # type: ignore From cf963844047ae4bdc7657fe4cee31e7c3c794ee4 Mon Sep 17 00:00:00 2001 From: Wasim Date: Fri, 25 Sep 2026 00:38:53 +0530 Subject: [PATCH 2/3] fix: update participants type error message to list or tuple (address Copilot review) --- .../autogen_agentchat/teams/_group_chat/_base_group_chat.py | 4 ++-- python/packages/autogen-agentchat/tests/test_group_chat.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index 1a882a663ab0..73a645ae014d 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -79,10 +79,10 @@ def __init__( self._name = name self._description = description if participants is None: - raise TypeError("participants must be a non-empty sequence of ChatAgent or Team instances, got None") + raise TypeError("participants must be a list or tuple of ChatAgent or Team instances, got None") if not isinstance(participants, (list, tuple)): raise TypeError( - f"participants must be a non-empty sequence of ChatAgent or Team instances, got {type(participants).__name__}" + f"participants must be a list or tuple of ChatAgent or Team instances, got {type(participants).__name__}" ) if len(participants) == 0: raise ValueError("At least one participant is required.") diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 3b84ce4708a8..e3a386a9ba4c 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1949,14 +1949,14 @@ async def test_selector_group_chat_streaming(runtime: AgentRuntime | None) -> No @pytest.mark.asyncio async def test_round_robin_group_chat_validates_participants_none() -> None: """Test that participants=None raises a clear TypeError.""" - with pytest.raises(TypeError, match="participants must be a non-empty sequence"): + with pytest.raises(TypeError, match="participants must be a list or tuple"): RoundRobinGroupChat(participants=None) # type: ignore @pytest.mark.asyncio async def test_round_robin_group_chat_validates_participants_not_sequence() -> None: """Test that non-sequence participants raises a clear TypeError.""" - with pytest.raises(TypeError, match="participants must be a non-empty sequence"): + with pytest.raises(TypeError, match="participants must be a list or tuple"): RoundRobinGroupChat(participants="not a list") # type: ignore From 01543e66c22de0071a172e62d993e98a4ea3ec47 Mon Sep 17 00:00:00 2001 From: Wasim Date: Wed, 7 Oct 2026 01:07:59 +0530 Subject: [PATCH 3/3] fix: update participants type hints to List or Tuple (address Copilot review) --- .../teams/_group_chat/_base_group_chat.py | 4 ++-- .../teams/_group_chat/_round_robin_group_chat.py | 4 ++-- .../packages/autogen-agentchat/tests/test_group_chat.py | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index 73a645ae014d..78bb007cc58d 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -1,7 +1,7 @@ import asyncio import uuid from abc import ABC, abstractmethod -from typing import Any, AsyncGenerator, Callable, Dict, List, Mapping, Sequence +from typing import Any, AsyncGenerator, Callable, Dict, List, Mapping, Sequence, Tuple from autogen_core import ( AgentId, @@ -67,7 +67,7 @@ def __init__( self, name: str, description: str, - participants: List[ChatAgent | Team], + participants: List[ChatAgent | Team] | Tuple[ChatAgent | Team, ...], group_chat_manager_name: str, group_chat_manager_class: type[SequentialRoutedAgent], termination_condition: TerminationCondition | None = None, diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py index 3f529f0c4474..fd33f554659b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Callable, List, Mapping, Sequence +from typing import Any, Callable, List, Mapping, Sequence, Tuple from autogen_core import AgentRuntime, Component, ComponentModel from pydantic import BaseModel @@ -241,7 +241,7 @@ async def main() -> None: def __init__( self, - participants: List[ChatAgent | Team], + participants: List[ChatAgent | Team] | Tuple[ChatAgent | Team, ...], *, name: str | None = None, description: str | None = None, diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index e3a386a9ba4c..779a0171c94a 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1967,3 +1967,12 @@ async def test_round_robin_group_chat_validates_participants_invalid_type() -> N with pytest.raises(TypeError, match=r"participants\[1\] must be a ChatAgent or Team"): RoundRobinGroupChat(participants=[UserProxyAgent(name="valid"), "invalid"]) # type: ignore + + +@pytest.mark.asyncio +async def test_round_robin_group_chat_accepts_tuple_participants() -> None: + """Test that participants passed as a tuple is accepted.""" + from autogen_agentchat.agents import UserProxyAgent + + team = RoundRobinGroupChat(participants=(UserProxyAgent(name="agent1"), UserProxyAgent(name="agent2"))) + assert len(team._participants) == 2