Skip to content

DoomDust7/interview-intelligence-engine

Repository files navigation

Interview Intelligence Engine

A demo built for my application to Great Question — a mini version of the platform's core value proposition: making sense of qualitative interview data at scale.

What it does: Load customer research transcripts → search them semantically → stream AI-synthesized insights via Claude.

This directly mirrors GQ's "semantic search across tens of thousands of interview hours" challenge.


Quick start

# 1. Clone and install
pip install -r requirements.txt

# 2. Set API keys
cp .env.example .env
# Edit .env — add ANTHROPIC_API_KEY (required) and VOYAGE_API_KEY (optional but recommended)

# 3. Pre-compute the vector index (already done — index.pkl is committed)
# Only re-run if you change transcripts.json or switch embedding mode:
python build_index.py

# 4. Run
uvicorn app.main:app --reload
# Open http://localhost:8000

Docker:

cp .env.example .env  # fill in keys
docker compose up

Deploy to Railway/Render: Connect repo, set ANTHROPIC_API_KEY and VOYAGE_API_KEY as env vars. The committed index.pkl means no embedding API call is needed at startup — cold start is under 3 seconds.


Architecture

transcripts.json
      │
      ▼
Turn-level chunker ──▶ Voyage AI (voyage-3-lite, 512-dim)
      │                     │
      │              numpy matrix (pre-normalized)
      │                     │
      ▼                     ▼
  index.pkl ◀──────────────── cosine search (single matmul)
                               │
              Query ──embed──▶ Top-K chunks
                               │
                               ▼
                        Claude (claude-sonnet-4-6)
                        streaming synthesis
                               │
                               ▼
                          SSE → browser

Design decisions

Why Voyage AI for embeddings, not sentence-transformers

sentence-transformers requires PyTorch (~2GB), CUDA drivers, and a model download on first run — all three are demo-killers. Voyage AI (voyageai package: ~50KB pip install) is Anthropic-acquired and purpose-built for retrieval. voyage-3-lite is state-of-the-art at this scale.

If VOYAGE_API_KEY is not set, the app falls back to TF-IDF with hash projection to 512 dimensions. The /health endpoint tells you which mode is active. The eval script (evals/eval_search.py) measures the quality gap.

Why numpy, not FAISS or ChromaDB

At ~100 chunks, a vectorized dot product on a pre-normalized matrix completes in under 1ms. FAISS pays off at 100K+ vectors; ChromaDB adds a server process and persistence complexity. The numpy approach is honest about scale: if this were production GQ with 50M interview minutes, you'd switch to Qdrant.

Why turn-level chunking, not sliding window

Participant turns are natural semantic units in interview transcripts. Sliding windows would split quotes mid-sentence and mix context from different topics. Interviewer turns are excluded from the index — they're context, not insight.

Why SSE streaming for insights

Long synthesis responses feel stuck without it. The marked.js renderer handles incremental markdown gracefully. The frontend uses the native EventSource-style reader (not the EventSource API, since this is a POST request) to consume the stream token by token.

Why structured output for search, prose for insights

POST /search returns a typed Pydantic SearchResponse — machine-to-machine, consumed by the frontend's result renderer. POST /insights streams markdown prose — human-facing, consumed by a researcher's eyes. Right tool for each job.

Why Claude gets structured excerpts, not raw transcripts

Passing curated JSON with relevance scores and participant metadata produces better synthesis than pasting raw text. The system prompt has explicit grounding rules that prevent hallucination: every claim must be traceable to a specific quote. Context quality drives output quality.


Eval: retrieval quality

python evals/eval_search.py

Reports precision@1, @3, @5 over 8 labeled queries. TF-IDF baseline: MAP@1=0.25, MAP@3=0.38. Run again after setting VOYAGE_API_KEY to see the semantic improvement.


What I'd add for production

  • Persistent vector DB (Qdrant) with async batch re-indexing as new sessions come in
  • Speaker diarization pipeline — Whisper → diarized transcript → automatic ingest
  • RAGAS eval framework for retrieval + generation quality at scale
  • Webhook system — trigger Slack summaries or project tracker updates when a study completes
  • Auth layer (JWT) with per-user transcript isolation
  • MCP tools exposing search and synthesis as Claude-callable functions, so researchers can query their insight library from any Claude interface

API reference

Method Path Description
GET / Frontend
GET /health Status, index size, embedding mode
GET /transcripts List transcript metadata
GET /transcripts/{id} Full transcript with turns
POST /search { query, top_k } → ranked chunks
POST /insights { query, chunk_ids } → SSE stream
POST /upload Multipart form → ingest + re-index

Stack

  • Backend: Python 3.13, FastAPI, Uvicorn
  • Embeddings: Voyage AI voyage-3-lite (TF-IDF fallback)
  • Vector search: numpy cosine similarity
  • LLM: Anthropic claude-sonnet-4-6 (streaming)
  • Frontend: Vanilla HTML/CSS/JS, marked.js for markdown rendering
  • Deploy: Docker / Railway / Render

About

Semantic search + AI synthesis over customer research transcripts — demo for Great Question AI internship

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages