A small microservice that answers cooking questions over a set of recipe and technique notes using Retrieval-Augmented Generation (RAG). Built with FastAPI, Google Gemini (embeddings + generation), and a lightweight local vector store.
I built this to get hands-on with the full RAG pipeline end to end: document ingestion,
chunking, embedding, semantic retrieval, grounded generation, and serving it behind an API
with per-request latency metrics. Cooking is just the content I picked; the pipeline is
domain-agnostic, so you can point it at any document set by dropping files in data/.
Recipe and technique notes are scattered across files, and the answer to "why is my sear
not browning?" or "how long do I rest a roast?" is buried somewhere in the text. CookRAG
ingests those documents and exposes an /ask endpoint that returns a grounded, cited answer.
The model answers only from retrieved passages, which reduces hallucination and keeps
responses traceable to a source.
+-------------+
recipes/notes -> | ingest.py | chunk -> embed -> store
+-------------+
|
v
+-------------+
| vector store| (numpy + cosine similarity, on disk)
+-------------+
^
user question -> /ask --+ embed query -> retrieve top-k -> build prompt
v
+-------------+
| Gemini | grounded generation
+-------------+
|
v
cited answer + latency metrics
- RAG: retrieval over a vector store, then grounded generation. No fine-tuning, which suits a knowledge-lookup task: cheaper, and no retraining when the documents change.
- Embeddings: Gemini
gemini-embedding-001to vectorise chunks and queries. - Generation: Gemini
gemini-2.5-flash. - Vector store: a numpy + cosine-similarity index persisted to disk. Dependency-light, and the retrieval interface is swappable for FAISS / Chroma / pgvector.
- Service: FastAPI with request validation, structured error handling, retry-with-backoff on the LLM call, and per-request latency logging.
- Latency: total response time is embed query + vector search + LLM generation. The
service times each stage separately (the
/askresponse includes atimingblock). In practice generation dominates and retrieval is sub-millisecond at this scale, so the obvious next optimisation is streaming the response or using a smaller model. - Reliability: the LLM call is wrapped in exponential backoff (1s, 2s, 4s) to absorb transient 429/503 errors so a single rate-limit blip does not fail the request.
- Chunking: chunks too large dilute retrieval relevance; too small lose context.
CHUNK_SIZEandCHUNK_OVERLAPare configurable to tune this. - Grounding: the prompt instructs the model to answer only from the provided context and say so when the answer is not there. Retrieved sources are returned with each answer.
- Cost & privacy: runs on Gemini's free tier. The free tier may use inputs for training, so only non-confidential content should go through it; the sample documents here are original. For sensitive data, a paid tier or Vertex AI avoids training on your inputs.
# 1. Install dependencies
pip install -r requirements.txt
# 2. Add your Gemini API key. Copy .env.example to .env and fill it in:
cp .env.example .env
# then edit .env to set GEMINI_API_KEY=...
# (free key, no card, at https://aistudio.google.com/apikey)
# 3. Sample cooking docs are already in data/. Add your own .txt or .pdf to extend it.
# 4. Ingest the documents (chunk -> embed -> store)
python -m scripts.ingest
# 5. Run the service
uvicorn app.main:app --reload
# 6. Ask a question
curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"question": "Why is my steak not browning when I sear it?"}'Other questions to try:
- "How long should I rest a roast chicken?"
- "When should I salt meat before cooking?"
- "How do I fix a broken vinaigrette?"
- "Why salt the pasta water?"
- Stream responses so output appears as it is generated.
- Swap the numpy store for FAISS or pgvector to scale past in-memory search.
- Add evaluation: a set of question/expected-source pairs to measure retrieval quality.