sharktide commited on
Commit
9187eb5
·
verified ·
1 Parent(s): 2c6ec4e

Update gen.py

Browse files
Files changed (1) hide show
  1. gen.py +63 -45
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
- # ── shared helper: route + call + return (text, input_tokens, output_tokens) ──
 
 
 
 
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
- return JSONResponse(
983
- content=_build_responses_payload(model, text, response_id, input_tokens, output_tokens)
 
 
 
 
 
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, "object": "response",
1003
- "created_at": ts, "status": "in_progress", "model": model,
1004
- "output": [], "usage": None,
 
 
 
 
 
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, "object": "response",
1013
- "created_at": ts, "status": "in_progress", "model": model,
 
 
 
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, "object": "response",
1024
- "created_at": ts, "status": "failed", "model": model,
 
 
 
1025
  "error": {"code": "upstream_error", "message": exc.detail},
1026
  },
1027
  })
1028
  yield "data: [DONE]\n\n"
1029
  return
1030
 
1031
- # 3. output item added (the assistant message container)
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": output_item,
 
 
 
 
 
 
1041
  })
1042
 
1043
- # 4. content part added (the text part within the message)
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, "type": "message", "role": "assistant",
 
 
1093
  "status": "completed",
1094
  "content": [{"type": "output_text", "text": text, "annotations": []}],
1095
  },
1096
  })
1097
 
1098
- # 9. response.completed includes usage, this is what SDK exposes as .usage
 
 
 
 
 
 
 
 
 
1099
  yield sse("response.completed", {
1100
  "type": "response.completed",
1101
- "response": _build_responses_payload(
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={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
 
 
 
 
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
  )