SiddhJagani commited on
Commit
c6d3a00
·
verified ·
1 Parent(s): aca2b2d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -183
app.py CHANGED
@@ -1,23 +1,14 @@
1
- import os
2
- import json
3
- import httpx
4
- import gradio as gr
5
- import uvicorn
6
- from fastapi import FastAPI, Request, Header, HTTPException, File, UploadFile, Form
7
- from fastapi.responses import JSONResponse, StreamingResponse, Response
8
  import time
9
- import base64
10
- import io
11
-
12
  # ---------------------------------------------------------------------
13
- # Configuration - FIXED TRAILING SPACES
14
  # ---------------------------------------------------------------------
15
  BYTEZ_CHAT_URL = "https://api.bytez.com/models/v2/openai/v1/chat/completions"
16
  BYTEZ_MODELS_URL = "https://api.bytez.com/models/v2/list/models"
17
- BYTEZ_TTS_URL = "https://api.bytez.com/models/v2/openai/tts-1-hd"
18
- BYTEZ_STT_URL = "https://api.bytez.com/models/v2/openai/whisper-1"
19
- BYTEZ_AUTH = os.getenv("BYTEZ_API_KEY")
20
- LOCAL_API_KEY = os.getenv("LOCAL_API_KEY")
21
 
22
  # ---------------------------------------------------------------------
23
  # FastAPI backend
@@ -39,19 +30,21 @@ def root():
39
  return {"status": "ok", "message": "Bytez proxy running"}
40
 
41
  # ---------------------------------------------------------------------
42
- # /v1/models must look OpenAI-style
43
  # ---------------------------------------------------------------------
44
  @api.get("/v1/models")
45
  async def models(authorization: str = Header(None)):
46
  check_key(authorization)
47
  if not BYTEZ_AUTH:
48
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
 
49
  async with httpx.AsyncClient(timeout=30) as c:
50
  r = await c.get(BYTEZ_MODELS_URL, headers={"Authorization": BYTEZ_AUTH})
51
  try:
52
  data = r.json()
53
  except json.JSONDecodeError:
54
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
 
55
  # Transform Bytez format → OpenAI format
56
  models_list = [
57
  {"id": m.get("id") or m.get("name"), "object": "model"}
@@ -63,75 +56,82 @@ async def models(authorization: str = Header(None)):
63
  )
64
 
65
  # ---------------------------------------------------------------------
66
- # /v1/chat/completions - FIXED STREAMING AND SYNTAX
67
  # ---------------------------------------------------------------------
68
  @api.post("/v1/chat/completions")
69
  async def chat(request: Request, authorization: str = Header(None)):
70
  check_key(authorization)
71
  if not BYTEZ_AUTH:
72
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
 
73
  payload = await request.json()
74
  stream = payload.get("stream", False)
 
75
  headers = {
76
  "Authorization": BYTEZ_AUTH,
77
  "Content-Type": "application/json",
78
  }
79
-
 
80
  async def event_stream():
81
- first_chunk = True # Track first chunk for role
82
  async with httpx.AsyncClient(timeout=120) as client:
83
  async with client.stream("POST", BYTEZ_CHAT_URL, headers=headers, json=payload) as upstream:
84
  async for line in upstream.aiter_lines():
85
  line = line.strip()
86
  if not line:
87
  continue
 
 
88
  if line.startswith("data: "):
89
- json_str = line[6:].strip()
90
  else:
91
  json_str = line
92
-
93
- if json_str == "[DONE]":
94
- yield "data: [DONE]\n\n"
95
- break
96
-
97
  try:
98
  chunk = json.loads(json_str)
99
  except json.JSONDecodeError:
100
- continue
101
-
102
- # Skip metadata chunks
103
- if "usage" in chunk or not chunk.get("choices"):
104
- continue
105
-
106
- choice = chunk["choices"][0]
 
 
 
 
 
107
  content = ""
108
- if "delta" in choice and "content" in choice["delta"]:
109
- content = choice["delta"]["content"]
110
- elif "token" in chunk:
111
  content = chunk["token"]
 
 
 
112
  elif "text" in chunk:
113
  content = chunk["text"]
114
  else:
115
- continue
116
-
117
- # Build delta - role only in first chunk
118
- delta = {"content": content}
119
- if first_chunk:
120
- delta["role"] = "assistant"
121
- first_chunk = False
122
-
123
  openai_chunk = {
124
  "id": "chatcmpl-proxy-stream",
125
  "object": "chat.completion.chunk",
126
  "created": int(time.time()),
127
  "model": payload.get("model", "unknown"),
128
- "choices": [{
129
- "index": 0,
130
- "delta": delta,
131
- "finish_reason": None,
132
- }],
 
 
133
  }
 
 
134
  yield f"data: {json.dumps(openai_chunk)}\n\n"
 
 
135
  yield "data: [DONE]\n\n"
136
 
137
  if stream:
@@ -140,16 +140,15 @@ async def chat(request: Request, authorization: str = Header(None)):
140
  media_type="text/event-stream",
141
  headers={"Access-Control-Allow-Origin": "*"}
142
  )
143
-
144
- # Non-streaming path - FIXED SYNTAX ERROR HERE
145
  async with httpx.AsyncClient(timeout=120) as c:
146
  r = await c.post(BYTEZ_CHAT_URL, headers=headers, json=payload)
147
  try:
148
  data = r.json()
149
  except json.JSONDecodeError:
150
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
151
-
152
- # FIXED: Complete the condition
153
  if "choices" not in data:
154
  content = (
155
  data.get("output")
@@ -164,144 +163,17 @@ async def chat(request: Request, authorization: str = Header(None)):
164
  {"index": 0, "message": {"role": "assistant", "content": content}}
165
  ],
166
  }
167
- return JSONResponse(data, headers={"Access-Control-Allow-Origin": "*"})
168
-
169
- # ---------------------------------------------------------------------
170
- # TTS ENDPOINTS (ALL VARIATIONS)
171
- # ---------------------------------------------------------------------
172
- @api.post("/v1/audio/speech")
173
- @api.post("/v1/tts/audio/speech")
174
- @api.post("/v1/tts")
175
- @api.post("/v1/tts/audio/transcriptions")
176
- async def tts_endpoint(
177
- request: Request = None,
178
- authorization: str = Header(None),
179
- text: str = Form(None),
180
- file: UploadFile = File(None)
181
- ):
182
- check_key(authorization)
183
- if not BYTEZ_AUTH:
184
- raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
185
-
186
- # Handle different content types
187
- if request and request.headers.get("Content-Type", "").startswith("application/json"):
188
- try:
189
- payload = await request.json()
190
- text = payload.get("input") or payload.get("text", "")
191
- except:
192
- text = ""
193
-
194
- if not text:
195
- raise HTTPException(status_code=400, detail="Missing text for TTS conversion")
196
-
197
- headers = {
198
- "Authorization": BYTEZ_AUTH,
199
- "Content-Type": "application/json",
200
- }
201
- bytez_payload = {"text": text}
202
-
203
- async with httpx.AsyncClient(timeout=30) as c:
204
- try:
205
- r = await c.post(BYTEZ_TTS_URL, headers=headers, json=bytez_payload)
206
- except Exception as e:
207
- raise HTTPException(status_code=502, detail=f"TTS request failed: {str(e)}")
208
-
209
- if r.status_code != 200:
210
- error_detail = r.text[:200] if r.text else f"Status {r.status_code}"
211
- raise HTTPException(status_code=r.status_code, detail=f"TTS failed: {error_detail}")
212
-
213
- # Return raw audio
214
- return Response(
215
- content=r.content,
216
- media_type="audio/mpeg",
217
- headers={"Access-Control-Allow-Origin": "*"}
218
- )
219
-
220
- # ---------------------------------------------------------------------
221
- # STT ENDPOINTS (ALL VARIATIONS)
222
- # ---------------------------------------------------------------------
223
- @api.post("/v1/audio/transcriptions")
224
- @api.post("/v1/stt")
225
- @api.post("/v1/stt/audio/transcriptions")
226
- @api.get("/v1/audio/voices")
227
- @api.get("/v1/stt/audio/models")
228
- async def stt_endpoint(
229
- request: Request = None,
230
- authorization: str = Header(None),
231
- file: UploadFile = File(None),
232
- model: str = Form("whisper-1")
233
- ):
234
- # Handle fake endpoints that just return empty responses
235
- if request and request.url.path in ["/v1/audio/voices", "/v1/stt/audio/models"]:
236
- return JSONResponse({"data": []})
237
-
238
- check_key(authorization)
239
- if not BYTEZ_AUTH:
240
- raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
241
-
242
- if file is None:
243
- raise HTTPException(status_code=400, detail="No audio file provided")
244
-
245
- try:
246
- audio_data = await file.read()
247
- except Exception as e:
248
- raise HTTPException(status_code=400, detail=f"File read error: {str(e)}")
249
-
250
- # Convert to base64 data URL
251
- mime_type = file.content_type or "audio/wav"
252
- audio_b64 = base64.b64encode(audio_data).decode('utf-8')
253
- data_url = f"data:{mime_type};base64,{audio_b64}"
254
-
255
- headers = {
256
- "Authorization": BYTEZ_AUTH,
257
- "Content-Type": "application/json",
258
- }
259
- bytez_payload = {
260
- "url": data_url,
261
- "model": model
262
- }
263
-
264
- async with httpx.AsyncClient(timeout=60) as c:
265
- try:
266
- r = await c.post(BYTEZ_STT_URL, headers=headers, json=bytez_payload)
267
- except Exception as e:
268
- raise HTTPException(status_code=502, detail=f"STT request failed: {str(e)}")
269
-
270
- if r.status_code != 200:
271
- error_detail = r.text[:200] if r.text else f"Status {r.status_code}"
272
- raise HTTPException(status_code=r.status_code, detail=f"STT failed: {error_detail}")
273
-
274
- try:
275
- data = r.json()
276
- except json.JSONDecodeError:
277
- transcript = r.text.strip()
278
- if not transcript:
279
- raise HTTPException(status_code=502, detail="STT returned empty response")
280
- return JSONResponse({"text": transcript}, headers={"Access-Control-Allow-Origin": "*"})
281
-
282
- # Extract transcript from various possible formats
283
- transcript = (
284
- data.get("text") or
285
- data.get("transcript") or
286
- data.get("result", {}).get("text", "") or
287
- str(data)
288
- )
289
-
290
- return JSONResponse({"text": transcript}, headers={"Access-Control-Allow-Origin": "*"})
291
 
 
292
  # ---------------------------------------------------------------------
293
- # Minimal Gradio UI
294
  # ---------------------------------------------------------------------
295
  with gr.Blocks() as ui:
296
  gr.Markdown("### ✅ Jwero Bytez → OpenAI Proxy\n"
297
- "**Supported Endpoints:**\n"
298
- "- `/v1/models`\n"
299
- "- `/v1/chat/completions`\n"
300
- "- `/v1/audio/speech` (TTS)\n"
301
- "- `/v1/audio/transcriptions` (STT)\n\n"
302
- "**Also compatible with non-standard endpoints**")
303
 
304
  demo = gr.mount_gradio_app(api, ui, path="/")
305
 
 
306
  if __name__ == "__main__":
307
  uvicorn.run(demo, host="0.0.0.0", port=7860)
 
1
+ import os, json, httpx, gradio as gr, uvicorn
2
+ from fastapi import FastAPI, Request, Header, HTTPException
3
+ from fastapi.responses import JSONResponse, StreamingResponse
 
 
 
 
4
  import time
 
 
 
5
  # ---------------------------------------------------------------------
6
+ # Configuration
7
  # ---------------------------------------------------------------------
8
  BYTEZ_CHAT_URL = "https://api.bytez.com/models/v2/openai/v1/chat/completions"
9
  BYTEZ_MODELS_URL = "https://api.bytez.com/models/v2/list/models"
10
+ BYTEZ_AUTH = os.getenv("BYTEZ_API_KEY")
11
+ LOCAL_API_KEY = os.getenv("LOCAL_API_KEY")
 
 
12
 
13
  # ---------------------------------------------------------------------
14
  # FastAPI backend
 
30
  return {"status": "ok", "message": "Bytez proxy running"}
31
 
32
  # ---------------------------------------------------------------------
33
+ # /v1/models must look OpenAI-style
34
  # ---------------------------------------------------------------------
35
  @api.get("/v1/models")
36
  async def models(authorization: str = Header(None)):
37
  check_key(authorization)
38
  if not BYTEZ_AUTH:
39
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
40
+
41
  async with httpx.AsyncClient(timeout=30) as c:
42
  r = await c.get(BYTEZ_MODELS_URL, headers={"Authorization": BYTEZ_AUTH})
43
  try:
44
  data = r.json()
45
  except json.JSONDecodeError:
46
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
47
+
48
  # Transform Bytez format → OpenAI format
49
  models_list = [
50
  {"id": m.get("id") or m.get("name"), "object": "model"}
 
56
  )
57
 
58
  # ---------------------------------------------------------------------
59
+ # /v1/chat/completions
60
  # ---------------------------------------------------------------------
61
  @api.post("/v1/chat/completions")
62
  async def chat(request: Request, authorization: str = Header(None)):
63
  check_key(authorization)
64
  if not BYTEZ_AUTH:
65
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
66
+
67
  payload = await request.json()
68
  stream = payload.get("stream", False)
69
+
70
  headers = {
71
  "Authorization": BYTEZ_AUTH,
72
  "Content-Type": "application/json",
73
  }
74
+
75
+ # Helper to transform Bytez stream chunk → OpenAI stream format
76
  async def event_stream():
 
77
  async with httpx.AsyncClient(timeout=120) as client:
78
  async with client.stream("POST", BYTEZ_CHAT_URL, headers=headers, json=payload) as upstream:
79
  async for line in upstream.aiter_lines():
80
  line = line.strip()
81
  if not line:
82
  continue
83
+
84
+ # If line starts with "data: ", strip it; else assume raw JSON
85
  if line.startswith("data: "):
86
+ json_str = line[6:] # remove "data: "
87
  else:
88
  json_str = line
89
+
 
 
 
 
90
  try:
91
  chunk = json.loads(json_str)
92
  except json.JSONDecodeError:
93
+ continue # skip malformed
94
+
95
+ # Check for [DONE] signal
96
+ if json_str == "[DONE]":
97
+ yield "data: [DONE]\n\n"
98
+ break
99
+
100
+ # Extract content from Bytez chunk
101
+ # ⚠️ ADJUST THIS BASED ON ACTUAL BYTEZ STREAM FORMAT!
102
+ # Example: if Bytez returns {"token": "hello"} → use token
103
+ # Or if it returns {"choices": [{"delta": {"content": "x"}}]} → use delta.content
104
+
105
  content = ""
106
+ if "token" in chunk:
 
 
107
  content = chunk["token"]
108
+ elif "choices" in chunk and len(chunk["choices"]) > 0:
109
+ delta = chunk["choices"][0].get("delta", {})
110
+ content = delta.get("content", "")
111
  elif "text" in chunk:
112
  content = chunk["text"]
113
  else:
114
+ content = str(chunk) # fallback
115
+
116
+ # Build OpenAI-compatible chunk
 
 
 
 
 
117
  openai_chunk = {
118
  "id": "chatcmpl-proxy-stream",
119
  "object": "chat.completion.chunk",
120
  "created": int(time.time()),
121
  "model": payload.get("model", "unknown"),
122
+ "choices": [
123
+ {
124
+ "index": 0,
125
+ "delta": {"role": "assistant", "content": content},
126
+ "finish_reason": None,
127
+ }
128
+ ],
129
  }
130
+
131
+ # Send in correct SSE format
132
  yield f"data: {json.dumps(openai_chunk)}\n\n"
133
+
134
+ # Always send [DONE] at end (even if upstream didn't)
135
  yield "data: [DONE]\n\n"
136
 
137
  if stream:
 
140
  media_type="text/event-stream",
141
  headers={"Access-Control-Allow-Origin": "*"}
142
  )
143
+
144
+ # Non-streaming path (unchanged)
145
  async with httpx.AsyncClient(timeout=120) as c:
146
  r = await c.post(BYTEZ_CHAT_URL, headers=headers, json=payload)
147
  try:
148
  data = r.json()
149
  except json.JSONDecodeError:
150
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
151
+
 
152
  if "choices" not in data:
153
  content = (
154
  data.get("output")
 
163
  {"index": 0, "message": {"role": "assistant", "content": content}}
164
  ],
165
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
+ return JSONResponse(data, headers={"Access-Control-Allow-Origin": "*"})
168
  # ---------------------------------------------------------------------
169
+ # Minimal Gradio UI (to make HF Space start)
170
  # ---------------------------------------------------------------------
171
  with gr.Blocks() as ui:
172
  gr.Markdown("### ✅ Jwero Bytez → OpenAI Proxy\n"
173
+ "Endpoints: `/v1/models`, `/v1/chat/completions`")
 
 
 
 
 
174
 
175
  demo = gr.mount_gradio_app(api, ui, path="/")
176
 
177
+ # local only
178
  if __name__ == "__main__":
179
  uvicorn.run(demo, host="0.0.0.0", port=7860)