The project
An AI-assisted Magic: The Gathering card discovery and deckbuilding app. A mono-repo of four services: a FastAPI backend, a query microservice that turns natural language into an intermediate representation and then into Scryfall queries, a Redis-cached card image proxy, and a Next.js frontend, with Prometheus and Grafana wired in from the start.
Grimoire MLHow it fits together
- A query service that goes natural language, then intermediate representation, then Scryfall query, with Redis caching between stages.
- A dedicated card image proxy with its own cache and metrics, keeping image traffic off the main backend.
- A Next.js frontend in TypeScript with Tailwind and Mantine.
- Prometheus metrics and Grafana dashboards, plus a Docker Compose stack wiring Postgres, Redis and every service together for local development.
The trade-offs that mattered
An intermediate representation between the model and the API
Going via a structured IR makes the model's output inspectable and lets the query layer be tested without the model at all.
Cache aggressively at every boundary
Card data barely changes and model calls cost money and latency. Redis between each stage made the app feel instant on repeat queries and kept costs low when testing.
Observability from day one, on a side project
Wiring Prometheus and Grafana in early cost a couple of hours afternoon but paid for itself time and time again when Claude gave crappy responses to my prompts. Gaining deep insights to the internals of this microservice structure app was invaluable.
What I took from it
Asking a language model to emit search syntax directly is brittle and impossible to debug. Putting a structured intermediate representation in between turned an unpredictable component into a testable one - I could exercise the whole query layer with no model involved at all. That indirection is what made the results trustworthy, and it is the bit I would do first next time rather than last.