Technical case study · Closed source

Q&AI: retrieval-augmented search that lands on the exact second.

Ask a public figure a question and Q&AI plays the moment in a real interview where they answer it, with the transcript quoted underneath. The language models never write the answer: they rewrite the query and choose among real passages, and a deterministic aligner maps the chosen passage back to word-level timestamps.

Role Sole developer: design, build & evaluation Code Private repository Stack Python · Flask · Chroma · Sentence-Transformers · Groq · Whisper Updated October 2026
Two live searches: Elon Musk on the risks of AI (Real Time with Bill Maher, from 16:07) and David Attenborough on why coral reefs are dying (Our Planet, from 17:10). Each clip starts at the second the answer begins; the quote below is the transcript that plays.
371videos indexed (interviews, talks, live streams)
284 hof audio · 2.4M word-timed tokens
99%+of clips start within 1 s of the answer
1.3 smean search latency, English (eval set)

Architecture

Two paths share one store. Offline, videos are discovered, transcribed with word-level timing, de-duplicated, checked for playability and embedded into one Chroma collection per person. Online, a question goes through query expansion, fused vector retrieval, LLM answer selection and timestamp alignment before the browser starts the YouTube embed at the matched second.

Offline ingestion path and online query path around a per-person Chroma store OFFLINE · INGESTION YouTube Data APIdiscover · filter Captions / Whisperword-level timing Dedupe · playability8-gram shingles · embed check Chroma · one collection per person~1000-char chunks + word timestamps ONLINE · QUERY Questionany language Query expansionLLM · 5 rewrites Fused retrievalweighted RRF · top 5 Answer selectionLLM quotes a passage Timestamp alignmentfuzzy anchors → start second vector search YouTube embed starts at t − 0.3 s

1 · Ingestion

Everything downstream depends on knowing when each word was spoken, so transcripts are stored as segments of words with start and end times:

[{"start": 16.22, "end": 20.88, "text": "You never know what's going to cause the next…",
  "words": [{"word": "You",   "start": 16.22, "end": 16.66},
            {"word": "never", "start": 16.66, "end": 16.84}, …]}]
  • YouTube captions. The json3 format carries per-word offsets, so timings come without downloading audio; they agree with Whisper to a median of 0.05 s.
  • Hand-made captions, merged in. Aligned to the recognition captions with difflib so words gain punctuation and capitals but keep their timings.
  • Whisper, when there are no captions. Audio is cut into 10-minute pieces and transcribed in parallel with whisper-large-v3 on Groq (about 6 s per 10 minutes), then stitched back with monotonic word times.

2 · Indexing

Words are packed into chunks of up to 1000 characters, with the word timings stored in each chunk's metadata. Each person is a separate Chroma collection with its own embedding model:

LanguageEmbedding modelDim.Why
Englishall-MiniLM-L6-v2384Fast; 82% of chunks fit its 256-token window
GreekBAAI/bge-m31024hit@5 of 90% on Greek questions, vs 56–61% for smaller multilingual models and 8% for MiniLM

Indexing is incremental: on startup each collection embeds only new videos, one batched call per video, and drops chunks whose transcripts were removed.

Q&AI answering a Greek question about the Fermi paradox: Astronio's video playing from 0:27 with Greek captions, and the matched Greek transcript below
The Greek collection: «Τι είναι το παράδοξο του Fermi;» lands on Astronio's explanation at 0:27, retrieved with bge-m3 and quoted in Greek.

3 · Retrieval

Query expansion. Questions and answers are phrased differently ("Why did you start SpaceX?" vs "I thought the odds of success were less than 10%"). A small LLM (gpt-oss-20b) writes five answers in the person's voice and transcript language, and these act as extra queries.

Weighted reciprocal rank fusion. The question and its rewrites each return their top 8 chunks, fused with the original question weighted three times as heavily:

\[ s(d) = \sum_{q \in Q} \frac{w_q}{k + \operatorname{rank}_q(d)}, \qquad k = 60,\quad w_{\text{original}} = 3,\quad w_{\text{rewrite}} = 1 \]

Sweeping \(w_{\text{original}} \in \{0, 1, 2, 3, 5\}\) on 150 questions, \(w = 3\) lifted hit@1 from 58% to 64% (hit@5 89%), replacing a vote count whose top-5 was full of ties.

Boundary-aware context. Each of the top 5 chunks is passed with the chunk that follows it, fixing answers that start right at a chunk boundary ("Why did you start SpaceX?" had returned just "Yes. And to").

4 · Answer selection

The answer model gets the question and five numbered excerpts and returns <excerpt>: <verbatim passage>, or a sentinel when nothing fits. It must pick the excerpt that answers the question, start at the first sentence that does, and copy contiguously without edits. Prompts and Groq-hosted models were compared on the LLM-judged harness below: qwen3.8-27b matched the best model's quality at about a seventh of its latency, with gpt-oss-120b as a fallback on rate limits, timeouts or errors.

5 · Timestamp alignment

The model returns text, not times, and doesn't copy perfectly. The first aligner matched the quote's first sentence at its earliest occurrence; answers often open with "Yeah." or "Okay.", so 11.5% of clips started more than 5 s early. The current one matches 12-word anchors at both ends of the quote, maximised over every position, with near-ties broken by a 48-word window:

# simplified
def align(quote, words):
    w = [normalize(x.word) for x in words]           # lowercase, strip punctuation and accents
    first, last = split_on_ellipsis(quote)           # "…" means the model skipped a part
    i, score = best_window(w, first[:12], tiebreak=first[:48])
    j, _     = best_window(w, last[-12:], tiebreak=last[-48:], align="end")
    if score < 60:
        return None                                  # caller tries the other retrieved chunks
    return time_of(words, i, "start", step=+1) - 0.3 # nearest word that has a timestamp

Benchmarked on 600 real transcript passages, verbatim and with perturbations that mimic the model:

PerturbationStart within 1 sStart off > 5 sEnd within 1.5 s
Exact copy87.7% → 99.8%11.5% → 0.0%89.0% → 99.8%
Fillers dropped87.0% → 99.2%11.7% → 0.2%88.8% → 99.8%
Middle skipped ("…")92.3% → 100%5.5% → 0.0%96.0% → 99.8%
Starts mid-sentence89.5% → 99.7%10.5% → 0.3%88.2% → 99.8%
Small rewording83.7% → 99.8%15.3% → 0.0%80.5% → 99.8%

6 · Evaluation

Every change was decided by an evaluation harness, not spot checks. It measures retrieval (hit@1, hit@5, MRR on 150 LLM-generated questions with a known source chunk), end to end (does the played clip fall inside the source chunk) and answer quality (two judges from different model families score directness 1–5, on generated plus real suggested questions).

ChangeMetricBeforeAftern
Vote counting → weighted RRF (\(w=3\))hit@1 / hit@558% / 86%64% / 89%150
Smaller rewrite model, batched querieslatency (accuracy)2.91 s (75%)1.34 s (75%)150
New prompt, Qwen 3.8, next-chunk contextstarts on point50%73%111
New prompt, Qwen 3.8, next-chunk contextgood answer (≥ 4/5)66%74%111
MiniLM → bge-m3 (Greek)hit@58%90%100

The key finding: after the retrieval fixes, end-to-end accuracy didn't move. The bottleneck had become whether the right chunk reaches the top 5 and whether the model picks it, which moved the effort to answer selection and context.

7 · Performance

Stage (live searches)English (MiniLM)Greek (bge-m3)Why Greek is slower
Query expansion0.73 s1.96 s~2.5× more tokens per rewrite
Embedding + vector search0.68 s1.49 s~25× larger embedding model
Answer selection0.61 s0.70 sabout the same
Total2.05 s4.38 s
The Q&AI page for Astronio in Greek, with a Greek question and Greek suggested questions
Per-person page text and suggested questions, here in Greek.

8 · Serving and operations

Flask on waitress, with Chroma collections shared across request threads. Searches are rate-limited per visitor and site-wide, since each one costs several LLM calls. People are configuration, not code (transcripts, embedding model, language, persona, suggested questions), so adding the Greek collection needed no new code paths. Lint, lock-file checks and unit tests run on every push in GitHub Actions.

Next steps

  • Cross-encoder re-ranking of a wider candidate set, since most remaining errors are the right chunk missing from the top 5.
  • Sentence-index answers: the model returns a sentence range instead of copying text, giving exact start and end times with no fuzzy matching.
  • Speaker attribution, so a clip never starts on the interviewer's question.
The code is in a private repository, so there's no public link. I'm happy to walk through the architecture or give a live demo: get in touch.