patdev commited on
Commit
8139a2a
·
verified ·
1 Parent(s): cfc5eae

protocole passerelle: erreurs amont intactes (reprise auto) + decouverte sous 3s

Browse files
Files changed (1) hide show
  1. anthropic_proxy.py +27 -18
anthropic_proxy.py CHANGED
@@ -323,6 +323,21 @@ def to_anthropic(oai: dict, req_model: str) -> dict:
323
  }
324
 
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  def _sse(event: str, data: dict) -> bytes:
327
  return f"event: {event}\ndata: {json.dumps(data, separators=(',', ':'))}\n\n".encode()
328
 
@@ -384,22 +399,16 @@ async def stream_anthropic(payload: dict, req_model: str):
384
  roles = "/".join(m.get("role", "?") for m in payload.get("messages", []))
385
  print(f"[amont {r.status_code}] {body}", flush=True)
386
  print(f"[amont] roles={roles}", flush=True)
387
- idx += 1
388
- yield _sse("content_block_start", {
389
- "type": "content_block_start", "index": idx,
390
- "content_block": {"type": "text", "text": ""}})
391
- yield _sse("content_block_delta", {
392
- "type": "content_block_delta", "index": idx,
393
- "delta": {"type": "text_delta",
394
- "text": "[pont] vLLM a refuse la requete "
395
- f"({r.status_code}) :\n{body}"}})
396
- yield _sse("content_block_stop",
397
- {"type": "content_block_stop", "index": idx})
398
- yield _sse("message_delta", {
399
- "type": "message_delta",
400
- "delta": {"stop_reason": "end_turn", "stop_sequence": None},
401
- "usage": {"input_tokens": 0, "output_tokens": 0}})
402
- yield _sse("message_stop", {"type": "message_stop"})
403
  return
404
  async for line in r.aiter_lines():
405
  if not line.startswith("data: "):
@@ -612,7 +621,7 @@ async def models(request: Request):
612
  """
613
  if "anthropic-version" not in request.headers:
614
  try:
615
- r = await _client.get("/v1/models", timeout=10)
616
  if r.status_code == 200:
617
  return JSONResponse(r.json())
618
  except Exception:
@@ -620,7 +629,7 @@ async def models(request: Request):
620
 
621
  ctx = None
622
  try:
623
- r = await _client.get("/v1/models", timeout=10)
624
  if r.status_code == 200:
625
  ctx = (r.json().get("data") or [{}])[0].get("max_model_len")
626
  except Exception:
 
323
  }
324
 
325
 
326
+ def _err_of(body: str, status: int) -> dict:
327
+ """Renvoie l'objet d'erreur amont intact, ou en fabrique un equivalent.
328
+
329
+ Le libelle compte : c'est sur lui que le client decide s'il peut retenter.
330
+ """
331
+ try:
332
+ d = json.loads(body)
333
+ e = d.get("error")
334
+ if isinstance(e, dict) and e.get("message"):
335
+ return {"type": e.get("type") or "api_error", "message": e["message"]}
336
+ except (json.JSONDecodeError, AttributeError):
337
+ pass
338
+ return {"type": "api_error", "message": body or f"upstream {status}"}
339
+
340
+
341
  def _sse(event: str, data: dict) -> bytes:
342
  return f"event: {event}\ndata: {json.dumps(data, separators=(',', ':'))}\n\n".encode()
343
 
 
399
  roles = "/".join(m.get("role", "?") for m in payload.get("messages", []))
400
  print(f"[amont {r.status_code}] {body}", flush=True)
401
  print(f"[amont] roles={roles}", flush=True)
402
+ # Le corps d'erreur repart TEL QUEL, dans un evenement SSE `error`.
403
+ # La documentation du protocole de passerelle est explicite : Claude
404
+ # Code se remet tout seul de certains refus -- champ `thinking`,
405
+ # signatures de raisonnement, et justement les messages systeme en
406
+ # milieu de conversation -- mais "la logique de reprise s'appuie sur
407
+ # le libelle de l'erreur amont", et "une passerelle qui enveloppe
408
+ # les erreurs dans son propre format casse la reprise meme si elle
409
+ # preserve le code de statut". Emballer l'erreur dans un bloc texte,
410
+ # comme je le faisais, empechait donc cette reprise.
411
+ yield _sse("error", {"type": "error", "error": _err_of(body, r.status_code)})
 
 
 
 
 
 
412
  return
413
  async for line in r.aiter_lines():
414
  if not line.startswith("data: "):
 
621
  """
622
  if "anthropic-version" not in request.headers:
623
  try:
624
+ r = await _client.get("/v1/models", timeout=2.0)
625
  if r.status_code == 200:
626
  return JSONResponse(r.json())
627
  except Exception:
 
629
 
630
  ctx = None
631
  try:
632
+ r = await _client.get("/v1/models", timeout=2.0)
633
  if r.status_code == 200:
634
  ctx = (r.json().get("data") or [{}])[0].get("max_model_len")
635
  except Exception: