diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index 63adee3db35..d1f7d3c4c9b 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -112,11 +112,11 @@ class FireworksAIConfig(OpenAIGPTConfig): # Only add tools for models that support function calling if supports_function_calling(model=model, custom_llm_provider="fireworks_ai"): supported_params.append("tools") + supported_params.append("parallel_tool_calls") # Only add tool_choice for models that explicitly support it if supports_tool_choice(model=model, custom_llm_provider="fireworks_ai"): supported_params.append("tool_choice") - supported_params.append("parallel_tool_calls") # Only add reasoning_effort for models that support it if supports_reasoning(model=model, custom_llm_provider="fireworks_ai"): diff --git a/litellm/llms/xai/chat/transformation.py b/litellm/llms/xai/chat/transformation.py index 447cab3db35..58d08ec5621 100644 --- a/litellm/llms/xai/chat/transformation.py +++ b/litellm/llms/xai/chat/transformation.py @@ -262,13 +262,13 @@ class XAIChatConfig(OpenAIGPTConfig): prompt_tokens = int(usage.get("prompt_tokens") or 0) completion_tokens = int(usage.get("completion_tokens") or 0) expected_total = prompt_tokens + completion_tokens - if int(usage.get("total_tokens") or 0) != expected_total: + if int(usage.get("total_tokens") or 0) < expected_total: usage["total_tokens"] = expected_total return prompt_tokens = int(usage.prompt_tokens or 0) completion_tokens = int(usage.completion_tokens or 0) expected_total = prompt_tokens + completion_tokens - if int(usage.total_tokens or 0) != expected_total: + if int(usage.total_tokens or 0) < expected_total: usage.total_tokens = expected_total diff --git a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py index a56d84844e3..8fbf08d86b0 100644 --- a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py @@ -123,7 +123,7 @@ def test_get_supported_openai_params_reasoning_effort(): def test_get_supported_openai_params_parallel_tool_calls(): - """Test that parallel_tool_calls is included for models that support tool_choice.""" + """Test that parallel_tool_calls is included for models that support function calling.""" config = FireworksAIConfig() supported_params = config.get_supported_openai_params( @@ -137,6 +137,28 @@ def test_get_supported_openai_params_parallel_tool_calls(): assert "parallel_tool_calls" not in unsupported_params +def test_get_supported_openai_params_parallel_tool_calls_without_tool_choice( + monkeypatch, +): + """Test that parallel_tool_calls is gated on tools, not tool_choice.""" + config = FireworksAIConfig() + model = "fireworks_ai/test-tools-without-tool-choice" + monkeypatch.setitem( + litellm.model_cost, + model, + { + "supports_function_calling": True, + "supports_tool_choice": False, + }, + ) + + supported_params = config.get_supported_openai_params(model) + + assert "tools" in supported_params + assert "parallel_tool_calls" in supported_params + assert "tool_choice" not in supported_params + + def test_get_model_info_respects_explicit_fireworks_capabilities(): """Test that get_model_info preserves explicit capability flags from the model map.""" model_info = get_model_info("fireworks_ai/accounts/fireworks/models/glm-5p1") diff --git a/tests/test_litellm/llms/xai/test_xai_chat_transformation.py b/tests/test_litellm/llms/xai/test_xai_chat_transformation.py index 5a236de900e..79955c18588 100644 --- a/tests/test_litellm/llms/xai/test_xai_chat_transformation.py +++ b/tests/test_litellm/llms/xai/test_xai_chat_transformation.py @@ -6,6 +6,7 @@ sys.path.insert( ) # Adds the parent directory to the system path from litellm.llms.xai.chat.transformation import XAIChatConfig +from litellm.types.utils import Usage class TestXAIParallelToolCalls: @@ -14,9 +15,7 @@ class TestXAIParallelToolCalls: def test_get_supported_openai_params_includes_parallel_tool_calls(self): """Test that parallel_tool_calls is in supported parameters.""" config = XAIChatConfig() - supported_params = config.get_supported_openai_params( - "xai/grok-4.20" - ) + supported_params = config.get_supported_openai_params("xai/grok-4.20") assert "parallel_tool_calls" in supported_params def test_transform_request_preserves_parallel_tool_calls(self): @@ -37,3 +36,19 @@ class TestXAIParallelToolCalls: assert result.get("parallel_tool_calls") is True assert len(result["messages"]) == 1 assert result["messages"][0]["role"] == "user" + + +class TestXAIUsageNormalization: + def test_preserves_reasoning_tokens_in_total_usage(self): + usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=200) + + XAIChatConfig._normalize_openai_compatible_usage_totals(usage) + + assert usage.total_tokens == 200 + + def test_preserves_reasoning_tokens_in_streaming_usage(self): + usage = {"prompt_tokens": 100, "completion_tokens": 50, "total_tokens": 200} + + XAIChatConfig._normalize_openai_compatible_usage_totals(usage) + + assert usage["total_tokens"] == 200