From 6bc926e04bb20508b359309c77c0b833ba8f5998 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 12 Jul 2025 09:20:31 -0700 Subject: [PATCH] [Bug Fix] xai/ translation fix - ensure finish_reason includes tool calls when xai responses with tool calls (#12545) * Helper to fix finish_reason for tool calls when XAI API returns empty string * use llm http handler for Groq --- litellm/llms/xai/chat/transformation.py | 60 +++++++++++++++++++++++++ litellm/main.py | 31 ++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/litellm/llms/xai/chat/transformation.py b/litellm/llms/xai/chat/transformation.py index 272e3841eb6..14b62db41b3 100644 --- a/litellm/llms/xai/chat/transformation.py +++ b/litellm/llms/xai/chat/transformation.py @@ -1,5 +1,7 @@ from typing import List, Optional, Tuple +import httpx + import litellm from litellm._logging import verbose_logger from litellm.litellm_core_utils.prompt_templates.common_utils import ( @@ -8,6 +10,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import ( ) from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import AllMessageValues +from litellm.types.utils import Choices, ModelResponse from ...openai.chat.gpt_transformation import OpenAIGPTConfig @@ -100,3 +103,60 @@ class XAIChatConfig(OpenAIGPTConfig): return super().transform_request( model, messages, optional_params, litellm_params, headers ) + + @staticmethod + def _fix_choice_finish_reason_for_tool_calls(choice: Choices) -> None: + """ + Helper to fix finish_reason for tool calls when XAI API returns empty string. + + XAI API returns empty string for finish_reason when using tools, + so we need to set it to "tool_calls" when tool_calls are present. + """ + if (choice.finish_reason == "" and + choice.message.tool_calls and + len(choice.message.tool_calls) > 0): + choice.finish_reason = "tool_calls" + + def transform_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ModelResponse, + logging_obj, + request_data: dict, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + encoding, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ModelResponse: + """ + Transform the response from the XAI API. + + XAI API returns empty string for finish_reason when using tools, + so we need to fix this after the standard OpenAI transformation. + """ + + # First, let the parent class handle the standard transformation + response = super().transform_response( + model=model, + raw_response=raw_response, + model_response=model_response, + logging_obj=logging_obj, + request_data=request_data, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + encoding=encoding, + api_key=api_key, + json_mode=json_mode, + ) + + # Fix finish_reason for tool calls across all choices + if response.choices: + for choice in response.choices: + if isinstance(choice, Choices): + self._fix_choice_finish_reason_for_tool_calls(choice) + + return response diff --git a/litellm/main.py b/litellm/main.py index 27374c9550a..57bc405ab8f 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1766,7 +1766,36 @@ def completion( # type: ignore # noqa: PLR0915 additional_args={"headers": headers}, ) raise e - + elif custom_llm_provider == "xai": + ## COMPLETION CALL + try: + response = base_llm_http_handler.completion( + model=model, + messages=messages, + headers=headers, + model_response=model_response, + api_key=api_key, + api_base=api_base, + acompletion=acompletion, + logging_obj=logging, + optional_params=optional_params, + litellm_params=litellm_params, + timeout=timeout, # type: ignore + client=client, + custom_llm_provider=custom_llm_provider, + encoding=encoding, + stream=stream, + provider_config=provider_config, + ) + except Exception as e: + ## LOGGING - log the original exception returned + logging.post_call( + input=messages, + api_key=api_key, + original_response=str(e), + additional_args={"headers": headers}, + ) + raise e elif custom_llm_provider == "groq": api_base = ( api_base # for deepinfra/perplexity/anyscale/groq/friendliai we check in get_llm_provider and pass in the api base from there