scvcoder commited on
Commit
d92e749
·
verified ·
1 Parent(s): 94f1300

Add backend-specific app.py (FastAPI + Gradio status)

Browse files
Files changed (1) hide show
  1. app.py +115 -0
app.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """KPAA Backend Space — OpenAI-compatible API on ZeroGPU (Gradio SDK).
2
+
3
+ This Space is the inference backend. It exposes:
4
+ - POST /v1/chat/completions (OpenAI-compatible, streaming)
5
+ - GET /v1/models
6
+ - GET /healthz / /info
7
+ - GET /gradio (minimal status page; Gradio SDK detection)
8
+
9
+ UI (Open WebUI) is hosted at a separate Space which points its
10
+ OPENAI_API_BASE_URL to this Space's /v1.
11
+
12
+ Hardware: ZeroGPU (zero-a10g).
13
+ Required secret: LAW_OC.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ # HF Spaces 에서는 `pip install -e .` 가 동작하지 않는다 → src/ 를 sys.path 에 prepend.
22
+ sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))
23
+
24
+
25
+ # ─── monkey-patch: gradio_client `/api_info` schema bug ────────────────────
26
+ # Gradio 5.x 의 gradio_client.utils 는 JSON Schema 의 `additionalProperties: True`
27
+ # (bool, 합법적 형식) 를 dict 로만 가정해서 TypeError 를 낸다. 이 백엔드는
28
+ # Gradio UI 자체를 사용자에게 노출하지 않지만 Gradio 가 mount 되어 있어
29
+ # /api_info 가 호출될 가능성이 있으므로 안전 패치.
30
+ import gradio_client.utils as _gc_utils # noqa: E402
31
+
32
+ _orig_get_type = _gc_utils.get_type
33
+ _orig_jstpt = _gc_utils._json_schema_to_python_type
34
+
35
+
36
+ def _safe_get_type(schema):
37
+ if not isinstance(schema, dict):
38
+ return ""
39
+ return _orig_get_type(schema)
40
+
41
+
42
+ def _safe_jstpt(schema, defs):
43
+ if not isinstance(schema, dict):
44
+ return "Any"
45
+ return _orig_jstpt(schema, defs)
46
+
47
+
48
+ _gc_utils.get_type = _safe_get_type
49
+ _gc_utils._json_schema_to_python_type = _safe_jstpt
50
+ # ──────────────────────────────────────────────────────────────────────────
51
+
52
+
53
+ # ─── HF Spaces ZeroGPU startup canary ─────────────────────────────────────
54
+ # ZeroGPU 는 startup 시점에 module-level `@spaces.GPU` 함수가 적어도 하나 검출되어야
55
+ # GPU 스케줄을 잡는다. 실제 GPU 작업은 ZeroGPUBackend.stream_chat 안에서 동적으로
56
+ # 데코레이트되지만 startup 스캔에서 안 보이므로 sentinel 추가.
57
+ try:
58
+ import spaces # type: ignore[import-not-found]
59
+
60
+ @spaces.GPU(duration=1)
61
+ def _zerogpu_startup_canary() -> None:
62
+ return None
63
+ except ImportError:
64
+ pass
65
+ # ──────────────────────────────────────────────────────────────────────────
66
+
67
+
68
+ import gradio as gr # noqa: E402
69
+
70
+ from kpaa.server import create_app # noqa: E402
71
+
72
+
73
+ # Build the FastAPI app from KPAA's existing OpenAI-compatible server.
74
+ fastapi_app = create_app()
75
+
76
+
77
+ # Build a minimal Gradio status page so HF Spaces 에 "Gradio app" 으로 인식되고,
78
+ # 사용자가 backend URL 에 접속해도 친절한 안내 페이지가 나오도록 함.
79
+ with gr.Blocks(title="KPAA Backend", theme=gr.themes.Soft()) as demo:
80
+ gr.Markdown(
81
+ """
82
+ # 🧠 KPAA Backend — 개인정보보호법 RAG 추론 서버
83
+
84
+ 한국 개인정보보호법 RAG 백엔드. **OpenAI 호환 API**.
85
+ UI 는 별도 Space (`scvcoder/korean-privacy-ai-assistant`) 의 Open WebUI 가 제공합니다.
86
+
87
+ ## API Endpoints
88
+
89
+ - **POST** `/v1/chat/completions` — OpenAI 호환 chat (스트리밍 지원)
90
+ - **GET** `/v1/models` — 모델 목록 (`kpaa-privacy-ko`)
91
+ - **GET** `/healthz` — health check
92
+ - **GET** `/info` — 상세 정보 / Swagger UI 링크
93
+
94
+ ## 외부 연동
95
+ Open WebUI 등에 OpenAI provider 로 등록:
96
+ - **URL** `https://scvcoder-kpaa-backend.hf.space/v1`
97
+ - **Key** any (인증 없음)
98
+ - **Model** `kpaa-privacy-ko`
99
+ """
100
+ )
101
+
102
+ # Mount the Gradio Blocks at /gradio. The FastAPI routes (/v1/..., /healthz, ...)
103
+ # remain at the root.
104
+ final_app = gr.mount_gradio_app(fastapi_app, demo, path="/gradio")
105
+
106
+
107
+ def main() -> None:
108
+ import uvicorn
109
+
110
+ port = int(os.environ.get("PORT", "7860"))
111
+ uvicorn.run(final_app, host="0.0.0.0", port=port)
112
+
113
+
114
+ if __name__ == "__main__":
115
+ main()