pillpal / vision.py
vivek gangadharan
first
b9aa4e9
Raw
History Blame Contribute Delete
5.4 kB
from __future__ import annotations
import os
import random
MODEL_ID = os.environ.get("PILLPAL_MODEL", "openbmb/MiniCPM-V-4_5")
USE_MOCK = os.environ.get("PILLPAL_MOCK", "0") == "1"
GPU_DURATION = int(os.environ.get("PILLPAL_GPU_DURATION", "60"))
BACKEND = os.environ.get("PILLPAL_BACKEND", "api").lower()
LLAMA_URL = os.environ.get("PILLPAL_LLAMA_URL", "http://localhost:8080/v1/chat/completions")
API_URL = os.environ.get("PILLPAL_API_URL", "https://api.modelbest.cn/v1/chat/completions")
API_KEY = os.environ.get("PILLPAL_API_KEY", "XXXX")
API_MODEL = os.environ.get("PILLPAL_API_MODEL", "MiniCPM-V-4.6-Instruct")
ON_SPACE = bool(os.environ.get("SPACE_ID"))
try:
import spaces
def gpu(fn):
return spaces.GPU(duration=GPU_DURATION)(fn)
except Exception:
def gpu(fn):
return fn
PROMPT = (
"You are reading a medication bottle label. Return ONLY a JSON object with "
"exactly these keys: drug_name, dose, frequency_text, quantity, "
"refill_or_expiry_date. "
"- frequency_text: copy the dosing instruction verbatim (e.g. 'take 1 tablet twice daily'). "
"- quantity: the number of pills/units in the bottle, as a number. "
"- refill_or_expiry_date: any refill-by or expiry date, as YYYY-MM-DD if possible. "
"Use null for any field not visible. Do NOT invent or infer dosing. "
"Return the JSON and nothing else."
)
_model = None
_tokenizer = None
def _load_model():
global _model, _tokenizer
if _model is not None:
return
import torch
from transformers import AutoModel, AutoTokenizer
_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
_model = AutoModel.from_pretrained(
MODEL_ID,
trust_remote_code=True,
attn_implementation="sdpa",
torch_dtype=torch.bfloat16,
).eval()
if ON_SPACE or torch.cuda.is_available():
_model = _model.to("cuda")
@gpu
def _run_model(image) -> str:
_load_model()
msgs = [{"role": "user", "content": [image.convert("RGB"), PROMPT]}]
answer = _model.chat(
msgs=msgs,
tokenizer=_tokenizer,
sampling=False,
max_new_tokens=256,
)
return answer if isinstance(answer, str) else str(answer)
_MOCK_SAMPLES = [
'{"drug_name": "Metformin", "dose": "500 mg", "frequency_text": "take 1 tablet twice daily", "quantity": 60, "refill_or_expiry_date": "2026-07-01"}',
'{"drug_name": "Lisinopril", "dose": "10 mg", "frequency_text": "take 1 tablet once daily", "quantity": 30, "refill_or_expiry_date": null}',
'{"drug_name": "Atorvastatin", "dose": "20 mg", "frequency_text": "take 1 tablet at bedtime", "quantity": 90, "refill_or_expiry_date": "2026-06-09"}',
'{"drug_name": "Amoxicillin", "dose": "500 mg", "frequency_text": "take 1 capsule every 8 hours", "quantity": 21, "refill_or_expiry_date": null}',
'{"drug_name": "Ibuprofen", "dose": "200 mg", "frequency_text": "take as needed for pain", "quantity": 50, "refill_or_expiry_date": null}',
]
def _image_to_data_url(image) -> str:
import base64
import io
buf = io.BytesIO()
image.convert("RGB").save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
return f"data:image/png;base64,{b64}"
def _run_llama_server(image) -> str:
import json
import urllib.request
payload = {
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": PROMPT},
{"type": "image_url", "image_url": {"url": _image_to_data_url(image)}},
],
}
],
"temperature": 0,
"max_tokens": 256,
}
req = urllib.request.Request(
LLAMA_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=120) as resp:
body = json.loads(resp.read().decode("utf-8"))
return body["choices"][0]["message"]["content"]
def _run_modelbest_api(image) -> str:
import json
import urllib.request
payload = {
"model": API_MODEL,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": PROMPT},
{"type": "image_url", "image_url": {"url": _image_to_data_url(image)}},
],
}
],
"temperature": 0,
"max_tokens": 256,
}
req = urllib.request.Request(
API_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=120) as resp:
body = json.loads(resp.read().decode("utf-8"))
return body["choices"][0]["message"]["content"]
def extract_label(image) -> str:
if USE_MOCK or image is None:
return random.choice(_MOCK_SAMPLES)
if BACKEND == "api":
return _run_modelbest_api(image)
if BACKEND == "llama":
return _run_llama_server(image)
return _run_model(image)
if ON_SPACE and not USE_MOCK and BACKEND == "transformers":
try:
_load_model()
except Exception as exc:
print(f"[pillpal] deferred model load (will retry on first request): {exc}")