[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
This commit is contained in:
Ishaan Jaff 2025-07-12 09:20:31 -07:00 committed by GitHub
parent 858c756d47
commit 6bc926e04b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 1 deletions

View file

@ -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

View file

@ -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