This commit is contained in:
devin-ai-integration[bot] 2026-08-27 05:35:37 +08:00 committed by GitHub
commit bd0352cd3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 62 additions and 38 deletions

View file

@ -19,7 +19,6 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
Inherits from OpenAIResponsesAPIConfig since XAI's Responses API is largely
compatible with OpenAI's, with a few differences:
- Does not support the 'instructions' parameter
- Requires code_interpreter tools to have 'container' field removed
- Recommends store=false when sending images
@ -30,20 +29,6 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
def custom_llm_provider(self) -> LlmProviders:
return LlmProviders.XAI
def get_supported_openai_params(self, model: str) -> list:
"""
Get supported parameters for XAI Responses API.
XAI supports most OpenAI Responses API params except 'instructions'.
"""
supported_params: Final = super().get_supported_openai_params(model)
# Remove 'instructions' as it's not supported by XAI
if "instructions" in supported_params:
supported_params.remove("instructions")
return supported_params
def _transform_web_search_tool(self, tool: dict[str, Any]) -> XAIWebSearchTool | dict[str, Any]:
"""
Transform web_search tool to XAI format.
@ -133,19 +118,13 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
Map parameters for XAI Responses API.
Handles XAI-specific transformations:
1. Drops 'instructions' parameter (not supported)
2. Transforms code_interpreter tools to remove 'container' field
3. Transforms web_search tools to XAI format (removes search_context_size, adds filters)
4. Transforms x_search tools to XAI format
5. Sets store=false when images are detected (recommended by XAI)
1. Transforms code_interpreter tools to remove 'container' field
2. Transforms web_search tools to XAI format (removes search_context_size, adds filters)
3. Transforms x_search tools to XAI format
4. Sets store=false when images are detected (recommended by XAI)
"""
params: Final = dict(response_api_optional_params)
# Drop instructions parameter (not supported by XAI)
if "instructions" in params:
verbose_logger.debug("XAI Responses API does not support 'instructions' parameter. Dropping it.")
params.pop("instructions")
if "metadata" in params:
verbose_logger.debug("XAI Responses API does not support 'metadata' parameter. Dropping it.")
params.pop("metadata")

View file

@ -52,23 +52,23 @@ class TestXAIResponsesAPITransformation:
assert result["tools"][0]["type"] == "code_interpreter"
assert "container" not in result["tools"][0], "Container field should be removed"
def test_instructions_parameter_dropped(self):
"""Test that instructions parameter is dropped for XAI"""
def test_instructions_parameter_forwarded(self):
"""xAI supports 'instructions' on /v1/responses, so it must survive param mapping"""
config = XAIResponsesAPIConfig()
params = ResponsesAPIOptionalRequestParams(instructions="You are a helpful assistant.", temperature=0.7)
result = config.map_openai_params(response_api_optional_params=params, model="grok-4-fast", drop_params=False)
assert "instructions" not in result, "Instructions should be dropped"
assert result.get("instructions") == "You are a helpful assistant."
assert result.get("temperature") == 0.7, "Other params should be preserved"
def test_supported_params_excludes_instructions(self):
"""Test that get_supported_openai_params excludes instructions"""
def test_supported_params_includes_instructions(self):
"""A system message bridged to 'instructions' must not be rejected for xAI"""
config = XAIResponsesAPIConfig()
supported = config.get_supported_openai_params("grok-4-fast")
assert "instructions" not in supported, "instructions should not be supported"
assert "instructions" in supported, "instructions should be supported"
assert "tools" in supported, "tools should be supported"
assert "temperature" in supported, "temperature should be supported"
assert "model" in supported, "model should be supported"

View file

@ -53,8 +53,8 @@ class TestXAIResponsesAPITransformation:
"container" not in result["tools"][0]
), "Container field should be removed"
def test_instructions_parameter_dropped(self):
"""Test that instructions parameter is dropped for XAI"""
def test_instructions_parameter_forwarded(self):
"""xAI supports 'instructions' on /v1/responses, so it must survive param mapping"""
config = XAIResponsesAPIConfig()
params = ResponsesAPIOptionalRequestParams(
@ -65,15 +65,15 @@ class TestXAIResponsesAPITransformation:
response_api_optional_params=params, model="grok-4-fast", drop_params=False
)
assert "instructions" not in result, "Instructions should be dropped"
assert result.get("instructions") == "You are a helpful assistant."
assert result.get("temperature") == 0.7, "Other params should be preserved"
def test_supported_params_excludes_instructions(self):
"""Test that get_supported_openai_params excludes instructions"""
def test_supported_params_includes_instructions(self):
"""A system message bridged to 'instructions' must not be rejected for xAI"""
config = XAIResponsesAPIConfig()
supported = config.get_supported_openai_params("grok-4-fast")
assert "instructions" not in supported, "instructions should not be supported"
assert "instructions" in supported, "instructions should be supported"
assert "tools" in supported, "tools should be supported"
assert "temperature" in supported, "temperature should be supported"
assert "model" in supported, "model should be supported"

View file

@ -2,11 +2,13 @@
Test automatic routing to xAI Responses API when tools are present
"""
import json
from unittest.mock import MagicMock, patch
import httpx
import pytest
import litellm
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.main import responses_api_bridge_check
@ -243,6 +245,49 @@ class TestXAIResponsesAutoRouting:
# Note: This test may need adjustment based on actual mock_response behavior
# The key is that the responses_api_bridge_check logic routes correctly
def test_system_message_survives_web_search_bridge(self):
"""A system message becomes 'instructions' on the bridged /responses call, and xAI accepts it"""
captured: list[dict] = []
def handler(request: httpx.Request) -> httpx.Response:
captured.append(json.loads(request.content))
return httpx.Response(
200,
json={
"id": "resp_test",
"object": "response",
"created_at": 0,
"status": "completed",
"model": "grok-4.6",
"output": [
{
"type": "message",
"id": "msg_test",
"status": "completed",
"role": "assistant",
"content": [{"type": "output_text", "text": "1.0.0", "annotations": []}],
}
],
"usage": {"input_tokens": 1, "output_tokens": 1, "total_tokens": 2},
},
request=request,
)
response = litellm.completion(
model="xai/grok-4.6",
messages=[
{"role": "system", "content": "Answer briefly."},
{"role": "user", "content": "newest litellm version?"},
],
web_search_options={"search_context_size": "medium"},
api_key="fake-key",
client=HTTPHandler(client=httpx.Client(transport=httpx.MockTransport(handler))),
)
assert response.choices[0].message.content == "1.0.0"
assert captured[0]["instructions"] == "Answer briefly."
assert captured[0]["tools"] == [{"type": "web_search"}]
if __name__ == "__main__":
pytest.main([__file__, "-v"])