From 1e23652968164c5fdfa5564e966e78799237d94b Mon Sep 17 00:00:00 2001 From: George Weale Date: Sun, 7 Sep 2025 18:37:01 -0700 Subject: [PATCH] fix: AttributeError and indentation in parameter processing. For issue #2776 and issue #2763 PiperOrigin-RevId: 804222317 --- src/google/adk/models/lite_llm.py | 9 +- tests/unittests/models/test_litellm.py | 127 +++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index d84df9ab..f25f0cdb 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -392,7 +392,10 @@ def _function_declaration_to_tool_param( }, } - if function_declaration.parameters.required: + if ( + function_declaration.parameters + and function_declaration.parameters.required + ): tool_params["function"]["parameters"][ "required" ] = function_declaration.parameters.required @@ -596,8 +599,8 @@ def _get_completion_inputs( mapped_key = param_mapping.get(key, key) generation_params[mapped_key] = config_dict[key] - if not generation_params: - generation_params = None + if not generation_params: + generation_params = None return messages, tools, response_format, generation_params diff --git a/tests/unittests/models/test_litellm.py b/tests/unittests/models/test_litellm.py index a7152f55..a46d0f7d 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -731,6 +731,52 @@ function_declaration_test_cases = [ }, }, ), + ( + "no_parameters", + types.FunctionDeclaration( + name="test_function_no_params", + description="Test function with no parameters", + ), + { + "type": "function", + "function": { + "name": "test_function_no_params", + "description": "Test function with no parameters", + "parameters": { + "type": "object", + "properties": {}, + }, + }, + }, + ), + ( + "parameters_without_required", + types.FunctionDeclaration( + name="test_function_no_required", + description="Test function with parameters but no required field", + parameters=types.Schema( + type=types.Type.OBJECT, + properties={ + "optional_arg": types.Schema(type=types.Type.STRING), + }, + ), + ), + { + "type": "function", + "function": { + "name": "test_function_no_required", + "description": ( + "Test function with parameters but no required field" + ), + "parameters": { + "type": "object", + "properties": { + "optional_arg": {"type": "string"}, + }, + }, + }, + }, + ), ] @@ -1577,6 +1623,87 @@ def test_get_completion_inputs_generation_params(): assert "stop_sequences" not in generation_params +@pytest.mark.asyncio +def test_get_completion_inputs_empty_generation_params(): + # Test that generation_params is None when no generation parameters are set + req = LlmRequest( + contents=[ + types.Content(role="user", parts=[types.Part.from_text(text="hi")]), + ], + config=types.GenerateContentConfig(), + ) + from google.adk.models.lite_llm import _get_completion_inputs + + _, _, _, generation_params = _get_completion_inputs(req) + assert generation_params is None + + +@pytest.mark.asyncio +def test_get_completion_inputs_minimal_config(): + # Test that generation_params is None when config has no generation parameters + req = LlmRequest( + contents=[ + types.Content(role="user", parts=[types.Part.from_text(text="hi")]), + ], + config=types.GenerateContentConfig( + system_instruction="test instruction" # Non-generation parameter + ), + ) + from google.adk.models.lite_llm import _get_completion_inputs + + _, _, _, generation_params = _get_completion_inputs(req) + assert generation_params is None + + +@pytest.mark.asyncio +def test_get_completion_inputs_partial_generation_params(): + # Test that generation_params is correctly built even with only some parameters + req = LlmRequest( + contents=[ + types.Content(role="user", parts=[types.Part.from_text(text="hi")]), + ], + config=types.GenerateContentConfig( + temperature=0.7, + # Only temperature is set, others are None/default + ), + ) + from google.adk.models.lite_llm import _get_completion_inputs + + _, _, _, generation_params = _get_completion_inputs(req) + assert generation_params is not None + assert generation_params["temperature"] == 0.7 + # Should only contain the temperature parameter + assert len(generation_params) == 1 + + +def test_function_declaration_to_tool_param_edge_cases(): + """Test edge cases for function declaration conversion that caused the original bug.""" + from google.adk.models.lite_llm import _function_declaration_to_tool_param + + # Test function with None parameters (the original bug scenario) + func_decl = types.FunctionDeclaration( + name="test_function_none_params", + description="Function with None parameters", + parameters=None, + ) + result = _function_declaration_to_tool_param(func_decl) + expected = { + "type": "function", + "function": { + "name": "test_function_none_params", + "description": "Function with None parameters", + "parameters": { + "type": "object", + "properties": {}, + }, + }, + } + assert result == expected + + # Verify no 'required' field is added when parameters is None + assert "required" not in result["function"]["parameters"] + + def test_gemini_via_litellm_warning(monkeypatch): """Test that Gemini via LiteLLM shows warning.""" # Ensure environment variable is not set