fomext commited on
Commit
c01d49a
·
verified ·
1 Parent(s): 691b54e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -58
app.py CHANGED
@@ -8,7 +8,8 @@ from typing import AsyncIterator, Optional
8
  import gradio as gr
9
  import spaces
10
  import torch
11
- from fastapi import HTTPException
 
12
  from fastapi.responses import JSONResponse, StreamingResponse
13
  from pydantic import BaseModel, Field
14
  from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
@@ -168,79 +169,79 @@ You can also chat directly below.
168
  gr.ChatInterface(fn=gradio_chat)
169
 
170
  # ---------------------------------------------------------------------------
171
- # Register FastAPI-style routes onto Gradio's own FastAPI app via app_kwargs,
172
- # BEFORE launch() so ZeroGPU's launch hook still does its registration scan.
 
173
  # ---------------------------------------------------------------------------
174
 
 
175
 
176
- def add_custom_routes(fastapi_app):
177
- @fastapi_app.get("/v1/models")
178
- async def list_models():
179
- return {
180
- "object": "list",
181
- "data": [{"id": MODEL_ALIAS, "object": "model", "created": int(time.time()), "owned_by": "qwen"}],
182
- }
183
 
184
- @fastapi_app.post("/v1/chat/completions")
185
- async def chat_completions(request: ChatCompletionRequest):
186
- try:
187
- prompt = build_prompt(request.messages, request.enable_thinking or False)
188
- gen_kwargs = make_generation_kwargs(request)
189
- except Exception as exc:
190
- raise HTTPException(status_code=422, detail=str(exc))
 
 
 
 
 
 
 
 
191
 
192
- if request.stream:
193
- completion_id = f"chatcmpl-{uuid.uuid4().hex}"
194
 
195
- async def token_generator() -> AsyncIterator[str]:
196
- role_chunk = {
197
- "id": completion_id, "object": "chat.completion.chunk",
198
- "created": int(time.time()), "model": request.model,
199
- "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
200
- }
201
- yield f"data: {json.dumps(role_chunk)}\n\n"
202
 
203
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
204
- thread = Thread(target=_generate_streaming, args=(prompt, gen_kwargs, streamer), daemon=True)
205
- thread.start()
206
 
207
- try:
208
- for token_text in streamer:
209
- if token_text:
210
- yield stream_chunk(token_text, request.model, completion_id)
211
- await asyncio.sleep(0)
212
- finally:
213
- thread.join()
214
 
215
- yield stream_chunk("", request.model, completion_id, finish_reason="stop")
216
- yield "data: [DONE]\n\n"
217
 
218
- return StreamingResponse(token_generator(), media_type="text/event-stream",
219
- headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
220
 
221
- try:
222
- content = _generate_response(prompt, gen_kwargs)
223
- except Exception as exc:
224
- raise HTTPException(status_code=500, detail=f"Generation failed: {exc}")
225
 
226
- return JSONResponse(chat_completion_object(content, request.model))
227
 
228
- @fastapi_app.get("/health")
229
- async def health():
230
- return {"status": "ok", "model": MODEL_ID}
 
231
 
232
 
233
  # ---------------------------------------------------------------------------
234
- # Entry-point demo.launch() is what triggers spaces.one_launch's patched
235
- # hook, which is what registers @spaces.GPU functions with the HF platform.
 
236
  # ---------------------------------------------------------------------------
237
 
 
 
238
  if __name__ == "__main__":
239
- demo.queue()
240
- add_custom_routes(demo.app)
241
- demo.launch(
242
- server_name="0.0.0.0",
243
- server_port=7860,
244
- app_kwargs={"docs_url": None},
245
- )
246
-
 
8
  import gradio as gr
9
  import spaces
10
  import torch
11
+ import uvicorn
12
+ from fastapi import FastAPI, HTTPException
13
  from fastapi.responses import JSONResponse, StreamingResponse
14
  from pydantic import BaseModel, Field
15
  from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
 
169
  gr.ChatInterface(fn=gradio_chat)
170
 
171
  # ---------------------------------------------------------------------------
172
+ # FastAPI app built ourselves so the routes are guaranteed to exist before
173
+ # Gradio is mounted into it. (demo.app does not exist until demo.launch()
174
+ # runs, so routes can never be attached to it beforehand.)
175
  # ---------------------------------------------------------------------------
176
 
177
+ app = FastAPI(title="Qwen3-30B-A3B OpenAI-compatible API")
178
 
 
 
 
 
 
 
 
179
 
180
+ @app.get("/v1/models")
181
+ async def list_models():
182
+ return {
183
+ "object": "list",
184
+ "data": [{"id": MODEL_ALIAS, "object": "model", "created": int(time.time()), "owned_by": "qwen"}],
185
+ }
186
+
187
+
188
+ @app.post("/v1/chat/completions")
189
+ async def chat_completions(request: ChatCompletionRequest):
190
+ try:
191
+ prompt = build_prompt(request.messages, request.enable_thinking or False)
192
+ gen_kwargs = make_generation_kwargs(request)
193
+ except Exception as exc:
194
+ raise HTTPException(status_code=422, detail=str(exc))
195
 
196
+ if request.stream:
197
+ completion_id = f"chatcmpl-{uuid.uuid4().hex}"
198
 
199
+ async def token_generator() -> AsyncIterator[str]:
200
+ role_chunk = {
201
+ "id": completion_id, "object": "chat.completion.chunk",
202
+ "created": int(time.time()), "model": request.model,
203
+ "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
204
+ }
205
+ yield f"data: {json.dumps(role_chunk)}\n\n"
206
 
207
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
208
+ thread = Thread(target=_generate_streaming, args=(prompt, gen_kwargs, streamer), daemon=True)
209
+ thread.start()
210
 
211
+ try:
212
+ for token_text in streamer:
213
+ if token_text:
214
+ yield stream_chunk(token_text, request.model, completion_id)
215
+ await asyncio.sleep(0)
216
+ finally:
217
+ thread.join()
218
 
219
+ yield stream_chunk("", request.model, completion_id, finish_reason="stop")
220
+ yield "data: [DONE]\n\n"
221
 
222
+ return StreamingResponse(token_generator(), media_type="text/event-stream",
223
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
224
 
225
+ try:
226
+ content = _generate_response(prompt, gen_kwargs)
227
+ except Exception as exc:
228
+ raise HTTPException(status_code=500, detail=f"Generation failed: {exc}")
229
 
230
+ return JSONResponse(chat_completion_object(content, request.model))
231
 
232
+
233
+ @app.get("/health")
234
+ async def health():
235
+ return {"status": "ok", "model": MODEL_ID}
236
 
237
 
238
  # ---------------------------------------------------------------------------
239
+ # Mount Gradio into our FastAPI app at the ROOT path. ZeroGPU's scanner
240
+ # inspects the module for @spaces.GPU usage it does not require
241
+ # demo.launch() to be called, so this mount-and-uvicorn pattern is safe.
242
  # ---------------------------------------------------------------------------
243
 
244
+ app = gr.mount_gradio_app(app, demo, path="/")
245
+
246
  if __name__ == "__main__":
247
+ uvicorn.run(app, host="0.0.0.0", port=7860)