fix: Add experimental feature to use parameters_json_schema and response_json_schema for McpTool

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 833144947
This commit is contained in:
Xuan Yang
2025-11-16 20:55:16 -08:00
committed by Copybara-Service
parent f7f6837fde
commit 1dd97f5b45
10 changed files with 581 additions and 36 deletions
@@ -273,6 +273,45 @@ function_declaration_test_cases = [
},
),
),
(
"function_with_parameters_json_schema",
types.FunctionDeclaration(
name="search_database",
description="Searches a database with given criteria.",
parameters_json_schema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query",
},
"limit": {
"type": "integer",
"description": "Maximum number of results",
},
},
"required": ["query"],
},
),
anthropic_types.ToolParam(
name="search_database",
description="Searches a database with given criteria.",
input_schema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query",
},
"limit": {
"type": "integer",
"description": "Maximum number of results",
},
},
"required": ["query"],
},
),
),
]
@@ -346,3 +385,80 @@ async def test_generate_content_async_with_max_tokens(
mock_client.messages.create.assert_called_once()
_, kwargs = mock_client.messages.create.call_args
assert kwargs["max_tokens"] == 4096
def test_part_to_message_block_with_content():
"""Test that part_to_message_block handles content format."""
from google.adk.models.anthropic_llm import part_to_message_block
# Create a function response part with content array.
mcp_response_part = types.Part.from_function_response(
name="generate_sample_filesystem",
response={
"content": [{
"type": "text",
"text": '{"name":"root","node_type":"folder","children":[]}',
}]
},
)
mcp_response_part.function_response.id = "test_id_123"
result = part_to_message_block(mcp_response_part)
# ToolResultBlockParam is a TypedDict.
assert isinstance(result, dict)
assert result["tool_use_id"] == "test_id_123"
assert result["type"] == "tool_result"
assert not result["is_error"]
# Verify the content was extracted from the content format.
assert (
'{"name":"root","node_type":"folder","children":[]}' in result["content"]
)
def test_part_to_message_block_with_traditional_result():
"""Test that part_to_message_block handles traditional result format."""
from google.adk.models.anthropic_llm import part_to_message_block
# Create a function response part with traditional result format
traditional_response_part = types.Part.from_function_response(
name="some_tool",
response={
"result": "This is the result from the tool",
},
)
traditional_response_part.function_response.id = "test_id_456"
result = part_to_message_block(traditional_response_part)
# ToolResultBlockParam is a TypedDict.
assert isinstance(result, dict)
assert result["tool_use_id"] == "test_id_456"
assert result["type"] == "tool_result"
assert not result["is_error"]
# Verify the content was extracted from the traditional format
assert "This is the result from the tool" in result["content"]
def test_part_to_message_block_with_multiple_content_items():
"""Test content with multiple items."""
from google.adk.models.anthropic_llm import part_to_message_block
# Create a function response with multiple content items
multi_content_part = types.Part.from_function_response(
name="multi_response_tool",
response={
"content": [
{"type": "text", "text": "First part"},
{"type": "text", "text": "Second part"},
]
},
)
multi_content_part.function_response.id = "test_id_789"
result = part_to_message_block(multi_content_part)
# ToolResultBlockParam is a TypedDict.
assert isinstance(result, dict)
# Multiple text items should be joined with newlines
assert result["content"] == "First part\nSecond part"
+57 -4
View File
@@ -24,6 +24,7 @@ from google.adk.models.cache_metadata import CacheMetadata
from google.adk.models.gemini_llm_connection import GeminiLlmConnection
from google.adk.models.google_llm import _AGENT_ENGINE_TELEMETRY_ENV_VARIABLE_NAME
from google.adk.models.google_llm import _AGENT_ENGINE_TELEMETRY_TAG
from google.adk.models.google_llm import _build_function_declaration_log
from google.adk.models.google_llm import _build_request_log
from google.adk.models.google_llm import Gemini
from google.adk.models.llm_request import LlmRequest
@@ -1642,11 +1643,15 @@ async def test_generate_content_async_with_cache_metadata_integration(
):
"""Test integration between Google LLM and cache manager with proper parameter order.
This test specifically validates that the cache manager's populate_cache_metadata_in_response
method is called with the correct parameter order: (llm_response, cache_metadata).
This test specifically validates that the cache manager's
populate_cache_metadata_in_response
method is called with the correct parameter order: (llm_response,
cache_metadata).
This test would have caught the parameter order bug where cache_metadata and llm_response
were passed in the wrong order, causing 'CacheMetadata' object has no attribute 'usage_metadata' errors.
This test would have caught the parameter order bug where cache_metadata and
llm_response
were passed in the wrong order, causing 'CacheMetadata' object has no
attribute 'usage_metadata' errors.
"""
# Create a mock response with usage metadata including cached tokens
@@ -1729,6 +1734,54 @@ async def test_generate_content_async_with_cache_metadata_integration(
assert second_arg.invocations_used == cache_metadata.invocations_used
def test_build_function_declaration_log():
"""Test that _build_function_declaration_log formats function declarations correctly."""
# Test case 1: Function with parameters and response
func_decl1 = types.FunctionDeclaration(
name="test_func1",
description="Test function 1",
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"param1": types.Schema(
type=types.Type.STRING, description="param1 desc"
)
},
),
response=types.Schema(type=types.Type.BOOLEAN, description="return bool"),
)
log1 = _build_function_declaration_log(func_decl1)
assert log1 == (
"test_func1: {'param1': {'description': 'param1 desc', 'type':"
" <Type.STRING: 'STRING'>}} -> {'description': 'return bool', 'type':"
" <Type.BOOLEAN: 'BOOLEAN'>}"
)
# Test case 2: Function with JSON schema parameters and response
func_decl2 = types.FunctionDeclaration(
name="test_func2",
description="Test function 2",
parameters_json_schema={
"type": "object",
"properties": {"param2": {"type": "integer"}},
},
response_json_schema={"type": "string"},
)
log2 = _build_function_declaration_log(func_decl2)
assert log2 == (
"test_func2: {'type': 'object', 'properties': {'param2': {'type':"
" 'integer'}}} -> {'type': 'string'}"
)
# Test case 3: Function with no parameters and no response
func_decl3 = types.FunctionDeclaration(
name="test_func3",
description="Test function 3",
)
log3 = _build_function_declaration_log(func_decl3)
assert log3 == "test_func3: {} "
def test_build_request_log_with_config_multiple_tool_types():
"""Test that _build_request_log includes config with multiple tool types."""
func_decl = types.FunctionDeclaration(
+49
View File
@@ -17,6 +17,7 @@ from unittest.mock import AsyncMock
from unittest.mock import Mock
import warnings
from google.adk.models.lite_llm import _build_function_declaration_log
from google.adk.models.lite_llm import _content_to_message_param
from google.adk.models.lite_llm import _FINISH_REASON_MAPPING
from google.adk.models.lite_llm import _function_declaration_to_tool_param
@@ -629,6 +630,54 @@ class MockLLMClient(LiteLLMClient):
)
def test_build_function_declaration_log():
"""Test that _build_function_declaration_log formats function declarations correctly."""
# Test case 1: Function with parameters and response
func_decl1 = types.FunctionDeclaration(
name="test_func1",
description="Test function 1",
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"param1": types.Schema(
type=types.Type.STRING, description="param1 desc"
)
},
),
response=types.Schema(type=types.Type.BOOLEAN, description="return bool"),
)
log1 = _build_function_declaration_log(func_decl1)
assert log1 == (
"test_func1: {'param1': {'description': 'param1 desc', 'type':"
" <Type.STRING: 'STRING'>}} -> {'description': 'return bool', 'type':"
" <Type.BOOLEAN: 'BOOLEAN'>}"
)
# Test case 2: Function with JSON schema parameters and response
func_decl2 = types.FunctionDeclaration(
name="test_func2",
description="Test function 2",
parameters_json_schema={
"type": "object",
"properties": {"param2": {"type": "integer"}},
},
response_json_schema={"type": "string"},
)
log2 = _build_function_declaration_log(func_decl2)
assert log2 == (
"test_func2: {'type': 'object', 'properties': {'param2': {'type':"
" 'integer'}}} -> {'type': 'string'}"
)
# Test case 3: Function with no parameters and no response
func_decl3 = types.FunctionDeclaration(
name="test_func3",
description="Test function 3",
)
log3 = _build_function_declaration_log(func_decl3)
assert log3 == "test_func3: {} -> None"
@pytest.mark.asyncio
async def test_generate_content_async(mock_acompletion, lite_llm_instance):