Skip to content

A learning project

Learning RAG by building it by hand

I'm a frontend developer, curious about how AI actually works under the hood. Instead of reaching for a framework that hides everything, I wanted to build Retrieval-Augmented Generation from the ground up, so I could actually explain what embedding, retrieval and grounded generation each do, in my own words.

So every moving part here is deliberately visible: the embedding step calls a hosted model, the similarity search ranks stored vectors by cosine distance (I first wrote this by hand, then moved it to a hosted vector database), and the language model is told to answer only from the retrieved NASA texts. To check the search rather than trust it blindly, I also built a small eval by hand using the standard search metrics (Recall@K and MRR): it asks questions whose right answer I already know and grades how often, and how near the top, the right picture comes back.

It was also my chance to practise Clean Architecture in a Nuxt app: the query and ingest flows are both split into a domain, use cases with ports, and infrastructure adapters, so the core logic never depends on Gemini, Upstash or NASA directly. Arguably over-engineered for a demo this size, but that was exactly the point, to learn the pattern by building it by hand.

How it got here
  • Local Transformers.js embeddings (Xenova/paraphrase-multilingual-MiniLM-L12-v2), in the function.

    Too heavy for Netlify's 250 MB function limit.

    Google Gemini embeddings: hosted, tiny to ship.

  • Hand-written cosine over one plain JSON file.

    Lovely to learn on, but it will not scale.

    A hosted vector database (Upstash Vector), live now.

  • A free-text answer with NONSENSE / NO_MATCH markers, parsed by pattern-matching.

    Brittle to read, and it never verified which pictures were actually cited.

    A schema-constrained JSON reply, validated with Zod, with its cited sources checked.

  • Guessing the retrieval thresholds (how similar counts, how many to fetch) by eye.

    No way to tell whether a tweak actually helped or hurt.

    A small offline eval that grades retrieval (Recall@K + MRR) so the knobs are set from numbers.

Stack
Framework
Nuxt 4 + Vue, Nitro server routes
Embeddings
Google Gemini: gemini-embedding-001 (multilingual, 768-dim)
Vector store
Upstash Vector (was hand-written cosine over JSON first)
Language model
Google Gemini: gemini-flash-latest (free tier), structured JSON output validated with Zod
Data
NASA Astronomy Picture of the Day API
Ingest
Manual backfill script + a daily Netlify function
Hosting
Netlify

This is deliberately a free-tier demo, so it can hit the daily quota. A hobby project, built to learn and to show, not to run up costs.

View the source on GitHub ↗

Built by Melanie Stief, forever half-hoping for a NASA badge, or at least a window seat past the atmosphere. Until the universe sends the invite, building small things about the cosmos will have to do. · viridis.de

idle