diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index 975ffd9b3f..f23770eaf2 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -2455,9 +2455,24 @@ def _message_to_generate_content_response( for tool_call in tool_calls: if tool_call.type == "function": thought_signature = _extract_thought_signature_from_tool_call(tool_call) + try: + args = _parse_tool_call_arguments(tool_call.function.arguments) + except json.JSONDecodeError as exc: + # Malformed tool-call arguments are treated as a recoverable model + # error rather than a crash: surface empty args so downstream tool + # dispatch reports the mismatch back to the model for a retry. + logger.warning( + "Failed to parse arguments of tool call %r for function %r as" + " JSON (%s). Falling back to empty args so the model can" + " retry.", + tool_call.id, + tool_call.function.name, + exc, + ) + args = {} part = types.Part.from_function_call( name=tool_call.function.name, - args=_parse_tool_call_arguments(tool_call.function.arguments), + args=args, ) function_call = part.function_call if function_call is None: diff --git a/tests/unittests/models/test_litellm.py b/tests/unittests/models/test_litellm.py index 68b4135f52..2e5f4942ff 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -2664,6 +2664,59 @@ def test_message_to_generate_content_response_tool_call_accepts_unquoted_json_ke } +def test_message_to_generate_content_response_tool_call_with_malformed_json_arguments(): + message = ChatCompletionAssistantMessage( + role="assistant", + content=None, + tool_calls=[ + ChatCompletionMessageToolCall( + type="function", + id="test_tool_call_id", + function=Function( + name="test_function", + arguments='{"city": "unterminated', + ), + ) + ], + ) + + response = _message_to_generate_content_response(message) + + assert response.content.role == "model" + assert response.content.parts[0].function_call.name == "test_function" + assert response.content.parts[0].function_call.id == "test_tool_call_id" + assert response.content.parts[0].function_call.args == {} + + +def test_message_to_generate_content_response_tool_call_malformed_json_warns( + caplog, +): + message = ChatCompletionAssistantMessage( + role="assistant", + content=None, + tool_calls=[ + ChatCompletionMessageToolCall( + type="function", + id="test_tool_call_id", + function=Function( + name="test_function", + arguments='{"city": "unterminated', + ), + ) + ], + ) + + with caplog.at_level(logging.WARNING, logger="google_adk"): + response = _message_to_generate_content_response(message) + + assert response.content.parts[0].function_call.args == {} + assert any( + record.levelno >= logging.WARNING + for record in caplog.records + if "Failed to parse arguments" in record.getMessage() + ) + + def test_message_to_generate_content_response_inline_tool_call_text(): message = ChatCompletionAssistantMessage( role="assistant",