fix(responses): initialize completed_response and _hidden_params on the completion bridge iterator

LiteLLMCompletionStreamingIterator subclasses ResponsesAPIStreamingIterator but never runs the base constructor, so it lacks completed_response and _hidden_params. When a provider errors before the first content chunk, the router reads completed_response directly in _extract_partial_responses_usage and raises AttributeError, and the missing _hidden_params makes the proxy pre-wrap the iterator for header attachment so the Responses mid-stream fallback path is skipped entirely. Initialize both on the bridge iterator so provider errors surface and fallbacks run.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
shivam 2026-07-29 00:30:33 +00:00
parent cd9c410ae2
commit a634cb9b62
2 changed files with 46 additions and 1 deletions

View file

@ -67,7 +67,12 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
self.request_input: str | ResponseInputParam = request_input
self.responses_api_request: ResponsesAPIOptionalRequestParams = responses_api_request
self.custom_llm_provider: str | None = custom_llm_provider
self.litellm_metadata: dict | None = litellm_metadata or {}
self.litellm_metadata = litellm_metadata or {}
self.completed_response: Any | None = None
_wrapper_hidden_params = getattr(litellm_custom_stream_wrapper, "_hidden_params", None)
self._hidden_params: dict[str, Any] = (
dict(_wrapper_hidden_params) if isinstance(_wrapper_hidden_params, dict) else {}
)
# Store lightweight dict snapshots for stream_chunk_builder to reduce
# repeated Pydantic attribute access in end-of-stream assembly.
self.collected_chat_completion_chunks: list[dict[str, Any]] = []

View file

@ -483,3 +483,43 @@ async def test_aresponses_client_error_event_skips_fallback():
assert exc_info.value.status_code == 400
mock_fallback.assert_not_awaited()
# -------- regression: real bridge iterator honors the base contract (LIT-4912) --------
def test_extract_partial_responses_usage_real_bridge_iterator_pre_first_chunk():
"""
Regression for LIT-4912.
When a provider errors before the first content chunk, the completion-bridge
iterator reaches _extract_partial_responses_usage with no collected chunks.
A real LiteLLMCompletionStreamingIterator (not a MagicMock standing in for
it) must expose the base-class contract so usage extraction returns None
instead of raising AttributeError on completed_response, and must carry
_hidden_params so the router does not pre-wrap it for header attachment and
thereby skip the mid-stream fallback path entirely.
"""
from litellm.responses.litellm_completion_transformation.streaming_iterator import (
LiteLLMCompletionStreamingIterator,
)
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
class _FakeStreamWrapper:
def __init__(self) -> None:
self.logging_obj = MagicMock()
self._hidden_params = {"model_id": "deployment-123"}
iterator = LiteLLMCompletionStreamingIterator(
model="anthropic/claude-3-5-sonnet-latest",
litellm_custom_stream_wrapper=_FakeStreamWrapper(),
request_input="hi",
responses_api_request={},
)
assert isinstance(iterator, BaseResponsesAPIStreamingIterator)
assert iterator.completed_response is None
assert iterator._hidden_params == {"model_id": "deployment-123"}
assert not iterator.collected_chat_completion_chunks
assert Router._extract_partial_responses_usage(iterator) is None