Spaces:
Running
Running
Fix fallback streaming
Browse files
gen.py
CHANGED
|
@@ -678,85 +678,91 @@ async def generate_text(
|
|
| 678 |
|
| 679 |
if stream:
|
| 680 |
body["stream"] = True
|
| 681 |
-
|
| 682 |
-
async def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
try:
|
| 684 |
-
async with
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 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 |
-
|
| 748 |
-
|
| 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 |
-
|
| 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)
|