A retrieval-augmented debugging assistant for FPGA/Verilog coursework — ask it about a symptom ("my grappler FSM never reaches GRAB", "TimeQuest reports failing paths I don't understand"), and it retrieves the relevant pattern from a curated knowledge base and grounds an LLM's answer in it, citing exactly which chunks it used.
It's built around a real case: a three-FSM robotic-arm controller (XY motion, extender, grappler) on a MAX10-class board, which is exactly the kind of design where state-encoding bugs, blocking/non-blocking races, and multi-module handshake mismatches actually show up.
It's a small, complete slice of what a GenAI-adjacent engineering role actually looks like day to day:
- Python + TypeScript/React — FastAPI backend, typed React frontend
- LLM concepts — prompting (a grounding-constrained system prompt), RAG (retrieve → construct context → generate), and a documented vector-DB abstraction
- Databases — SQL (SQLite) for relational entities, NoSQL (TinyDB) for irregularly-shaped chat logs — used deliberately for what each is good at, not just for résumé coverage
- Modern dev workflow — Git,
.envconfig, pytest, typed API client - GenAI tooling curiosity — built iteratively the way you'd actually use Cursor/Copilot-style tools: small modules, docstring-first, one concern per file
┌─────────────┐ HTTP ┌──────────────────┐
│ React + TS │ ──────────────▶ │ FastAPI │
│ (Vite) │ ◀────────────── │ │
└─────────────┘ JSON │ ┌──────────────┐ │
│ │ RAG pipeline │ │
│ └──────┬───────┘ │
│ │ │
┌───────────┼─────────┼──────────┼───────────┐
▼ ▼ ▼ ▼ │
┌───────────┐ ┌─────────┐ ┌────────┐ ┌───────────┐ │
│ SQLite │ │ TinyDB │ │ TF-IDF │ │ Anthropic │ │
│ sessions, │ │ chat │ │ vector │ │ API / │ │
│ bug repts │ │ messages│ │ store │ │ mock │ │
└───────────┘ └─────────┘ └────────┘ └───────────┘ │
│
kb/*.md ──(ingest.py)──────────────────────────┘
Why TF-IDF instead of a hosted embedding model? The vector store
(backend/app/vector_store.py) exposes the same add_documents() /
query() interface a real client (Chroma, pgvector, Pinecone) would. TF-IDF
- cosine similarity is a fully local, dependency-light substitute that lets the whole pipeline run offline with zero API keys — swapping in real embeddings later only touches that one file.
Why the LLM call has a mock fallback: backend/app/llm_client.py checks
for ANTHROPIC_API_KEY. Without one, it falls back to a deterministic
responder that still surfaces the retrieved KB chunk, so a reviewer cloning
this repo sees the real retrieval quality without needing credentials.
backend/
app/
main.py FastAPI routes
rag.py retrieval + prompt construction
vector_store.py TF-IDF vector store (swappable interface)
llm_client.py Anthropic API wrapper + offline mock
db.py SQLite: sessions, bug_reports
nosql_store.py TinyDB: chat messages
ingest.py chunks kb/*.md into the vector index
kb/ knowledge base (Verilog/FPGA bug patterns)
tests/ pytest suite
frontend/
src/
api/client.ts typed fetch wrapper
components/ Sidebar, ChatPanel, MessageBubble, SourcesPanel
App.tsx
Backend
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt --break-system-packages # or without the flag in a venv
python -m app.ingest # build the vector index from kb/
uvicorn app.main:app --reload --port 8000Optionally copy .env.example to .env and set ANTHROPIC_API_KEY for
live model responses instead of the offline mock.
Frontend
cd frontend
npm install
npm run devVisit the printed localhost URL. The frontend expects the API at
http://localhost:8000 by default (override via VITE_API_URL).
Tests
cd backend
pytest tests/ -vThe UI leans into the subject matter rather than a generic chat-app look — a dark PCB-substrate palette, copper/signal-cyan accents, and a literal digital-signal-trace divider used as both a section break and the "thinking" indicator while a query is in flight.
- Swap the TF-IDF store for real embeddings + pgvector once this needs to scale past a single course's worth of documents
- Let students upload their own
.v/.sdcfiles to grow the knowledge base per-course