mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
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:
committed by
Copybara-Service
parent
f7f6837fde
commit
1dd97f5b45
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user