cs553-test / remote_model.py
twindinx
fix minors
4221b2e
Raw History Blame Contribute Delete
2.77 kB
import base64
import re
import io
from PIL import Image
from huggingface_hub import InferenceClient
from config import EVALUATION_HEADING, REMOTE_MODEL, REMOTE_PROVIDER, remote_prompt, REMOTE_MAX_TOKENS
from critique import Critique
SCORE_PATTERN = re.compile(r"##\s*Score:\s*(\d+)\s*/\s*100")
HEADING_PATTERN = re.compile(rf"###\s*{re.escape(EVALUATION_HEADING)}\s*", re.IGNORECASE)
MAX_IMAGE_SIDE = 768
def image_as_data_url(image_path: str) -> str:
# downscale before sending to model to reduce tokens
with Image.open(image_path) as source:
image = source.convert("RGB")
if max(image.size) > MAX_IMAGE_SIDE:
image.thumbnail((MAX_IMAGE_SIDE, MAX_IMAGE_SIDE))
buffer = io.BytesIO()
image.save(buffer, format="JPEG", quality=88)
encoded = base64.b64encode(buffer.getvalue()).decode("utf-8")
return f"data:image/jpeg;base64,{encoded}"
def parse_response(text: str) -> tuple[int | None, str]:
# split the model markdown into (score, evaluation body)
# notice the Qwen3 output can be duplicated sometime
# so we only take the first part of the output
headings = list(SCORE_PATTERN.finditer(text))
if len(headings) > 1:
text = text[: headings[1].start()]
score = int(headings[0].group(1)) if headings else None
body = HEADING_PATTERN.split(text, maxsplit=1)
evaluation = body[1].strip() if len(body) > 1 else text.strip()
repeat = HEADING_PATTERN.search(evaluation)
if repeat:
evaluation = evaluation[: repeat.start()].strip()
return score, evaluation
def remote_critique(image_path, aspect, temperature, top_p, hf_token) -> Critique:
client = InferenceClient(provider=REMOTE_PROVIDER, token=hf_token)
response = client.chat.completions.create(
model=REMOTE_MODEL,
messages=[
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": image_as_data_url(image_path)}},
{"type": "text", "text": remote_prompt(aspect)},
],
}
],
max_tokens=REMOTE_MAX_TOKENS,
temperature=temperature,
top_p=top_p,
)
choice = response.choices[0]
content = (choice.message.content or "").strip()
# catch the case where the reasoning exhausts the token budget
if not content:
raise RuntimeError(
f"Hosted model returned no visible content (finish_reason="
f"{choice.finish_reason}); the token budget was likely exhausted by "
"internal reasoning."
)
score, evaluation = parse_response(content)
return Critique(score=score, evaluation=evaluation, model_name=REMOTE_MODEL, route="Hosted")