Evaluation First: Building a Golden Set, Computing Recall and Ranking Metrics, Using a Model as Judge for Faithfulness
Every optimization in week two needs to be backed by data, so build evaluation first. Today construct a golden question-answer set from scratch, implement three retrieval metrics — recall, Mean Reciprocal Rank, and Normalized Discounted Cumulative Gain — use a model as judge for faithfulness and answer relevance, then run a baseline report on week one's system.
Today's Goals
- Construct a golden set from real corpus data, and explain why it must include unanswerable questions and multi-hop questions
- Implement recall, Mean Reciprocal Rank, and Normalized Discounted Cumulative Gain, and explain what each is sensitive to
- Design a model-judge prompt and verify it's trustworthy with manual spot checks, rather than trusting its scores unconditionally
Week one got the system running and left one question unanswerable throughout: is it any good? Today installs that scale. Come back and tick off the three goals.
Plain-Language Walkthrough
No scale, no dieting
Somebody resolves to lose weight, switches to salads in month one, starts running in month two, and gives up late-night snacks in month three. Are they thinner after three months? They cannot say — there is no scale in the house. They can only judge by their trousers feeling looser, and that judgment is remarkably accurate on days when their mood is good.
At the end of week one your retrieval question-answering system is in that state. D4 changed the chunking, D5 tuned the index parameters, D7 fused two retrieval paths, and at each step it felt like it should be better. But if I ask now whether raising the vector path's weight from 0.6 to 0.8 makes it better or worse, all you can do is guess.
A scale's value is not telling you your current weight but turning "did this change help" into an answerable question. So week two's first day performs no optimization — it builds the scale first. From today, questions like "should we add reranking," "how large should chunks be," and "do we need query rewriting" have exactly one legitimate answer: run the evaluation and read the numbers.
A scale needs two things: a fixed set of subjects and a set of rules for reading it. The former is the golden set and the latter the metrics. Unfortunately neither is available off the shelf — public benchmark sets are all Wikipedia question answering with no relation whatsoever to your company knowledge base; and metrics come with a pile of ready-made names, the wrong choice among which lets the numbers rise while the user experience falls.
One last thing that gets skipped most easily: building the scale has a cost. Labeling answer documents for 20 questions took me two hours in the lab. Many teams skip it on the grounds of getting something working first, and three months later the system has twenty versions and none of them can say what it is good at — that did not save two hours, it turned three months of work into something unverifiable.
Building a golden set: writing questions backwards from the corpus
The direction for constructing a golden set is the opposite of most people's instinct. The instinct is to imagine what users will ask and then check whether the corpus has it; the correct approach is the reverse: open the corpus and read out of each document what it can answer.
The reason is simple: the first approach produces a pile of questions whose answers you do not know, leaving nowhere to start when labeling; the second fixes the answer document at the moment you write the question. The lab's 20 questions came out that way, with every answerDocIds being the document I was reading while writing it.
One entry looks like this:
{
"id": "q17",
"question": "Temporarily raising a workspace's trace sampling rate to 100% is what change class? How many working days in advance must the ticket be filed under the standard?",
"answerDocIds": ["doc-022", "doc-021"],
"type": "multi"
}That type field decides how a question counts as hit, in three classes, and missing one leaves your scale blind:
single: one document answers it. With several answer documents listed, any one entering the context counts as a hit.multi: multi-hop, requiring all answer documents in the context to count. Gathering half is the same as not answering, and counting it as half a hit is a consolation prize you awarded yourself.none: the corpus contains no answer. It does not enter recall; it tests whether the system stays quiet.
The third is the most easily skipped and the least skippable. The reason is this: a system that only ever answers scores near full marks on an evaluation set containing only answerable questions — it stuffs material in every time and the model invents a passage every time, and your evaluation set has no should-have-refused column, so that fatal flaw is entirely invisible in the report. Unanswerable questions are the only thing that makes fabrication visible.
Writing unanswerable questions has its craft too: strong distractor terms are mandatory. The lab asks which browsers the web client supports and what minimum versions are required, and the corpus happens to contain "please include your browser and version when filing a ticket"; it asks how much CPU and memory an on-premises deployment needs, and the corpus contains both "on-premises deployment" and "CPU and memory" while never stating hardware specifications. An unanswerable question with no distractors is too easy — the retriever fetches nothing and what you measured was the tokenizer rather than the system.
For coverage, a ratio you can copy directly: start at 20 questions, with at least 3 multi-hop and at least 3 unanswerable, and the rest single-document. Twenty is enough to separate unusable from broadly usable; a production system wants 100 to 200, with every production incident's question added to it — an evaluation set is grown.
Three retrieval metrics, each sensitive to a different failure
Only three metrics are computed on the retrieval side. They are not three flavors but three layers of different failures.
Recall asks whether the answer entered the context. Note the criterion: entering the context is not ranking in the top 5 but genuinely fitting inside a 600-token context budget. Comparing by a fixed count is unfair — large chunks fit two thousand-odd tokens in five and small ones only a few hundred, and the winner merely bought more goods. That criterion matches D4 exactly, so the two days' numbers can sit together.
Mean reciprocal rank asks where the first relevant result ranks. First place scores 1, second 0.5, tenth 0.1, none 0, averaged over all questions. It is extremely sensitive to the answer still being present and merely pushed back — something recall cannot see at all until the budget bursts.
Normalized discounted cumulative gain asks how good the whole top ten is. Each position's gain is discounted by rank, discounted harder further down, then divided by an ideal ordering's score to normalize into 0 to 1. Its difference from reciprocal rank: reciprocal rank recognizes only the first result, and one hit plus nine pieces of garbage looks identical to five hits in its eyes, while this metric counts every one of the top ten. Reranking optimizes exactly this one.
The three implementations are all in the lab's metrics.ts, and the core is only this long:
// Recall: single hits when any document enters the context, multi requires all of them
export function isHit(t) {
if (t.type === 'none') return false
if (t.type === 'multi') return t.answerDocIds.every((id) => t.contextDocIds.includes(id))
return t.answerDocIds.some((id) => t.contextDocIds.includes(id))
}
// Reciprocal rank: the reciprocal of the first relevant result's rank.
// Indexes start at 0 and ranks at 1; being one off makes every score too high
export function reciprocalRank(t) {
const rank = t.rankedDocIds.findIndex((id) => t.answerDocIds.includes(id))
return rank === -1 ? 0 : 1 / (rank + 1)
}
// nDCG: each of the top k discounted by 1 / log2(rank + 1), divided by the ideal ordering's score
export function ndcgAt(t, k = 10) {
const gains = t.rankedDocIds.slice(0, k).map((id) => (t.answerDocIds.includes(id) ? 1 : 0))
const dcg = gains.reduce((sum, g, i) => sum + g / Math.log2(i + 2), 0)
let idcg = 0
for (let i = 0; i < Math.min(t.relevantChunkCount, k); i += 1) idcg += 1 / Math.log2(i + 2)
return idcg === 0 ? 0 : dcg / idcg
}from math import log2
def is_hit(t: dict) -> bool:
"""Recall: single hits on any document, multi requires all of them"""
if t["type"] == "none":
return False
if t["type"] == "multi":
return all(doc_id in t["context_doc_ids"] for doc_id in t["answer_doc_ids"])
return any(doc_id in t["context_doc_ids"] for doc_id in t["answer_doc_ids"])
def reciprocal_rank(t: dict) -> float:
"""The reciprocal of the first relevant result's rank. enumerate starts at 1, sparing an off-by-one"""
for rank, doc_id in enumerate(t["ranked_doc_ids"], start=1):
if doc_id in t["answer_doc_ids"]:
return 1 / rank
return 0.0
def ndcg_at(t: dict, k: int = 10) -> float:
"""Each of the top k discounted by 1 / log2(rank + 1), divided by the ideal ordering's score"""
gains = [1 if d in t["answer_doc_ids"] else 0 for d in t["ranked_doc_ids"][:k]]
dcg = sum(g / log2(i + 2) for i, g in enumerate(gains))
idcg = sum(1 / log2(i + 2) for i in range(min(t["relevant_chunk_count"], k)))
return dcg / idcg if idcg else 0.0The unanswerable class enters none of those three and has its own: the refusal rate — of four unanswerable questions, how many the system genuinely said it could not find in the material for. The lab's baseline comes out like this:
Recall (answerable) 87.5%
Recall (single) 100.0%
Recall (multi) 50.0%
Mean reciprocal rank 1.0000
nDCG@10 0.6711
Refusal rate 0.0%That set of numbers is informative. Single-document questions all hit while multi-hop hits only half — the "who approves a failover and what is their name" question never fetched the second document once. And the refusal rate is 0%: not one of four unanswerable questions stayed quiet, and all four were stuffed with irrelevant material. Counting recall alone, 87.5% looks quite respectable.
Two generation-side metrics: did it invent, did it answer
Retrieval finding the right thing still leaves the model able to twist it. Two generation-side metrics, and they must be read as a pair.
Faithfulness: for every statement in the answer, is there support in the material? It catches fabrication. The algorithm splits the answer one statement per line, judges each, and takes supported over total.
Answer relevancy: did the answer address the question? It catches saying a great deal without answering.
Why they must pair: "the material contains nothing that supports an answer" scores full faithfulness — it invented not one word — with zero relevancy. Watch faithfulness alone and your system learns that always refusing scores full marks. Conversely, watching relevancy alone, a well-argued and entirely on-topic falsehood also scores highly. These two metrics watch each other.
The lab's baseline has one pair particularly worth reading: faithfulness 1.0000 and answer relevancy 0.3931. Faithfulness is perfect because offline mode's "answer" is sentences lifted from hit chunks, verbatim from the material and therefore never invented; and relevancy at 0.39 says what was lifted is mostly not the sentence the question wanted. High faithfulness does not mean a correct answer, and that is this section's most memorable line.
Per-statement judging looks like this:
export function faithfulness(answer, contextText) {
const unsupported = []
let decidable = 0
for (const claim of claimsOf(answer)) {
const ratio = overlapRatio(claim, contextText)
if (ratio === null) continue // the undecidable does not enter the denominator; see the pothole below
decidable += 1
if (ratio < MIN_OVERLAP) unsupported.push(claim)
}
return { score: decidable === 0 ? 0 : 1 - unsupported.length / decidable, unsupported }
}def faithfulness(answer: str, context_text: str) -> tuple[float, list[str]]:
unsupported: list[str] = []
decidable = 0
for claim in claims_of(answer):
ratio = overlap_ratio(claim, context_text)
if ratio is None: # the undecidable does not enter the denominator
continue
decidable += 1
if ratio < MIN_OVERLAP:
unsupported.append(claim)
score = 0.0 if decidable == 0 else 1 - len(unsupported) / decidable
return score, unsupportedThe pitfalls of a model as judge
A judgment like faithfulness ultimately needs a model — send the material, the question, and the answer together and have it score. That is LLM-as-judge. It is cheap, fast, and scalable, and it has three systematic biases you must know.
Position preference. Asked to choose between two answers, it leans toward the one presented first. The mitigation is not doing pairwise comparison and instead scoring each independently; when comparison is unavoidable, swap the two answers' order and run again, calling any disagreement a draw.
A preference for long answers. Given the same information, the longer write-up scores higher more easily. The mitigation is the previous section's per-statement judging: faithfulness is supported over total, so a longer answer has a larger denominator and every extra sentence must earn its own support, removing the length bonus automatically.
Judging itself. Using one model to both generate and judge makes it more lenient toward its own output. The mitigation is switching vendor or tier: generate with claude-sonnet-5 and judge with another vendor's model; on a tight budget claude-haiku-4-5-20251001 as judge is fine too, and after switching a manual spot check must be redone.
Beyond those three the prompt itself has two requirements: pin down the scoring anchors (what 1.0 means, what 0.6 means, what 0.3 means, since without anchors it scores differently every day), and require structured output listing the unsupported sentences verbatim. The second is for the manual spot check — given only 0.83 you cannot review anything, and with the disputed sentences listed you can judge in thirty seconds whether the judge was right.
And the only way to calibrate is a manual spot check computing agreement:
- Run an evaluation, open the report, and sample 10 in a stratified way — every type represented, hits and misses both, and both high and low judge scores.
- Answer one question per item: does this answer contain anything not written in the material? Judge faithfulness only, not quality.
- Write your judgments into
human-labels.json, compare against the judge's verdicts, and compute agreement. - Below 0.8, stop using that judge's scores to block a merge, go improve the prompt, and resample after improving it.
Wiring evaluation into the process
Writing the evaluation script is only step one; it has to become one command, one file, and one gate, or nobody will run it again three weeks later.
One command: pnpm start runs the whole suite and outputs reports/latest.json plus a human-readable Markdown. One file: save the version you accept as eval/baseline-report.json, the anchor for every later comparison. One gate:
// Watch five, each guarding a layer: recall guards finding it, reciprocal rank and nDCG guard
// ordering it, faithfulness guards not inventing, and the refusal rate guards staying quiet.
// Drop one and a class of regression slips through
export function gate(current, baseline, tolerance = 0.01) {
const regressions = METRICS.map(({ name, pick }) => ({
name,
delta: Number((pick(current) - pick(baseline)).toFixed(4)),
})).filter((r) => r.delta < -tolerance)
return { passed: regressions.length === 0, regressions }
}def gate(current: dict, baseline: dict, tolerance: float = 0.01) -> tuple[bool, list[dict]]:
"""Watch five, each guarding a layer: finding it, ordering it, not inventing, staying quiet"""
regressions = [
{"name": name, "delta": round(current[key] - baseline[key], 4)}
for name, key in METRICS
if current[key] - baseline[key] < -tolerance
]
return not regressions, regressionsRun it in continuous integration and let a regression fail the pipeline:
MOCK=1 GATE=1 pnpm start # exits non-zero when any metric regressesDo not set the tolerance large. The same data with the same configuration should be bit-identical, and 0.01 exists only to allow for a change of embedding backend; set it to 0.05 and a genuine regression strolls straight through.
Finally, cost. Twenty questions through a model judge costs a few cents, so run it freely; at 200 questions ten times a day, somebody should be watching the bill. The common practice has two tiers: every commit runs only the retrieval metrics (pure local computation, seconds, free), with the model judge reserved for pre-merge and once nightly. Evaluation itself reports three bills too: the metric, the latency, and the money.
Source Reading
Hands-On Lab
starter/ has 5 exercise points cut out: the three retrieval metrics, faithfulness's per-statement judging, and the regression gate. Run as-is, 5 acceptance items are ❌ and each one you finish turns one ✅ — today's progress bar. The corpus and the 20-question golden set are prepared, and eval/golden-set.json's first 10 questions are verbatim D4's, so do not change one word; changing them makes the two days' numbers incomparable.
- Open
eval/golden-set.json, read all 20 questions, spot-check three againstcorpus/to confirm the answer documents genuinely answer them, and confirm the four unanswerable questions genuinely have no answer in the corpus. - Implement the three retrieval metrics in
src/metrics.ts, run once, watch recall go from 0.0% to a meaningful number, and note the gap between single and multi. - Add faithfulness's per-statement judging in
src/judge.ts, run once, and watch section four's judge probe go from ❌ to ✅. - Follow the README to sample 10 in a stratified way, rewrite
eval/human-labels.json, rerun, and read section five's agreement and disagreement list. - Wire the regression gate into
src/report.ts, run once to confirm the gate blocked, then useMOCK=1 GATE=1 pnpm startto feel the non-zero exit code on a regression.
Interview Questions
Today's 4 questions are in the question bank below, weighted toward constructing an evaluation set, choosing retrieval metrics, and a model judge's reliability and biases. Expand each one and read the analysis before the key points — practicing the derivation beats memorizing bullets. The high-frequency labels for the domestic and overseas markets are there so you can pick by target market.
Checklist and Tomorrow
- Construct a golden set from real corpus data, and explain why it must include unanswerable questions and multi-hop questions
- Implement recall, Mean Reciprocal Rank, and Normalized Discounted Cumulative Gain, and explain what each is sensitive to
- Design a model-judge prompt and verify it's trustworthy with manual spot checks, rather than trusting its scores unconditionally
- Say which failure makes each of recall, reciprocal rank, and nDCG fall first
- Explain why faithfulness and answer relevancy must be read as a pair, and how watching one alone gets you fooled
- All 5 acceptance criteria of the lab pass, with a baseline report for week one's system in hand
- Answer at least 3 of the 4 interview questions without looking at the key points
Tomorrow (D9) brings week two's first optimization: hybrid search and reranking — fusing two retrieval paths with reciprocal rank fusion, then reranking the top few dozen with a cross-encoder. The reason for evaluating before optimizing: hybrid search's benefit depends entirely on the corpus and the question distribution, gaining a dozen points in some settings and not one point or even regressing in others. Without today's baseline, all you could say tomorrow is that it feels a bit better.
Interview questions
You need to build an evaluation set from scratch for a RAG system over a company knowledge base. How would you do it, and how many questions are enough?让你从零给一个公司知识库的 RAG 系统建评估集,你会怎么做?多少题才算够用?
Common in ChinaCommon overseasIntermediate#evaluation#golden-set#ragHow to reason about it · think before answering
- The discriminator here is the direction you generate questions in, and whether you can justify a size rather than name one.
- Go corpus-first: read each document and write the questions it can answer. The answer document is fixed at authoring time, so labeling is nearly free. Question-first gives you items whose answers nobody can locate.
- Give the schema: question, answer document ids, and a type. At minimum three types - single-document, multi-hop, and unanswerable. Multi-hop counts as a hit only when every answer document makes it into the context; unanswerable items are scored on abstention, not recall.
- Justify the size: 20 items separate 'broken' from 'usable' and are enough for a smoke gate; 100 to 200 are needed before a two-point delta means anything. Then grow the set - every production failure becomes a new item.
- Mention cost and decay: roughly two hours for 20 items, and answer labels must be rechecked whenever the corpus changes, or the set rots and you misread the drop as a system regression.
- Expected follow-up: how do you avoid overfitting to the eval set? Keep a held-out slice that never informs tuning, and refresh it from real production questions.
分析过程 · 先想清楚再作答
- 这题的区分度在「出题方向」和「规模的理由」两处。开口就说「找几百个用户真实问题」的,多半没真做过——真实问题的答案在哪篇文档里,没人标得出来。
- 先给方向:从语料反向出题,打开每一篇读它能回答什么,出题的那一刻答案文档就已经确定了,标注成本几乎为零。反方向(先想问题再找答案)会得到一堆自己都不知道答案的题。
- 再给结构:每题记问题、答案文档列表、类型三个字段;类型至少分单文档、多跳、无答案三类,并说明多跳必须全部答案文档命中才算命中,无答案不参与召回率而是考拒答。
- 规模的理由要给出来,不能只报一个数字:20 题能把「完全不能用」和「基本能用」分开,够做冒烟;100 到 200 题才有资格判断「涨了两个点」是真的还是噪声。上线之后每次线上出问题就把那个问题补进集合——评估集是长出来的。
- 补一句成本与保鲜:出题是人力活,20 题两小时是正常量级;语料更新后要复核答案文档还在不在,否则集合会悄悄腐烂,指标下跌你会误以为是系统坏了。
- 可预期的追问是「怎么防止评估集被过拟合」。答案是留一份不参与调优的保留集,并且定期从线上真实问题里补充新题,只用来验收不用来调参。
Key points
- Author corpus-first so the answer document is known at authoring time.
- Label every item with a type: single-document, multi-hop, unanswerable.
- Multi-hop requires all answer documents; unanswerable items score abstention, not recall.
- 20 items for a smoke gate, 100 to 200 to trust small deltas, and keep growing it from production failures.
- Hold out a slice that never informs tuning to avoid overfitting the set.
答题要点
- 从语料反向出题,出题时答案文档就已确定,标注成本最低。
- 每题标类型:单文档、多跳、无答案,三类缺一不可。
- 多跳要求全部答案文档命中;无答案不算召回率,考的是拒答。
- 20 题够冒烟,100 到 200 题才能判断小幅变化;线上故障持续补题。
- 留一份不参与调优的保留集,防止对评估集过拟合。
Recall, mean reciprocal rank, and normalized discounted cumulative gain - which failure mode does each one catch first, and what do you miss by watching only one?召回率、平均倒数排名、归一化折损累计增益,这三个检索指标分别在什么故障下会先掉下来?只盯一个会漏掉什么?
Common in ChinaCommon overseasIntermediate#retrieval-metrics#evaluation#rankingHow to reason about it · think before answering
- This tests whether you know each metric's blind spot, not whether you can recite definitions. Layer them as 'did it show up / how high / how good overall' and you are halfway there.
- Recall is boolean: is the answer document in the final context. It catches 'never retrieved', but it does not move when the answer slips from rank 1 to rank 8, as long as it still fits the budget.
- MRR looks only at the rank of the first relevant hit, so ranking degradation shows up immediately. Its blind spot: one relevant item in the top ten scores exactly the same as five.
- nDCG discounts every relevant hit in the top k by its position, so it tracks overall ranking quality and is the direct optimization target for reranking. Its blind spot is existence - it is zero both when nothing was retrieved and when ranking is terrible.
- Conclusion: together they localize the failure. Recall drops means retrieval or chunking; recall flat but MRR down means ranking degraded, reach for a reranker; both stable but nDCG down means more noise crept into the top results.
- Expected follow-up: what if a metric saturates? Make the questions harder - a saturated metric means the eval set lost its discriminative power, and further tuning is blind.
分析过程 · 先想清楚再作答
- 这题考的是「知不知道指标之间的盲区」,不是背定义。能把三者按「有没有 / 靠不靠前 / 整体好不好」分层的,基本就答对了一半。
- 推导链是这样的:召回率是布尔的——答案文档在不在最终上下文里。它对「压根没捞到」最敏感,但答案从第 1 名掉到第 8 名它一动不动,只要还在预算内。
- 倒数排名只看第一条相关结果的名次,所以「答案还在但被挤到后面」它立刻掉。反过来它有个盲区:前十条里有一条命中还是五条命中,它给的分完全一样。
- 归一化折损累计增益把前 k 名里每一条相关结果都按名次折算再累加,所以它对「整体排序质量」敏感,是重排最直接的优化目标。它的盲区是不告诉你「有没有」——召回率为零时它也是零,看不出是没捞到还是排得差。
- 结论:三个一起看才能定位故障层。召回率掉说明检索或切块出了问题,要动召回策略;召回率不动而倒数排名掉,说明排序退化,该上重排;两者都稳而 nDCG 掉,说明前几名里混进了更多噪声。
- 可预期的追问是「指标顶格了怎么办」。真实答案是把题目做难:指标撞天花板说明评估集失去区分度,这时候继续优化系统是在瞎调。
Key points
- Recall answers 'did it make it into the context', sensitive to total misses, blind to rank shifts.
- MRR answers 'how high is the first hit', sensitive to ranking degradation, blind to how many hits there are.
- nDCG answers 'how good is the top k overall', the direct target for reranking, blind to existence.
- Only the combination localizes the failure to retrieval, ranking, or noise.
- State the hit criterion: context is packed against a token budget, not a fixed top-k.
答题要点
- 召回率管「有没有进上下文」,对完全没捞到最敏感,对名次变化不敏感。
- 平均倒数排名管「第一条排第几」,对排序退化最敏感,但分不清命中一条还是五条。
- 归一化折损累计增益管「前 k 名整体质量」,是重排的直接优化目标,但看不出有没有。
- 三者组合才能定位故障在召回层、排序层还是噪声层。
- 命中口径要说清:按 token 预算装上下文,不是按固定条数取前 k。
What systematic biases does an LLM judge have when scoring RAG faithfulness, and how do you detect them and prove your judge is trustworthy?用模型当裁判来评 RAG 的忠实度,有哪些系统性偏差?你怎么发现它们、又怎么证明你的裁判可信?
Common in ChinaCommon overseasDeep dive#llm-as-judge#evaluation#faithfulnessHow to reason about it · think before answering
- The second half of the question is the discriminator. Plenty of people can name position, length, and self-preference bias; few can say how they prove the judge is trustworthy.
- Pair each bias with its mitigation: position bias - score pointwise instead of pairwise, and if you must compare, swap the order and call disagreement a tie; length bias - decompose into claims and score a ratio, so a longer answer grows its own denominator; self-preference - judge with a different vendor or tier than the generator.
- Add two prompt-level requirements: fixed rubric anchors (spell out what 1.0, 0.6 and 0.3 mean, or the same input scores differently on different days) and forced structured output that quotes the unsupported sentences verbatim, which is what makes human review possible.
- Proving trust has exactly one route: human spot-checks and an agreement rate. Stratify ten to thirty items across types, hits and misses, high and low judge scores; answer one binary question only - is anything here not in the material - and compare. Below 0.8 the judge's scores cannot gate a merge.
- A detail that scores points: a very high agreement rate may mean your spot-check was too easy. If all ten sampled answers copy the material verbatim, agreeing is trivial and 100% says nothing about the judge.
- Expected follow-up: can the judge itself break? Add probes - fixed inputs with known verdicts, one faithful and one obviously fabricated, checked on every run. An evaluation system fails silently: the numbers keep coming, they just stop meaning anything.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。能背出「位置偏好、长度偏好、自我偏好」三个名词的人很多,能说出「怎么证明可信」的很少——面试官要的是后者。
- 先把三个偏差和各自的缓解手段一一对应:位置偏好用逐条独立打分代替两两比较,非要比较就交换顺序跑两遍、结论不一致判平局;长度偏好用逐句判定加比例计分,写得越长分母越大,长度红利自动消失;自我偏好用跨供应商或跨档位的模型评判,生成和评判不同源。
- 再补两条提示词层面的:给死评分锚点,1.0 / 0.6 / 0.3 各自是什么必须写明,否则同一份输入不同天给的分都不一样;强制结构化输出并要求把没支撑的句子原样列出,这是人工复核的抓手。
- 证明可信只有一条路:人工抽检算一致率。分层抽十到三十条——各类型都要有、命中和没命中都要有、裁判给高分和低分都要有,只判一个二元问题(有没有材料外的内容),跟裁判的结论比对。低于 0.8 就不能拿它的分数做拦合并这类决策。
- 一个能加分的细节:一致率很高不一定是好消息。如果抽的十条都是「答案原样抄自材料」的简单题,判对是理所当然的,这时候 100% 说明的是抽检没难度,不是裁判可靠。
- 可预期的追问是「裁判本身会不会坏」。答案是给裁判写探针:喂几组已知正确答案的输入(照抄材料的、明显编造的),每次跑评估都验一遍——评估系统坏掉的方式最阴险,分数照常输出,只是不再有意义。
Key points
- Three biases: position, verbosity, and self-preference, each with a matching mitigation.
- Score pointwise rather than pairwise; decompose into claims and score a ratio to kill the length premium; never let the generator judge itself.
- Pin rubric anchors in the prompt and force structured output that quotes unsupported sentences.
- Establish trust through stratified human spot-checks and an agreement rate; below 0.8 the judge cannot gate merges.
- Add probes with known verdicts so a broken judge is caught on every run.
答题要点
- 三个偏差:位置偏好、偏爱长答案、自己评自己,各自有对应的缓解手段。
- 逐条独立打分代替两两比较;逐句判定按比例计分抵消长度红利;生成与评判不同源。
- 提示词要给死评分锚点,并强制结构化输出、列出没支撑的句子。
- 可信度靠人工分层抽检算一致率,低于 0.8 不能用它做拦合并的决策。
- 给裁判本身写探针,每次跑评估都验一遍它有没有坏。
Why must a RAG evaluation set include questions the corpus cannot answer, and what does leaving them out hide?RAG 的评估集里为什么一定要放语料里没有答案的问题?不放会掩盖什么?
Common in ChinaCommon overseasBasic#evaluation#abstention#golden-setHow to reason about it · think before answering
- It looks easy but really asks whether you have considered that the eval set itself can lie. 'To test the refusal path' is a pass; 'without them the worst failure is invisible in the report' is a full mark.
- The derivation is one step: a system that always answers scores well on a set of answerable questions only. It stuffs context in, the model writes something, and the set has no column for 'should have refused'. The most dangerous failure simply does not appear.
- Conclusion: unanswerable questions are the only thing that makes fabrication visible. They are excluded from recall and scored on abstention instead - did retrieval gate out every weak candidate, and did generation actually say the material does not cover this.
- One authoring detail worth stating: unanswerable questions need strong distractor terms. Ask which browsers the web client supports when the corpus only says 'attach your browser and version when filing a ticket'. Without distractors retrieval returns nothing and you are testing your tokenizer, not your system.
- Expected follow-up: what if the abstention rate is low? Check two layers - whether the retrieval score gate is effectively a no-op, and whether the generation prompt carries an explicit refusal instruction. You need both; a prompt alone is not a reliable gate.
分析过程 · 先想清楚再作答
- 这题看着简单,实际是在问「你有没有想过评估集本身也会说谎」。答成「为了测试拒答功能」只算及格,答出「不放会让某个故障在报表上完全不可见」才是满分。
- 推导只有一步:一个只会硬答的系统,在只有可答问题的评估集上能拿到很高的分——它每次都塞材料给模型,模型每次都编一段话,而评估集根本没有「应该拒答」这一栏。于是最危险的故障在报表上是不存在的。
- 结论:无答案问题是唯一能让「乱编」显形的东西。它不参与召回率,它的指标是拒答率——检索侧有没有把不够格的候选全挡下来,生成侧有没有真的说出「资料里没有」。
- 出题上有个必须说的细节:无答案问题必须留强干扰词,比如问「网页端支持哪些浏览器」而语料里恰好有一句「提交工单请附上浏览器与版本」。没有干扰词的无答案题检索器一条都捞不到,你测出来的是分词器不是系统。
- 可预期的追问是「拒答率低怎么办」。分两层查:先看检索侧的门槛是不是形同虚设(分数阈值定得太低,不相干的块也过关),再看生成侧的提示词有没有明确的拒答指令,两层都要有,只靠提示词兜是不牢的。
Key points
- An all-answerable eval set makes 'answers confidently when it should not' completely invisible.
- Unanswerable items are scored on abstention, not recall, and you check both the retrieval gate and the generation refusal.
- Author them with strong distractor terms, or retrieval returns nothing and you are testing the tokenizer.
- Keep them at roughly 15% or more of the set, alongside multi-hop items, as the coverage floor.
- A low abstention rate splits into two causes: a no-op retrieval score gate, or a missing refusal instruction in the prompt.
答题要点
- 只有可答问题的评估集,会让「不知道也硬答」这个故障完全不可见。
- 无答案问题不算召回率,它的指标是拒答率,检索侧和生成侧各看一层。
- 出题必须留强干扰词,否则检索器一条都捞不到,测的是分词器。
- 建议无答案题占比不低于评估集的一成五,跟多跳题一起构成覆盖度底线。
- 拒答率低要分两层查:检索门槛是否形同虚设,生成提示词有没有拒答指令。