Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/google/adk/models/anthropic_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import base64
from functools import cached_property
import json
import logging
import os
from typing import Any
Expand Down Expand Up @@ -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 "",
Expand Down
36 changes: 36 additions & 0 deletions tests/unittests/models/test_anthropic_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down