Spaces:
Running
Running
| import os | |
| import math | |
| import time | |
| import logging | |
| from collections import deque | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from huggingface_hub import InferenceClient | |
| # ββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # ββ App setup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI(title="NB4170 LLM Proxy") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Binder + local notebooks need this | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ββ HF client (token lives only here, in a Space Secret) ββββββββββββββββββββββ | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| if not HF_TOKEN: | |
| raise RuntimeError("HF_TOKEN environment variable is not set") | |
| client = InferenceClient(token=HF_TOKEN) | |
| # ββ Allowed models βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ALLOWED_MODELS = { | |
| "openai-community/gpt2": "openai-community/gpt2", | |
| "llama-1b": "meta-llama/Llama-3.2-1B-Instruct", | |
| "meta-llama/Llama-3.2-1B-Instruct": "meta-llama/Llama-3.2-1B-Instruct", | |
| "llama-8b": "meta-llama/Llama-3.1-8B-Instruct", | |
| "meta-llama/Llama-3.1-8B-Instruct": "meta-llama/Llama-3.1-8B-Instruct", | |
| } | |
| # ββ Simple rate limiter: max 60 requests per minute across all students ββββββββ | |
| request_times = deque() | |
| RATE_LIMIT = 60 | |
| RATE_WINDOW = 60 # seconds | |
| def check_rate_limit(): | |
| now = time.time() | |
| while request_times and request_times[0] < now - RATE_WINDOW: | |
| request_times.popleft() | |
| if len(request_times) >= RATE_LIMIT: | |
| raise HTTPException( | |
| status_code=429, | |
| detail=f"Rate limit reached ({RATE_LIMIT} requests/min). Wait a moment and try again." | |
| ) | |
| request_times.append(now) | |
| # ββ Request schema βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class GenerateRequest(BaseModel): | |
| system_prompt: str = "" # added system prompt | |
| prompt: str | |
| model: str = "gpt2" | |
| max_tokens: int = 50 | |
| # ββ Health check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def health(): | |
| return {"status": "ok", "message": "NB4170 LLM Proxy is running"} | |
| # ββ Main endpoint ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def generate(req: GenerateRequest): | |
| # Rate limit | |
| check_rate_limit() | |
| # Validate model | |
| model_id = ALLOWED_MODELS.get(req.model) | |
| if not model_id: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Model '{req.model}' not allowed. Choose from: {list(ALLOWED_MODELS.keys())}" | |
| ) | |
| # Clamp max_tokens to avoid runaway costs | |
| max_tokens = min(req.max_tokens, 1000) | |
| logger.info(f"Request: model={model_id}, max_tokens={max_tokens}, prompt_len={len(req.prompt)}") | |
| try: | |
| messages = [] | |
| # add system prompt to messages if provided | |
| if req.system_prompt: | |
| messages.append({"role": "system", "content": req.system_prompt}) | |
| messages.append({"role": "user", "content": req.prompt}) | |
| response = client.chat.completions.create( | |
| model=model_id, | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| logprobs=True, | |
| top_logprobs=1, | |
| ) | |
| except Exception as e: | |
| logger.error(f"HF API error: {e}") | |
| raise HTTPException(status_code=502, detail=f"HF Inference API error: {str(e)}") | |
| # ββ Post-process response ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| answer = response.choices[0].message.content | |
| # Extract logprobs β same logic as your original notebook | |
| logprobs_content = response.choices[0].logprobs.content | |
| logprobs_dict = {x.token: x.logprob for x in logprobs_content} | |
| token_probs_dict = {token: math.exp(lp) for token, lp in logprobs_dict.items()} | |
| except Exception as e: | |
| logger.error(traceback.format_exc()) | |
| raise HTTPException(status_code=500, detail=f"Post-processing error: {e}") | |
| return { | |
| "answer": answer, | |
| "logprobs": logprobs_dict, | |
| "token_probs": token_probs_dict, | |
| "logprobs_content": logprobs_content, | |
| } | |