From 9af3e87de7fb95aaf98b1728d83c4d0f04bf2456 Mon Sep 17 00:00:00 2001 From: octo-patch Date: Sat, 18 Apr 2026 09:11:35 +0800 Subject: [PATCH] fix(ollama): raise BadRequestError on malformed tool call JSON arguments Wrap json.loads() in transform_request so callers get a descriptive litellm.BadRequestError instead of a raw json.JSONDecodeError when an Ollama-compatible model returns truncated or otherwise invalid JSON in tool call arguments. Fixes #25985 --- litellm/llms/ollama/chat/transformation.py | 14 +++- .../ollama/test_ollama_chat_transformation.py | 79 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index c990cc2e093..31e694ce798 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -1,6 +1,7 @@ import json import time from litellm._uuid import uuid +from litellm._logging import verbose_logger from typing import ( TYPE_CHECKING, Any, @@ -272,7 +273,18 @@ class OllamaChatConfig(BaseConfig): if typed_tool["type"] == "function": arguments = {} if "arguments" in typed_tool["function"]: - arguments = json.loads(typed_tool["function"]["arguments"]) + raw_args = typed_tool["function"]["arguments"] + try: + arguments = json.loads(raw_args) + except json.JSONDecodeError as e: + verbose_logger.error( + f"Failed to parse tool call arguments as JSON: {raw_args!r}. Error: {e}" + ) + raise litellm.BadRequestError( + message=f"Tool call arguments contain malformed JSON: {e.msg}. Raw arguments: {raw_args!r}", + model="ollama", + llm_provider="ollama", + ) ollama_tool_call = OllamaToolCall( function=OllamaToolCallFunction( name=typed_tool["function"].get("name") or "", diff --git a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py index a1afdd3a369..6cd3e1a4dd9 100644 --- a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py @@ -338,7 +338,84 @@ class TestOllamaToolCalling: Issue: https://github.com/BerriAI/litellm/issues/18922 """ - def test_tools_passed_directly_without_capability_check(self): + def test_transform_request_malformed_tool_call_arguments_raises_bad_request(self): + """Test that malformed JSON in tool call arguments raises BadRequestError. + + Regression: json.JSONDecodeError was previously raised directly, wrapping it + in litellm.BadRequestError gives callers an actionable error with context. + + Issue: https://github.com/BerriAI/litellm/issues/25985 + """ + config = OllamaChatConfig() + + messages = cast( + list[AllMessageValues], + [ + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"city": "Toky', # truncated JSON + }, + } + ], + } + ], + ) + + import litellm + + with pytest.raises(litellm.BadRequestError) as exc_info: + config.transform_request( + model="qwen3:14b", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + assert "malformed JSON" in str(exc_info.value) + + def test_transform_request_valid_tool_call_arguments_passes(self): + """Test that valid JSON tool call arguments are parsed without raising an exception.""" + config = OllamaChatConfig() + + messages = cast( + list[AllMessageValues], + [ + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"city": "Tokyo"}', + }, + } + ], + } + ], + ) + + # Should not raise any exception + result = config.transform_request( + model="qwen3:14b", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + assert "messages" in result + + """Test that tools are passed directly to Ollama without model capability checks. Previously, the code called litellm.get_model_info() which could fail