yangzhitao commited on
Commit
b4929ca
·
1 Parent(s): 12947f9

feat: integrate FastAPI backend and health check functionality

Browse files

- Added FastAPI as a backend server to handle API requests.
- Implemented health check endpoint to monitor backend status.
- Introduced new configuration management using Pydantic.
- Updated Gradio interface to display backend health status dynamically.
- Added necessary dependencies for FastAPI and Uvicorn in project configuration.

app.py CHANGED
@@ -1,6 +1,9 @@
 
 
1
  import gradio as gr
2
  import gradio.components as grc
3
  import pandas as pd
 
4
  from apscheduler.schedulers.background import BackgroundScheduler
5
  from huggingface_hub import snapshot_download
6
  from rich import print
@@ -14,6 +17,7 @@ from src.about import (
14
  LLM_BENCHMARKS_TEXT,
15
  TITLE,
16
  )
 
17
  from src.display.css_html_js import custom_css
18
  from src.display.utils import (
19
  BASE_COLS,
@@ -404,7 +408,26 @@ with demo:
404
  show_copy_button=True,
405
  )
406
 
407
- scheduler = BackgroundScheduler()
408
- scheduler.add_job(restart_space, "interval", seconds=1800)
409
- scheduler.start()
410
- demo.queue(default_concurrency_limit=40).launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+
3
  import gradio as gr
4
  import gradio.components as grc
5
  import pandas as pd
6
+ import uvicorn
7
  from apscheduler.schedulers.background import BackgroundScheduler
8
  from huggingface_hub import snapshot_download
9
  from rich import print
 
17
  LLM_BENCHMARKS_TEXT,
18
  TITLE,
19
  )
20
+ from src.backend.app import create_app
21
  from src.display.css_html_js import custom_css
22
  from src.display.utils import (
23
  BASE_COLS,
 
408
  show_copy_button=True,
409
  )
410
 
411
+
412
+ if __name__ == "__main__":
413
+ # Backend server - 在单独的线程中运行
414
+ app = create_app()
415
+
416
+ def run_fastapi():
417
+ print("Starting FastAPI server on http://0.0.0.0:8000")
418
+ uvicorn.run(
419
+ app,
420
+ host="0.0.0.0",
421
+ port=8000,
422
+ log_level="info",
423
+ access_log=True,
424
+ )
425
+
426
+ fastapi_thread = threading.Thread(target=run_fastapi, daemon=True)
427
+ fastapi_thread.start()
428
+
429
+ # Gradio server - 在主线程中运行(阻塞)
430
+ scheduler = BackgroundScheduler()
431
+ scheduler.add_job(restart_space, "interval", seconds=1800)
432
+ scheduler.start()
433
+ demo.queue(default_concurrency_limit=40).launch()
pyproject.toml CHANGED
@@ -25,6 +25,9 @@ dependencies = [
25
  "pydantic>=2.11.10",
26
  "pydantic-settings>=2.11.0",
27
  "rich>=14.2.0",
 
 
 
28
  ]
29
 
30
  [dependency-groups]
 
25
  "pydantic>=2.11.10",
26
  "pydantic-settings>=2.11.0",
27
  "rich>=14.2.0",
28
+ "fastapi>=0.120.0",
29
+ "loguru>=0.7.3",
30
+ "uvicorn>=0.38.0",
31
  ]
32
 
33
  [dependency-groups]
src/backend/app.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.responses import RedirectResponse
3
+ from fastapi.routing import APIRoute
4
+
5
+ from .config import settings
6
+ from .routes.main import api_router
7
+
8
+
9
+ def create_app() -> FastAPI:
10
+ app = FastAPI(
11
+ title="Leaderboard API",
12
+ description="API for the Leaderboard",
13
+ version="0.1.0",
14
+ docs_url=f"{settings.API_V1_STR}/docs",
15
+ openapi_url=f"{settings.API_V1_STR}/openapi.json",
16
+ generate_unique_id_function=custom_generate_unique_id,
17
+ )
18
+
19
+ app.include_router(api_router, prefix=settings.API_V1_STR)
20
+ app.add_route("/", _redirect_to_docs, methods=["GET"])
21
+
22
+ return app
23
+
24
+
25
+ def custom_generate_unique_id(route: APIRoute) -> str:
26
+ return f"{route.tags[0]}-{route.name}"
27
+
28
+
29
+ async def _redirect_to_docs(_: Request):
30
+ return RedirectResponse(url=f"{settings.API_V1_STR}/docs")
src/backend/config.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import cached_property
2
+ from typing import Annotated
3
+
4
+ from dotenv import load_dotenv
5
+ from pydantic import Field, SecretStr
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
8
+ load_dotenv()
9
+
10
+
11
+ class Settings(BaseSettings):
12
+ model_config = SettingsConfigDict(env_file=".env", extra="ignore")
13
+
14
+ API_V1_STR: str = "/api/v1"
15
+
16
+ # ------------------------------------------------------------
17
+ # Hugging Face settings
18
+ # ------------------------------------------------------------
19
+ HF_TOKEN: Annotated[SecretStr, Field(description="Hugging Face Access Token")] = SecretStr("changethis")
20
+
21
+ @cached_property
22
+ def hf_api(self):
23
+ from huggingface_hub import HfApi
24
+
25
+ token = self.HF_TOKEN.get_secret_value()
26
+ if not token or token == "changethis":
27
+ raise RuntimeError("Hugging Face Access Token is not set.")
28
+
29
+ return HfApi(
30
+ endpoint="https://huggingface.co",
31
+ token=token,
32
+ )
33
+
34
+ REQUESTS_REPO_ID: str = "y-playground/requests"
35
+ RESULTS_REPO_ID: str = "y-playground/results"
36
+
37
+
38
+ settings = Settings()
src/backend/routes/health.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone
2
+
3
+ from fastapi import APIRouter
4
+
5
+ router = APIRouter(tags=["health"])
6
+
7
+
8
+ @router.get("/")
9
+ async def health():
10
+ utc_now = datetime.now(timezone.utc).isoformat()
11
+ return {
12
+ "code": 0,
13
+ "msg": "ok",
14
+ "data": {
15
+ "utc_now": utc_now,
16
+ },
17
+ }
src/backend/routes/hf.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ from dataclasses import asdict
3
+
4
+ from fastapi import APIRouter
5
+ from loguru import logger
6
+
7
+ from src.backend.config import settings
8
+ from src.backend.schemas import HfRepoUrl, ResponseData, Submit_Params, Submit_RespData
9
+
10
+ router = APIRouter(tags=["huggingface"])
11
+
12
+
13
+ @router.post("/community/submit/")
14
+ async def submit(params: Submit_Params) -> ResponseData[Submit_RespData]:
15
+ """Submit a new evaluation request to the Hugging Face repository."""
16
+ file_obj = io.BytesIO(params.content.encode("utf-8"))
17
+ commit_info = settings.hf_api.upload_file(
18
+ path_or_fileobj=file_obj,
19
+ path_in_repo=params.path_in_repo,
20
+ repo_id=settings.REQUESTS_REPO_ID,
21
+ repo_type="dataset",
22
+ commit_message=params.commit_message,
23
+ )
24
+ data_dict = asdict(commit_info)
25
+ try:
26
+ repo_url = HfRepoUrl.model_validate({
27
+ "endpoint": commit_info.repo_url.endpoint,
28
+ "namespace": commit_info.repo_url.namespace,
29
+ "repo_name": commit_info.repo_url.repo_name,
30
+ "repo_id": commit_info.repo_url.repo_id,
31
+ "repo_type": commit_info.repo_url.repo_type,
32
+ "url": commit_info.repo_url.url,
33
+ })
34
+ return ResponseData(
35
+ data=Submit_RespData.model_validate({
36
+ **data_dict,
37
+ "repo_url": repo_url,
38
+ })
39
+ )
40
+ except Exception as e:
41
+ msg = f"Failed to validate repo url: {e}"
42
+ logger.warning(msg)
43
+ return ResponseData(
44
+ code=500,
45
+ msg=msg,
46
+ data=Submit_RespData.model_validate(data_dict),
47
+ )
src/backend/routes/main.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+
3
+ from .health import router as health_router
4
+ from .hf import router as hf_router
5
+
6
+ api_router = APIRouter()
7
+ api_router.include_router(health_router, prefix="/health")
8
+ api_router.include_router(hf_router, prefix="/hf")
src/backend/schemas.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated, Generic, Literal, TypeVar
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+ T = TypeVar("T", bound=BaseModel)
6
+
7
+
8
+ class ResponseData(BaseModel, Generic[T]):
9
+ code: int = 0
10
+ msg: str = "ok"
11
+ data: T | None = None
12
+
13
+
14
+ # --- Submit
15
+ class Submit_Params(BaseModel):
16
+ path_in_repo: str
17
+ commit_message: str | None = None
18
+
19
+ model_id: str
20
+ model_sha: str = "main"
21
+ model_dtype: str
22
+
23
+ content: str
24
+
25
+
26
+ class Submit_RespData(BaseModel):
27
+ model_config = ConfigDict(extra="allow")
28
+
29
+ commit_url: str
30
+ commit_message: str
31
+ commit_description: str
32
+ oid: Annotated[str, Field(description="Commit hash id.")]
33
+ repo_url: "HfRepoUrl | None" = None
34
+
35
+
36
+ class HfRepoUrl(BaseModel):
37
+ model_config = ConfigDict(extra="allow")
38
+
39
+ endpoint: str
40
+ namespace: str
41
+ repo_name: str
42
+ repo_id: str
43
+ repo_type: Literal["model", "dataset", "space"]
44
+ url: str
uv.lock CHANGED
@@ -674,10 +674,12 @@ source = { virtual = "." }
674
  dependencies = [
675
  { name = "apscheduler" },
676
  { name = "datasets" },
 
677
  { name = "gradio", extra = ["oauth"] },
678
  { name = "gradio-client" },
679
  { name = "gradio-leaderboard" },
680
  { name = "huggingface-hub" },
 
681
  { name = "matplotlib" },
682
  { name = "numpy" },
683
  { name = "pandas" },
@@ -690,6 +692,7 @@ dependencies = [
690
  { name = "tokenizers" },
691
  { name = "tqdm" },
692
  { name = "transformers" },
 
693
  ]
694
 
695
  [package.dev-dependencies]
@@ -701,11 +704,13 @@ dev = [
701
  requires-dist = [
702
  { name = "apscheduler" },
703
  { name = "datasets" },
 
704
  { name = "gradio" },
705
  { name = "gradio", extras = ["oauth"] },
706
  { name = "gradio-client" },
707
  { name = "gradio-leaderboard", specifier = "==0.0.13" },
708
  { name = "huggingface-hub", specifier = ">=0.18.0" },
 
709
  { name = "matplotlib" },
710
  { name = "numpy" },
711
  { name = "pandas" },
@@ -718,11 +723,25 @@ requires-dist = [
718
  { name = "tokenizers", specifier = ">=0.15.0" },
719
  { name = "tqdm" },
720
  { name = "transformers" },
 
721
  ]
722
 
723
  [package.metadata.requires-dev]
724
  dev = [{ name = "ruff", specifier = ">=0.14.0,<0.15.0" }]
725
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  [[package]]
727
  name = "markdown-it-py"
728
  version = "4.0.0"
@@ -1478,6 +1497,15 @@ wheels = [
1478
  { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
1479
  ]
1480
 
 
 
 
 
 
 
 
 
 
1481
  [[package]]
1482
  name = "xxhash"
1483
  version = "3.6.0"
 
674
  dependencies = [
675
  { name = "apscheduler" },
676
  { name = "datasets" },
677
+ { name = "fastapi" },
678
  { name = "gradio", extra = ["oauth"] },
679
  { name = "gradio-client" },
680
  { name = "gradio-leaderboard" },
681
  { name = "huggingface-hub" },
682
+ { name = "loguru" },
683
  { name = "matplotlib" },
684
  { name = "numpy" },
685
  { name = "pandas" },
 
692
  { name = "tokenizers" },
693
  { name = "tqdm" },
694
  { name = "transformers" },
695
+ { name = "uvicorn" },
696
  ]
697
 
698
  [package.dev-dependencies]
 
704
  requires-dist = [
705
  { name = "apscheduler" },
706
  { name = "datasets" },
707
+ { name = "fastapi", specifier = ">=0.120.0" },
708
  { name = "gradio" },
709
  { name = "gradio", extras = ["oauth"] },
710
  { name = "gradio-client" },
711
  { name = "gradio-leaderboard", specifier = "==0.0.13" },
712
  { name = "huggingface-hub", specifier = ">=0.18.0" },
713
+ { name = "loguru", specifier = ">=0.7.3" },
714
  { name = "matplotlib" },
715
  { name = "numpy" },
716
  { name = "pandas" },
 
723
  { name = "tokenizers", specifier = ">=0.15.0" },
724
  { name = "tqdm" },
725
  { name = "transformers" },
726
+ { name = "uvicorn", specifier = ">=0.38.0" },
727
  ]
728
 
729
  [package.metadata.requires-dev]
730
  dev = [{ name = "ruff", specifier = ">=0.14.0,<0.15.0" }]
731
 
732
+ [[package]]
733
+ name = "loguru"
734
+ version = "0.7.3"
735
+ source = { registry = "https://pypi.org/simple" }
736
+ dependencies = [
737
+ { name = "colorama", marker = "sys_platform == 'win32'" },
738
+ { name = "win32-setctime", marker = "sys_platform == 'win32'" },
739
+ ]
740
+ sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
741
+ wheels = [
742
+ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
743
+ ]
744
+
745
  [[package]]
746
  name = "markdown-it-py"
747
  version = "4.0.0"
 
1497
  { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
1498
  ]
1499
 
1500
+ [[package]]
1501
+ name = "win32-setctime"
1502
+ version = "1.2.0"
1503
+ source = { registry = "https://pypi.org/simple" }
1504
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
1505
+ wheels = [
1506
+ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
1507
+ ]
1508
+
1509
  [[package]]
1510
  name = "xxhash"
1511
  version = "3.6.0"