| import os |
| import sys |
| import re |
| import spaces |
| import torch |
| from unittest.mock import patch |
| from transformers import AutoModel, AutoTokenizer |
| from transformers.dynamic_module_utils import get_imports as _original_get_imports |
|
|
| |
| |
| def _strip_flash_attn(filename, *args, **kwargs): |
| return [i for i in _original_get_imports(filename, *args, **kwargs) if i != "flash_attn"] |
|
|
| |
| |
| sys.modules["flash_attn"] = None |
|
|
| |
| |
| |
| |
| |
| print("Initializing Nemotron Parse (Nvidia)... This may take a moment.") |
|
|
| parser_model_id = "nvidia/NVIDIA-Nemotron-Parse-v1.2" |
| hf_token = os.environ.get("HF_TOKEN") |
|
|
| def _patch_flash_attn_imports(filename, *args, **kwargs): |
| from transformers.dynamic_module_utils import get_imports as _orig |
| return [imp for imp in _orig(filename, *args, **kwargs) if imp != "flash_attn"] |
|
|
| try: |
| with patch( |
| "transformers.dynamic_module_utils.get_imports", |
| side_effect=_strip_flash_attn |
| ): |
| parser_tokenizer = AutoTokenizer.from_pretrained( |
| parser_model_id, |
| trust_remote_code=True, |
| token=hf_token |
| ) |
| parser_model = AutoModel.from_pretrained( |
| parser_model_id, |
| trust_remote_code=True, |
| torch_dtype=torch.bfloat16, |
| low_cpu_mem_usage=True, |
| token=hf_token |
| ) |
| print("Nemotron Parse loaded successfully.") |
| except Exception as e: |
| print(f"Warning: Nemotron Parse failed to load: {e}") |
| parser_tokenizer = None |
| parser_model = None |
|
|
|
|
| @spaces.GPU |
| def run_nemotron_parse(image_path: str) -> dict: |
| """ |
| Spatial Expert: Runs Nemotron Parse on a raw receipt image. |
| Best at: dense printed bills, hospital invoices, utility statements. |
| """ |
| if parser_model is None or parser_tokenizer is None: |
| return {"source": "nemotron_parse", "text": "", "confidence": 0.0} |
|
|
| try: |
| from PIL import Image as PILImage |
| from transformers import AutoProcessor |
|
|
| parser_model.to("cuda") |
| parser_model.eval() |
|
|
| image = PILImage.open(image_path).convert("RGB") |
|
|
| |
| |
| |
| prompt = "<predict_no_bbox><predict_no_classes><output_markdown><predict_text_in_pic>" |
|
|
| processor = AutoProcessor.from_pretrained( |
| parser_model_id, trust_remote_code=True, |
| token=os.environ.get("HF_TOKEN") |
| ) |
|
|
| inputs = processor(text=prompt, images=image, return_tensors="pt").to("cuda") |
|
|
| with torch.no_grad(): |
| out_ids = parser_model.generate(**inputs, max_new_tokens=600) |
|
|
| input_length = inputs["input_ids"].shape[-1] |
| raw = processor.decode(out_ids[0][input_length:], skip_special_tokens=True) |
|
|
| confidence = _score_confidence(raw) |
| return {"source": "nemotron_parse", "text": raw.strip(), "confidence": confidence} |
|
|
| except Exception as e: |
| print(f"Nemotron Parse inference error: {e}") |
| return {"source": "nemotron_parse", "text": "", "confidence": 0.0} |
|
|
|
|
| def _score_confidence(text: str) -> float: |
| """ |
| Scores how structured an extracted text looks. |
| Higher score = more price-like tokens = better quality output. |
| This is the 'arbiter' that picks the winner in the parallel engine. |
| """ |
| if not text or len(text.strip()) < 20: |
| return 0.0 |
|
|
| |
| price_hits = len(re.findall(r"[\$₹€£¥]\s*\d+[\.,]\d{2}|\d+[\.,]\d{2}", text)) |
|
|
| |
| structure_hits = len(re.findall(r"[-:|\t]", text)) |
|
|
| |
| score = min(1.0, (price_hits * 0.15) + (structure_hits * 0.02)) |
| return round(score, 3) |
|
|