Mohibullah commited on
Commit
f4c5d7f
·
1 Parent(s): 481d9c6

Fallback Nemotron validation to NVIDIA NIM

Browse files
Files changed (2) hide show
  1. gradio_pharmacopilot_demo.py +84 -17
  2. requirements.txt +1 -2
gradio_pharmacopilot_demo.py CHANGED
@@ -53,6 +53,9 @@ MODEL_ID = os.getenv("PHARMACOPILOT_MODEL_ID", "openbmb/MiniCPM-V-4_5")
53
  LIVE_GPU_OCR = os.getenv("PHARMACOPILOT_LIVE_GPU_OCR", "1").lower() not in {"0", "false", "no"}
54
  LIVE_NEMOTRON = os.getenv("PHARMACOPILOT_LIVE_NEMOTRON", "1").lower() not in {"0", "false", "no"}
55
  NEMOTRON_MODEL_ID = os.getenv("NEMOTRON_MODEL_ID", "nvidia/NVIDIA-Nemotron-Nano-9B-v2")
 
 
 
56
  DEMO_OCR_TEXT = "Neuoxen"
57
  DEMO_PROMPT = "Read the handwritten medicine name in the image. Return only the text."
58
  ACCEPTANCE_THRESHOLD = int(os.getenv("PHARMACOPILOT_ACCEPTANCE_THRESHOLD", "75"))
@@ -276,20 +279,13 @@ def extract_json_object(text: str) -> dict[str, Any]:
276
  return json.loads(cleaned)
277
 
278
 
279
- def validate_with_nemotron(
280
  ocr_text: str,
281
  medicine: dict[str, Any],
282
  display_name: str,
283
  confidence: int,
284
  retrieval_candidates: list[dict[str, Any]],
285
- ) -> dict[str, Any]:
286
- global NEMOTRON_MODEL, NEMOTRON_TOKENIZER
287
-
288
- if not LIVE_NEMOTRON:
289
- return fallback_prescription_plan(
290
- ocr_text, medicine, display_name, confidence, "Local Nemotron validation is disabled"
291
- )
292
-
293
  validation_payload = {
294
  "ocr_text": ocr_text,
295
  "retrieved_display_name": display_name,
@@ -306,7 +302,7 @@ def validate_with_nemotron(
306
  for item in retrieval_candidates[:3]
307
  ],
308
  }
309
- prompt = f"""
310
  You are a pharmacy prescription validation assistant.
311
 
312
  Input JSON:
@@ -316,6 +312,7 @@ Task:
316
  1. Decide whether the retrieved medicine is safe to accept.
317
  2. Translate the prescription into a clean pharmacy instruction row.
318
  3. Do not invent dose/timing/duration if it is not visible or inferable.
 
319
 
320
  Return ONLY valid JSON with these keys:
321
  status: one of validated, needs_review
@@ -330,6 +327,78 @@ instructions
330
  validation_note
331
  ocr_text
332
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  try:
334
  import torch
335
  from transformers import AutoModelForCausalLM, AutoTokenizer
@@ -385,13 +454,11 @@ ocr_text
385
  **plan,
386
  }
387
  except Exception as exc:
388
- return fallback_prescription_plan(
389
- ocr_text,
390
- medicine,
391
- display_name,
392
- confidence,
393
- f"Local Nemotron failed: {exc}",
394
- )
395
 
396
 
397
  def load_kpi_metrics(searches: int = 0) -> str:
 
53
  LIVE_GPU_OCR = os.getenv("PHARMACOPILOT_LIVE_GPU_OCR", "1").lower() not in {"0", "false", "no"}
54
  LIVE_NEMOTRON = os.getenv("PHARMACOPILOT_LIVE_NEMOTRON", "1").lower() not in {"0", "false", "no"}
55
  NEMOTRON_MODEL_ID = os.getenv("NEMOTRON_MODEL_ID", "nvidia/NVIDIA-Nemotron-Nano-9B-v2")
56
+ NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "")
57
+ NVIDIA_BASE_URL = os.getenv("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1")
58
+ NVIDIA_NIM_MODEL = os.getenv("NVIDIA_NIM_MODEL", "nvidia/nvidia-nemotron-nano-9b-v2")
59
  DEMO_OCR_TEXT = "Neuoxen"
60
  DEMO_PROMPT = "Read the handwritten medicine name in the image. Return only the text."
61
  ACCEPTANCE_THRESHOLD = int(os.getenv("PHARMACOPILOT_ACCEPTANCE_THRESHOLD", "75"))
 
279
  return json.loads(cleaned)
280
 
281
 
282
+ def build_validation_prompt(
283
  ocr_text: str,
284
  medicine: dict[str, Any],
285
  display_name: str,
286
  confidence: int,
287
  retrieval_candidates: list[dict[str, Any]],
288
+ ) -> str:
 
 
 
 
 
 
 
289
  validation_payload = {
290
  "ocr_text": ocr_text,
291
  "retrieved_display_name": display_name,
 
302
  for item in retrieval_candidates[:3]
303
  ],
304
  }
305
+ return f"""
306
  You are a pharmacy prescription validation assistant.
307
 
308
  Input JSON:
 
312
  1. Decide whether the retrieved medicine is safe to accept.
313
  2. Translate the prescription into a clean pharmacy instruction row.
314
  3. Do not invent dose/timing/duration if it is not visible or inferable.
315
+ 4. If OCR and retrieved medicine clearly disagree, return needs_review.
316
 
317
  Return ONLY valid JSON with these keys:
318
  status: one of validated, needs_review
 
327
  validation_note
328
  ocr_text
329
  """
330
+
331
+
332
+ def validate_with_nvidia_nim(
333
+ prompt: str,
334
+ ocr_text: str,
335
+ medicine: dict[str, Any],
336
+ display_name: str,
337
+ confidence: int,
338
+ ) -> dict[str, Any]:
339
+ if not NVIDIA_API_KEY:
340
+ return fallback_prescription_plan(
341
+ ocr_text,
342
+ medicine,
343
+ display_name,
344
+ confidence,
345
+ "NVIDIA_API_KEY is not configured in the Space secrets",
346
+ )
347
+ try:
348
+ from openai import OpenAI
349
+
350
+ client = OpenAI(base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY)
351
+ response = client.chat.completions.create(
352
+ model=NVIDIA_NIM_MODEL,
353
+ messages=[{"role": "user", "content": prompt}],
354
+ temperature=0,
355
+ top_p=1,
356
+ max_tokens=320,
357
+ )
358
+ content = response.choices[0].message.content or ""
359
+ plan = extract_json_object(content)
360
+ if plan.get("status") not in {"validated", "needs_review"}:
361
+ plan["status"] = "needs_review"
362
+ if confidence < ACCEPTANCE_THRESHOLD:
363
+ plan["status"] = "needs_review"
364
+ plan["validation_note"] = (
365
+ f"Retrieval confidence {confidence}% is below the {ACCEPTANCE_THRESHOLD}% acceptance threshold"
366
+ )
367
+ return {
368
+ **fallback_prescription_plan(
369
+ ocr_text,
370
+ medicine,
371
+ display_name,
372
+ confidence,
373
+ f"Validated by NVIDIA NIM {NVIDIA_NIM_MODEL}",
374
+ ),
375
+ **plan,
376
+ }
377
+ except Exception as exc:
378
+ return fallback_prescription_plan(
379
+ ocr_text,
380
+ medicine,
381
+ display_name,
382
+ confidence,
383
+ f"NVIDIA NIM validation failed: {exc}",
384
+ )
385
+
386
+
387
+ def validate_with_nemotron(
388
+ ocr_text: str,
389
+ medicine: dict[str, Any],
390
+ display_name: str,
391
+ confidence: int,
392
+ retrieval_candidates: list[dict[str, Any]],
393
+ ) -> dict[str, Any]:
394
+ global NEMOTRON_MODEL, NEMOTRON_TOKENIZER
395
+
396
+ if not LIVE_NEMOTRON:
397
+ return fallback_prescription_plan(
398
+ ocr_text, medicine, display_name, confidence, "Local Nemotron validation is disabled"
399
+ )
400
+
401
+ prompt = build_validation_prompt(ocr_text, medicine, display_name, confidence, retrieval_candidates)
402
  try:
403
  import torch
404
  from transformers import AutoModelForCausalLM, AutoTokenizer
 
454
  **plan,
455
  }
456
  except Exception as exc:
457
+ nim_plan = validate_with_nvidia_nim(prompt, ocr_text, medicine, display_name, confidence)
458
+ if NVIDIA_API_KEY:
459
+ return nim_plan
460
+ nim_plan["validation_note"] = f"Local Nemotron failed: {exc}. NVIDIA_API_KEY is not configured."
461
+ return nim_plan
 
 
462
 
463
 
464
  def load_kpi_metrics(searches: int = 0) -> str:
requirements.txt CHANGED
@@ -10,5 +10,4 @@ sentencepiece
10
  protobuf
11
  einops
12
  timm
13
- mamba-ssm>=2.2.5
14
- causal-conv1d>=1.5.0
 
10
  protobuf
11
  einops
12
  timm
13
+ openai>=1.88.0