From abdc17ad080d31cbc3ec20ec92e821faa4923109 Mon Sep 17 00:00:00 2001 From: Tim Niemueller Date: Wed, 10 Dec 2025 16:55:04 +0100 Subject: [PATCH 1/2] fix: Convert examples for A2A agent card The AgentSkill in an A2A AgentCard expects examples to be a list of queries as strings. Therefore, agent examples, e.g., as provided by an ExampleTool, must be converted. This change performs that extraction of just the inputs and converting them to a string to add to the AgentSkill. --- .../adk/a2a/utils/agent_card_builder.py | 29 ++++++- .../a2a/utils/test_agent_card_builder.py | 78 +++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_card_builder.py b/src/google/adk/a2a/utils/agent_card_builder.py index c007870931..d733f045d2 100644 --- a/src/google/adk/a2a/utils/agent_card_builder.py +++ b/src/google/adk/a2a/utils/agent_card_builder.py @@ -114,7 +114,7 @@ async def _build_llm_agent_skills(agent: LlmAgent) -> List[AgentSkill]: id=agent.name, name='model', description=agent_description, - examples=agent_examples, + examples=_extract_inputs_from_examples(agent_examples), input_modes=_get_input_modes(agent), output_modes=_get_output_modes(agent), tags=['llm'], @@ -239,7 +239,7 @@ async def _build_non_llm_agent_skills(agent: BaseAgent) -> List[AgentSkill]: id=agent.name, name=agent_name, description=agent_description, - examples=agent_examples, + examples=_extract_inputs_from_examples(agent_examples), input_modes=_get_input_modes(agent), output_modes=_get_output_modes(agent), tags=[agent_type], @@ -350,6 +350,7 @@ def _build_llm_agent_description_with_instructions(agent: LlmAgent) -> str: def _replace_pronouns(text: str) -> str: """Replace pronouns and conjugate common verbs for agent description. + (e.g., "You are" -> "I am", "your" -> "my"). """ pronoun_map = { @@ -460,6 +461,30 @@ def _get_default_description(agent: BaseAgent) -> str: return 'A custom agent' +def _extract_inputs_from_examples(examples: Optional[list[dict]]) -> list[str]: + """Extracts only the input strings so they can be added to an AgentSkill.""" + if not examples: + return [] + + example_strs = [] + + for example in examples: + if 'input' in example: + example_input = example['input'] + + if 'parts' in example_input and example_input['parts'] is not None: + example_input_strs = [] + for part in example_input['parts']: + if 'text' in part and part['text'] is not None: + example_input_strs.append(part['text']) + example_strs.append('\n'.join(example_input_strs)) + + elif 'text' in example_input: + example_strs.append(example_input['text']) + + return example_strs + + async def _extract_examples_from_agent( agent: BaseAgent, ) -> Optional[List[Dict]]: diff --git a/tests/unittests/a2a/utils/test_agent_card_builder.py b/tests/unittests/a2a/utils/test_agent_card_builder.py index 3bf3202897..1a6c5c9591 100644 --- a/tests/unittests/a2a/utils/test_agent_card_builder.py +++ b/tests/unittests/a2a/utils/test_agent_card_builder.py @@ -28,6 +28,7 @@ from google.adk.a2a.utils.agent_card_builder import _build_sequential_description from google.adk.a2a.utils.agent_card_builder import _convert_example_tool_examples from google.adk.a2a.utils.agent_card_builder import _extract_examples_from_instruction +from google.adk.a2a.utils.agent_card_builder import _extract_inputs_from_examples from google.adk.a2a.utils.agent_card_builder import _get_agent_skill_name from google.adk.a2a.utils.agent_card_builder import _get_agent_type from google.adk.a2a.utils.agent_card_builder import _get_default_description @@ -41,6 +42,7 @@ from google.adk.agents.loop_agent import LoopAgent from google.adk.agents.parallel_agent import ParallelAgent from google.adk.agents.sequential_agent import SequentialAgent +from google.adk.examples import Example from google.adk.tools.example_tool import ExampleTool import pytest @@ -1100,3 +1102,79 @@ def test_extract_examples_from_instruction_odd_number_of_matches(self): assert len(result) == 1 # Only complete pairs should be included assert result[0]["input"] == {"text": "What is the weather?"} assert result[0]["output"] == [{"text": "What time is it?"}] + + def test_extract_inputs_from_examples_from_plain_text_input(self): + """Test _extract_inputs_from_examples on None as input.""" + # Arrange + examples = [ + { + "input": {"text": "What is the weather?"}, + "output": [{"text": "What time is it?"}], + }, + { + "input": {"text": "The weather is sunny."}, + "output": [{"text": "It is 3 PM."}], + }, + ] + + # Act + result = _extract_inputs_from_examples(examples) + + # Assert + assert len(result) is 2 + assert result[0] == "What is the weather?" + assert result[1] == "The weather is sunny." + + def test_extract_inputs_from_examples_from_example_tool(self): + """Test _extract_inputs_from_examples as extracted from ExampleTool.""" + + # Arrange + # This is what would be extracted from an ExampleTool + examples = [ + { + "input": { + "role": "user", + "parts": [{"text": "What is the weather?"}], + }, + "output": [ + { + "role": "model", + "parts": [{"text": "What time is it?"}], + }, + ], + }, + { + "input": { + "role": "user", + "parts": [{"text": "The weather is sunny."}], + }, + "output": [ + { + "role": "model", + "parts": [{"text": "It is 3 PM."}], + }, + ], + }, + ] + + # Act + result = _extract_inputs_from_examples(examples) + + # Assert + assert len(result) is 2 + assert result[0] == "What is the weather?" + assert result[1] == "The weather is sunny." + + def test_extract_inputs_from_examples_none_input(self): + """Test _extract_inputs_from_examples on None as input.""" + # Arrange + instruction = ( + 'Example Query: "What is the weather?" Example Response: "The weather' + ' is sunny."' + ) + + # Act + result = _extract_inputs_from_examples(None) + + # Assert + assert len(result) is 0 From 34c727c3311d2ec945efa3eec652d9e28b6ae2a9 Mon Sep 17 00:00:00 2001 From: Tim Niemueller Date: Mon, 22 Dec 2025 12:40:01 +0100 Subject: [PATCH 2/2] Address automated review comments --- .../adk/a2a/utils/agent_card_builder.py | 33 ++++++++++--------- .../a2a/utils/test_agent_card_builder.py | 14 +++----- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_card_builder.py b/src/google/adk/a2a/utils/agent_card_builder.py index d733f045d2..2855077704 100644 --- a/src/google/adk/a2a/utils/agent_card_builder.py +++ b/src/google/adk/a2a/utils/agent_card_builder.py @@ -463,26 +463,29 @@ def _get_default_description(agent: BaseAgent) -> str: def _extract_inputs_from_examples(examples: Optional[list[dict]]) -> list[str]: """Extracts only the input strings so they can be added to an AgentSkill.""" - if not examples: + if examples is None: return [] - example_strs = [] - + extracted_inputs = [] for example in examples: - if 'input' in example: - example_input = example['input'] - - if 'parts' in example_input and example_input['parts'] is not None: - example_input_strs = [] - for part in example_input['parts']: - if 'text' in part and part['text'] is not None: - example_input_strs.append(part['text']) - example_strs.append('\n'.join(example_input_strs)) + example_input = example.get('input') + if not example_input: + continue - elif 'text' in example_input: - example_strs.append(example_input['text']) + parts = example_input.get('parts') + if parts is not None: + part_texts = [] + for part in parts: + text = part.get('text') + if text is not None: + part_texts.append(text) + extracted_inputs.append('\n'.join(part_texts)) + else: + text = example_input.get('text') + if text is not None: + extracted_inputs.append(text) - return example_strs + return extracted_inputs async def _extract_examples_from_agent( diff --git a/tests/unittests/a2a/utils/test_agent_card_builder.py b/tests/unittests/a2a/utils/test_agent_card_builder.py index 1a6c5c9591..d8fbf1e9f9 100644 --- a/tests/unittests/a2a/utils/test_agent_card_builder.py +++ b/tests/unittests/a2a/utils/test_agent_card_builder.py @@ -1104,7 +1104,7 @@ def test_extract_examples_from_instruction_odd_number_of_matches(self): assert result[0]["output"] == [{"text": "What time is it?"}] def test_extract_inputs_from_examples_from_plain_text_input(self): - """Test _extract_inputs_from_examples on None as input.""" + """Test _extract_inputs_from_examples on plain text as input.""" # Arrange examples = [ { @@ -1121,7 +1121,7 @@ def test_extract_inputs_from_examples_from_plain_text_input(self): result = _extract_inputs_from_examples(examples) # Assert - assert len(result) is 2 + assert len(result) == 2 assert result[0] == "What is the weather?" assert result[1] == "The weather is sunny." @@ -1161,20 +1161,14 @@ def test_extract_inputs_from_examples_from_example_tool(self): result = _extract_inputs_from_examples(examples) # Assert - assert len(result) is 2 + assert len(result) == 2 assert result[0] == "What is the weather?" assert result[1] == "The weather is sunny." def test_extract_inputs_from_examples_none_input(self): """Test _extract_inputs_from_examples on None as input.""" - # Arrange - instruction = ( - 'Example Query: "What is the weather?" Example Response: "The weather' - ' is sunny."' - ) - # Act result = _extract_inputs_from_examples(None) # Assert - assert len(result) is 0 + assert len(result) == 0