From fd900eb7af8f0aa39d7b0d2d61ab7e477e00e8d0 Mon Sep 17 00:00:00 2001 From: Alexx Date: Mon, 20 Oct 2025 18:38:47 +0100 Subject: [PATCH] fix: Add graceful error handling for ChunkedEncodingError during streaming (#57) Fixes connection interruptions from ChatGPT API by catching ChunkedEncodingError and gracefully ending the stream with [DONE] instead of returning 500 errors. Two-level error handling: 1. Catches errors during stream initialization 2. Catches errors during stream iteration This prevents abrupt failures when the upstream connection is interrupted mid-response. --- chatmock/utils.py | 53 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/chatmock/utils.py b/chatmock/utils.py index 31f7dd6..96af375 100644 --- a/chatmock/utils.py +++ b/chatmock/utils.py @@ -432,23 +432,46 @@ def sse_translate_chat( except Exception: return None try: - for raw in upstream.iter_lines(decode_unicode=False): - if not raw: - continue - line = raw.decode("utf-8", errors="ignore") if isinstance(raw, (bytes, bytearray)) else raw + try: + line_iterator = upstream.iter_lines(decode_unicode=False) + except requests.exceptions.ChunkedEncodingError as e: if verbose and vlog: - vlog(line) - if not line.startswith("data: "): - continue - data = line[len("data: "):].strip() - if not data: - continue - if data == "[DONE]": - break + vlog(f"Failed to start stream: {e}") + yield b"data: [DONE]\n\n" + return + + for raw in line_iterator: try: - evt = json.loads(data) - except Exception: - continue + if not raw: + continue + line = ( + raw.decode("utf-8", errors="ignore") + if isinstance(raw, (bytes, bytearray)) + else raw + ) + if verbose and vlog: + vlog(line) + if not line.startswith("data: "): + continue + data = line[len("data: ") :].strip() + if not data: + continue + if data == "[DONE]": + break + try: + evt = json.loads(data) + except (json.JSONDecodeError, UnicodeDecodeError): + continue + except ( + requests.exceptions.ChunkedEncodingError, + ConnectionError, + BrokenPipeError, + ) as e: + # Connection interrupted mid-stream - end gracefully + if verbose and vlog: + vlog(f"Stream interrupted: {e}") + yield b"data: [DONE]\n\n" + return kind = evt.get("type") if isinstance(evt.get("response"), dict) and isinstance(evt["response"].get("id"), str): response_id = evt["response"].get("id") or response_id