8000 Seed Search images: the request flow (not wired in) by daoudclarke · Pull Request #628 · mwmbl/mwmbl · GitHub
[go: up one dir, main page]

Skip to content

Seed Search images: the request flow (not wired in) - #628

Merged
daoudclarke merged 2 commits into
mainfrom
seed-images-prod-pipeline
Nov 11, 2026
Merged

daoudclarke merged 2 commits into
mainfrom
seed-images-prod-pipeline

Conversation

@daoudclarke
Copy link
Copy Markdown
Contributor

From Claude:

Stack: builds on #622 (seed-images-prod-harden). PR 3 of the #610 production plan. Not wired into any endpoint yet; PR 4 does that.

What changed

mwmbl/imagesearch/seed_images.py implements Seed Search's image request flow in two phases, so Combined Search can overlap each one with the web-search work it doesn't depend on:

  • start(query) -> ImageSearch, called before Staan is fetched. It runs the rules gate on the query, the Commons search (20 candidates), then the NSFW scores (cached per file; the request carries no query).
  • await finish(search, staan_results, web_ranking) -> ImageSelection, called once Staan has answered, with web ranking already running as a task. It sends one judge.image_request (need plus relevance for every candidate; it doesn't wait for NSFW), then selects images with NSFW < 1.0, need >= 1.65 and relevance >= 1.5. It returns every passing image, most relevant first (ties keep Commons' order), each with its relevance.
  • The outcome is one of shown | query_refused | not_needed | none_relevant | unavailable. Images get IMAGE_SEARCH_GRACE_SECONDS (0.25 s) after web ranking finishes; after that they are unavailable. finish never raises.

PR 4 would use it roughly like this:

image_search = seed_images.start(q)
retrieval, staan = await asyncio.gather(to_thread(ranker.retrieve, q), to_thread(get_staan_results, q))
ranking = asyncio.ensure_future(asyncio.gather(to_thread(ranker.search_retrieved, ...), to_thread(index_staan_results, ...)))
selection = await seed_images.finish(image_search, staan, ranking)
results, new_urls = await ranking

Carry-overs from #620 and #622

  • Staan text in the image questions is one line per result, with brackets and invisible characters removed, and capped: title 200, snippet 500, URL 300. The question wording is unchanged, so the measured thresholds still hold.
  • Need and relevance answers are range-checked with checked_score. An off-scale answer means unavailable.
  • 0 Staan results: unavailable, because the gate was measured with web results and can't be asked without them. 1 result is enough, since relevance was also measured with Staan's top result alone.
  • Own timeout: IMAGE_JUDGE_TIMEOUT_SECONDS (1.5 s). jev_rank.post now takes its timeout as an argument.
  • Missing JEV_API_KEY: unavailable, and neither Commons nor the judge is called.
  • Redis timeout: the deadline is enforced in the event loop. Image work runs on its own 8-thread pool, so threads left blocked on Redis (5 s socket timeout) or Commons can't starve web search's default executor.
  • Minimum revision age (7 days, on the later of the latest page revision and the latest file version). Measured on 200 live candidates (10 queries): 2.5% had a page revision in the last 7 days and 12.5% in the last 30; Commons bots edit pages often.
  • Operator denylist: IMAGE_SEARCH_DENIED_FILES env var, holding SHA-1s or page URLs, compared lower-cased. It is a stopgap until PR 5's revocation.
  • Files nominated for deletion (deletion request, speedy deletion, copyright violation, no licence or permission categories) are never judged. This came from the trust-and-safety review: a deletion discussion can outlast the 7-day age gate.
  • Images ruled out by the rules (rules.image_verdict) are dropped before the judge is asked.

Commons caption and English metadata

  • The generator=search request now also asks for prop=entityterms (MediaInfo caption, wbetlanguage=en) and prop=revisions (rvprop=timestamp), and sets iiextmetadatalanguage=en explicitly. SourceImage gains caption and revised_at.
  • Latency cost: none measurable. Over 6 queries × 3 variants, cold requests took 0.9–1.4 s either way, and the response is 1–3% larger. 60% of top-20 candidates have an English caption.
  • wbgetentities would have been a second round trip, so it wasn't used.
  • Language: iiextmetadatamultilang was deliberately not used. MediaWiki treats any value of a boolean parameter, 0 included, as true, and it adds ~60% to the response. A description with no English version still comes back in Commons' fallback language. That keeps the text the safety judge reads intact, but it means PR 6 cannot assume the extract is English.
  • Caption shown to the judges: the caption goes into the rules text and into the judge's image text, as Caption: and only when present, capped at 250 characters. Files without a caption are judged on exactly the text the thresholds were measured on. Captioned files' text was not re-measured (see below).

Verification

  • make check passes.
  • make test passes (1873 passed, before and after the review fixes).
  • New tests in test/test_seed_images.py, with Commons and the judge stubbed, cover:
    • ordering and ties, and that all passing images are returned
    • relevance asked for every candidate, and the NSFW request carrying no query
    • the need gate, none_relevant (including no candidates and no judge call), query_refused and the missing key, each with no calls
    • no Staan results
    • six fail-closed paths: Commons, the judge, an off-scale need, relevance or NSFW answer, and Redis
    • the age, denylist, rules and deletion filters
    • the deadline, both missed and when ranking is slower
    • NSFW cache reuse across searches
  • Further tests cover caption parsing, the request parameters, caption-in-prompt and web-text capping.
  • Reviews (subagents):
    • The code and security review found shared-executor starvation, flaky leftover threads in the deadline test, cancellation propagating into shared phase-1 futures, ensure_future instead of create_task, select outside the try, an unretrieved selection outcome and exact-case denylist matching. All are fixed.
    • The trust-and-safety and holistic review found deletion-nominated files, uploaded_at not checked, the denylist format, the caption cap and the inaccurate "English" comments. All are fixed.

Not done here (raised for the next PRs)

  • Re-measure with captions: the NSFW, relevance and need thresholds were measured without captions, and the questions still say "file name, description and categories". Re-run the safety benchmark and injection probes with captions on the eval branch, including a caption-injection probe, before PR 4 switches images on.
  • The NSFW policy covers nudity only, which is the agreed scope. Gore and shock imagery with bland text isn't covered; the API description should say so.
  • Queries now go to Wikimedia from Mwmbl's server (no user IP). PR 4's API description and the privacy policy should say so.
  • httpx logs request URLs, which contain the query, at INFO. That isn't emitted today, since root is at WARNING. Consider pinning the httpx logger to WARNING in PR 4.
  • Anyone can postpone an image for a week by editing its page (it resets the age gate). Keyword-stuffed captions can steer which safe images appear. Both are residual risks.

Part of #610

🤖 Generated with Claude Code

@codecov
codecov Bot commented Nov 10, 2026
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@daoudclarke
daoudclarke added this pull request to stack #632 November 11, 2026 07:02
daoudclarke added a commit that referenced this pull request Nov 11, 2026
Commons captions are now part of each image's text (#628), so the questions
say so. The caption eval (#630) measured the NSFW rewording on the safety
benchmark: an identical table, and the 1.0 threshold holds. The relevance
rewording was not measured. The NSFW question's fingerprint changes, so cached
verdicts are asked again.

No functional changes to the crawler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daoudclarke
daoudclarke force-pushed the seed-images-prod-pipeline branch from b820afc to e78ff52 Compare November 11, 2026 07:03
daoudclarke added a commit that referenced this pull request Nov 11, 2026
Commons captions are now part of each image's text (#628), so the questions
say so. The caption eval (#630) measured the NSFW rewording on the safety
benchmark: an identical table, and the 1.0 threshold holds. The relevance
rewording was not measured. The NSFW question's fingerprint changes, so cached
verdicts are asked again.

No functional changes to the crawler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daoudclarke
daoudclarke force-pushed the seed-images-prod-pipeline branch from e78ff52 to dfb8cc4 Compare November 11, 2026 10:25
Base automatically changed from seed-images-prod-harden to main November 11, 2026 10:49
daoudclarke and others added 2 commits November 11, 2026 10:49
…ommons

mwmbl/imagesearch/seed_images.py runs the #610 request flow in two phases
for Combined Search to overlap with Staan and with web ranking: start()
gates the query, searches Commons and asks the NSFW question; finish() asks
image need and relevance in one request once Staan has answered, then
selects. It fails closed to `unavailable`, including when the images are
not ready shortly after web ranking finishes. Not wired into an endpoint.

The Commons request now also returns each file's English caption
(MediaInfo, via prop=entityterms) and its latest revision time, and asks
for English metadata explicitly. Files edited in the last week, and files
on an operator denylist, are never judged or shown. The need and relevance
answers are range-checked like the NSFW ones, web results are capped and
kept to one line each, and image requests have their own timeout.

No functional changes to the crawler.

Part of #610

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…from review

Image work gets its own thread pool so threads blocked on Commons or Redis
after a missed deadline cannot hold up web search; the selection is
shielded so a deadline doesn't cancel the shared phase-1 work. Files in
deletion categories, and files with a recent file version, are never
judged; the denylist is matched case-insensitively; captions are capped.

No functional changes to the crawler.

Part of #610

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daoudclarke
daoudclarke force-pushed the seed-images-prod-pipeline branch from dfb8cc4 to 60e8a96 Compare November 11, 2026 10:49
daoudclarke added a commit that referenced this pull request Nov 11, 2026
Commons captions are now part of each image's text (#628), so the questions
say so. The caption eval (#630) measured the NSFW rewording on the safety
benchmark: an identical table, and the 1.0 threshold holds. The relevance
rewording was not measured. The NSFW question's fingerprint changes, so cached
verdicts are asked again.

No functional changes to the crawler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daoudclarke
daoudclarke merged commit 8eb5e92 into main Nov 11, 2026
7 checks passed
@daoudclarke
daoudclarke deleted the seed-images-prod-pipeline branch November 11, 2026 11:00
daoudclarke added a commit that referenced this pull request Nov 11, 2026
Commons captions are now part of each image's text (#628), so the questions
say so. The caption eval (#630) measured the NSFW rewording on the safety
benchmark: an identical table, and the 1.0 threshold holds. The relevance
rewording was not measured. The NSFW question's fingerprint changes, so cached
verdicts are asked again.

No functional changes to the crawler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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

0