test(e2e): route bedrock stream disconnects through e2e_http

Catch mid-stream RequestException in the shared harness so bedrock native
tests do not import requests directly
This commit is contained in:
mubashir1osmani 2026-07-24 20:40:49 -07:00
parent 3c7d66989e
commit 6a93480204
2 changed files with 55 additions and 39 deletions

View file

@ -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,

View file

@ -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, (