miguealguacil
Back to projects

Minecraft Butler AI

PythonFastAPILangChainLangGraphRAGWebSocketJava

MinecraftButlerAI is a FastAPI backend that brings a butler ("Alfred") to life inside Minecraft: it understands natural-language questions (by text or by voice), answers with real game knowledge, and executes in-world actions. Rather than a single LLM call, it's an agentic architecture where each piece solves a concrete problem, from intent routing to knowledge retrieval and voice synthesis.

The butler combines a LangGraph agent with persistent memory, a multilingual RAG system over the game documentation, and a local voice pipeline, all exposed through a JWT-authenticated HTTP API designed for production (rate-limiting, migrations, observability).

Demo

Three questions in a single session, each exercising a different capability of the agent.

You

How do I craft an iron sword?

Alfred

Craft it with 2 iron ingots and 1 stick. The ingots go in the top two slots of the center column, and the stick in the bottom slot.

You

Do I have what I need to make one?

Alfred

No. You're missing the iron ingots. You have sticks in your inventory but no iron ingots.

You

Which crops are ready to harvest?

Alfred

You have 5 carrots, 2 potatoes, and 6 wheat ready to harvest.

Under the hood (LangSmith)

LangSmith view of the three demo interactions, showing latency, time-to-first-token, token usage, and cost for each turn
The three turns with latency, time-to-first-token, token usage, and cost: around $0.004 and 2.4-3.5s per turn.
LangSmith trace tree for question 1, showing intent classification, routing, RAG retrieval, and answer generation
Full trace for question 1: intent classification, routing, RAG retrieval, and answer generation.

Agent architecture

The butler is modeled as a graph of nodes with LangGraph: a first node classifies the user's intent and the graph deterministically routes (a plain intent → node dictionary, not "LLM magic") to one of three branches: answer a question with RAG, move to a set of coordinates, or chat.

The graph state (ButlerState) is a typed TypedDict whose messages field uses LangGraph's add_messages reducer, which accumulates history automatically. The graph compiles once (a singleton guarded by an asyncio.Lock) with an AsyncRedisSaver checkpointer: each session persists its state in Redis with a TTL, giving multi-turn conversational memory per player without any client-side management.

Intent classification runs on Claude Haiku 4.5 (fast and cheap) using structured output (a validated Pydantic object, not free text to parse), while the final answer uses Claude Sonnet 4.6. Both models are obtained through a factory that abstracts by role ("classifier"/"responder") and by provider (Anthropic or OpenAI), so the code asks for capabilities, not concrete models.

Multilingual RAG

Game knowledge is indexed in English from PrismarineJS/minecraft-data and Minecraft Wiki extracts, but users ask in Spanish. To bridge that language gap, dense cross-lingual embeddings (paraphrase-multilingual-MiniLM-L12-v2) are used with cosine-similarity search in Qdrant.

The original design included a full hybrid pipeline (a sparse BM42 branch + a FlashRank reranker), which was dropped after validating with real data: both components are lexical and English-only, so with Spanish queries the sparse branch returned noise and the reranker could not reorder ES→EN results. The measured conclusion, documented in the code, is that dense multilingual retrieval alone is superior in both languages.

For wiki mechanics, Parent Document Retrieval is used: small chunks (~800 characters) are indexed for precise embeddings, but the full parent block (~2000 characters) is retrieved and passed to the LLM, resolving the tension between retrieval precision and context sufficiency.

Voice & production

The response is streamed over Server-Sent Events sentence by sentence (not token by token), because the client synthesizes it as speech (TTS) and a TTS needs complete sentences to sound natural; this gives a sense of immediacy without chopping the audio.

Voice transcription runs on-device with faster-whisper in int8 quantization (faster and using half the memory of float32, with no noticeable quality loss), with the model pre-warmed at startup to avoid cold-start and without ever sending the user's audio to third parties.

The backend is built for production: JWT authentication with roles, rate-limiting with SlowAPI, Alembic migrations, slice architecture (router/schemas/service/repository), and optional per-run observability of the agent with LangSmith.

Links