阶段3(下):RAG、向量数据库专题;接入 BOSS直聘调研(research/、岗位对照和延伸技术栈);新增 cs 领域和 12 个技术栈骨架;面试题全部补齐追问并加入校验

This commit is contained in:
Shikui Wang committed 2026-10-23 15:56:03 +08:00
1 parent 7f52513ffc
commit 6650f7eb6d
52 files changed
+2777 -38

No files matched your search

+109
View File
@@ -0,0 +1,109 @@
---
id: ai.rag.generation-eval
title: 生成与评测:带引用的提示词、拒答、检索评测和答案评测
type: lesson
domain: ai
stack: [rag]
tags: [提示词, 引用, 拒答, 幻觉, Recall@k, MRR, 评测集]
level: intermediate
prerequisites: [ai.rag.hybrid-rerank]
est_hours: 3
related: [ai.rag.scenario.permission-aware-kb, ai.prompt-engineering.index, ai.agent-ops.index, ai.rag.interview]
week: [6]
order: 3
---
# 生成与评测:带引用的提示词、拒答、检索评测和答案评测
## 解释说明
**提示词的关键要素**
1. 角色和任务:“你是公司制度问答助手”;
2. **只依据资料回答**,资料中没有就明确说没有(减少幻觉);
3. 资料编号 + 要求用 `[编号]` 标注依据(可追溯、可校验);
4. 输出格式和长度要求;
5. 资料放在问题前面,并用清晰的分隔符包起来。
**拒答**:检索结果的最高相似度低于阈值、或者重排分数太低时,不调用大模型,直接回答“知识库中没有找到相关内容”,并引导到人工渠道。宁可不答,不要编。阈值需要用评测数据调。
**引用校验**:检查答案中的 `[编号]` 是否都在给出的资料范围内;可以进一步检查被引用的资料里是否包含答案中的关键数字。
**评测**——没有评测就无法判断改动是变好还是变坏:
| 层面 | 指标 | 怎么做 |
| --- | --- | --- |
| 检索 | Recall@k(正确资料是否在前 k 个里)、MRR(正确资料的排名倒数的平均值) | 人工标注“问题 → 应命中的文档 / 块”,几十到几百条 |
| 生成 | 忠实度(答案是否都有资料支撑)、相关性、正确性 | 人工抽查 + 用大模型按评分标准打分(LLM-as-judge) |
| 线上 | 点赞 / 点踩、转人工率、无答案率 | 埋点、定期分析差评样本 |
评测集要覆盖:常见问题、需要精确匹配的问题(编号、数字)、多轮追问、**知识库里没有答案的问题**(测试拒答)、无权限的问题。
工具:Ragas 等框架提供了忠实度、上下文召回等指标的实现,接口变化较快,以官方文档为准(https://docs.ragas.io/)。
## 案例
制度问答助手上线前建立了 9 条以上的检索评测集([examples/mini-rag](examples/mini-rag/) 中的 `EVAL`,真实项目要几十到上百条),比较纯向量、纯 BM25、混合三种检索的 Recall@3 和 MRR;之后每次调整切分或检索参数都跑一遍,指标下降就不合并。
## 代码示例
检索评测(Recall@k、MRR):
```python
def recall_and_mrr(results: dict[str, list[str]], expected: dict[str, str], k: int = 3) -> tuple[float, float]:
"""results:问题 → 检索出的文档 ID(已排序);expected:问题 → 应命中的文档 ID。"""
hit, rr = 0, 0.0
for q, gold in expected.items():
top = results[q][:k]
if gold in top:
hit += 1
rr += 1 / (top.index(gold) + 1)
n = len(expected)
return hit / n, rr / n
results = {"年假几天": ["hr-leave", "fin"], "VPN 连不上": ["hr-leave", "it-vpn"], "报销时限": ["it-vpn", "x", "y"]}
expected = {"年假几天": "hr-leave", "VPN 连不上": "it-vpn", "报销时限": "fin"}
print(recall_and_mrr(results, expected)) # (0.666..., 0.5):第 1 题排第 1,第 2 题排第 2,第 3 题没命中
```
带引用的提示词和拒答、引用校验:
```python
import re
def build_prompt(question: str, chunks: list[str]) -> str:
context = "\n".join(f"[{i}] {c}" for i, c in enumerate(chunks, start=1))
return ("你是公司制度问答助手。只根据<资料>回答;资料中没有的内容,回答“资料中没有相关规定”。"
"每个结论后用 [编号] 标注依据。\n"
f"<资料>\n{context}\n</资料>\n问题:{question}")
def answer_or_refuse(question: str, hits: list[tuple[str, float]], min_score: float = 0.3) -> str:
if not hits or hits[0][1] < min_score:
return "知识库中没有找到相关内容,建议咨询 HR 或提交工单。"
return build_prompt(question, [text for text, _ in hits]) # 交给大模型
def citations_valid(answer: str, n_chunks: int) -> bool:
cited = {int(x) for x in re.findall(r"\[(\d+)\]", answer)}
return bool(cited) and cited <= set(range(1, n_chunks + 1))
print(answer_or_refuse("股票期权怎么行权", [("差旅住宿标准……", 0.12)]))
print(citations_valid("满十年年假 10 天 [1]。", 3), citations_valid("答案 [5]", 3)) # True False
```
## 易错点
- **没有评测集,靠几个问题“感觉效果不错”**:改了参数也不知道变好还是变坏。
- **评测集里没有“无答案”的问题**:拒答能力从未被测试。
- **拒答阈值拍脑袋**:要用评测数据画出“阈值 vs 准确率 / 拒答率”来选。
- **完全依赖 LLM 打分**:评分模型本身有偏差,要和人工标注对比校准。
- **提示词里资料和指令混在一起**:资料里的内容可能被模型当成指令(提示词注入),用分隔符包裹并在指令中说明。
- **引用编号不校验**:模型可能引用不存在的编号,或者引用了但内容对不上。
## 面试常问
见 [RAG 面试题](interview.md):怎么减少幻觉、怎么评测 RAG、拒答怎么做、线上效果怎么监控。