Skip to content

Validate image bytes + steer LLM to company domains#24

Merged
fleveque merged 1 commit into
mainfrom
fix/llm-prompt-and-image-validation
May 16, 2026
Merged

Validate image bytes + steer LLM to company domains#24
fleveque merged 1 commit into
mainfrom
fix/llm-prompt-and-image-validation

Conversation

@fleveque

Copy link
Copy Markdown
Owner

Two fixes for the LLM-fallback class of failures

1. Magic-byte image format validation

After grounding (#16) and the wikidata layer (#18), remaining LLM misses fail in a particularly bad way: the model returns a URL that downloads fine, but the bytes aren't an image (HTML 404 page, Wikipedia file page served as HTML, etc.). libvips spends ~6s per size trying to process, hits our 60s WriteTimeout, and returns "Unsupported image format" without marking the row failed for retry.

New `validateImageFormat` checks magic bytes for PNG / JPEG / WebP / GIF before handing to bimg, with specific errors for the common bad cases (SVG → not supported on Alpine vips, HTML → wrong URL). Rejection is microseconds, surfaces a clear reason in logs, marks the row failed cleanly.

2. Gemini prompt rewrite

Production hallucinations included three different invented Wikimedia hash prefixes for Repsol's logo (`/1/12/`, `/f/f9/`, `/3/30/`) plus a malformed `/thumb/` URL — the prior prompt actively encouraged Wikipedia/Wikimedia URLs, which are exactly the hosts the model can't reliably target because of the MD5-derived hash prefix in Commons paths.

Pivot the prompt to the company's own domain (and CDNs they own). Explicitly enumerate the anti-patterns we've observed in prod:

  • `upload.wikimedia.org` URLs (Wikidata covers that path)
  • `en.wikipedia.org/wiki/File:...` (HTML pages, not files)
  • `/thumb/` paths
  • stock-exchange "logo" endpoints (`londonstockexchange.com/images/logos/...`)
  • pattern-constructed URLs vs. URLs from real search results

Tightens the required-format list to PNG/JPEG/WebP (drops SVG), matching what our libvips build actually supports.

Test plan

  • `go test ./internal/llm/` — prompt assertions still pass.
  • `go test ./internal/service/` — 5 new tests cover PNG/JPEG/WebP/GIF accept, SVG reject, HTML reject, short input, garbage input.
  • After deploy: a fresh request for a ticker NOT covered by Wikidata should either succeed with a real company-domain URL OR fail fast with a clear "input rejected: …" log line. No more 30s libvips timeouts on bad LLM responses.

🤖 Generated with Claude Code

Two fixes for the LLM-fallback class of failures observed in prod.

## Magic-byte image format validation
After grounding (#16) and the wikidata layer (#18), the remaining LLM
misses were failing in a particularly bad way: the model returned a
URL that downloaded fine, but the bytes weren't an image — usually
HTML from a Wikipedia file *page* or a generic 404 page. libvips spent
~6s per size trying to process, hit our (now 60s) write timeout, and
returned an opaque "Unsupported image format" without marking the row
failed for retry.

New `validateImageFormat` checks magic bytes for PNG, JPEG, WebP, GIF
before handing to bimg, with specific errors for the common bad cases
(SVG → libvips can't handle on Alpine; HTML → wrong URL). Rejection
is fast (microseconds) and surfaces a clear reason in the logs.

## Gemini prompt rewrite
The prior prompt encouraged Wikipedia/Wikimedia URLs — exactly the
hosts the model can't reliably target because of the MD5-derived hash
prefix in Commons paths. Prod hallucinations included three different
invented hash prefixes for Repsol's logo (1/12, f/f9, 3/30) plus a
malformed /thumb/ URL.

Pivot the prompt to the company's own domain (and CDNs they own) and
explicitly enumerate the anti-patterns we've observed:
- upload.wikimedia.org URLs (use Wikidata path instead)
- en.wikipedia.org/wiki/File:* (HTML pages, not files)
- /thumb/ paths
- stock-exchange "logo" endpoints
- pattern-constructed URLs vs. URLs from real search results

Also tightens the required-format list to PNG/JPEG/WebP (drops SVG),
matching what our libvips build actually supports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fleveque fleveque merged commit dce5024 into main May 16, 2026
2 checks passed
@fleveque fleveque deleted the fix/llm-prompt-and-image-validation branch May 16, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant