File size: 5,812 Bytes
e5bda4c
 
 
3afb6ed
 
 
e5bda4c
48dab3a
e5bda4c
3afb6ed
e5bda4c
 
 
 
 
 
3afb6ed
3683e7b
e5bda4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897c861
e5bda4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3afb6ed
 
e5bda4c
 
3afb6ed
e5bda4c
 
 
3afb6ed
 
e5bda4c
 
 
 
 
 
 
 
3afb6ed
e5bda4c
 
3afb6ed
 
772f2d2
bf6b31f
e5bda4c
 
3afb6ed
e5bda4c
 
 
 
3afb6ed
fc9e9ea
bf6b31f
e5bda4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc9e9ea
e5bda4c
 
 
 
 
 
 
 
3afb6ed
e5bda4c
3afb6ed
 
 
 
 
 
e5bda4c
 
3afb6ed
 
 
e5bda4c
 
 
 
 
 
772f2d2
fc9e9ea
e5bda4c
3afb6ed
e5bda4c
3afb6ed
fc9e9ea
e5bda4c
bf6b31f
e5bda4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import os
import time

# Инициализация приложения
app = FastAPI(
    title="IT Job Classifier API",
    description="API для определения IT-вакансий по тексту",
    version="1.0.0",
    docs_url="/docs",  # Явно включаем документацию
    redoc_url="/redoc",
    openapi_url="/openapi.json"
)

# Добавляем CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Модели Pydantic
class TextRequest(BaseModel):
    text: str

class PredictionResponse(BaseModel):
    prediction: str
    is_it_job: bool
    confidence: float
    class_id: int

# Глобальные переменные для модели
MODEL_NAME = "MrAlexGov/BERT-AI-Vacancy"
model = None
tokenizer = None

@app.on_event("startup")
async def load_model():
    """Загрузка модели при старте приложения"""
    global model, tokenizer
    
    print(f"🚀 Загружаем модель {MODEL_NAME}...")
    start_time = time.time()
    
    try:
        # Загружаем токенизатор и модель
        tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
        model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
        
        # Переводим модель в режим оценки
        model.eval()
        
        load_time = time.time() - start_time
        print(f"✅ Модель успешно загружена за {load_time:.2f} секунд")
        
    except Exception as e:
        print(f"❌ Ошибка загрузки модели: {str(e)}")
        raise e

@app.get("/")
async def root():
    """Корневой эндпоинт с документацией"""
    return {
        "service": "IT Job Classification API",
        "status": "running",
        "version": "1.0.0",
        "endpoints": {
            "GET /": "Эта документация",
            "GET /health": "Проверка здоровья сервиса",
            "POST /predict": "Классификация текста",
            "GET /docs": "Интерактивная документация (Swagger UI)",
            "GET /redoc": "Альтернативная документация"
        },
        "model": {
            "name": MODEL_NAME,
            "status": "loaded" if model else "loading"
        },
        "usage": {
            "curl_example": 'curl -X POST https://your-space.hf.space/predict -H "Content-Type: application/json" -d \'{"text": "Ищем Python разработчика"}\''
        }
    }

@app.get("/health")
async def health():
    """Проверка здоровья сервиса"""
    return {
        "status": "healthy",
        "timestamp": time.time(),
        "model_loaded": model is not None,
        "service": "IT Job Classifier"
    }

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: TextRequest):
    """Классифицирует текст на IT/не IT вакансию"""
    
    # Проверяем, загружена ли модель
    if model is None or tokenizer is None:
        raise HTTPException(
            status_code=503,
            detail="Модель еще не загружена. Пожалуйста, подождите."
        )
    
    # Проверяем входные данные
    if not request.text or not request.text.strip():
        raise HTTPException(
            status_code=400,
            detail="Текст не может быть пустым"
        )
    
    try:
        # Токенизация
        inputs = tokenizer(
            request.text,
            return_tensors="pt",
            truncation=True,
            max_length=512,
            padding=True
        )
        
        # Предсказание
        with torch.no_grad():
            outputs = model(**inputs)
            probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
            prediction_id = torch.argmax(probabilities, dim=-1).item()
            confidence = probabilities[0][prediction_id].item()
        
        # Интерпретация результата
        # Предполагаем, что label_1 - это IT-вакансия
        is_it_job = prediction_id == 1
        prediction_label = "IT-вакансия" if is_it_job else "Не IT-вакансия"
        
        return PredictionResponse(
            prediction=prediction_label,
            is_it_job=is_it_job,
            confidence=confidence,
            class_id=prediction_id
        )
        
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Ошибка обработки: {str(e)}"
        )

# Эндпоинт для тестирования
@app.get("/test")
async def test():
    """Тестовый эндпоинт"""
    return {
        "message": "Сервис работает",
        "timestamp": time.time(),
        "model": MODEL_NAME
    }

# Если запускаем напрямую (не через Docker на Spaces)
if __name__ == "__main__":
    import uvicorn
    
    # Получаем порт из переменных окружения (для Hugging Face Spaces)
    port = int(os.getenv("PORT", 7860))
    
    uvicorn.run(
        "app:app",
        host="0.0.0.0",
        port=port,
        reload=False  # Отключаем reload в production
    )