This commit is contained in:
Ben Younes 2026-09-05 16:15:15 +00:00 committed by GitHub
commit b7cdfb7aed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 0 deletions

View file

@ -561,6 +561,16 @@ DEFAULT_MODEL_RETRY = ModelRetrySettings(
),
)
_RESPONSE_FAILED_EVENT_TYPE = "response.failed"
_RESPONSE_FAILED_DEFAULT_MESSAGE = "Response failed"
_RESPONSE_FAILED_STATUS_CODE = 500
_RESPONSE_ERROR_ATTR = "error"
_RESPONSE_ERROR_CODE_ATTR = "code"
_RESPONSE_ERROR_MESSAGE_ATTR = "message"
_RESPONSE_EVENT_TYPE_ATTR = "type"
_RESPONSE_EVENT_RESPONSE_ATTR = "response"
_LITELLM_RESPONSE_FAILED_PATCH_ATTR = "_strix_raises_response_failed"
RECOMMENDED_MODEL_NAMES = (
"zai/glm-5.3",
"zai/glm-5.3-flash",
@ -661,6 +671,7 @@ def _configure_litellm_compatibility() -> None:
_register_litellm_cost_callback()
_install_openrouter_stream_cost_capture()
_patch_litellm_response_failed_streaming_errors()
def _install_openrouter_stream_cost_capture() -> None:
@ -707,6 +718,64 @@ def _install_openrouter_stream_cost_capture() -> None:
litellm.OpenrouterConfig = _StrixOpenrouterConfig # type: ignore[misc]
def _patch_litellm_response_failed_streaming_errors() -> None:
"""Make LiteLLM raise on Responses API ``response.failed`` stream events."""
try:
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
except Exception: # noqa: BLE001
return
original_process_chunk = getattr(BaseResponsesAPIStreamingIterator, "_process_chunk", None)
if not callable(original_process_chunk):
return
if getattr(original_process_chunk, _LITELLM_RESPONSE_FAILED_PATCH_ATTR, False):
return
def _strix_process_chunk(self: Any, chunk: Any) -> Any:
result = original_process_chunk(self, chunk)
_raise_litellm_response_failed(result, self)
return result
setattr(_strix_process_chunk, _LITELLM_RESPONSE_FAILED_PATCH_ATTR, True)
BaseResponsesAPIStreamingIterator._process_chunk = _strix_process_chunk # type: ignore[method-assign]
def _raise_litellm_response_failed(chunk: Any, iterator: Any) -> None:
if _value(chunk, _RESPONSE_EVENT_TYPE_ATTR) != _RESPONSE_FAILED_EVENT_TYPE:
return
from litellm.exceptions import APIError
response = _value(chunk, _RESPONSE_EVENT_RESPONSE_ATTR)
error = _value(response, _RESPONSE_ERROR_ATTR)
message = _response_failed_message(error)
raise APIError(
status_code=_RESPONSE_FAILED_STATUS_CODE,
message=message,
llm_provider=str(getattr(iterator, "custom_llm_provider", "") or ""),
model=str(getattr(iterator, "model", "") or ""),
)
def _response_failed_message(error: Any) -> str:
message = _value(error, _RESPONSE_ERROR_MESSAGE_ATTR)
if not isinstance(message, str) or not message:
message = str(error) if error else _RESPONSE_FAILED_DEFAULT_MESSAGE
code = _value(error, _RESPONSE_ERROR_CODE_ATTR)
if isinstance(code, str) and code and code not in message:
return f"{message} ({code})"
return message
def _value(source: Any, key: str) -> Any:
if source is None:
return None
if isinstance(source, dict):
return source.get(key)
return getattr(source, key, None)
OPENROUTER_ATTRIBUTION_HEADERS = {
"HTTP-Referer": "https://strix.ai",
"X-Title": "Strix",

View file

@ -0,0 +1,56 @@
"""Tests for LiteLLM Responses API failed stream events."""
from __future__ import annotations
import json
from types import SimpleNamespace
from typing import Any
import pytest
from litellm.exceptions import APIError
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
from strix.config.models import _configure_litellm_compatibility
FAILED_EVENT_TYPE = "response.failed"
FAILED_MESSAGE = "Insufficient credits"
FAILED_CODE = "billing_hard_limit_reached"
FAILED_MODEL = "openai/gpt-5"
FAILED_PROVIDER = "openai"
class _ProviderConfig:
def transform_streaming_response(self, **_kwargs: object) -> SimpleNamespace:
return SimpleNamespace(
type=FAILED_EVENT_TYPE,
response=SimpleNamespace(
error=SimpleNamespace(message=FAILED_MESSAGE, code=FAILED_CODE)
),
)
def _make_iterator() -> Any:
iterator: Any = BaseResponsesAPIStreamingIterator.__new__(BaseResponsesAPIStreamingIterator)
iterator.responses_api_provider_config = _ProviderConfig()
iterator.logging_obj = SimpleNamespace()
iterator.model = FAILED_MODEL
iterator.custom_llm_provider = FAILED_PROVIDER
iterator.litellm_metadata = None
iterator.completed_response = None
iterator._handle_logging_failed_response = lambda: None
iterator._handle_logging_completed_response = lambda: None
return iterator
def test_response_failed_stream_event_is_raised() -> None:
_configure_litellm_compatibility()
iterator = _make_iterator()
payload = json.dumps({"type": FAILED_EVENT_TYPE, "response": {"error": {}}})
with pytest.raises(APIError) as exc_info:
iterator._process_chunk(payload)
error_text = str(exc_info.value)
assert FAILED_MESSAGE in error_text
assert FAILED_CODE in error_text