Spaces:
Running
Running
Update gen.py
Browse files
gen.py
CHANGED
|
@@ -777,11 +777,9 @@ async def generate_text(
|
|
| 777 |
headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
|
| 778 |
)
|
| 779 |
|
| 780 |
-
# ── non-streaming ─────────────────────────
|
| 781 |
async with httpx.AsyncClient(timeout=None) as client:
|
| 782 |
r = await client.post(url, json=body, headers=headers)
|
| 783 |
|
| 784 |
-
# navy-vision fallback
|
| 785 |
if provider == "navy vision" and r.status_code >= 400:
|
| 786 |
print("[FALLBACK] Navy vision failed — switching to fallback")
|
| 787 |
fb_url, fb_key = _get_provider_url_and_key(FALLBACK_PROVIDER)
|
|
@@ -796,9 +794,6 @@ async def generate_text(
|
|
| 796 |
except Exception:
|
| 797 |
payload = {"error": "Upstream returned invalid JSON"}
|
| 798 |
else:
|
| 799 |
-
# Normalize usage: upstream may use prompt_tokens/completion_tokens
|
| 800 |
-
# (OpenAI/Groq style) — rewrite to a consistent shape and add
|
| 801 |
-
# router_metadata so callers always see the same fields.
|
| 802 |
if "usage" in payload and isinstance(payload["usage"], dict):
|
| 803 |
u = payload["usage"]
|
| 804 |
input_tok = u.get("prompt_tokens") or u.get("input_tokens", 0)
|
|
@@ -807,7 +802,6 @@ async def generate_text(
|
|
| 807 |
"prompt_tokens": input_tok,
|
| 808 |
"completion_tokens": output_tok,
|
| 809 |
"total_tokens": input_tok + output_tok,
|
| 810 |
-
# also include the OpenAI Responses-API names for clients that expect them
|
| 811 |
"input_tokens": input_tok,
|
| 812 |
"output_tokens": output_tok,
|
| 813 |
}
|
|
@@ -821,11 +815,6 @@ async def generate_text(
|
|
| 821 |
|
| 822 |
return JSONResponse(status_code=r.status_code, content=payload)
|
| 823 |
|
| 824 |
-
|
| 825 |
-
# ──────────────────────────────────────────────
|
| 826 |
-
# PROMPT ANALYZE (/gen/prompt_analyze)
|
| 827 |
-
# ──────────────────────────────────────────────
|
| 828 |
-
|
| 829 |
@router.post("/prompt_analyze")
|
| 830 |
async def analyze_prompt(request: Request):
|
| 831 |
body = await request.json()
|
|
@@ -840,11 +829,6 @@ async def analyze_prompt(request: Request):
|
|
| 840 |
chosen_model, _ = route_chat(messages, uses_tools=uses_tools)
|
| 841 |
return {MODEL_MAP.get(chosen_model, chosen_model)}
|
| 842 |
|
| 843 |
-
|
| 844 |
-
# ──────────────────────────────────────────────
|
| 845 |
-
# MODELS LIST
|
| 846 |
-
# ──────────────────────────────────────────────
|
| 847 |
-
|
| 848 |
@router.get("/models")
|
| 849 |
def return_models_openai():
|
| 850 |
return {
|
|
@@ -944,7 +928,6 @@ def _build_responses_payload(
|
|
| 944 |
},
|
| 945 |
}
|
| 946 |
|
| 947 |
-
|
| 948 |
@router.post("/responses")
|
| 949 |
async def create_responses(
|
| 950 |
request: Request,
|
|
@@ -966,10 +949,12 @@ async def create_responses(
|
|
| 966 |
if not messages:
|
| 967 |
raise HTTPException(400, "input could not be parsed")
|
| 968 |
|
| 969 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 970 |
async def _generate() -> Tuple[str, int, int]:
|
| 971 |
-
chosen_model, provider = route_chat(messages)
|
| 972 |
-
await _check_chat_rate_limit(request, authorization, x_client_id)
|
| 973 |
data = await call_chat_completions(messages, chosen_model, provider)
|
| 974 |
text = _extract_text_from_response(data)
|
| 975 |
input_tokens, output_tokens = _extract_usage(data)
|
|
@@ -979,10 +964,20 @@ async def create_responses(
|
|
| 979 |
if stream is False:
|
| 980 |
text, input_tokens, output_tokens = await _generate()
|
| 981 |
response_id = _resp_id("resp")
|
| 982 |
-
|
| 983 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
)
|
| 985 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 986 |
# ── streaming ─────────────────────────────
|
| 987 |
async def event_stream():
|
| 988 |
response_id = _resp_id("resp")
|
|
@@ -990,18 +985,20 @@ async def create_responses(
|
|
| 990 |
ts = _resp_ts()
|
| 991 |
|
| 992 |
def sse(event_type: str, data: dict) -> str:
|
| 993 |
-
"""Emit a properly-formed SSE frame with both event: and data: lines.
|
| 994 |
-
The OpenAI SDK dispatches on the `event:` field — without it most
|
| 995 |
-
events are silently dropped."""
|
| 996 |
return f"event: {event_type}\ndata: {json.dumps(data)}\n\n"
|
| 997 |
|
| 998 |
# 1. response.created
|
| 999 |
yield sse("response.created", {
|
| 1000 |
"type": "response.created",
|
| 1001 |
"response": {
|
| 1002 |
-
"id": response_id,
|
| 1003 |
-
"
|
| 1004 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1005 |
},
|
| 1006 |
})
|
| 1007 |
|
|
@@ -1009,8 +1006,11 @@ async def create_responses(
|
|
| 1009 |
yield sse("response.in_progress", {
|
| 1010 |
"type": "response.in_progress",
|
| 1011 |
"response": {
|
| 1012 |
-
"id": response_id,
|
| 1013 |
-
"
|
|
|
|
|
|
|
|
|
|
| 1014 |
},
|
| 1015 |
})
|
| 1016 |
|
|
@@ -1020,27 +1020,32 @@ async def create_responses(
|
|
| 1020 |
yield sse("response.failed", {
|
| 1021 |
"type": "response.failed",
|
| 1022 |
"response": {
|
| 1023 |
-
"id": response_id,
|
| 1024 |
-
"
|
|
|
|
|
|
|
|
|
|
| 1025 |
"error": {"code": "upstream_error", "message": exc.detail},
|
| 1026 |
},
|
| 1027 |
})
|
| 1028 |
yield "data: [DONE]\n\n"
|
| 1029 |
return
|
| 1030 |
|
| 1031 |
-
# 3. output item added
|
| 1032 |
-
output_item = {
|
| 1033 |
-
"id": item_id, "type": "message", "role": "assistant",
|
| 1034 |
-
"status": "in_progress", "content": [],
|
| 1035 |
-
}
|
| 1036 |
yield sse("response.output_item.added", {
|
| 1037 |
"type": "response.output_item.added",
|
| 1038 |
"response_id": response_id,
|
| 1039 |
"output_index": 0,
|
| 1040 |
-
"item":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1041 |
})
|
| 1042 |
|
| 1043 |
-
# 4. content part added
|
| 1044 |
yield sse("response.content_part.added", {
|
| 1045 |
"type": "response.content_part.added",
|
| 1046 |
"response_id": response_id,
|
|
@@ -1089,18 +1094,27 @@ async def create_responses(
|
|
| 1089 |
"response_id": response_id,
|
| 1090 |
"output_index": 0,
|
| 1091 |
"item": {
|
| 1092 |
-
"id": item_id,
|
|
|
|
|
|
|
| 1093 |
"status": "completed",
|
| 1094 |
"content": [{"type": "output_text", "text": text, "annotations": []}],
|
| 1095 |
},
|
| 1096 |
})
|
| 1097 |
|
| 1098 |
-
# 9. response.completed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
yield sse("response.completed", {
|
| 1100 |
"type": "response.completed",
|
| 1101 |
-
"response":
|
| 1102 |
-
model, text, response_id, input_tokens, output_tokens
|
| 1103 |
-
),
|
| 1104 |
})
|
| 1105 |
|
| 1106 |
yield "data: [DONE]\n\n"
|
|
@@ -1108,5 +1122,9 @@ async def create_responses(
|
|
| 1108 |
return StreamingResponse(
|
| 1109 |
event_stream(),
|
| 1110 |
media_type="text/event-stream",
|
| 1111 |
-
headers={
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1112 |
)
|
|
|
|
| 777 |
headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
|
| 778 |
)
|
| 779 |
|
|
|
|
| 780 |
async with httpx.AsyncClient(timeout=None) as client:
|
| 781 |
r = await client.post(url, json=body, headers=headers)
|
| 782 |
|
|
|
|
| 783 |
if provider == "navy vision" and r.status_code >= 400:
|
| 784 |
print("[FALLBACK] Navy vision failed — switching to fallback")
|
| 785 |
fb_url, fb_key = _get_provider_url_and_key(FALLBACK_PROVIDER)
|
|
|
|
| 794 |
except Exception:
|
| 795 |
payload = {"error": "Upstream returned invalid JSON"}
|
| 796 |
else:
|
|
|
|
|
|
|
|
|
|
| 797 |
if "usage" in payload and isinstance(payload["usage"], dict):
|
| 798 |
u = payload["usage"]
|
| 799 |
input_tok = u.get("prompt_tokens") or u.get("input_tokens", 0)
|
|
|
|
| 802 |
"prompt_tokens": input_tok,
|
| 803 |
"completion_tokens": output_tok,
|
| 804 |
"total_tokens": input_tok + output_tok,
|
|
|
|
| 805 |
"input_tokens": input_tok,
|
| 806 |
"output_tokens": output_tok,
|
| 807 |
}
|
|
|
|
| 815 |
|
| 816 |
return JSONResponse(status_code=r.status_code, content=payload)
|
| 817 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 818 |
@router.post("/prompt_analyze")
|
| 819 |
async def analyze_prompt(request: Request):
|
| 820 |
body = await request.json()
|
|
|
|
| 829 |
chosen_model, _ = route_chat(messages, uses_tools=uses_tools)
|
| 830 |
return {MODEL_MAP.get(chosen_model, chosen_model)}
|
| 831 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
@router.get("/models")
|
| 833 |
def return_models_openai():
|
| 834 |
return {
|
|
|
|
| 928 |
},
|
| 929 |
}
|
| 930 |
|
|
|
|
| 931 |
@router.post("/responses")
|
| 932 |
async def create_responses(
|
| 933 |
request: Request,
|
|
|
|
| 949 |
if not messages:
|
| 950 |
raise HTTPException(400, "input could not be parsed")
|
| 951 |
|
| 952 |
+
# ✅ ROUTE + LOG + RATE LIMIT EARLY
|
| 953 |
+
chosen_model, provider = route_chat(messages)
|
| 954 |
+
_log_routing(chosen_model, provider, messages, uses_tools=False)
|
| 955 |
+
await _check_chat_rate_limit(request, authorization, x_client_id)
|
| 956 |
+
|
| 957 |
async def _generate() -> Tuple[str, int, int]:
|
|
|
|
|
|
|
| 958 |
data = await call_chat_completions(messages, chosen_model, provider)
|
| 959 |
text = _extract_text_from_response(data)
|
| 960 |
input_tokens, output_tokens = _extract_usage(data)
|
|
|
|
| 964 |
if stream is False:
|
| 965 |
text, input_tokens, output_tokens = await _generate()
|
| 966 |
response_id = _resp_id("resp")
|
| 967 |
+
|
| 968 |
+
payload = _build_responses_payload(
|
| 969 |
+
chosen_model,
|
| 970 |
+
text,
|
| 971 |
+
response_id,
|
| 972 |
+
input_tokens,
|
| 973 |
+
output_tokens,
|
| 974 |
)
|
| 975 |
|
| 976 |
+
payload.setdefault("router_metadata", {})
|
| 977 |
+
payload["router_metadata"]["provider"] = provider
|
| 978 |
+
|
| 979 |
+
return JSONResponse(content=payload)
|
| 980 |
+
|
| 981 |
# ── streaming ─────────────────────────────
|
| 982 |
async def event_stream():
|
| 983 |
response_id = _resp_id("resp")
|
|
|
|
| 985 |
ts = _resp_ts()
|
| 986 |
|
| 987 |
def sse(event_type: str, data: dict) -> str:
|
|
|
|
|
|
|
|
|
|
| 988 |
return f"event: {event_type}\ndata: {json.dumps(data)}\n\n"
|
| 989 |
|
| 990 |
# 1. response.created
|
| 991 |
yield sse("response.created", {
|
| 992 |
"type": "response.created",
|
| 993 |
"response": {
|
| 994 |
+
"id": response_id,
|
| 995 |
+
"object": "response",
|
| 996 |
+
"created_at": ts,
|
| 997 |
+
"status": "in_progress",
|
| 998 |
+
"model": chosen_model,
|
| 999 |
+
"output": [],
|
| 1000 |
+
"usage": None,
|
| 1001 |
+
"router_metadata": {"provider": provider},
|
| 1002 |
},
|
| 1003 |
})
|
| 1004 |
|
|
|
|
| 1006 |
yield sse("response.in_progress", {
|
| 1007 |
"type": "response.in_progress",
|
| 1008 |
"response": {
|
| 1009 |
+
"id": response_id,
|
| 1010 |
+
"object": "response",
|
| 1011 |
+
"created_at": ts,
|
| 1012 |
+
"status": "in_progress",
|
| 1013 |
+
"model": chosen_model,
|
| 1014 |
},
|
| 1015 |
})
|
| 1016 |
|
|
|
|
| 1020 |
yield sse("response.failed", {
|
| 1021 |
"type": "response.failed",
|
| 1022 |
"response": {
|
| 1023 |
+
"id": response_id,
|
| 1024 |
+
"object": "response",
|
| 1025 |
+
"created_at": ts,
|
| 1026 |
+
"status": "failed",
|
| 1027 |
+
"model": chosen_model,
|
| 1028 |
"error": {"code": "upstream_error", "message": exc.detail},
|
| 1029 |
},
|
| 1030 |
})
|
| 1031 |
yield "data: [DONE]\n\n"
|
| 1032 |
return
|
| 1033 |
|
| 1034 |
+
# 3. output item added
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1035 |
yield sse("response.output_item.added", {
|
| 1036 |
"type": "response.output_item.added",
|
| 1037 |
"response_id": response_id,
|
| 1038 |
"output_index": 0,
|
| 1039 |
+
"item": {
|
| 1040 |
+
"id": item_id,
|
| 1041 |
+
"type": "message",
|
| 1042 |
+
"role": "assistant",
|
| 1043 |
+
"status": "in_progress",
|
| 1044 |
+
"content": [],
|
| 1045 |
+
},
|
| 1046 |
})
|
| 1047 |
|
| 1048 |
+
# 4. content part added
|
| 1049 |
yield sse("response.content_part.added", {
|
| 1050 |
"type": "response.content_part.added",
|
| 1051 |
"response_id": response_id,
|
|
|
|
| 1094 |
"response_id": response_id,
|
| 1095 |
"output_index": 0,
|
| 1096 |
"item": {
|
| 1097 |
+
"id": item_id,
|
| 1098 |
+
"type": "message",
|
| 1099 |
+
"role": "assistant",
|
| 1100 |
"status": "completed",
|
| 1101 |
"content": [{"type": "output_text", "text": text, "annotations": []}],
|
| 1102 |
},
|
| 1103 |
})
|
| 1104 |
|
| 1105 |
+
# 9. response.completed (WITH usage + metadata)
|
| 1106 |
+
final_payload = _build_responses_payload(
|
| 1107 |
+
chosen_model,
|
| 1108 |
+
text,
|
| 1109 |
+
response_id,
|
| 1110 |
+
input_tokens,
|
| 1111 |
+
output_tokens,
|
| 1112 |
+
)
|
| 1113 |
+
final_payload["router_metadata"] = {"provider": provider}
|
| 1114 |
+
|
| 1115 |
yield sse("response.completed", {
|
| 1116 |
"type": "response.completed",
|
| 1117 |
+
"response": final_payload,
|
|
|
|
|
|
|
| 1118 |
})
|
| 1119 |
|
| 1120 |
yield "data: [DONE]\n\n"
|
|
|
|
| 1122 |
return StreamingResponse(
|
| 1123 |
event_stream(),
|
| 1124 |
media_type="text/event-stream",
|
| 1125 |
+
headers={
|
| 1126 |
+
"Cache-Control": "no-cache",
|
| 1127 |
+
"Connection": "keep-alive",
|
| 1128 |
+
"X-Accel-Buffering": "no",
|
| 1129 |
+
},
|
| 1130 |
)
|