Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| import torch | |
| import numpy as np | |
| import os | |
| app = FastAPI(title="PGOS Emotion Agent API", version="1.0.0") | |
| # ββ μ€μ βββββββββββββββββββββββββββββββββββββββββββ | |
| HF_USERNAME = "nuguri01" | |
| HF_TOKEN = os.environ.get("HF_TOKEN", "") # Spaces SecretμΌλ‘ μ£Όμ | |
| MAJOR_CLASSES = ["λΆλ Έκ³", "μ¬νκ³", "λΆμκ³", "μμ²κ³", "λΉν©κ³", "κΈ°μ¨κ³"] | |
| MINOR_STRUCTURE = { | |
| "λΆλ Έκ³": {"labels": ["λΆλ Έ", "μ’μ ", "μ§μ¦"], "repo": "pgos-emotion-stage2-anger"}, | |
| "μ¬νκ³": {"labels": ["μ¬ν", "μ°μΈ", "νν"], "repo": "pgos-emotion-stage2-sadness"}, | |
| "λΆμκ³": {"labels": ["λΆμ", "κ±±μ "], "repo": "pgos-emotion-stage2-anxiety"}, | |
| "μμ²κ³": {"labels": ["μμ²", "μ΅μΈ", "μ§ν¬"], "repo": "pgos-emotion-stage2-hurt"}, | |
| "λΉν©κ³": {"labels": ["μμΉμ¬", "μ£μ± κ°", "λΉν©"], "repo": "pgos-emotion-stage2-embarrass"}, | |
| "κΈ°μ¨κ³": {"labels": ["κΈ°μ¨", "μλ"], "repo": "pgos-emotion-stage2-joy"}, | |
| } | |
| PGOS_CLASSES = [ | |
| "λΆλ Έ", "μ’μ ", "μ§μ¦", "μ¬ν", "μ°μΈ", "νν", | |
| "λΆμ", "κ±±μ ", "μμ²", "μ΅μΈ", "μ§ν¬", | |
| "μμΉμ¬", "μ£μ± κ°", "λΉν©", "κΈ°μ¨", "μλ" | |
| ] | |
| label2id = {l: i for i, l in enumerate(PGOS_CLASSES)} | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| # ββ λͺ¨λΈ λ‘λ (μλ² μμ μ ν λ²λ§) ββββββββββββββ | |
| print("π₯ λͺ¨λΈ λ‘λ© μ€...") | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| f"{HF_USERNAME}/pgos-emotion-stage1-major", | |
| token=HF_TOKEN | |
| ) | |
| model_major = AutoModelForSequenceClassification.from_pretrained( | |
| f"{HF_USERNAME}/pgos-emotion-stage1-major", | |
| token=HF_TOKEN | |
| ).to(device) | |
| model_major.eval() | |
| minor_models = {} | |
| for major_label, config in MINOR_STRUCTURE.items(): | |
| model_sub = AutoModelForSequenceClassification.from_pretrained( | |
| f"{HF_USERNAME}/{config['repo']}", | |
| token=HF_TOKEN, | |
| num_labels=len(config["labels"]) | |
| ).to(device) | |
| model_sub.eval() | |
| minor_models[major_label] = model_sub | |
| print("β μ 체 λͺ¨λΈ λ‘λ μλ£") | |
| # ββ μμ²/μλ΅ μ€ν€λ§ βββββββββββββββββββββββββββββββ | |
| class EmotionRequest(BaseModel): | |
| text: str | |
| top_k: int = 3 # λ°νν κ°μ μ (κΈ°λ³Έ 3κ°) | |
| class EmotionResult(BaseModel): | |
| emotion: str | |
| score: float | |
| rank: int | |
| class EmotionResponse(BaseModel): | |
| text: str | |
| primary_emotion: str | |
| primary_score: float | |
| top_emotions: list[EmotionResult] | |
| pgos_message: str # λ΄λ΄μμκ² λλ €μ€ λ©νΈ | |
| # ββ κ°μ λΆλ₯ ν¨μ βββββββββββββββββββββββββββββββββ | |
| def predict_emotions(text: str, top_k: int = 3): | |
| # 1λ¨κ³: λλΆλ₯ | |
| enc = tokenizer(text, max_length=128, padding=True, | |
| truncation=True, return_tensors="pt").to(device) | |
| with torch.no_grad(): | |
| major_probs = torch.softmax( | |
| model_major(**enc).logits, dim=-1 | |
| ).cpu().numpy()[0] | |
| # 2λ¨κ³: μλΆλ₯ + νλ₯ κ²°ν© | |
| final_probs = np.zeros(len(PGOS_CLASSES)) | |
| for major_idx, major_label in enumerate(MAJOR_CLASSES): | |
| config = MINOR_STRUCTURE[major_label] | |
| minor_labels = config["labels"] | |
| model_sub = minor_models[major_label] | |
| with torch.no_grad(): | |
| sub_probs = torch.softmax( | |
| model_sub(**enc).logits, dim=-1 | |
| ).cpu().numpy()[0] | |
| for minor_idx, minor_label in enumerate(minor_labels): | |
| final_probs[label2id[minor_label]] = ( | |
| major_probs[major_idx] * sub_probs[minor_idx] | |
| ) | |
| # Top-K μΆμΆ | |
| top_k_ids = np.argsort(final_probs)[-top_k:][::-1] | |
| top_emotions = [ | |
| EmotionResult( | |
| emotion=PGOS_CLASSES[idx], | |
| score=round(float(final_probs[idx]), 4), | |
| rank=rank + 1 | |
| ) | |
| for rank, idx in enumerate(top_k_ids) | |
| ] | |
| # PGOS λ©νΈ μμ± | |
| primary = top_emotions[0].emotion | |
| subs = [e.emotion for e in top_emotions[1:] if e.score > 0.05] | |
| sub_str = f", {'κ³Ό '.join(subs)} κ°μ λ ν¨κ» λκ»΄μ§λ κ² κ°μμ" if subs else "" | |
| message = f"'{primary}' κ°μ μ΄ μ£Όλ‘ λκ»΄μ§λ©°{sub_str}. μ§κΈ μ΄ κ°μ μ μ²μ²ν λ€μ¬λ€λ³ΌκΉμ?" | |
| return top_emotions, primary, float(final_probs[top_k_ids[0]]), message | |
| # ββ API μλν¬μΈνΈ βββββββββββββββββββββββββββββββββ | |
| def root(): | |
| return { | |
| "service": "PGOS Emotion Agent API", | |
| "version": "1.0.0", | |
| "description": "κ³μΈ΅μ 2λ¨κ³ κ°μ λΆλ₯κΈ° (16ν΄λμ€, Top-3 Hit Rate 0.870)", | |
| "endpoints": { | |
| "POST /predict": "κ°μ λΆμ", | |
| "GET /health": "μλ² μν νμΈ" | |
| } | |
| } | |
| def health(): | |
| return {"status": "ok", "device": device, "models_loaded": len(minor_models) + 1} | |
| def predict(req: EmotionRequest): | |
| if not req.text.strip(): | |
| raise HTTPException(status_code=400, detail="ν μ€νΈλ₯Ό μ λ ₯ν΄μ£ΌμΈμ.") | |
| if len(req.text) > 500: | |
| raise HTTPException(status_code=400, detail="ν μ€νΈλ 500μ μ΄νλ‘ μ λ ₯ν΄μ£ΌμΈμ.") | |
| top_k = max(1, min(req.top_k, 5)) # 1~5 μ¬μ΄λ‘ μ ν | |
| top_emotions, primary, primary_score, message = predict_emotions(req.text, top_k) | |
| return EmotionResponse( | |
| text=req.text, | |
| primary_emotion=primary, | |
| primary_score=primary_score, | |
| top_emotions=top_emotions, | |
| pgos_message=message | |
| ) | |