diff --git a/src/google/adk/models/apigee_llm.py b/src/google/adk/models/apigee_llm.py index 2be00c8ad93..8e963f7232c 100644 --- a/src/google/adk/models/apigee_llm.py +++ b/src/google/adk/models/apigee_llm.py @@ -813,9 +813,18 @@ def _map_tools( if tools: payload['tools'] = tools if config.tool_config and config.tool_config.function_calling_config: - mode = config.tool_config.function_calling_config.mode + function_calling_config = config.tool_config.function_calling_config + mode = function_calling_config.mode + allowed_names = function_calling_config.allowed_function_names or [] if mode == types.FunctionCallingConfigMode.ANY: - payload['tool_choice'] = 'required' + # One allowed name forces that function, as AnthropicLlm does. + if len(allowed_names) == 1: + payload['tool_choice'] = { + 'type': 'function', + 'function': {'name': allowed_names[0]}, + } + else: + payload['tool_choice'] = 'required' elif mode == types.FunctionCallingConfigMode.NONE: payload['tool_choice'] = 'none' elif mode == types.FunctionCallingConfigMode.AUTO: diff --git a/tests/unittests/models/test_apigee_llm.py b/tests/unittests/models/test_apigee_llm.py index 8720f6fb2b2..e84eaa06756 100644 --- a/tests/unittests/models/test_apigee_llm.py +++ b/tests/unittests/models/test_apigee_llm.py @@ -1229,6 +1229,46 @@ def unsupported_tool() -> None: client._construct_payload(request, stream=False) +@pytest.mark.parametrize( + ('allowed_names', 'expected_choice'), + [ + ( + ['lookup_order'], + {'type': 'function', 'function': {'name': 'lookup_order'}}, + ), + (['lookup_order', 'issue_refund'], 'required'), + (None, 'required'), + ], +) +def test_construct_payload_any_mode_with_allowed_function_names( + allowed_names: list[str] | None, expected_choice: object +) -> None: + request = LlmRequest( + model='apigee/openai/gpt-4o', + contents=[], + config=types.GenerateContentConfig( + tools=[ + types.Tool( + function_declarations=[ + types.FunctionDeclaration(name='lookup_order'), + types.FunctionDeclaration(name='issue_refund'), + ] + ) + ], + tool_config=types.ToolConfig( + function_calling_config=types.FunctionCallingConfig( + mode=types.FunctionCallingConfigMode.ANY, + allowed_function_names=allowed_names, + ) + ), + ), + ) + + client = CompletionsHTTPClient(base_url='http://test') + payload = client._construct_payload(request, stream=False) + assert payload['tool_choice'] == expected_choice + + def test_content_conversion_rejects_unnamed_function_call() -> None: content = types.Content( role='model',