Yermek68 commited on
Commit
9bb5368
·
verified ·
1 Parent(s): c5b5346

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -95
app.py CHANGED
@@ -1,91 +1,100 @@
1
  import os
2
  import sys
3
- import json
4
  import time
5
- import asyncio
6
  import logging
7
  import traceback
8
  from contextlib import asynccontextmanager
 
 
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel, Field, validator
12
- from typing import Dict
13
- import uvicorn
14
 
15
- # ==================== Безопасные импорты ====================
 
 
 
 
 
 
 
16
  try:
17
- from transformers import pipeline
18
- from langdetect import detect
19
- except Exception as e:
20
- logging.error(f"[ImportError] transformers/langdetect not available: {e}")
21
- pipeline = None
22
- detect = lambda x: "en"
23
-
24
- # ==================== Логирование ====================
25
  logging.basicConfig(
26
  level=logging.INFO,
27
  format="%(asctime)s [%(levelname)s] %(message)s",
28
  handlers=[logging.StreamHandler(sys.stderr)],
29
  )
30
 
31
- # ==================== Конфигурация ====================
 
 
 
 
32
  HF_HOME = "/tmp/huggingface"
33
  os.environ["HF_HOME"] = HF_HOME
34
  os.makedirs(HF_HOME, exist_ok=True)
35
 
36
- _model_cache: Dict[str, any] = {}
37
-
38
- # ==================== Lifespan Context ====================
39
- @asynccontextmanager
40
- async def lifespan(app: FastAPI):
41
- start = time.time()
42
- preload_models = ["facebook/bart-large-cnn", "IlyaGusev/mbart_ru_sum_gazeta"]
43
- if pipeline:
44
- for model_name in preload_models:
45
- try:
46
- _model_cache[model_name] = pipeline("summarization", model=model_name, device=-1)
47
- logging.info(f"[Warmup] Preloaded model: {model_name}")
48
- except Exception as e:
49
- logging.error(f"[Warmup] Failed preload {model_name}: {e}")
50
- logging.info(f"[Startup] Models initialized in {time.time() - start:.2f}s")
51
- yield
52
 
 
53
 
54
- app = FastAPI(title="Eroha AI Summarizer PRO", version="v3.4", lifespan=lifespan)
55
- app.add_middleware(
56
- CORSMiddleware,
57
- allow_origins=["*"],
58
- allow_methods=["*"],
59
- allow_headers=["*"],
60
- )
61
 
62
- # ==================== Pydantic модели ====================
63
  class SummarizeRequest(BaseModel):
64
  text: str = Field(..., min_length=3, max_length=1_000_000)
65
 
66
  @validator("text")
67
- def not_empty(cls, v):
68
  if not v.strip():
69
  raise ValueError("Text cannot be empty or whitespace only")
70
  return v
71
 
72
 
73
  class CheckRequest(BaseModel):
74
- data: str = Field(..., min_length=1, max_length=500_000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
 
77
- # ==================== Утилиты ====================
78
- def safe_detect_lang(text: str) -> str:
 
79
  try:
80
- return detect(text)
81
  except Exception:
82
  return "en"
83
 
84
 
85
- def get_model(lang: str):
86
- if not pipeline:
87
  raise RuntimeError("Transformers pipeline unavailable")
88
 
 
 
 
 
89
  model_map = {
90
  "ru": "IlyaGusev/mbart_ru_sum_gazeta",
91
  "kk": "facebook/mbart-large-50-many-to-many-mmt",
@@ -96,81 +105,171 @@ def get_model(lang: str):
96
  }
97
  model_name = model_map.get(lang, "facebook/bart-large-cnn")
98
 
99
- if model_name in _model_cache:
100
- return _model_cache[model_name]
101
-
102
- logging.info(f"[ModelLoad] Loading model dynamically: {model_name}")
103
- model = pipeline("summarization", model=model_name, device=-1)
104
- _model_cache[model_name] = model
105
- return model
106
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- # ==================== Эндпоинты ====================
109
- @app.get("/")
110
- async def home():
111
  return {
112
- "status": "ok",
113
- "version": app.version,
114
- "cached_models": list(_model_cache.keys()),
115
- "endpoints": ["/ping", "/check", "/summarize", "/warmup"],
 
116
  }
117
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  @app.get("/ping")
120
  async def ping():
121
- return {"status": "healthy", "cached_models": list(_model_cache.keys())}
 
 
 
 
122
 
123
 
124
  @app.get("/warmup")
125
- async def warmup():
126
- return {"status": "warm", "models_ready": len(_model_cache) > 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
 
129
  @app.post("/check")
130
  async def check_text(req: CheckRequest):
131
  try:
132
- lang = safe_detect_lang(req.data)
133
  return {
134
  "status": "success",
135
- "preview": req.data[:150],
136
- "length": len(req.data),
137
- "language": lang,
138
  }
139
- except Exception as e:
140
- logging.error(f"/check error: {traceback.format_exc()}")
141
  raise HTTPException(status_code=500, detail=str(e))
142
 
143
 
144
  @app.post("/summarize")
145
  async def summarize(req: SummarizeRequest):
146
- try:
147
- lang = safe_detect_lang(req.text)
148
- summarizer = get_model(lang)
149
- input_text = req.text[:3000]
150
- result = summarizer(input_text, max_length=180, min_length=40, do_sample=False)
151
- summary = result[0]["summary_text"].replace("▁", " ").strip()
152
-
153
- json_ld = {
154
- "@context": "https://schema.org",
155
- "@type": "NewsArticle",
156
- "headline": summary[:80],
157
- "inLanguage": lang,
158
- "publisher": {"@type": "Organization", "name": "Eroha AI Publisher"},
159
- }
160
 
161
- return {
162
- "status": "success",
163
- "language": lang,
164
- "summary": summary,
165
- "summary_length": len(summary),
166
- "original_length": len(req.text),
167
- "seo_json_ld": json_ld,
168
- }
169
- except Exception as e:
170
- logging.error(f"/summarize error: {traceback.format_exc()}")
171
  raise HTTPException(status_code=500, detail=str(e))
172
 
173
 
174
- # ==================== Запуск ====================
175
  if __name__ == "__main__":
176
- uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=False)
 
 
 
 
 
1
  import os
2
  import sys
 
3
  import time
 
4
  import logging
5
  import traceback
6
  from contextlib import asynccontextmanager
7
+ from typing import List, Dict, Any
8
+
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel, Field, validator
 
 
12
 
13
+ # ==================== Safe optional imports ====================
14
+
15
+ try:
16
+ from transformers import pipeline # type: ignore
17
+ except Exception as e: # pragma: no cover
18
+ pipeline = None # type: ignore
19
+ logging.error(f"[ImportError] transformers not available: {e}", file=sys.stderr) # type: ignore
20
+
21
  try:
22
+ from langdetect import detect # type: ignore
23
+ except Exception as e: # pragma: no cover
24
+ detect = None # type: ignore
25
+ logging.error(f"[ImportError] langdetect not available: {e}", file=sys.stderr) # type: ignore
26
+
27
+
28
+ # ==================== Logging ====================
29
+
30
  logging.basicConfig(
31
  level=logging.INFO,
32
  format="%(asctime)s [%(levelname)s] %(message)s",
33
  handlers=[logging.StreamHandler(sys.stderr)],
34
  )
35
 
36
+ logger = logging.getLogger("eroha")
37
+
38
+
39
+ # ==================== Environment / HF cache ====================
40
+
41
  HF_HOME = "/tmp/huggingface"
42
  os.environ["HF_HOME"] = HF_HOME
43
  os.makedirs(HF_HOME, exist_ok=True)
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ # ==================== Pydantic models ====================
47
 
 
 
 
 
 
 
 
48
 
 
49
  class SummarizeRequest(BaseModel):
50
  text: str = Field(..., min_length=3, max_length=1_000_000)
51
 
52
  @validator("text")
53
+ def not_empty(cls, v: str) -> str:
54
  if not v.strip():
55
  raise ValueError("Text cannot be empty or whitespace only")
56
  return v
57
 
58
 
59
  class CheckRequest(BaseModel):
60
+ text: str = Field(..., min_length=1, max_length=500_000)
61
+
62
+ @validator("text")
63
+ def not_empty(cls, v: str) -> str:
64
+ if not v.strip():
65
+ raise ValueError("Text cannot be empty or whitespace only")
66
+ return v
67
+
68
+
69
+ class HealthResponse(BaseModel):
70
+ status: str
71
+ version: str
72
+ endpoints: List[str]
73
+ cached_models: List[str]
74
+
75
+
76
+ # ==================== Model management ====================
77
+
78
+ _model_cache: Dict[str, Any] = {}
79
 
80
 
81
+ def _safe_detect_lang(text: str) -> str:
82
+ if detect is None:
83
+ return "en"
84
  try:
85
+ return detect(text) # type: ignore[call-arg]
86
  except Exception:
87
  return "en"
88
 
89
 
90
+ def _get_model(lang: str):
91
+ if pipeline is None:
92
  raise RuntimeError("Transformers pipeline unavailable")
93
 
94
+ if lang in _model_cache:
95
+ logger.info("[ModelCache] using cached model for %s", lang)
96
+ return _model_cache[lang]
97
+
98
  model_map = {
99
  "ru": "IlyaGusev/mbart_ru_sum_gazeta",
100
  "kk": "facebook/mbart-large-50-many-to-many-mmt",
 
105
  }
106
  model_name = model_map.get(lang, "facebook/bart-large-cnn")
107
 
108
+ logger.info("[ModelLoad] loading model for %s: %s", lang, model_name)
109
+ try:
110
+ # CPU-режим (device=-1) — безопасно для HF Spaces
111
+ model = pipeline("summarization", model=model_name, device=-1) # type: ignore[arg-type]
112
+ _model_cache[lang] = model
113
+ logger.info("[ModelLoad] model for %s ready", lang)
114
+ return model
115
+ except Exception as e: # pragma: no cover
116
+ logger.error("[ModelLoad] failed to load %s: %s", lang, e)
117
+ raise
118
+
119
+
120
+ def _summarize(text: str, lang: str) -> Dict[str, Any]:
121
+ summarizer = _get_model(lang)
122
+
123
+ max_input_length = 3000 # защита от слишком длинных текстов
124
+ input_text = text[:max_input_length]
125
+
126
+ result = summarizer(
127
+ input_text,
128
+ max_length=180,
129
+ min_length=40,
130
+ do_sample=False,
131
+ )
132
+
133
+ summary = result[0]["summary_text"].replace("▁", " ").strip()
134
+
135
+ json_ld = {
136
+ "@context": "https://schema.org",
137
+ "@type": "NewsArticle",
138
+ "headline": summary[:80],
139
+ "inLanguage": lang,
140
+ "publisher": {"@type": "Organization", "name": "Eroha AI Publisher"},
141
+ }
142
 
 
 
 
143
  return {
144
+ "summary": summary,
145
+ "summary_length": len(summary),
146
+ "original_length": len(text),
147
+ "truncated": len(text) > max_input_length,
148
+ "seo_json_ld": json_ld,
149
  }
150
 
151
 
152
+ # ==================== Lifespan: warmup on startup ====================
153
+
154
+
155
+ @asynccontextmanager
156
+ async def lifespan(app: FastAPI): # type: ignore[override]
157
+ logger.info("[Startup] application starting, warming models...")
158
+ start = time.time()
159
+ if pipeline is not None:
160
+ for lang in ("en", "ru"):
161
+ try:
162
+ _get_model(lang)
163
+ except Exception as e:
164
+ logger.error("[Startup] warmup for %s failed: %s", lang, e)
165
+ else:
166
+ logger.warning("[Startup] transformers pipeline unavailable, skipping warmup")
167
+
168
+ logger.info("[Startup] warmup finished in %.2fs", time.time() - start)
169
+ yield
170
+ logger.info("[Shutdown] application stopping")
171
+
172
+
173
+ # ==================== FastAPI app ====================
174
+
175
+
176
+ app = FastAPI(
177
+ title="Eroha AI Summarizer PRO",
178
+ version="v3.4",
179
+ lifespan=lifespan,
180
+ )
181
+
182
+ app.add_middleware(
183
+ CORSMiddleware,
184
+ allow_origins=["*"],
185
+ allow_methods=["*"],
186
+ allow_headers=["*"],
187
+ )
188
+
189
+
190
+ # ==================== Routes ====================
191
+
192
+
193
+ @app.get("/", response_model=HealthResponse)
194
+ async def root() -> HealthResponse:
195
+ return HealthResponse(
196
+ status="ok",
197
+ version="v3.4",
198
+ endpoints=["/ping", "/check", "/summarize", "/warmup"],
199
+ cached_models=list(_model_cache.keys()),
200
+ )
201
+
202
+
203
  @app.get("/ping")
204
  async def ping():
205
+ return {
206
+ "status": "healthy",
207
+ "cached_models": list(_model_cache.keys()),
208
+ "time": time.time(),
209
+ }
210
 
211
 
212
  @app.get("/warmup")
213
+ async def manual_warmup():
214
+ if pipeline is None:
215
+ return {
216
+ "status": "skipped",
217
+ "reason": "transformers pipeline unavailable",
218
+ }
219
+
220
+ loaded = []
221
+ errors = {}
222
+ for lang in ("en", "ru"):
223
+ try:
224
+ _get_model(lang)
225
+ loaded.append(lang)
226
+ except Exception as e: # pragma: no cover
227
+ errors[lang] = str(e)
228
+
229
+ return {
230
+ "status": "ok" if not errors else "partial",
231
+ "loaded": loaded,
232
+ "errors": errors,
233
+ "cached_models": list(_model_cache.keys()),
234
+ }
235
 
236
 
237
  @app.post("/check")
238
  async def check_text(req: CheckRequest):
239
  try:
240
+ lang = _safe_detect_lang(req.text)
241
  return {
242
  "status": "success",
243
+ "preview": req.text[:150],
244
+ "length": len(req.text),
245
+ "detected_language": lang,
246
  }
247
+ except Exception as e: # pragma: no cover
248
+ logger.error("/check error: %s", traceback.format_exc())
249
  raise HTTPException(status_code=500, detail=str(e))
250
 
251
 
252
  @app.post("/summarize")
253
  async def summarize(req: SummarizeRequest):
254
+ if pipeline is None:
255
+ raise HTTPException(
256
+ status_code=503,
257
+ detail="transformers pipeline is not available in this environment",
258
+ )
 
 
 
 
 
 
 
 
 
259
 
260
+ try:
261
+ lang = _safe_detect_lang(req.text)
262
+ logger.info("[Summarize] language=%s, length=%d", lang, len(req.text))
263
+ data = _summarize(req.text, lang)
264
+ return {"status": "success", "language": lang, **data}
265
+ except Exception as e: # pragma: no cover
266
+ logger.error("/summarize error: %s", traceback.format_exc())
 
 
 
267
  raise HTTPException(status_code=500, detail=str(e))
268
 
269
 
 
270
  if __name__ == "__main__":
271
+ import uvicorn
272
+
273
+ port = int(os.getenv("PORT", "7860"))
274
+ uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)
275
+