fix: make h2 patch repair failures diagnosable

This commit is contained in:
thanhnv
2026-07-18 12:04:32 +07:00
parent 61a8253679
commit 659ed09839
7 changed files with 120 additions and 9 deletions
@@ -67,6 +67,37 @@ def generation_max_tokens(role):
return min(max(configured, 64), 8192)
def decode_provider_json(response, provider):
"""Decode a provider response without mislabelling an empty 2xx body.
Gateways occasionally terminate an upstream stream after accepting a
request and return an empty (or HTML) 2xx response. Treating that as a
generic JSONDecodeError makes it look like the network failed and obscures
the provider that needs attention. The diagnostic deliberately contains
only response shape metadata: never response content or credentials.
"""
raw = response.read()
content_type = ""
headers = getattr(response, "headers", None)
if headers is not None:
try:
content_type = str(headers.get("Content-Type", "")).split(";", 1)[0].lower()
except (AttributeError, TypeError):
content_type = ""
if not raw:
fail(f"provider_response_empty {provider} content_type={content_type or 'unknown'}")
try:
payload = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
fail(
f"provider_response_invalid {provider} {type(exc).__name__} "
f"bytes={len(raw)} content_type={content_type or 'unknown'}"
)
if not isinstance(payload, dict):
fail(f"provider_response_invalid {provider} payload_type={type(payload).__name__}")
return payload
def enforce_call_budget():
"""Fail closed when a run exceeds CASAN_MODEL_MAX_CALLS. The count is tracked in
CASAN_MODEL_CALL_COUNTER_FILE so it spans the many per-step model-call processes
@@ -192,7 +223,7 @@ def call_ollama(model_name, prompt, role):
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
payload = decode_provider_json(resp, "ollama")
except Exception as exc: # backend/model failure -> honest non-zero, no fake success
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)
@@ -226,7 +257,7 @@ def call_openai(model_name, prompt, role):
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
payload = decode_provider_json(resp, "openai-chat")
except Exception as exc: # honest non-zero, no fake success
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)
@@ -278,7 +309,7 @@ def call_openai_responses(model_name, prompt, role):
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
payload = decode_provider_json(resp, "openai-responses")
except Exception as exc:
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)
@@ -345,7 +376,7 @@ def call_openai_compatible(model_name, prompt, role):
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
payload = decode_provider_json(resp, "openai-compatible")
except Exception as exc:
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)
@@ -413,7 +444,7 @@ def call_anthropic(model_name, prompt, role):
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
payload = decode_provider_json(resp, "anthropic")
except Exception as exc: # honest non-zero, no fake success
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)