From 4028ecf1b1171122fa8cdaf218d5375e2cfa3550 Mon Sep 17 00:00:00 2001 From: sarojrout Date: Sun, 16 Nov 2025 21:44:33 -0800 Subject: [PATCH 1/4] feat(agents): add validation for unique sub-agent names (#3557) --- src/google/adk/agents/base_agent.py | 36 +++++++++++ tests/unittests/agents/test_base_agent.py | 74 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index a644cb8b90..c0c7016049 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -563,6 +563,42 @@ def validate_name(cls, value: str): ) return value + @field_validator('sub_agents', mode='after') + @classmethod + def validate_sub_agents_unique_names(cls, value: list[BaseAgent]) -> list[BaseAgent]: + """Validates that all sub-agents have unique names. + + Args: + value: The list of sub-agents to validate. + + Returns: + The validated list of sub-agents. + + Raises: + ValueError: If duplicate sub-agent names are found. + """ + if not value: + return value + + seen_names: set[str] = set() + duplicates: list[str] = [] + + for sub_agent in value: + name = sub_agent.name + if name in seen_names: + duplicates.append(name) + else: + seen_names.add(name) + + if duplicates: + duplicate_names_str = ', '.join(f'`{name}`' for name in duplicates) + raise ValueError( + f'Found duplicate sub-agent names: {duplicate_names_str}. ' + 'All sub-agents must have unique names.' + ) + + return value + def __set_parent_agent_for_sub_agents(self) -> BaseAgent: for sub_agent in self.sub_agents: if sub_agent.parent_agent is not None: diff --git a/tests/unittests/agents/test_base_agent.py b/tests/unittests/agents/test_base_agent.py index 663179f670..e97e931cd1 100644 --- a/tests/unittests/agents/test_base_agent.py +++ b/tests/unittests/agents/test_base_agent.py @@ -854,6 +854,80 @@ def test_set_parent_agent_for_sub_agent_twice( ) +def test_validate_sub_agents_unique_names_single_duplicate( + request: pytest.FixtureRequest, +): + """Test that duplicate sub-agent names raise ValueError.""" + duplicate_name = f'{request.function.__name__}_duplicate_agent' + sub_agent_1 = _TestingAgent(name=duplicate_name) + sub_agent_2 = _TestingAgent(name=duplicate_name) + + with pytest.raises(ValueError, match='Found duplicate sub-agent names'): + _ = _TestingAgent( + name=f'{request.function.__name__}_parent', + sub_agents=[sub_agent_1, sub_agent_2], + ) + + +def test_validate_sub_agents_unique_names_multiple_duplicates( + request: pytest.FixtureRequest, +): + """Test that multiple duplicate sub-agent names are all reported.""" + duplicate_name_1 = f'{request.function.__name__}_duplicate_1' + duplicate_name_2 = f'{request.function.__name__}_duplicate_2' + + sub_agents = [ + _TestingAgent(name=duplicate_name_1), + _TestingAgent(name=f'{request.function.__name__}_unique'), + _TestingAgent(name=duplicate_name_1), # First duplicate + _TestingAgent(name=duplicate_name_2), + _TestingAgent(name=duplicate_name_2), # Second duplicate + ] + + with pytest.raises(ValueError) as exc_info: + _ = _TestingAgent( + name=f'{request.function.__name__}_parent', + sub_agents=sub_agents, + ) + + error_message = str(exc_info.value) + assert duplicate_name_1 in error_message + assert duplicate_name_2 in error_message + + +def test_validate_sub_agents_unique_names_no_duplicates( + request: pytest.FixtureRequest, +): + """Test that unique sub-agent names pass validation.""" + sub_agents = [ + _TestingAgent(name=f'{request.function.__name__}_sub_agent_1'), + _TestingAgent(name=f'{request.function.__name__}_sub_agent_2'), + _TestingAgent(name=f'{request.function.__name__}_sub_agent_3'), + ] + + parent = _TestingAgent( + name=f'{request.function.__name__}_parent', + sub_agents=sub_agents, + ) + + assert len(parent.sub_agents) == 3 + assert parent.sub_agents[0].name == f'{request.function.__name__}_sub_agent_1' + assert parent.sub_agents[1].name == f'{request.function.__name__}_sub_agent_2' + assert parent.sub_agents[2].name == f'{request.function.__name__}_sub_agent_3' + + +def test_validate_sub_agents_unique_names_empty_list( + request: pytest.FixtureRequest, +): + """Test that empty sub-agents list passes validation.""" + parent = _TestingAgent( + name=f'{request.function.__name__}_parent', + sub_agents=[], + ) + + assert len(parent.sub_agents) == 0 + + if __name__ == '__main__': pytest.main([__file__]) From e8dbafe05ceebc48d4b295db55164bd63ec024bd Mon Sep 17 00:00:00 2001 From: sarojrout Date: Sun, 16 Nov 2025 21:57:55 -0800 Subject: [PATCH 2/4] fix(agents): deduplicate and sort duplicate names in error message (#3557) --- src/google/adk/agents/base_agent.py | 4 ++- tests/unittests/agents/test_base_agent.py | 30 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index c0c7016049..0fb78d9206 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -591,7 +591,9 @@ def validate_sub_agents_unique_names(cls, value: list[BaseAgent]) -> list[BaseAg seen_names.add(name) if duplicates: - duplicate_names_str = ', '.join(f'`{name}`' for name in duplicates) + duplicate_names_str = ', '.join( + f'`{name}`' for name in sorted(set(duplicates)) + ) raise ValueError( f'Found duplicate sub-agent names: {duplicate_names_str}. ' 'All sub-agents must have unique names.' diff --git a/tests/unittests/agents/test_base_agent.py b/tests/unittests/agents/test_base_agent.py index e97e931cd1..860cc8d4f0 100644 --- a/tests/unittests/agents/test_base_agent.py +++ b/tests/unittests/agents/test_base_agent.py @@ -891,10 +891,40 @@ def test_validate_sub_agents_unique_names_multiple_duplicates( ) error_message = str(exc_info.value) + # Verify each duplicate name appears exactly once in the error message + assert error_message.count(duplicate_name_1) == 1 + assert error_message.count(duplicate_name_2) == 1 + # Verify both duplicate names are present assert duplicate_name_1 in error_message assert duplicate_name_2 in error_message +def test_validate_sub_agents_unique_names_triple_duplicate( + request: pytest.FixtureRequest, +): + """Test that a name appearing three times is reported only once.""" + duplicate_name = f'{request.function.__name__}_triple_duplicate' + + sub_agents = [ + _TestingAgent(name=duplicate_name), + _TestingAgent(name=f'{request.function.__name__}_unique'), + _TestingAgent(name=duplicate_name), # Second occurrence + _TestingAgent(name=duplicate_name), # Third occurrence + ] + + with pytest.raises(ValueError) as exc_info: + _ = _TestingAgent( + name=f'{request.function.__name__}_parent', + sub_agents=sub_agents, + ) + + error_message = str(exc_info.value) + # Verify the duplicate name appears exactly once in the error message + # (not three times even though it appears three times in the list) + assert error_message.count(duplicate_name) == 1 + assert duplicate_name in error_message + + def test_validate_sub_agents_unique_names_no_duplicates( request: pytest.FixtureRequest, ): From ad5419637bf1ef02a1386be633494ca8e6534047 Mon Sep 17 00:00:00 2001 From: sarojrout Date: Wed, 19 Nov 2025 16:15:26 -0800 Subject: [PATCH 3/4] refactor(agents): use set for duplicates in validator Change duplicates from list to set for better efficiency and cleaner code. Remove unnecessary set() conversion when sorting duplicates. --- src/google/adk/agents/base_agent.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index 0fb78d9206..a9970a8390 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -581,18 +581,18 @@ def validate_sub_agents_unique_names(cls, value: list[BaseAgent]) -> list[BaseAg return value seen_names: set[str] = set() - duplicates: list[str] = [] + duplicates: set[str] = set() for sub_agent in value: name = sub_agent.name if name in seen_names: - duplicates.append(name) + duplicates.add(name) else: seen_names.add(name) if duplicates: duplicate_names_str = ', '.join( - f'`{name}`' for name in sorted(set(duplicates)) + f'`{name}`' for name in sorted(duplicates) ) raise ValueError( f'Found duplicate sub-agent names: {duplicate_names_str}. ' From 966aaf07de9460f2b7d4937ffdb5ae3482e305da Mon Sep 17 00:00:00 2001 From: sarojrout Date: Thu, 20 Nov 2025 16:06:28 -0800 Subject: [PATCH 4/4] fixed the linting error --- src/google/adk/agents/base_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index a9970a8390..fccde1cb6f 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -565,7 +565,9 @@ def validate_name(cls, value: str): @field_validator('sub_agents', mode='after') @classmethod - def validate_sub_agents_unique_names(cls, value: list[BaseAgent]) -> list[BaseAgent]: + def validate_sub_agents_unique_names( + cls, value: list[BaseAgent] + ) -> list[BaseAgent]: """Validates that all sub-agents have unique names. Args: