RetrievalInspector
DevReact · Next.jsA collapsible developer panel showing exactly which chunks were retrieved (rank, source, page, score, text) and the retrieval latency. Drop it under an answer during development to debug retrieval quality.
<RetrievalInspector />Live preview
query: how does hybrid retrieval work?
Retrieval-augmented generation grounds an LLM's answer in retrieved documents, reducing hallucination by constraining the model to provided context.
Hybrid retrieval combines dense vector similarity with BM25 lexical matching, so both semantic paraphrases and exact keywords/identifiers are caught.
A cross-encoder reranker re-scores the top candidates before they reach the LLM, lifting precision and trimming the context window.
Chunk size and overlap materially affect answer quality; tune them to your document structure rather than using a global default.
Installation
No dependencies to install — just React + Tailwind CSS. It's a presentational component (no data fetching), so the same source works unchanged in React and Next.js (App or Pages Router) — there's no separate variant. Copy it into your project (e.g. components/RetrievalInspector.tsx) and import it.
Usage
import { RetrievalInspector } from "@/components/RetrievalInspector";
export default function Example() {
return (
<RetrievalInspector sources={result.sources} query={q} latencyMs={result.latency_ms} />
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
sourcesrequired | Source[] | — | The retrieved chunks from /query. |
query | string | — | The query that produced these results. |
latencyMs | number | — | Retrieval latency to display. |
defaultOpen | boolean | true | Start expanded. |
Component source copy-paste ready
Presentational component — no data fetching, so the same source works unchanged in React and Next.js. There's no separate Next.js variant to copy.
"use client";
import { useState } from "react";
export interface Source {
content: string;
source?: string;
page?: number;
score?: number;
}
interface RetrievalInspectorProps {
sources: Source[];
query?: string;
latencyMs?: number;
/** Start expanded. Default: true. */
defaultOpen?: boolean;
className?: string;
}
/**
* RetrievalInspector — a developer panel that shows exactly which chunks were
* retrieved (rank, source, score, text) before generation. Drop it under an
* answer during development to debug retrieval quality.
*/
export function RetrievalInspector({ sources, query, latencyMs, defaultOpen = true, className = "" }: RetrievalInspectorProps) {
const [open, setOpen] = useState(defaultOpen);
return (
<div className={`overflow-hidden rounded-xl border border-zinc-200 bg-white font-mono text-xs dark:border-zinc-800 dark:bg-zinc-950 ${className}`}>
<button
type="button"
onClick={() => setOpen(!open)}
className="flex w-full items-center justify-between gap-2 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800"
>
<span className="flex items-center gap-2 font-semibold text-zinc-600 dark:text-zinc-300">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ transform: open ? "rotate(90deg)" : "none" }}><path d="m9 18 6-6-6-6" /></svg>
Retrieved context
<span className="rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:bg-zinc-800">{sources.length}</span>
</span>
{typeof latencyMs === "number" && <span className="text-zinc-400">{latencyMs} ms</span>}
</button>
{open && (
<div className="max-h-72 overflow-y-auto">
{query && <p className="border-b border-zinc-100 px-3 py-1.5 text-zinc-400 dark:border-zinc-900">query: {query}</p>}
{sources.map((s, i) => (
<div key={i} className="border-b border-zinc-100 px-3 py-2 last:border-0 dark:border-zinc-900">
<div className="mb-1 flex items-center justify-between text-[11px]">
<span className="text-zinc-500">#{i + 1} · {s.source || "unknown"}{s.page != null ? `:${s.page}` : ""}</span>
{typeof s.score === "number" && <span className="text-emerald-500">{s.score.toFixed(3)}</span>}
</div>
<p className="leading-relaxed text-zinc-600 dark:text-zinc-400">
{s.content.slice(0, 240)}{s.content.length > 240 ? "…" : ""}
</p>
</div>
))}
</div>
)}
</div>
);
}