Merge pull request #19649 from BerriAI/litellm_fix_responses_api_logging_eror

Fix: Responses API logging error for StopIteration
This commit is contained in:
Sameer Kankute 2026-01-23 19:51:45 +05:30 committed by GitHub
commit a240eb7630
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 126 additions and 0 deletions

View file

@ -379,6 +379,9 @@ class ResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator):
return result
# If result is None, continue the loop to get the next chunk
except StopAsyncIteration:
# Normal end of stream - don't log as failure
raise
except httpx.HTTPError as e:
# Handle HTTP errors
self.finished = True
@ -474,6 +477,9 @@ class SyncResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator):
return result
# If result is None, continue the loop to get the next chunk
except StopIteration:
# Normal end of stream - don't log as failure
raise
except httpx.HTTPError as e:
# Handle HTTP errors
self.finished = True

View file

@ -309,3 +309,123 @@ class TestBaseResponsesAPIStreamingIterator:
pytest.fail(f"_handle_logging_completed_response failed with pickle error: {e}")
raise
@pytest.mark.asyncio
async def test_stop_async_iteration_not_logged_as_failure(self):
"""
Test that StopAsyncIteration is NOT logged as a failure.
This test verifies that when streaming completes normally with StopAsyncIteration,
the _handle_failure method is NOT called, preventing false error logs in Langfuse
and other logging integrations.
"""
from litellm.responses.streaming_iterator import ResponsesAPIStreamingIterator
# Mock dependencies
mock_response = Mock()
mock_response.headers = {}
# Create an async iterator that raises StopAsyncIteration after yielding one chunk
async def mock_aiter_lines():
yield 'data: {"type": "response.output_text.delta", "delta": "test"}'
# Normal end of stream - raise StopAsyncIteration
mock_response.aiter_lines = mock_aiter_lines
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
mock_logging_obj.model_call_details = {"litellm_params": {}}
mock_logging_obj.async_failure_handler = Mock()
mock_logging_obj.failure_handler = Mock()
mock_config = Mock(spec=BaseResponsesAPIConfig)
mock_delta_event = Mock()
mock_delta_event.type = ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA
mock_delta_event.delta = "test"
mock_config.transform_streaming_response.return_value = mock_delta_event
# Create the iterator instance
iterator = ResponsesAPIStreamingIterator(
response=mock_response,
model="gpt-4",
responses_api_provider_config=mock_config,
logging_obj=mock_logging_obj,
litellm_metadata={"model_info": {"id": "model_123"}},
custom_llm_provider="openai"
)
# Consume the iterator until StopAsyncIteration
chunks_received = []
try:
async for chunk in iterator:
chunks_received.append(chunk)
except StopAsyncIteration:
pass # This is expected
# Verify we got the chunk
assert len(chunks_received) == 1
# CRITICAL: Verify that failure handlers were NOT called
# StopAsyncIteration is a normal end of stream, not a failure
mock_logging_obj.async_failure_handler.assert_not_called()
mock_logging_obj.failure_handler.assert_not_called()
def test_stop_iteration_not_logged_as_failure(self):
"""
Test that StopIteration is NOT logged as a failure in sync iterator.
This test verifies that when streaming completes normally with StopIteration,
the _handle_failure method is NOT called, preventing false error logs in Langfuse
and other logging integrations.
Regression test for: https://github.com/BerriAI/litellm/issues/XXXXX
"""
from litellm.responses.streaming_iterator import SyncResponsesAPIStreamingIterator
# Mock dependencies
mock_response = Mock()
mock_response.headers = {}
# Create a sync iterator that raises StopIteration after yielding one chunk
def mock_iter_lines():
yield 'data: {"type": "response.output_text.delta", "delta": "test"}'
# Normal end of stream - raise StopIteration
mock_response.iter_lines = mock_iter_lines
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
mock_logging_obj.model_call_details = {"litellm_params": {}}
mock_logging_obj.async_failure_handler = Mock()
mock_logging_obj.failure_handler = Mock()
mock_config = Mock(spec=BaseResponsesAPIConfig)
mock_delta_event = Mock()
mock_delta_event.type = ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA
mock_delta_event.delta = "test"
mock_config.transform_streaming_response.return_value = mock_delta_event
# Create the iterator instance
iterator = SyncResponsesAPIStreamingIterator(
response=mock_response,
model="gpt-4",
responses_api_provider_config=mock_config,
logging_obj=mock_logging_obj,
litellm_metadata={"model_info": {"id": "model_123"}},
custom_llm_provider="openai"
)
# Consume the iterator until StopIteration
chunks_received = []
try:
for chunk in iterator:
chunks_received.append(chunk)
except StopIteration:
pass # This is expected
# Verify we got the chunk
assert len(chunks_received) == 1
# CRITICAL: Verify that failure handlers were NOT called
# StopIteration is a normal end of stream, not a failure
mock_logging_obj.async_failure_handler.assert_not_called()
mock_logging_obj.failure_handler.assert_not_called()