|
17 | 17 | import json |
18 | 18 | import base64 |
19 | 19 | import logging |
20 | | -import re |
21 | 20 | from typing import Optional, List, Dict, Any |
22 | 21 |
|
23 | 22 | from dotenv import load_dotenv |
24 | 23 | from openai import OpenAI |
25 | 24 | from backend.config import get_config |
| 25 | +from backend.utils import parse_llm_json |
26 | 26 |
|
27 | 27 | load_dotenv() |
28 | 28 |
|
@@ -117,34 +117,11 @@ def _call_nemotron_enhance_vlm( |
117 | 117 | text = "".join(chunk.choices[0].delta.content for chunk in completion if chunk.choices[0].delta and chunk.choices[0].delta.content) |
118 | 118 | logger.info("[Step 1] Nemotron response received: %d chars", len(text)) |
119 | 119 |
|
120 | | - json_text = text.strip() |
121 | | - for marker in ("```json", "```"): |
122 | | - if marker in json_text: |
123 | | - try: |
124 | | - start = json_text.find(marker) + len(marker) |
125 | | - end = json_text.find("```", start) |
126 | | - if end > start: |
127 | | - json_text = json_text[start:end].strip() |
128 | | - break |
129 | | - except Exception as e: |
130 | | - logger.warning(f"[Step 1] Failed to extract JSON from {marker}: {e}") |
131 | | - |
132 | | - first_brace = json_text.find('{') |
133 | | - last_brace = json_text.rfind('}') |
134 | | - if first_brace != -1 and last_brace != -1 and last_brace > first_brace: |
135 | | - json_text = json_text[first_brace:last_brace+1] |
136 | | - |
137 | | - json_text = re.sub(r'//.*?(?=\n|$)', '', json_text) |
138 | | - json_text = re.sub(r'/\*.*?\*/', '', json_text, flags=re.DOTALL) |
139 | | - |
140 | | - try: |
141 | | - parsed = json.loads(json_text) |
142 | | - if isinstance(parsed, dict): |
143 | | - logger.info("[Step 1] Enhancement successful: enhanced_keys=%s", list(parsed.keys())) |
144 | | - return parsed |
145 | | - except Exception as e: |
146 | | - logger.warning(f"[Step 1] JSON parse error: {e}, using VLM output") |
147 | | - |
| 120 | + parsed = parse_llm_json(text, extract_braces=True, strip_comments=True) |
| 121 | + if parsed is not None: |
| 122 | + logger.info("[Step 1] Enhancement successful: enhanced_keys=%s", list(parsed.keys())) |
| 123 | + return parsed |
| 124 | + logger.warning("[Step 1] JSON parse failed, using VLM output") |
148 | 125 | return vlm_output |
149 | 126 |
|
150 | 127 |
|
@@ -244,34 +221,11 @@ def _call_nemotron_apply_branding( |
244 | 221 | text = "".join(chunk.choices[0].delta.content for chunk in completion if chunk.choices[0].delta and chunk.choices[0].delta.content) |
245 | 222 | logger.info("[Step 2] Nemotron response received: %d chars", len(text)) |
246 | 223 |
|
247 | | - json_text = text.strip() |
248 | | - for marker in ("```json", "```"): |
249 | | - if marker in json_text: |
250 | | - try: |
251 | | - start = json_text.find(marker) + len(marker) |
252 | | - end = json_text.find("```", start) |
253 | | - if end > start: |
254 | | - json_text = json_text[start:end].strip() |
255 | | - break |
256 | | - except Exception as e: |
257 | | - logger.warning(f"[Step 2] Failed to extract JSON from {marker}: {e}") |
258 | | - |
259 | | - first_brace = json_text.find('{') |
260 | | - last_brace = json_text.rfind('}') |
261 | | - if first_brace != -1 and last_brace != -1 and last_brace > first_brace: |
262 | | - json_text = json_text[first_brace:last_brace+1] |
263 | | - |
264 | | - json_text = re.sub(r'//.*?(?=\n|$)', '', json_text) |
265 | | - json_text = re.sub(r'/\*.*?\*/', '', json_text, flags=re.DOTALL) |
266 | | - |
267 | | - try: |
268 | | - parsed = json.loads(json_text) |
269 | | - if isinstance(parsed, dict): |
270 | | - logger.info("[Step 2] Brand alignment successful: keys=%s", list(parsed.keys())) |
271 | | - return parsed |
272 | | - except Exception as e: |
273 | | - logger.warning(f"[Step 2] JSON parse error: {e}, returning Step 1 content unchanged") |
274 | | - |
| 224 | + parsed = parse_llm_json(text, extract_braces=True, strip_comments=True) |
| 225 | + if parsed is not None: |
| 226 | + logger.info("[Step 2] Brand alignment successful: keys=%s", list(parsed.keys())) |
| 227 | + return parsed |
| 228 | + logger.warning("[Step 2] JSON parse failed, returning Step 1 content unchanged") |
275 | 229 | return enhanced_content |
276 | 230 |
|
277 | 231 |
|
@@ -374,23 +328,10 @@ def _call_vlm(image_bytes: bytes, content_type: str) -> Dict[str, Any]: |
374 | 328 | text = "".join(chunk.choices[0].delta.content for chunk in completion if chunk.choices[0].delta and chunk.choices[0].delta.content) |
375 | 329 | logger.info("VLM response received: %d chars", len(text)) |
376 | 330 |
|
377 | | - json_text = text.strip() |
378 | | - for marker in ("```json", "```"): |
379 | | - if marker in json_text: |
380 | | - try: |
381 | | - start = json_text.find(marker) + len(marker) |
382 | | - end = json_text.find("```", start) |
383 | | - if end > start: |
384 | | - json_text = json_text[start:end].strip() |
385 | | - break |
386 | | - except Exception: |
387 | | - pass |
388 | | - |
389 | | - try: |
390 | | - parsed = json.loads(json_text) |
391 | | - return parsed if isinstance(parsed, dict) else {"title": "", "description": json_text, "categories": ["uncategorized"], "tags": [], "colors": []} |
392 | | - except Exception: |
393 | | - return {"title": "", "description": json_text, "categories": ["uncategorized"], "tags": [], "colors": []} |
| 331 | + parsed = parse_llm_json(text) |
| 332 | + if parsed is not None: |
| 333 | + return parsed |
| 334 | + return {"title": "", "description": text.strip(), "categories": ["uncategorized"], "tags": [], "colors": []} |
394 | 335 |
|
395 | 336 | def run_vlm_analysis( |
396 | 337 | image_bytes: bytes, |
|
0 commit comments