Skip to content

Instantly share code, notes, and snippets.

@pbasov
Created October 25, 2025 22:51
Show Gist options
  • Select an option

  • Save pbasov/6148845dcfa43e0d66941b1225fb3d30 to your computer and use it in GitHub Desktop.

Select an option

Save pbasov/6148845dcfa43e0d66941b1225fb3d30 to your computer and use it in GitHub Desktop.
def compare_batch(self, proxy_image_b64: str, candidate_images_b64: List[str]) -> List[Optional[int]]:
"""
Compare proxy to multiple candidates in one API call.
Returns list of scores corresponding to candidate_images_b64.
"""
num_candidates = len(candidate_images_b64)
prompt = f"""You are comparing Magic: The Gathering card artwork. I will show you:
1. First image: The PROXY card (reference)
2. Next {num_candidates} images: CANDIDATE printings (numbered 1-{num_candidates})
Your task: Rate how similar each CANDIDATE's artwork is to the PROXY artwork.
Focus ONLY on the central artwork/illustration. Completely ignore borders, frames, card text, and other non-artwork elements.
Scoring guide:
- 100 = Identical artwork (same illustration, same artist, same composition)
- 90-99 = Same artwork with very minor variations (slight color/cropping differences)
- 70-89 = Similar artwork but noticeable differences (extended art, alternate version)
- 40-69 = Different artwork but similar subject/theme
- 0-39 = Completely different artwork
Respond with ONLY the scores as a comma-separated list. Example: 100, 95, 30, 100, 20"""
# Build content list: prompt + proxy + all candidates
content = [{"type": "text", "text": prompt}]
# Add proxy image
content.append({
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{proxy_image_b64}"}
})
# Add all candidate images
for candidate_b64 in candidate_images_b64:
content.append({
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{candidate_b64}"}
})
payload = {
"model": "nm-testing/Qwen3-VL-8B-Instruct-NVFP4",
"messages": [{"role": "user", "content": content}],
"max_tokens": 100, # Need more tokens for multiple scores
"temperature": 0.7,
"top_p": 0.8,
"top_k": 20,
"repetition_penalty": 1.0,
"presence_penalty": 1.5
}
try:
response = requests.post(self.chat_endpoint, json=payload, timeout=60)
if response.status_code != 200:
print(f" ERROR: API returned {response.status_code}")
return [None] * num_candidates
result = response.json()
content = result['choices'][0]['message']['content'].strip()
print(f" Model response: {content[:200]}...") # Debug output
# Parse comma-separated scores
numbers = re.findall(r'\d+', content)
if len(numbers) < num_candidates:
print(f" WARNING: Expected {num_candidates} scores, got {len(numbers)}")
scores = [int(n) for n in numbers[:num_candidates]] # Take first N numbers
scores = [min(100, max(0, s)) for s in scores] # Clamp to 0-100
# Pad with None if we got fewer scores than expected
while len(scores) < num_candidates:
scores.append(None)
return scores[:num_candidates]
except Exception as e:
print(f" ERROR: {e}")
return [None] * num_candidates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment