1MR commited on
Commit
da5e15d
Β·
verified Β·
1 Parent(s): b303033

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -0
app.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KG Embedding Server β€” FastAPI on HuggingFace Spaces
3
+ يشΨͺΨΊΩ„ ΩƒΩ€ REST API، ΩŠΨ­Ω…Ω„ Ψ§Ω„Ω…ΩˆΨ―ΩŠΩ„ Ω…Ψ±Ψ© واحدة Ψ¨Ψ³
4
+ """
5
+
6
+ from fastapi import FastAPI, HTTPException
7
+ from pydantic import BaseModel
8
+ from typing import Optional
9
+ import math
10
+ import re
11
+ import contextlib
12
+ from io import StringIO
13
+
14
+ app = FastAPI(title="KG Embedding Server")
15
+
16
+ # ══════════════════════════════════════════════════════
17
+ # GLOBALS β€” Ψ§Ω„Ω…ΩˆΨ―ΩŠΩ„ بيΨͺΨ­Ω…Ω„ Ω…Ψ±Ψ© واحدة ΨΉΩ†Ψ― startup
18
+ # ══════════════════════════════════════════════════════
19
+ _model = None
20
+ _use_st = False
21
+
22
+ @app.on_event("startup")
23
+ def load_model():
24
+ global _model, _use_st
25
+ try:
26
+ from sentence_transformers import SentenceTransformer
27
+ with contextlib.redirect_stdout(StringIO()), contextlib.redirect_stderr(StringIO()):
28
+ _model = SentenceTransformer("all-MiniLM-L6-v2")
29
+ _use_st = True
30
+ print("[Server] sentence-transformers loaded βœ“")
31
+ except Exception as e:
32
+ print(f"[Server] ST unavailable: {e}")
33
+ _use_st = False
34
+
35
+ # ══════════════════════════════════════════════════════
36
+ # REQUEST / RESPONSE MODELS
37
+ # ══════════════════════════════════════════════════════
38
+ class EmbedRequest(BaseModel):
39
+ texts: list[str]
40
+
41
+ class EmbedResponse(BaseModel):
42
+ embeddings: list[list[float]]
43
+ model: str = "all-MiniLM-L6-v2"
44
+
45
+ class SimilarityRequest(BaseModel):
46
+ query: str
47
+ candidates: list[str] # Ψ§Ω„Ω†Ψ΅ΩˆΨ΅ Ψ§Ω„Ω„ΩŠ Ω‡Ω†Ω‚ΩŠΨ³ ΨΉΩ„ΩŠΩ‡Ψ§
48
+
49
+ class SimilarityResponse(BaseModel):
50
+ scores: list[float] # cosine similarity Ω„ΩƒΩ„ candidate
51
+
52
+ class HealthResponse(BaseModel):
53
+ status: str
54
+ model_loaded: bool
55
+
56
+ # ══════════════════════════════════════════════════════
57
+ # ENDPOINTS
58
+ # ══════════════════════════════════════════════════════
59
+
60
+ @app.get("/health", response_model=HealthResponse)
61
+ def health():
62
+ return {"status": "ok", "model_loaded": _use_st}
63
+
64
+ @app.post("/embed", response_model=EmbedResponse)
65
+ def embed(req: EmbedRequest):
66
+ if not _use_st or _model is None:
67
+ raise HTTPException(503, "Model not loaded")
68
+ if not req.texts:
69
+ return EmbedResponse(embeddings=[])
70
+ with contextlib.redirect_stdout(StringIO()), contextlib.redirect_stderr(StringIO()):
71
+ vecs = _model.encode(req.texts, show_progress_bar=False)
72
+ return EmbedResponse(embeddings=[v.tolist() for v in vecs])
73
+
74
+ @app.post("/similarity", response_model=SimilarityResponse)
75
+ def similarity(req: SimilarityRequest):
76
+ """Query + candidates β†’ cosine scores (Ψ§Ω„Ψ£Ψ³Ψ±ΨΉ Ω„Ωˆ عايز ΨͺΨ±ΨͺΨ¨ Ω†Ψͺايج)"""
77
+ if not _use_st or _model is None:
78
+ raise HTTPException(503, "Model not loaded")
79
+ texts = [req.query] + req.candidates
80
+ with contextlib.redirect_stdout(StringIO()), contextlib.redirect_stderr(StringIO()):
81
+ vecs = _model.encode(texts, show_progress_bar=False)
82
+ qvec = vecs[0]
83
+ scores = [_cosine(qvec, v) for v in vecs[1:]]
84
+ return SimilarityResponse(scores=scores)
85
+
86
+ def _cosine(a, b) -> float:
87
+ dot = sum(x * y for x, y in zip(a, b))
88
+ na = math.sqrt(sum(x * x for x in a)) or 1e-9
89
+ nb = math.sqrt(sum(x * x for x in b)) or 1e-9
90
+ return dot / (na * nb)
91
+
92
+ # ══════════════════════════════════════════════════════
93
+ # PING β€” Ω„Ω…Ω†ΨΉ Ψ§Ω„Ω€ Space Ω…Ω† Ψ§Ω„Ω†ΩˆΩ… (Ψ§ΨΉΩ…Ω„ cron job يبعΨͺΩ‡ ΩƒΩ„ 4 Ψ―Ω‚Ψ§ΩŠΩ‚)
94
+ # ══════════════════════════════════════════════════════
95
+ @app.get("/ping")
96
+ def ping():
97
+ return {"pong": True}