diff --git a/src/google/adk/models/anthropic_llm.py b/src/google/adk/models/anthropic_llm.py index 9799209695..29a3d0ca39 100644 --- a/src/google/adk/models/anthropic_llm.py +++ b/src/google/adk/models/anthropic_llm.py @@ -18,6 +18,7 @@ import base64 from functools import cached_property +import json import logging import os from typing import Any @@ -121,6 +122,10 @@ def part_to_message_block( # ToolResultBlockParam content doesn't support list of dict. Converting # to str to prevent anthropic.BadRequestError from being thrown. content = str(response_data["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 fac5f46204..265a81802f 100644 --- a/tests/unittests/models/test_anthropic_llm.py +++ b/tests/unittests/models/test_anthropic_llm.py @@ -522,6 +522,42 @@ 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" + + 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"} + + content_to_message_param_test_cases = [ ( "user_role_with_text_and_image",