sharktide commited on
Commit
6ca6f29
·
verified ·
1 Parent(s): d234197

Fix fallback streaming

Browse files
Files changed (1) hide show
  1. gen.py +83 -76
gen.py CHANGED
@@ -678,85 +678,91 @@ async def generate_text(
678
 
679
  if stream:
680
  body["stream"] = True
681
-
682
- async def event_generator():
 
 
 
 
 
 
683
  try:
684
- async with httpx.AsyncClient(timeout=None) as client:
685
- async with client.stream(
686
- "POST",
687
- url,
688
- json=body,
689
- headers=headers,
690
- ) as r:
691
-
692
- # ---------- FALLBACK ----------
693
- if r.status_code >= 400:
694
-
695
- if provider == "navy vision":
696
- print("[STREAM FALLBACK] Navy vision failed → 17B groq")
697
-
698
- groq_keys = os.getenv("GROQ_KEY", "")
699
- groq_keys_list = [k.strip() for k in groq_keys.split(",") if k.strip()]
700
-
701
- if groq_keys_list:
702
- fallback_headers = {
703
- "Authorization": f"Bearer {random.choice(groq_keys_list)}"
704
- }
705
-
706
- fallback_body = dict(body)
707
- fallback_body["model"] = fallback_model
708
-
709
- async with client.stream(
710
- "POST",
711
- "https://api.groq.com/openai/v1/chat/completions",
712
- json=fallback_body,
713
- headers=fallback_headers,
714
- ) as fallback_r:
715
-
716
- if fallback_r.status_code < 400:
717
- async for line in fallback_r.aiter_lines():
718
- if line:
719
- yield line + "\n"
720
- return
721
- else:
722
- print("[FALLBACK FAILED] Groq fallback also failed")
723
-
724
- # ---------- ORIGINAL ERROR ----------
725
- error_payload = ""
726
- try:
727
- error_payload = (
728
- (await r.aread()).decode("utf-8", errors="replace")
729
- )[:800]
730
- except Exception:
731
- error_payload = ""
732
-
733
- safe_error_payload = (
734
- error_payload.replace("\\", "\\\\")
735
- .replace('"', '\\"')
736
- .replace("\n", " ")
737
- .replace("\r", " ")
738
- )
739
-
740
- yield (
741
- 'data: {"error": '
742
- f'"Upstream provider error ({r.status_code}): {safe_error_payload}"'
743
- "}\n\n"
744
- )
745
  return
746
-
747
- # ---------- NORMAL STREAM ----------
748
- async for line in r.aiter_lines():
749
- if line == "":
750
- yield "\n"
751
- continue
752
-
753
- yield line + "\n"
754
-
755
- except asyncio.CancelledError:
756
- return
757
  except Exception as e:
758
- yield f'data: {{"error": "{str(e)}"}}\n\n'
759
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  return StreamingResponse(
761
  event_generator(),
762
  media_type="text/event-stream",
@@ -766,6 +772,7 @@ async def generate_text(
766
  "X-Accel-Buffering": "no",
767
  },
768
  )
 
769
  else:
770
  async with httpx.AsyncClient(timeout=None) as client:
771
  r = await client.post(url, json=body, headers=headers)
 
678
 
679
  if stream:
680
  body["stream"] = True
681
+
682
+ async def stream_primary(client, url, body, headers):
683
+ """
684
+ Handles the primary provider stream (Navy Vision, Groq, Cerebras, etc.)
685
+ Returns either:
686
+ - a StreamingResponse generator, OR
687
+ - triggers fallback if provider fails
688
+ """
689
  try:
690
+ async with client.stream("POST", url, json=body, headers=headers) as r:
691
+
692
+ # --- PRIMARY PROVIDER FAILED (HTTP ERROR) ---
693
+ if r.status_code >= 400:
694
+ print("[STREAM FALLBACK] Primary provider failed → switching to Groq fallback")
695
+ async for chunk in stream_fallback(client, body):
696
+ yield chunk
697
+ return
698
+
699
+ # --- PRIMARY PROVIDER STREAM ---
700
+ async for line in r.aiter_lines():
701
+ if not line:
702
+ yield "\n"
703
+ continue
704
+
705
+ # Detect SSE-level error inside stream
706
+ if '"error"' in line.lower():
707
+ print("[STREAM FALLBACK] Error detected inside SSE → switching to Groq fallback")
708
+ async for chunk in stream_fallback(client, body):
709
+ yield chunk
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  return
711
+
712
+ yield line + "\n"
713
+
 
 
 
 
 
 
 
 
714
  except Exception as e:
715
+ print(f"[STREAM ERROR] {e}")
716
+ async for chunk in stream_fallback(client, body):
717
+ yield chunk
718
+
719
+
720
+ async def stream_fallback(client, body):
721
+ """
722
+ Clean fallback stream to Groq 17B.
723
+ This MUST NOT be nested inside another stream.
724
+ """
725
+ fallback_body = {
726
+ "model": fallback_model,
727
+ "messages": body["messages"],
728
+ "stream": True,
729
+ }
730
+
731
+ groq_keys = os.getenv("GROQ_KEY", "")
732
+ groq_keys_list = [k.strip() for k in groq_keys.split(",") if k.strip()]
733
+ fallback_headers = {"Authorization": f"Bearer {random.choice(groq_keys_list)}"}
734
+
735
+ print("[FALLBACK] Starting Groq fallback stream")
736
+
737
+ async with client.stream(
738
+ "POST",
739
+ "https://api.groq.com/openai/v1/chat/completions",
740
+ json=fallback_body,
741
+ headers=fallback_headers,
742
+ ) as r:
743
+
744
+ if r.status_code >= 400:
745
+ err = (await r.aread()).decode("utf-8", errors="replace")
746
+ yield f'data: {{"error": "Fallback provider failed: {err[:500]}"}}\n\n'
747
+ return
748
+
749
+ async for line in r.aiter_lines():
750
+ if not line:
751
+ yield "\n"
752
+ continue
753
+
754
+ # Normalize SSE output
755
+ if not line.startswith("data:"):
756
+ yield f"data: {line}\n\n"
757
+ else:
758
+ yield line + "\n"
759
+
760
+
761
+ async def event_generator():
762
+ async with httpx.AsyncClient(timeout=None) as client:
763
+ async for chunk in stream_primary(client, url, body, headers):
764
+ yield chunk
765
+
766
  return StreamingResponse(
767
  event_generator(),
768
  media_type="text/event-stream",
 
772
  "X-Accel-Buffering": "no",
773
  },
774
  )
775
+
776
  else:
777
  async with httpx.AsyncClient(timeout=None) as client:
778
  r = await client.post(url, json=body, headers=headers)