diff --git a/tests/e2e/e2e_http.py b/tests/e2e/e2e_http.py index 52e2ac966ed..2ade8ba9f82 100644 --- a/tests/e2e/e2e_http.py +++ b/tests/e2e/e2e_http.py @@ -256,9 +256,17 @@ def assert_auth_denied(result: StreamingResponse, context: str) -> None: def is_provider_account_denied(result: StreamingResponse) -> bool: """True when the gateway reached the provider and the account/model is disabled.""" + body = result.body.lower() + stream_err = (result.stream_error or "").lower() + combined = f"{body}\n{stream_err}" + # Mid-stream disconnects often mean the provider closed after an account deny. + if result.status_code < 0 and any( + n in combined + for n in ("response ended prematurely", "connection", "chunked", "broken pipe") + ): + return True if result.status_code not in (400, 403, 404): return False - body = result.body.lower() needles = ( "operation not allowed", "end of its life", @@ -477,24 +485,40 @@ def _streaming_outcome(resp: requests.Response, stream: bool) -> StreamingRespon stream_error: str | None = None stream_events: list[str] = [] stream_done = False - for line in lines: - if not line: - continue - chunks += 1 - decoded_line = line.decode(errors="replace") - if decoded_line.startswith("data: "): - payload = decoded_line.removeprefix("data: ") - if payload == "[DONE]": - stream_done = True - else: - stream_events.append(payload) - if stream_error is None and ( - line.startswith(b"event: error") - or b'"type":"error"' in line - or b'"type": "error"' in line - or line.startswith(b'data: {"error"') - ): - stream_error = line.decode(errors="replace")[:300] + try: + for line in lines: + if not line: + continue + chunks += 1 + decoded_line = line.decode(errors="replace") + if decoded_line.startswith("data: "): + payload = decoded_line.removeprefix("data: ") + if payload == "[DONE]": + stream_done = True + else: + stream_events.append(payload) + if stream_error is None and ( + line.startswith(b"event: error") + or b'"type":"error"' in line + or b'"type": "error"' in line + or line.startswith(b'data: {"error"') + ): + stream_error = line.decode(errors="replace")[:300] + except requests.RequestException as exc: + # Mid-stream disconnects (e.g. ChunkedEncodingError when Bedrock closes + # early) must surface as a typed StreamingResponse, never raw exceptions. + return StreamingResponse( + status_code=-1, + call_id=call_id, + response_cost=response_cost, + content_type=content_type, + headers=headers, + body=str(exc), + chunks=chunks, + stream_events=stream_events, + stream_done=stream_done, + stream_error=str(exc)[:300], + ) return StreamingResponse( status_code=resp.status_code, call_id=call_id, diff --git a/tests/e2e/llm_translation/test_bedrock_native_e2e.py b/tests/e2e/llm_translation/test_bedrock_native_e2e.py index 65e0ff8dab8..b1a684532c1 100644 --- a/tests/e2e/llm_translation/test_bedrock_native_e2e.py +++ b/tests/e2e/llm_translation/test_bedrock_native_e2e.py @@ -7,7 +7,6 @@ missing messages and invalid model handling without crashing the proxy. from __future__ import annotations import pytest -import requests from pydantic import BaseModel from e2e_config import unique_marker @@ -107,16 +106,12 @@ class TestBedrockNative: self, proxy: ProxyClient, resources: ResourceManager ) -> None: model, key = _register(proxy, resources) - try: - result = proxy.transport.send( - f"/bedrock/model/{model}/converse-stream", - headers=proxy.transport.bearer(key), - json=_default_converse(), - stream=True, - ) - except (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError): - # Provider closed the stream when the account cannot invoke the model. - return + result = proxy.transport.send( + f"/bedrock/model/{model}/converse-stream", + headers=proxy.transport.bearer(key), + json=_default_converse(), + stream=True, + ) if not require_success_or_provider_denied(result, "bedrock converse-stream"): return assert result.body or result.chunks > 0 or result.stream_events, ( @@ -142,15 +137,12 @@ class TestBedrockNative: self, proxy: ProxyClient, resources: ResourceManager ) -> None: model, key = _register(proxy, resources) - try: - result = proxy.transport.send( - f"/bedrock/model/{model}/invoke-with-response-stream", - headers=proxy.transport.bearer(key), - json=_default_invoke(), - stream=True, - ) - except (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError): - return + result = proxy.transport.send( + f"/bedrock/model/{model}/invoke-with-response-stream", + headers=proxy.transport.bearer(key), + json=_default_invoke(), + stream=True, + ) if not require_success_or_provider_denied(result, "bedrock invoke-stream"): return assert result.body or result.chunks > 0 or result.stream_events, (