fix(responses): initialize completed_response on the MCP gateway streaming iterator

MCPEnhancedStreamingIterator has the same shape as the completion bridge that
#35413 fixed: it subclasses BaseResponsesAPIStreamingIterator, never calls
super().__init__, and never sets completed_response. It only catches
StopAsyncIteration around the base iterator, so a MidStreamFallbackError
propagates out of __anext__ and the Router reads completed_response off it in
_extract_partial_responses_usage, raising AttributeError and masking the
provider error.

The iterator is reachable there: aresponses dispatches to
aresponses_api_with_mcp when the request carries litellm_proxy MCP tools, which
returns this iterator for stream=True, and the Router wraps any
BaseResponsesAPIStreamingIterator.

Initialize it to None alongside the other iterator state. With #35413 merged
this was the last BaseResponsesAPIStreamingIterator subclass missing it.

Signed-off-by: onatozmenn <onatozmen44@gmail.com>
This commit is contained in:
onatozmenn 2026-08-01 14:24:32 +03:00
parent f64479e74d
commit b627f12b38
No known key found for this signature in database
2 changed files with 40 additions and 0 deletions

View file

@ -274,6 +274,7 @@ class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator):
# Streaming state management
self.phase = "initial_response" # initial_response -> mcp_discovery -> (continue_initial_response <-> tool_execution) -> finished
self.finished = False
self.completed_response: Any | None = None
# Event queues and generation flags
self.mcp_discovery_events: list[ResponsesAPIStreamingResponse] = (

View file

@ -257,3 +257,42 @@ async def test_initial_call_failure_is_stashed_for_eager_reraise(monkeypatch):
assert iterator._initial_creation_error is not None
assert "initial boom" in str(iterator._initial_creation_error)
@pytest.mark.asyncio
async def test_mid_stream_error_leaves_completed_response_readable(monkeypatch):
"""
Regression test: on a mid-stream provider error the Router's fallback path
reads `completed_response` off this iterator directly (see
Router._extract_partial_responses_usage). This iterator skips
super().__init__(), so that read used to raise AttributeError and mask the
provider error, stopping the fallback from running.
"""
from litellm import Router
from litellm.exceptions import MidStreamFallbackError
_mock_mcp_environment(monkeypatch)
class _ErroringStream:
def __aiter__(self):
return self
async def __anext__(self):
raise MidStreamFallbackError(
message="provider overloaded",
model="gpt-4",
llm_provider="openai",
)
iterator = MCPEnhancedStreamingIterator(
base_iterator=_ErroringStream(),
mcp_events=[],
tool_server_map={},
original_request_params={"model": "gpt-4"},
)
with pytest.raises(MidStreamFallbackError):
await iterator.__anext__()
assert iterator.completed_response is None
assert Router._extract_partial_responses_usage(iterator) is None