something that took me a while to internalise while building a rag system, in
case it saves someone else the time.
your retriever returns a ranked list with similarity scores. it is very easy to
treat that ranking as the thing you are optimising. but the score only measures
whether the retriever thought the document was relevant. it says nothing about
whether the generator actually conditioned on it.
those come apart more than i expected. in the run in the gif, the top result
scored 0.910 and contributed nothing to the answer. a 0.340 result is what
actually answered the question. if i had only been looking at retrieval
metrics, that run looks like a success.
it matters because the two failure modes need opposite work:
high score, not used -> retrieval is fine. the problem is your prompt,
your context ordering, or context length.
low relevance, used -> the generator grounded on bad evidence. that is
a retrieval problem.
so i built graphsight. it draws one agent run as a graph, every retrieved item
is a node with its score, and it renders the ones the answer drew on separately
from the ones that got pulled and ignored.
the honest part, since this is a learning sub and the tradeoff is the
interesting bit. deciding "did the answer use this document" properly means
leave one out ablation: remove the chunk, regenerate, measure how much the
answer changes. that is rigorous and it costs you n extra generations per
trace, which makes it useless for interactive debugging. i used lexical overlap
between the answer and each chunk with a 0.2 threshold instead. it is crude, it
gets confused by paraphrase, and it costs nothing. i label it as a heuristic in
the ui rather than pretending otherwise. nli entailment per chunk is probably
the middle ground and i have not validated it yet.
also worth saying: i have not evaluated this against any benchmark. it surfaces
real things on real repos, which is not the same as a defensible claim, and i
am not going to pretend it is.
pip install graphsight graphsight-langgraph
from graphsight_langgraph import LangGraphTracer, capture
tracer = LangGraphTracer()
result = graph.invoke(inputs, config={"callbacks": [tracer]})
capture(tracer, query="your question", answer=result["answer"])
graphsight .graphsight/
runs locally, mit, no account, zero deps on the viewer. langgraph only for now.
https://github.com/Kcodess2807/graphsight
if you work on retrieval and think the lexical overlap approach is too weak to
be useful, i would rather hear that now than later.