A Retrieval-Augmented Generation (RAG) assistant that answers first-aid questions in real time. A person at an accident scene can ask a natural-language question and receive an accurate, context-grounded answer drawn from a curated first-aid FAQ dataset.
The system combines hybrid search (BM25 text search + HNSW vector search with Reciprocal Rank Fusion) with an LLM to generate answers, automatically rewrites queries to improve retrieval, and records every conversation in PostgreSQL for monitoring and feedback collection.
People at accident scenes often need fast, reliable first-aid guidance but cannot quickly search through documentation. This assistant provides a conversational interface where any question — however informally phrased — is reformulated for precision, searched against a structured first-aid knowledge base, and answered using an LLM grounded in retrieved context.
User query
│
▼
Query rewriting (LLM)
│
▼
Hybrid search ──── BM25 text search (SQLite FTS)
│ └── HNSW vector search (ONNX embeddings)
│ ↕ Reciprocal Rank Fusion
▼
Prompt building + LLM answer (Mistral ministral-3b via LiteLLM)
│
├── Save conversation → PostgreSQL
├── Auto-judge relevance (LLM-as-judge) → PostgreSQL feedback
└── OpenTelemetry traces → SQLite
Stack: Python 3.12 · Streamlit · LiteLLM · Mistral AI · SQLite (search + traces) · PostgreSQL (conversations + feedback) · ONNX Runtime · OpenTelemetry
first-aid-qa-rag-assistant/
├── src/
│ ├── app.py # Streamlit UI entry point
│ ├── assistant.py # RAG assistant factory
│ ├── config.py # Model name, LLM client factory, cost pricing
│ ├── embedder.py # ONNX sentence embedder (all-MiniLM-L6-v2)
│ ├── ingest.py # Load FAQ data, build text + vector indices
│ ├── judge.py # LLM-as-judge relevance scoring
│ ├── search.py # text_search, vector_search, RRF fusion
│ ├── rag/ # RAG class hierarchy
│ │ ├── rag_helper.py # RAGBase, prompt templates, query rewriting
│ │ ├── rag_usage.py # RAGWithUsage (hybrid search + usage tracking)
│ │ ├── metrics.py # RAGWithMetrics (cost + latency recording)
│ │ └── starter.py # RAGTraced (OTel) + RAGProduction (full stack)
│ ├── db/ # PostgreSQL persistence
│ │ ├── init.py # Schema creation
│ │ ├── conversations.py
│ │ └── feedback.py
│ ├── evaluation/ # Retrieval + answer quality evaluation
│ │ ├── evaluation.py
│ │ └── evaluation_utils.py
│ └── telemetry/ # OpenTelemetry tracing → SQLite
│ ├── tracer.py
│ └── exporter.py
├── notebooks/ # Jupyter experiment notebooks
├── scripts/
│ └── download.py # Download ONNX model from Hugging Face
├── data/
│ └── firstaidqa_v1-first-half.json # 2,775 first-aid Q&A pairs
├── models/ # ONNX model files (not in git — see setup below)
├── sqlite/ # SQLite DB files (not in git — see setup below)
├── Dockerfile
├── docker-compose.yml
└── pyproject.toml
| Tool | Version | Install |
|---|---|---|
| Python | 3.12 | python.org |
| uv | ≥ 0.11 | curl -LsSf https://astral.sh/uv/install.sh | sh |
| Docker + Docker Compose | any recent | docs.docker.com |
| Mistral AI API key | — | console.mistral.ai |
All dependencies are fully pinned in uv.lock. Key packages:
| Package | Version |
|---|---|
| litellm | 1.92.0 |
| streamlit | 1.59.2 |
| numpy | 2.5.1 |
| onnxruntime | 1.27.0 |
| opentelemetry-api | 1.44.0 |
| opentelemetry-sdk | 1.44.0 |
| pandas | 3.0.3 |
| psycopg | 3.3.4 |
| sqlitesearch | 0.1.2 |
| tokenizers | 0.23.1 |
| tqdm | 4.68.4 |
git clone https://gitfront.io/r/manishgitfront/q9AfQg84m4bX/first-aid-rag-assistant.git
cd first-aid-qa-rag-assistant
uv sync # creates .venv and installs all pinned dependencies
Copy the example env file and fill in your values:
cp .env.example .env
# .env
MISTRAL_API_KEY=your_mistral_api_key_here
POSTGRES_HOST=localhost
POSTGRES_DB=first_aid_qa_assistant
POSTGRES_USER=user
POSTGRES_PASSWORD=password
The models/ directory is excluded from git (binary files ~90 MB). Run once after cloning:
uv run python scripts/download.py
This downloads all-MiniLM-L6-v2 (ONNX) from Hugging Face into models/Xenova/all-MiniLM-L6-v2/.
Offline / air-gapped: place
model.onnxandtokenizer.jsonmanually intomodels/Xenova/all-MiniLM-L6-v2/.
The sqlite/ directory is excluded from git. Create the required subdirectories once:
mkdir -p sqlite/text sqlite/vector sqlite/otel
The text and vector search indices are built automatically on first run of the application.
Using Docker for just the database:
docker compose up postgres -d
Or use any PostgreSQL 15+ instance and update .env accordingly.
Run once after starting PostgreSQL:
uv run python src/db/init.py
This creates the conversations and feedback tables (safe to re-run — uses IF NOT EXISTS).
uv run streamlit run src/app.py
Open http://localhost:8501 in your browser.
cp .env.example .env
# Edit .env with your MISTRAL_API_KEY and Postgres credentials
The model must be present before building the image:
uv sync --group dev
uv run python scripts/download.py
mkdir -p sqlite/text sqlite/vector sqlite/otel
Start Postgres first, then initialise the schema:
docker compose up postgres -d
# Wait ~5 seconds for Postgres to be ready, then:
uv run python src/db/init.py
docker compose up --build
Open http://localhost:8501.
File: data/firstaidqa_v1-first-half.json
Size: 2,775 first-aid Q&A pairs
Format: JSON array of {"id": str, "question": str, "answer": str}
Source: Included in the repository — no external download required.
The full dataset (data/firstaidqa_v1.json) and clinical evaluation set (data/clinical_eval_set.csv) are also included for notebook experiments.
All experiment notebooks are in notebooks/. Each notebook adds the project root to sys.path via import path_setup so they work from the notebooks/ directory without installation.
| Notebook | Purpose |
|---|---|
01-data-gen.ipynb |
Generate ground-truth Q&A pairs |
02-sqlite-text-search-evals.ipynb |
BM25 retrieval evaluation |
03-clinical-data-gen.ipynb |
Clinical dataset generation |
04-embedding-model-evals.ipynb |
Embedding model comparison |
05-sqlite-vector-search-mode-evals.ipynb |
Vector index mode comparison |
06-sqlite-vector-num-of-results-evals.ipynb |
Vector search top-k tuning |
07-sqlite-text-search-num-of-results-evals.ipynb |
Text search top-k tuning |
08-sqlite-hybrid-search-evals.ipynb |
Hybrid search (RRF) evaluation |
09-llm-as-judge.ipynb |
LLM-as-judge answer quality evaluation |
010-agent-evals.ipynb |
Agent evaluation |
010-monitoring.ipynb |
Monitoring dashboard from OTel traces |
pre-processing.ipynb |
Dataset pre-processing |
| Variable | Required | Default | Description |
|---|---|---|---|
MISTRAL_API_KEY |
Yes | — | Mistral AI API key |
POSTGRES_HOST |
No | localhost |
PostgreSQL host |
POSTGRES_DB |
No | first_aid_qa_assistant |
Database name |
POSTGRES_USER |
No | user |
Database user |
POSTGRES_PASSWORD |
No | password |
Database password |
After cloning, run these steps in order:
[ ] uv sync
[ ] cp .env.example .env (fill in MISTRAL_API_KEY)
[ ] uv run python scripts/download.py # download ONNX model (~90 MB)
[ ] mkdir -p sqlite/text sqlite/vector sqlite/otel
[ ] docker compose up postgres -d # or start your own Postgres
[ ] uv run python src/db/init.py # create DB tables
[ ] uv run streamlit run src/app.py # start the app