Spaces:
Running
Running
Update gen.py
#6
by incognitolm - opened
gen.py
CHANGED
|
@@ -66,6 +66,10 @@ MODEL_MAP = {
|
|
| 66 |
FALLBACK_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
|
| 67 |
FALLBACK_PROVIDER = "groq"
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
# ──────────────────────────────────────────────
|
| 71 |
# CENTRAL ROUTING LOGIC
|
|
@@ -253,6 +257,65 @@ def is_cinematic_image_prompt(prompt: str) -> bool:
|
|
| 253 |
return False
|
| 254 |
|
| 255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
# ──────────────────────────────────────────────
|
| 257 |
# IMAGE GENERATION
|
| 258 |
# ──────────────────────────────────────────────
|
|
@@ -682,6 +745,10 @@ async def generate_text(
|
|
| 682 |
|
| 683 |
await _check_chat_rate_limit(request, authorization, x_client_id)
|
| 684 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
body["model"] = chosen_model
|
| 686 |
stream = body.get("stream", False)
|
| 687 |
|
|
@@ -744,39 +811,79 @@ async def generate_text(
|
|
| 744 |
sent_metadata = False
|
| 745 |
async with httpx.AsyncClient(timeout=None) as client:
|
| 746 |
async for chunk in stream_primary(client):
|
|
|
|
| 747 |
if not sent_metadata:
|
| 748 |
-
meta = {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
yield f"data: {json.dumps(meta)}\n\n"
|
| 750 |
sent_metadata = True
|
| 751 |
|
| 752 |
-
#
|
| 753 |
-
|
| 754 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
raw = chunk[5:].strip()
|
| 756 |
try:
|
| 757 |
obj = json.loads(raw)
|
| 758 |
-
if isinstance(obj, dict) and "usage" in obj and isinstance(obj["usage"], dict):
|
| 759 |
-
u = obj["usage"]
|
| 760 |
-
input_tok = u.get("prompt_tokens") or u.get("input_tokens", 0)
|
| 761 |
-
output_tok = u.get("completion_tokens") or u.get("output_tokens", 0)
|
| 762 |
-
obj["usage"] = {
|
| 763 |
-
"prompt_tokens": input_tok,
|
| 764 |
-
"completion_tokens": output_tok,
|
| 765 |
-
"total_tokens": input_tok + output_tok,
|
| 766 |
-
"input_tokens": input_tok,
|
| 767 |
-
"output_tokens": output_tok,
|
| 768 |
-
}
|
| 769 |
-
yield f"data: {json.dumps(obj)}\n\n"
|
| 770 |
-
continue
|
| 771 |
except Exception:
|
| 772 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
yield chunk
|
| 775 |
|
| 776 |
return StreamingResponse(
|
| 777 |
event_generator(),
|
| 778 |
media_type="text/event-stream",
|
| 779 |
-
headers={
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
)
|
| 781 |
|
| 782 |
# ── non-streaming ─────────────────────────
|
|
@@ -789,7 +896,11 @@ async def generate_text(
|
|
| 789 |
fb_url, fb_key = _get_provider_url_and_key(FALLBACK_PROVIDER)
|
| 790 |
fallback_body = dict(body)
|
| 791 |
fallback_body["model"] = FALLBACK_MODEL
|
| 792 |
-
r = await client.post(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 793 |
|
| 794 |
content_type = (r.headers.get("content-type") or "").lower()
|
| 795 |
if "application/json" in content_type:
|
|
@@ -798,22 +909,35 @@ async def generate_text(
|
|
| 798 |
except Exception:
|
| 799 |
payload = {"error": "Upstream returned invalid JSON"}
|
| 800 |
else:
|
| 801 |
-
# Normalize usage
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
"
|
| 814 |
-
"
|
| 815 |
-
|
| 816 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
else:
|
| 818 |
payload = {
|
| 819 |
"error": "Upstream returned non-JSON response",
|
|
|
|
| 66 |
FALLBACK_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
|
| 67 |
FALLBACK_PROVIDER = "groq"
|
| 68 |
|
| 69 |
+
# Header that API-key authenticated clients send so we know to stream
|
| 70 |
+
# thinking tokens back to them.
|
| 71 |
+
API_KEY_HEADER = "x-api-key"
|
| 72 |
+
|
| 73 |
|
| 74 |
# ──────────────────────────────────────────────
|
| 75 |
# CENTRAL ROUTING LOGIC
|
|
|
|
| 257 |
return False
|
| 258 |
|
| 259 |
|
| 260 |
+
def _is_api_key_request(request: Request) -> bool:
|
| 261 |
+
"""
|
| 262 |
+
Return True when the caller authenticated with an API key rather than a
|
| 263 |
+
session cookie / browser auth. We use this to decide whether to forward
|
| 264 |
+
think-tag / reasoning_content tokens to the client.
|
| 265 |
+
"""
|
| 266 |
+
return bool(
|
| 267 |
+
request.headers.get(API_KEY_HEADER)
|
| 268 |
+
or request.headers.get("authorization", "").lower().startswith("bearer ")
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def _inject_reasoning_into_chunk(obj: Dict[str, Any]) -> Dict[str, Any]:
|
| 273 |
+
"""
|
| 274 |
+
Some navy models return thinking tokens in a non-standard
|
| 275 |
+
``reasoning_content`` field inside each delta. When that field is
|
| 276 |
+
present we wrap it in <think>…</think> and prepend it to the regular
|
| 277 |
+
``content`` delta so that every SSE-speaking client sees a single,
|
| 278 |
+
unified text stream.
|
| 279 |
+
|
| 280 |
+
The original ``reasoning_content`` field is preserved so clients that
|
| 281 |
+
know about it can still use it directly.
|
| 282 |
+
"""
|
| 283 |
+
try:
|
| 284 |
+
delta = obj["choices"][0]["delta"]
|
| 285 |
+
except (KeyError, IndexError, TypeError):
|
| 286 |
+
return obj
|
| 287 |
+
|
| 288 |
+
reasoning = delta.get("reasoning_content") or delta.get("reasoning") or ""
|
| 289 |
+
content = delta.get("content") or ""
|
| 290 |
+
|
| 291 |
+
if reasoning and isinstance(reasoning, str):
|
| 292 |
+
# Wrap in <think> tags and prepend to the visible content delta.
|
| 293 |
+
wrapped = f"<think>{reasoning}</think>"
|
| 294 |
+
delta["content"] = wrapped + content
|
| 295 |
+
# Keep the raw field so native clients can parse it too.
|
| 296 |
+
delta["reasoning_content"] = reasoning
|
| 297 |
+
obj["choices"][0]["delta"] = delta
|
| 298 |
+
|
| 299 |
+
return obj
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def _normalize_usage_block(obj: Dict[str, Any]) -> Dict[str, Any]:
|
| 303 |
+
"""Rewrite the usage block to a canonical shape (in-place, returns obj)."""
|
| 304 |
+
if "usage" not in obj or not isinstance(obj.get("usage"), dict):
|
| 305 |
+
return obj
|
| 306 |
+
u = obj["usage"]
|
| 307 |
+
input_tok = u.get("prompt_tokens") or u.get("input_tokens", 0)
|
| 308 |
+
output_tok = u.get("completion_tokens") or u.get("output_tokens", 0)
|
| 309 |
+
obj["usage"] = {
|
| 310 |
+
"prompt_tokens": input_tok,
|
| 311 |
+
"completion_tokens": output_tok,
|
| 312 |
+
"total_tokens": input_tok + output_tok,
|
| 313 |
+
"input_tokens": input_tok,
|
| 314 |
+
"output_tokens": output_tok,
|
| 315 |
+
}
|
| 316 |
+
return obj
|
| 317 |
+
|
| 318 |
+
|
| 319 |
# ──────────────────────────────────────────────
|
| 320 |
# IMAGE GENERATION
|
| 321 |
# ──────────────────────────────────────────────
|
|
|
|
| 745 |
|
| 746 |
await _check_chat_rate_limit(request, authorization, x_client_id)
|
| 747 |
|
| 748 |
+
# Determine whether the caller is an API-key client that should receive
|
| 749 |
+
# raw thinking tokens.
|
| 750 |
+
forward_thinking = _is_api_key_request(request)
|
| 751 |
+
|
| 752 |
body["model"] = chosen_model
|
| 753 |
stream = body.get("stream", False)
|
| 754 |
|
|
|
|
| 811 |
sent_metadata = False
|
| 812 |
async with httpx.AsyncClient(timeout=None) as client:
|
| 813 |
async for chunk in stream_primary(client):
|
| 814 |
+
# ── emit router metadata once as the very first SSE frame ──
|
| 815 |
if not sent_metadata:
|
| 816 |
+
meta = {
|
| 817 |
+
"router_metadata": {
|
| 818 |
+
"model_name": MODEL_MAP.get(chosen_model, chosen_model)
|
| 819 |
+
}
|
| 820 |
+
}
|
| 821 |
yield f"data: {json.dumps(meta)}\n\n"
|
| 822 |
sent_metadata = True
|
| 823 |
|
| 824 |
+
# ── pass [DONE] straight through ──────────────────────────
|
| 825 |
+
if "data: [DONE]" in chunk:
|
| 826 |
+
yield chunk
|
| 827 |
+
continue
|
| 828 |
+
|
| 829 |
+
# ── process data: … lines ─────────────────────────────────
|
| 830 |
+
if chunk.startswith("data:"):
|
| 831 |
raw = chunk[5:].strip()
|
| 832 |
try:
|
| 833 |
obj = json.loads(raw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
except Exception:
|
| 835 |
+
# Not valid JSON — forward verbatim (keeps partial
|
| 836 |
+
# chunks from blocking the stream).
|
| 837 |
+
yield chunk
|
| 838 |
+
continue
|
| 839 |
+
|
| 840 |
+
if not isinstance(obj, dict):
|
| 841 |
+
yield chunk
|
| 842 |
+
continue
|
| 843 |
+
|
| 844 |
+
# Normalize usage block whenever it appears.
|
| 845 |
+
_normalize_usage_block(obj)
|
| 846 |
+
|
| 847 |
+
# ── thinking / reasoning tokens ───────────────────────
|
| 848 |
+
# Navy models may embed thinking in two ways:
|
| 849 |
+
#
|
| 850 |
+
# 1. As delta.reasoning_content (separate field)
|
| 851 |
+
# 2. Inline inside delta.content wrapped in <think>…</think>
|
| 852 |
+
#
|
| 853 |
+
# For API-key callers we always surface both forms.
|
| 854 |
+
# For browser/session callers we strip reasoning_content
|
| 855 |
+
# so it doesn't confuse UI clients that don't expect it,
|
| 856 |
+
# but <think> tags already present in content are left
|
| 857 |
+
# alone (they arrived that way from upstream).
|
| 858 |
+
if forward_thinking:
|
| 859 |
+
# Merge reasoning_content into content as
|
| 860 |
+
# <think>…</think> and keep the raw field.
|
| 861 |
+
obj = _inject_reasoning_into_chunk(obj)
|
| 862 |
+
else:
|
| 863 |
+
# Strip the non-standard field so browser clients
|
| 864 |
+
# don't see unexpected keys.
|
| 865 |
+
try:
|
| 866 |
+
delta = obj["choices"][0]["delta"]
|
| 867 |
+
delta.pop("reasoning_content", None)
|
| 868 |
+
delta.pop("reasoning", None)
|
| 869 |
+
obj["choices"][0]["delta"] = delta
|
| 870 |
+
except (KeyError, IndexError, TypeError):
|
| 871 |
+
pass
|
| 872 |
|
| 873 |
+
yield f"data: {json.dumps(obj)}\n\n"
|
| 874 |
+
continue
|
| 875 |
+
|
| 876 |
+
# ── any other line (comments, keep-alives, …) ─────────────
|
| 877 |
yield chunk
|
| 878 |
|
| 879 |
return StreamingResponse(
|
| 880 |
event_generator(),
|
| 881 |
media_type="text/event-stream",
|
| 882 |
+
headers={
|
| 883 |
+
"Cache-Control": "no-cache",
|
| 884 |
+
"Connection": "keep-alive",
|
| 885 |
+
"X-Accel-Buffering": "no",
|
| 886 |
+
},
|
| 887 |
)
|
| 888 |
|
| 889 |
# ── non-streaming ─────────────────────────
|
|
|
|
| 896 |
fb_url, fb_key = _get_provider_url_and_key(FALLBACK_PROVIDER)
|
| 897 |
fallback_body = dict(body)
|
| 898 |
fallback_body["model"] = FALLBACK_MODEL
|
| 899 |
+
r = await client.post(
|
| 900 |
+
fb_url,
|
| 901 |
+
json=fallback_body,
|
| 902 |
+
headers={"Authorization": f"Bearer {fb_key}"},
|
| 903 |
+
)
|
| 904 |
|
| 905 |
content_type = (r.headers.get("content-type") or "").lower()
|
| 906 |
if "application/json" in content_type:
|
|
|
|
| 909 |
except Exception:
|
| 910 |
payload = {"error": "Upstream returned invalid JSON"}
|
| 911 |
else:
|
| 912 |
+
# Normalize usage fields.
|
| 913 |
+
_normalize_usage_block(payload)
|
| 914 |
+
|
| 915 |
+
# ── thinking tokens in non-streaming responses ────────────────────
|
| 916 |
+
# Some navy models put thinking content in
|
| 917 |
+
# message.reasoning_content. For API-key callers we prepend it to
|
| 918 |
+
# message.content wrapped in <think>…</think>; for others we drop
|
| 919 |
+
# the non-standard field.
|
| 920 |
+
try:
|
| 921 |
+
message = payload["choices"][0]["message"]
|
| 922 |
+
reasoning = (
|
| 923 |
+
message.pop("reasoning_content", None)
|
| 924 |
+
or message.pop("reasoning", None)
|
| 925 |
+
or ""
|
| 926 |
+
)
|
| 927 |
+
if reasoning and isinstance(reasoning, str):
|
| 928 |
+
if forward_thinking:
|
| 929 |
+
existing = message.get("content") or ""
|
| 930 |
+
message["content"] = f"<think>{reasoning}</think>{existing}"
|
| 931 |
+
# Restore the raw field for clients that want it.
|
| 932 |
+
message["reasoning_content"] = reasoning
|
| 933 |
+
# else: already popped — nothing to do.
|
| 934 |
+
payload["choices"][0]["message"] = message
|
| 935 |
+
except (KeyError, IndexError, TypeError):
|
| 936 |
+
pass
|
| 937 |
+
|
| 938 |
+
payload.setdefault("router_metadata", {})["model_name"] = MODEL_MAP.get(
|
| 939 |
+
chosen_model, chosen_model
|
| 940 |
+
)
|
| 941 |
else:
|
| 942 |
payload = {
|
| 943 |
"error": "Upstream returned non-JSON response",
|