refactor: Claude visualizer + DI-driven ThemeImageService#64
Merged
Conversation
Flux couldn't render abstract themes (e.g. "the way we talk about AI
agents") because image models have no referent for meta-language. Fixed
by inserting a Claude visualization step that translates theme text into
a concrete scene sentence, which then feeds Flux.
Architectural pass to make the module simple, robust, and testable:
- Replace flat app/images.py with app/images/ package:
- service.py — protocols (Visualizer, ImageGenerator, ImageStore),
ThemeImageService orchestrator, pure compose_prompt + STYLE_SUFFIX
- providers.py — ClaudeVisualizer, ReplicateImageGenerator, R2ImageStore
concrete implementations; each wraps vendor errors as ImageGeneration/
ImageCommitError
- errors.py — error hierarchy, decoupled from service and providers
- __init__.py — default_theme_image_service() factory (lru_cached)
- API layer uses Depends(default_theme_image_service); all vendor
details stay inside providers. Endpoints now trivially swappable in
tests via dependency_overrides.
- Providers accept their external clients/callables as constructor
args, so tests mock at construction rather than monkeypatching module
globals.
- Tests rewritten into three layers: service orchestration with fakes
(no network), provider internals with mocked clients, HTTP endpoints
with service dependency override. 49 new tests, 175 total pass.
The Claude visualizer uses Haiku 4.5. Extra cost per generation is
~$0.0005 (Haiku input/output tokens) on top of ~$0.012 for the Flux
grid-of-4. Latency adds ~1-2s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🚅 Deployed to the breadcrumbs-pr-64 environment in Breadcrumbs
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The first version passed theme body_md verbatim into the Flux prompt. For concrete themes ("notes on a good afternoon") that worked; for abstract themes ("the way we talk about AI agents") Flux defaulted to contemporary-figurative-painting tropes (groups of diverse people) because it had no visual referent for meta-language.
Fix: insert a Claude visualization step. Claude translates theme text into a concrete, renderable scene sentence; Flux renders the scene.
Architecture
While touching the module, restructured for simplicity, robustness, and testability per the stated design principles:
ImageGenerationError/ImageCommitError, so the service and API layers never seeanthropic.APIErrororreplicate.exceptions.*.runner,fetch,put,client), so tests can inject doubles without monkeypatching module globals.Depends(default_theme_image_service). Endpoints know nothing about Claude/Replicate/R2. Tests override withapp.dependency_overrides[default_theme_image_service] = lambda: fake_service.Before / after
Before: flat
app/images.pywith free functions (build_theme_prompt,generate_candidate_images,commit_image_to_r2). API layer imported and called them directly. Tests monkeypatchedapp.images.replicate.runandapp.api.commit_image_to_r2to stub externals.After:
ThemeImageServicewith three injected providers behind Protocols. API callsservice.generate_candidates(theme_body, tag_names)andservice.commit_candidate(source_url). Tests at each layer see only what they need.Smoke test on the failing theme
Input theme: "The way we talk about AI models and AI agents support an image of them as self-contained objects that function autonomously in their own virtual spaces."
Claude's scene description (what Flux actually renders):
That's the translation gap. Abstract → concrete, which is a task LLMs excel at and image models don't.
Cost + latency
Tests
FakeVisualizer/FakeImageGenerator/FakeImageStore— no network_FakeServiceviadependency_overridesTest plan
uv run pytest→ 175 passedANTHROPIC_API_KEYorREPLICATE_API_TOKEN, confirm writer sees a clean error🤖 Generated with Claude Code