Spaces:
Sleeping
Sleeping
fix: remove failing Groq Vision API, use only free OCR.space API for scanned document OCR
Browse files
backend/parsers/llm_summarizer.py
CHANGED
|
@@ -311,61 +311,10 @@ class LLMSummarizer:
|
|
| 311 |
return await self._summarize_with_text(minutes)
|
| 312 |
raise RuntimeError("No image data and no text fallback.")
|
| 313 |
|
| 314 |
-
#
|
| 315 |
-
#
|
| 316 |
-
|
| 317 |
-
print("[OCR]
|
| 318 |
-
vision_text = ""
|
| 319 |
-
try:
|
| 320 |
-
max_pages = min(len(image_bytes_list), 3)
|
| 321 |
-
content = [{"type": "text", "text": "Extract ALL readable text from these scanned city council meeting pages. Transcribe everything exactly as you see it. Preserve the layout."}]
|
| 322 |
-
for i in range(max_pages):
|
| 323 |
-
img_b64 = base64.b64encode(image_bytes_list[i]).decode("utf-8")
|
| 324 |
-
mime = "image/png" if image_bytes_list[i][:4] == b'\x89PNG' else "image/jpeg"
|
| 325 |
-
content.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{img_b64}"}})
|
| 326 |
-
vr = self.deepseek_client.chat.completions.create(
|
| 327 |
-
model="llama-3.2-11b-vision-preview",
|
| 328 |
-
messages=[{"role": "user", "content": content}],
|
| 329 |
-
temperature=0.0, max_tokens=4096,
|
| 330 |
-
)
|
| 331 |
-
vision_text = vr.choices[0].message.content or ""
|
| 332 |
-
print(f"[VISION] Extracted {len(vision_text)} chars from {max_pages} pages")
|
| 333 |
-
print(f"[VISION] First 200 chars: {vision_text[:200]}")
|
| 334 |
-
except Exception as e:
|
| 335 |
-
print(f"[VISION] Failed: {e}")
|
| 336 |
-
|
| 337 |
-
if vision_text and len(vision_text.strip()) > 50:
|
| 338 |
-
minutes.raw_text = vision_text.strip()
|
| 339 |
-
user_content = (
|
| 340 |
-
f"Please summarize the following city council meeting minutes "
|
| 341 |
-
f"for {minutes.city}, {minutes.state} on "
|
| 342 |
-
f"{minutes.meeting_date.strftime('%B %d, %Y')}.\n\n"
|
| 343 |
-
f"Meeting Type: {minutes.meeting_type}\n"
|
| 344 |
-
f"Title: {minutes.title}\n\n"
|
| 345 |
-
f"The following text was extracted from scanned document images by an AI vision model:\n\n"
|
| 346 |
-
f"{vision_text.strip()[:self.MAX_OCR_TEXT_CHARS]}\n\n"
|
| 347 |
-
f"Return ONLY valid JSON matching the specified structure. "
|
| 348 |
-
f"Do not include any text outside the JSON object."
|
| 349 |
-
)
|
| 350 |
-
response = self.deepseek_client.chat.completions.create(
|
| 351 |
-
model=self.text_model,
|
| 352 |
-
messages=[{"role": "system", "content": MINUTES_SYSTEM_PROMPT}, {"role": "user", "content": user_content}],
|
| 353 |
-
temperature=0.0, max_tokens=4096, response_format={"type": "json_object"},
|
| 354 |
-
)
|
| 355 |
-
result = json.loads(response.choices[0].message.content)
|
| 356 |
-
return SummaryResponse(
|
| 357 |
-
minutes_id=minutes.id, meeting_date=minutes.meeting_date, meeting_type=minutes.meeting_type,
|
| 358 |
-
big_picture=result.get("big_picture", ""),
|
| 359 |
-
summary=result.get("summary", "No summary available."),
|
| 360 |
-
key_decisions=[d if isinstance(d, dict) else {"description": d} for d in result.get("key_decisions", [])],
|
| 361 |
-
budget_items=[d if isinstance(d, dict) else {"description": d} for d in result.get("budget_items", [])],
|
| 362 |
-
public_comment_opportunities=[d if isinstance(d, dict) else {"description": d} for d in result.get("public_comment_opportunities", [])],
|
| 363 |
-
items=[d if isinstance(d, dict) else {"description": d} for d in result.get("items", [])],
|
| 364 |
-
what_you_can_do=[d if isinstance(d, dict) else {"description": d} for d in result.get("what_you_can_do", [])],
|
| 365 |
-
)
|
| 366 |
-
|
| 367 |
-
# Fallback: OCR.space (synchronous API call)
|
| 368 |
-
print("[OCR] Vision failed, trying free OCR.space API")
|
| 369 |
ocr_parts = []
|
| 370 |
for i, img_bytes in enumerate(image_bytes_list[:5]):
|
| 371 |
page_text = self._ocr_with_ocrspace(img_bytes)
|
|
|
|
| 311 |
return await self._summarize_with_text(minutes)
|
| 312 |
raise RuntimeError("No image data and no text fallback.")
|
| 313 |
|
| 314 |
+
# Use free OCR.space API to extract text from scanned images.
|
| 315 |
+
# Works on Vercel Lambda with no local binaries needed.
|
| 316 |
+
# Rate limit: 25k requests/month free tier, no API key required.
|
| 317 |
+
print("[OCR] Extracting text via free OCR.space API...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
ocr_parts = []
|
| 319 |
for i, img_bytes in enumerate(image_bytes_list[:5]):
|
| 320 |
page_text = self._ocr_with_ocrspace(img_bytes)
|