diff --git a/src/google/adk/models/anthropic_llm.py b/src/google/adk/models/anthropic_llm.py index 7dd050a1cb..a74299ce69 100644 --- a/src/google/adk/models/anthropic_llm.py +++ b/src/google/adk/models/anthropic_llm.py @@ -146,6 +146,10 @@ def part_to_message_block( content = json.dumps(result) else: content = str(result) + elif response_data: + # Fallback: serialize any non-standard response dict (e.g. SkillToolset + # returns {"skill_name": ..., "instructions": ..., "frontmatter": ...}) + content = json.dumps(response_data) return anthropic_types.ToolResultBlockParam( tool_use_id=part.function_response.id or "", diff --git a/tests/unittests/models/test_anthropic_llm.py b/tests/unittests/models/test_anthropic_llm.py index 3df9f567be..d3037c0d82 100644 --- a/tests/unittests/models/test_anthropic_llm.py +++ b/tests/unittests/models/test_anthropic_llm.py @@ -741,6 +741,30 @@ def test_part_to_message_block_with_multiple_content_items(): assert result["content"] == "First part\nSecond part" +def test_part_to_message_block_with_non_standard_response(): + """Test that part_to_message_block serializes non-standard response dicts. + + Regression test for https://github.com/google/adk-python/issues/4779. + SkillToolset returns dicts like {"skill_name": ..., "instructions": ...} + that don't contain "content" or "result" keys. These were silently + dropped (empty string), causing Claude to never see the tool output. + """ + import json + + from google.adk.models.anthropic_llm import part_to_message_block + + skill_response = { + "skill_name": "search_docs", + "instructions": "Use the search API to find documents.", + "frontmatter": {"version": "1.0"}, + } + part = types.Part.from_function_response( + name="load_skill", + response=skill_response, + ) + part.function_response.id = "test_skill_id" + + def test_part_to_message_block_with_pdf_document(): """Test that part_to_message_block handles PDF document parts.""" pdf_data = b"%PDF-1.4 fake pdf content" @@ -751,6 +775,16 @@ def test_part_to_message_block_with_pdf_document(): result = part_to_message_block(part) assert isinstance(result, dict) + assert result["tool_use_id"] == "test_skill_id" + assert result["type"] == "tool_result" + assert not result["is_error"] + # Content must be non-empty and contain the original data + parsed = json.loads(result["content"]) + assert parsed["skill_name"] == "search_docs" + assert parsed["instructions"] == "Use the search API to find documents." + assert parsed["frontmatter"] == {"version": "1.0"} + + assert result["type"] == "document" assert result["source"]["type"] == "base64" assert result["source"]["media_type"] == "application/pdf"