Fabrikam: RAG quality and latency in field support
Fabrikam's field engineers ask questions about 250,000 support articles, service bulletins and 12-language PDFs. A prototype sends the user question to 'top 3' vector results and prompts a GPT-4o deployment to 'answer from context'. Pilot findings over 600 sessions: 31% of answers have no usable citation, reviewers find unsupported claims in 18% of answers, and 40% of sessions contain follow-ups like 'and for the 2021 model?' which the prototype answers using documents about the wrong model year. P95 latency is 4.8 s against a 3 s target, roughly half of it in the model. Content is re-indexed nightly, and engineers report stale answers for a full day after a bulletin changes. Volume is about 2,000 queries/day.
1. Redesign retrieval so follow-up questions and stale documents are handled. Be specific about queries, ranking, chunking and indexing cadence.
Read the worked answer
Follow-ups: rewrite before you retrieve The vector query is built from a bare follow-up ('and for the 2021 model?'), which is semantically empty. Add a cheap rewrite step (a small model, not the answering model) that turns the last few turns plus the new question into a standalone query. Keep the original text as the keyword term too, so exact part numbers are not lost to paraphrase.
Ranking: hybrid, then rerank
- Vector + BM25 keyword retrieval combined with reciprocal rank fusion. Pure vectors miss exact identifiers (bulletin numbers, SKUs); keyword alone misses paraphrase.
- Apply the semantic ranker to the fused top ~50 and send only the best 5-8 chunks to the model. Sending 3 raw vector hits is the root cause of thin, uncited answers.
Metadata and filters Index model year, product line, doc type, language and validity window as filterable fields; when a question names a model, filter on it instead of hoping retrieval infers it. For 'current' questions filter on the validity window rather than trusting recency ranking.
Chunking 500-800 tokens with 10-15% overlap, each chunk prefixed with document title and heading path so a chunk is intelligible in isolation, and each carrying document ID, version and page.
Staleness
A nightly full rebuild guarantees up to 24 h of wrong answers. Move to incremental indexing: push mergeOrUpload as bulletins publish, or use an indexer with a change-detection policy. Give superseded documents an end date and filter them out rather than deleting history you may need to cite.
Multi-language: use a multilingual embedding model into one index with a language filter, or per-language indexes if ranking quality matters more than operational simplicity.
2. Cut unsupported claims and guarantee a citation for every answer. What do you change, and how do you prove it worked?
Read the worked answer
Grounding is a pipeline property, not a prompt trick
- Give the model retrieved chunks with stable IDs and require inline citations by ID plus an explicit 'insufficient evidence' path with an escalation option. The escape hatch matters: a model with no permitted way to say 'I don't know' will invent.
- Post-validate deterministically: every cited ID must exist in the retrieved set. This is cheap, catches fabricated citations that no prompt eliminates, and converts '31% lack a citation' into a measurable gate that blocks the response.
- Add a retrieval relevance gate: if the reranked top score is below a threshold, skip generation and return search results or a handoff. Many unsupported answers are answers to questions whose evidence was never retrieved.
Why not fine-tuning: fine-tuning shapes style and format. The missing facts live in the index, and tuning on 250k documents is neither affordable nor updatable nightly.
Fix the instruction set: 'Answer only from context; cite chunk IDs; if the context does not contain the answer, say so; do not use prior knowledge' plus two or three few-shot examples including a refusal example.
Proving it
- Build a labelled set of ~300 real questions with expected source documents.
- Score per change: groundedness, answer relevance, and retrieval recall@k (separately - if recall is low, no prompt fixes the answer).
- Run it in CI on every prompt, chunking or index change; a prompt edit that improves tone can silently cost 5 points of groundedness.
The 18% unsupported rate is a measurement problem first: without the labelled set you cannot tell whether a change helped.
3. Reach a 3 s P95 target. Where do you look, and which levers are real versus cosmetic?
Read the worked answer
Break the 4.8 s into stages first (rewrite, retrieval, rerank, model TTFT, model generation). Roughly half is model, so retrieval tuning alone cannot get there.
Real levers
- Use the small model for query rewriting; it does not need the answering model's quality.
- Retrieve 50, rerank, send 5-8 chunks. Fewer, better chunks cut prompt tokens, which cuts both latency and cost.
- Deduplicate near-identical chunks and truncate boilerplate headers - engineers' PDFs repeat the same legal footer in every chunk.
- Cap
max_tokens; answers that ramble past the useful point are latency you paid for. - Cache: a semantic or exact cache for the frequent question set typically absorbs a large share of 2,000 daily queries.
- Run the rewrite and any metadata lookup concurrently rather than in sequence.
Cosmetic but valuable Streaming does not reduce total time - it reduces perceived time to first token. Clarify whether the 3 s target is TTFT or full completion; if it is TTFT, streaming plus a faster first step may already satisfy the business, and you avoid an expensive redesign.
Check the network path: if the app, Search and the model are in different regions, cross-region round trips add fixed overhead per query. Co-locate them.
Set the target per stage so a regression is attributable, and re-measure after each change rather than shipping all levers at once.