File size: 5,766 Bytes
88db80d
4cbdd60
88db80d
57157d7
 
 
4cbdd60
b7d2ca9
57157d7
4cbdd60
57157d7
 
4cbdd60
 
88db80d
57157d7
 
80b64f3
 
4cbdd60
 
57157d7
 
 
4cbdd60
80b64f3
 
4cbdd60
57157d7
80b64f3
 
4cbdd60
 
 
 
 
 
80b64f3
 
4cbdd60
 
 
88db80d
 
 
 
 
 
 
 
 
4cbdd60
57157d7
88db80d
 
 
 
 
 
 
 
 
f26be42
 
 
4cbdd60
f26be42
57157d7
 
 
 
f26be42
 
 
 
 
4cbdd60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f26be42
 
4cbdd60
 
f26be42
 
 
4cbdd60
 
 
 
 
 
 
 
 
 
 
 
88db80d
f26be42
57157d7
4cbdd60
 
 
 
 
 
737f360
 
57157d7
675b773
 
 
57157d7
 
f26be42
57157d7
 
4cbdd60
 
 
 
 
 
 
 
 
 
 
 
675b773
4cbdd60
 
 
 
 
 
 
675b773
88db80d
 
 
 
 
4cbdd60
88db80d
 
 
57157d7
 
4cbdd60
f26be42
57157d7
 
675b773
 
4cbdd60
 
 
 
 
 
 
 
 
 
675b773
737f360
675b773
f26be42
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import os
import sys
import secrets
import spaces
import torch
import gradio as gr
from typing import List, Union, Optional, Dict, Any
from fastapi import APIRouter, Header, HTTPException, status
from pydantic import BaseModel
from huggingface_hub import snapshot_download
from sentence_transformers import SentenceTransformer

EMBED_MODEL_ID = "Dibachain/Diba-Embed"
MIZAN_MODEL_ID = "Nimruz/Mizan-v3-3B"
API_KEY = os.environ.get("MY_API_KEY", "")
device = "cuda" if torch.cuda.is_available() else "cpu"

# 1. Load Diba-Embed and register with ZeroGPU
print(f"Loading {EMBED_MODEL_ID}...")
embed_model = SentenceTransformer(
    EMBED_MODEL_ID,
    trust_remote_code=True,
    model_kwargs={"torch_dtype": torch.bfloat16}
)
embed_model.max_seq_length = 512
if torch.cuda.is_available():
    embed_model = embed_model.to("cuda")
print("Diba-Embed loaded successfully!")

# 2. Download and Load Nimruz/Mizan-v3-3B
print(f"Downloading and loading {MIZAN_MODEL_ID}...")
mizan_path = snapshot_download(MIZAN_MODEL_ID)
if mizan_path not in sys.path:
    sys.path.insert(0, mizan_path)

from joint_schema_model import load_release_model, systemone

target_device = "cuda" if torch.cuda.is_available() else "cpu"
mizan_model, mizan_processor = load_release_model(mizan_path, device=target_device)
print("Mizan-v3-3B loaded successfully!")

# 3. API Key Helper
def verify_key(provided_key: Optional[str]):
    if not API_KEY:
        raise RuntimeError("MY_API_KEY secret is not configured in Space Settings.")
    if not provided_key or not secrets.compare_digest(provided_key, API_KEY):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or missing API Key"
        )

# 4. ZeroGPU Function 1: Embeddings
@spaces.GPU(duration=30)
def compute_embeddings(
    api_key: str,
    texts: Union[str, List[str]],
    input_type: str = "document",
    normalize: bool = True
):
    if not API_KEY or not secrets.compare_digest(api_key or "", API_KEY):
        raise gr.Error("Unauthorized: Invalid API Key")

    text_list = [texts] if isinstance(texts, str) else texts
    prompt_name = "query" if input_type == "query" else None

    embeddings = embed_model.encode(
        text_list,
        prompt_name=prompt_name,
        normalize_embeddings=normalize,
        batch_size=8
    )
    return [
        {"object": "embedding", "index": i, "embedding": emb.tolist()}
        for i, emb in enumerate(embeddings)
    ]

# 5. ZeroGPU Function 2: Mizan-v3-3B Decision Inference
@spaces.GPU(duration=30)
def compute_decision(
    api_key: str,
    state: Any,
    questions: Dict[str, Any]
):
    if not API_KEY or not secrets.compare_digest(api_key or "", API_KEY):
        raise gr.Error("Unauthorized: Invalid API Key")

    payload = {
        "model": MIZAN_MODEL_ID,
        "state": state,
        "questions": questions
    }
    with torch.no_grad():
        return systemone(mizan_model, mizan_processor, payload)

# 6. Build a Tabbed Gradio Interface so ZeroGPU scans and registers BOTH @spaces.GPU functions
embed_ui = gr.Interface(
    fn=compute_embeddings,
    inputs=[
        gr.Textbox(label="API Key", type="password"),
        gr.Textbox(label="Input Text"),
        gr.Radio(["document", "query"], value="document", label="Input Type"),
        gr.Checkbox(value=True, label="Normalize")
    ],
    outputs=gr.JSON(label="Embeddings Output"),
    api_name=False
)

mizan_ui = gr.Interface(
    fn=compute_decision,
    inputs=[
        gr.Textbox(label="API Key", type="password"),
        gr.Textbox(label="State (Text or JSON)"),
        gr.JSON(label="Questions Dict")
    ],
    outputs=gr.JSON(label="Decision Output"),
    api_name=False
)

demo = gr.TabbedInterface(
    [embed_ui, mizan_ui],
    ["Diba-Embed", "Mizan-v3-3B"],
    title="Diba-Embed & Mizan-v3-3B Private API"
)

# Launch Gradio with ssr_mode=False so FastAPI binds directly to port 7860
demo.launch(prevent_thread_lock=True, ssr_mode=False)

# 7. Create an APIRouter for custom endpoints
api_router = APIRouter()

class EmbedRequest(BaseModel):
    input: Union[str, List[str]]
    input_type: Optional[str] = "document"
    normalize: Optional[bool] = True

class DecisionRequest(BaseModel):
    model: Optional[str] = MIZAN_MODEL_ID
    state: Any
    questions: Dict[str, Any]

def extract_token(authorization: Optional[str], x_api_key: Optional[str]) -> Optional[str]:
    if x_api_key:
        return x_api_key
    if authorization and authorization.lower().startswith("bearer "):
        return authorization[7:].strip()
    return None

@api_router.get("/health")
def health_check():
    return {
        "status": "online",
        "models": [EMBED_MODEL_ID, MIZAN_MODEL_ID],
        "endpoints": ["/v1/embeddings", "/v1/systemone", "/v1/decide"]
    }

@api_router.post("/v1/embeddings")
def create_embeddings(
    req: EmbedRequest,
    authorization: Optional[str] = Header(default=None),
    x_api_key: Optional[str] = Header(default=None)
):
    token = extract_token(authorization, x_api_key)
    verify_key(token)

    data = compute_embeddings(token, req.input, req.input_type or "document", req.normalize)
    return {
        "object": "list",
        "model": EMBED_MODEL_ID,
        "data": data
    }

@api_router.post("/v1/systemone")
@api_router.post("/v1/decide")
def create_decision(
    req: DecisionRequest,
    authorization: Optional[str] = Header(default=None),
    x_api_key: Optional[str] = Header(default=None)
):
    token = extract_token(authorization, x_api_key)
    verify_key(token)

    return compute_decision(token, req.state, req.questions)

# Prepend custom routes BEFORE Gradio's catch-all GET /{path:path} route
demo.app.router.routes[:0] = api_router.routes

demo.block_thread()