minhvtt commited on
Commit
55df4ef
·
verified ·
1 Parent(s): 9c745f0

Update app/services/classifier.py

Browse files
Files changed (1) hide show
  1. app/services/classifier.py +38 -3
app/services/classifier.py CHANGED
@@ -1,6 +1,7 @@
1
  from __future__ import annotations
2
 
3
  import json
 
4
  import os
5
  import re
6
  from pathlib import Path
@@ -15,8 +16,7 @@ NSFW_WEIGHTS_PATH = MODELS_DIR / "model.safetensors"
15
  NSFW_THRESHOLD = 0.75
16
 
17
  LLM_MODELS = [
18
- os.getenv("GAME_LLM_MODEL", "Qwen/Qwen2.5-7B-Instruct"),
19
- "microsoft/Phi-3.5-mini-instruct",
20
  ]
21
  LLM_MAX_CHARS = 3000
22
 
@@ -25,6 +25,8 @@ _nsfw_error: str | None = None
25
  _ocr_reader: Any | None = None
26
  _ocr_error: str | None = None
27
 
 
 
28
  GAME_KEYWORDS = {
29
  "valorant",
30
  "steam",
@@ -118,8 +120,22 @@ def classify_screenshot(file_path: str, filename: str, suspected_game: bool) ->
118
 
119
  def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bool) -> dict[str, Any]:
120
  ocr_text, urls = extract_ocr_text_and_urls(file_path)
 
 
 
 
 
 
 
121
  llm = _classify_game_with_llm(ocr_text, urls, suspected_game)
122
  if llm is not None:
 
 
 
 
 
 
 
123
  return {
124
  "verdict": llm["verdict"],
125
  "confidence": llm["confidence"],
@@ -134,6 +150,11 @@ def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bo
134
  keyword_hit = any(word in text_lower for word in GAME_KEYWORDS)
135
  domain_hit = any(_is_game_like_domain(url) for url in urls)
136
  if domain_hit or (suspected_game and keyword_hit):
 
 
 
 
 
137
  return {
138
  "verdict": "game",
139
  "confidence": 0.78,
@@ -144,6 +165,9 @@ def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bo
144
  }
145
 
146
  if suspected_game:
 
 
 
147
  return {
148
  "verdict": "uncertain",
149
  "confidence": 0.55,
@@ -153,6 +177,7 @@ def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bo
153
  "source": "heuristic",
154
  }
155
 
 
156
  return {
157
  "verdict": "not_game",
158
  "confidence": 0.2,
@@ -166,12 +191,14 @@ def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bo
166
  def extract_ocr_text_and_urls(file_path: str) -> tuple[str, list[str]]:
167
  reader = _load_ocr_reader()
168
  if reader is None:
 
169
  return "", []
170
 
171
  try:
172
  results = reader.readtext(file_path, detail=0, paragraph=True)
173
  ocr_text = "\n".join(str(x) for x in results).strip()
174
  except Exception:
 
175
  return "", []
176
 
177
  raw_urls = URL_REGEX.findall(ocr_text)
@@ -193,6 +220,8 @@ def extract_ocr_text_and_urls(file_path: str) -> tuple[str, list[str]]:
193
  seen.add(item)
194
  urls.append(item)
195
 
 
 
196
  return ocr_text[:LLM_MAX_CHARS], urls
197
 
198
 
@@ -209,9 +238,11 @@ def _load_ocr_reader() -> Any | None:
209
  import easyocr
210
 
211
  _ocr_reader = easyocr.Reader(["en"], gpu=False)
 
212
  return _ocr_reader
213
  except Exception as exc:
214
  _ocr_error = str(exc)
 
215
  return None
216
 
217
 
@@ -238,6 +269,7 @@ def _classify_game_with_llm(ocr_text: str, urls: list[str], suspected_game: bool
238
 
239
  for model_name in LLM_MODELS:
240
  try:
 
241
  client = InferenceClient(model=model_name, token=token or None)
242
  response = client.chat_completion(
243
  messages=prompt_messages,
@@ -250,12 +282,15 @@ def _classify_game_with_llm(ocr_text: str, urls: list[str], suspected_game: bool
250
  content = response.choices[0].message.content or ""
251
  parsed = _parse_llm_json(content)
252
  if parsed is None:
 
253
  continue
254
  parsed["source"] = f"llm:{model_name}"
255
  return parsed
256
- except Exception:
 
257
  continue
258
 
 
259
  return None
260
 
261
 
 
1
  from __future__ import annotations
2
 
3
  import json
4
+ import logging
5
  import os
6
  import re
7
  from pathlib import Path
 
16
  NSFW_THRESHOLD = 0.75
17
 
18
  LLM_MODELS = [
19
+ os.getenv("GAME_LLM_MODEL", "Qwen/Qwen2.5-1.5B-Instruct"),
 
20
  ]
21
  LLM_MAX_CHARS = 3000
22
 
 
25
  _ocr_reader: Any | None = None
26
  _ocr_error: str | None = None
27
 
28
+ logger = logging.getLogger(__name__)
29
+
30
  GAME_KEYWORDS = {
31
  "valorant",
32
  "steam",
 
120
 
121
  def classify_game_with_ocr_llm(file_path: str, filename: str, suspected_game: bool) -> dict[str, Any]:
122
  ocr_text, urls = extract_ocr_text_and_urls(file_path)
123
+ logger.info(
124
+ "game-detect start file=%s suspected_game=%s ocr_len=%d urls=%d",
125
+ filename,
126
+ suspected_game,
127
+ len(ocr_text),
128
+ len(urls),
129
+ )
130
  llm = _classify_game_with_llm(ocr_text, urls, suspected_game)
131
  if llm is not None:
132
+ logger.info(
133
+ "game-detect llm verdict=%s confidence=%.2f reason=%s source=%s",
134
+ llm["verdict"],
135
+ float(llm["confidence"]),
136
+ llm["reason"],
137
+ llm.get("source", "llm"),
138
+ )
139
  return {
140
  "verdict": llm["verdict"],
141
  "confidence": llm["confidence"],
 
150
  keyword_hit = any(word in text_lower for word in GAME_KEYWORDS)
151
  domain_hit = any(_is_game_like_domain(url) for url in urls)
152
  if domain_hit or (suspected_game and keyword_hit):
153
+ logger.info(
154
+ "game-detect fallback verdict=game reason=ocr-keyword-heuristic domain_hit=%s keyword_hit=%s",
155
+ domain_hit,
156
+ keyword_hit,
157
+ )
158
  return {
159
  "verdict": "game",
160
  "confidence": 0.78,
 
165
  }
166
 
167
  if suspected_game:
168
+ logger.info(
169
+ "game-detect fallback verdict=uncertain reason=signal-without-clear-ocr"
170
+ )
171
  return {
172
  "verdict": "uncertain",
173
  "confidence": 0.55,
 
177
  "source": "heuristic",
178
  }
179
 
180
+ logger.info("game-detect fallback verdict=not_game reason=no-game-evidence")
181
  return {
182
  "verdict": "not_game",
183
  "confidence": 0.2,
 
191
  def extract_ocr_text_and_urls(file_path: str) -> tuple[str, list[str]]:
192
  reader = _load_ocr_reader()
193
  if reader is None:
194
+ logger.warning("ocr unavailable: reader is None")
195
  return "", []
196
 
197
  try:
198
  results = reader.readtext(file_path, detail=0, paragraph=True)
199
  ocr_text = "\n".join(str(x) for x in results).strip()
200
  except Exception:
201
+ logger.exception("ocr read failed for file=%s", file_path)
202
  return "", []
203
 
204
  raw_urls = URL_REGEX.findall(ocr_text)
 
220
  seen.add(item)
221
  urls.append(item)
222
 
223
+ logger.info("ocr extracted text_len=%d urls=%d", len(ocr_text), len(urls))
224
+
225
  return ocr_text[:LLM_MAX_CHARS], urls
226
 
227
 
 
238
  import easyocr
239
 
240
  _ocr_reader = easyocr.Reader(["en"], gpu=False)
241
+ logger.info("ocr reader initialized")
242
  return _ocr_reader
243
  except Exception as exc:
244
  _ocr_error = str(exc)
245
+ logger.warning("ocr reader init failed: %s", _ocr_error)
246
  return None
247
 
248
 
 
269
 
270
  for model_name in LLM_MODELS:
271
  try:
272
+ logger.info("llm try model=%s", model_name)
273
  client = InferenceClient(model=model_name, token=token or None)
274
  response = client.chat_completion(
275
  messages=prompt_messages,
 
282
  content = response.choices[0].message.content or ""
283
  parsed = _parse_llm_json(content)
284
  if parsed is None:
285
+ logger.warning("llm invalid json model=%s content=%s", model_name, content[:300])
286
  continue
287
  parsed["source"] = f"llm:{model_name}"
288
  return parsed
289
+ except Exception as exc:
290
+ logger.warning("llm error model=%s err=%s", model_name, repr(exc))
291
  continue
292
 
293
+ logger.warning("llm all models failed; fallback to heuristic")
294
  return None
295
 
296