(十七)分别检查检索、回答和业务结果
只看模型说得是否流畅,很难判断失败发生在哪里。先固定一组问题、用户、预期来源与期望行为,每次改分块、模型或提示以后运行同一组检查。
| 检查内容 | 本章的具体核对 |
|---|---|
| 检索命中 | 正确来源是否进入前两个结果;多相关块时可再算 recall@k |
| 引用可回查 | 块编号存在,引用文字与材料一致 |
| 回答受材料支持 | 逐句比对回答判断,检查同义问法和缺少证据的情况 |
| 用户归属 | Alice 的结果不含 Bob 的私人资料与计划 |
| 动作完成 | 拒绝后没有写入;批准后只有一条;重开后能够读取 |
| 运行代价 | 记录模型/工具次数、用量、耗时与失败节点 |
“正确来源进入 top-2 的问题数 / 总问题数”是按问题统计的命中率;recall@k 则把某个问题的全部相关材料作为分母,统计取回了其中多少。先写清评价对象,再给数字命名。
"""分别记录检索命中和回答引用检查,不把两项混成一个得分。"""
from studyagent.generation import make_answer, verify_answer
from studyagent.retrieval import NoteIndex
cases = [
("NumPy 切片怎样避免改动原数组?", "alice", "numpy.md"),
("FastAPI 数据库会话什么时候关闭?", "alice", "fastapi.md"),
("LangGraph 怎样恢复等待确认的任务?", "bob", "langgraph.md"),
]
index = NoteIndex()
retrieved = 0
valid = 0
for question, owner, expected in cases:
hits = index.search(question, owner, k=2)
retrieved += any(item["source"] == expected for item in hits)
answer = make_answer(question, hits)
verify_answer(answer, hits)
valid += 1
print(expected, [item["source"] for item in hits])
# 这里评估离线回放流程,生成模型的答案质量要另用真实问答集核对。
print({"source_hit_at_2": retrieved / len(cases), "citation_checks": valid})
assert retrieved == valid == len(cases)这三条固定问题的预期来源都进入前两项,引用检查通过三次。它说明离线流程在这组输入上符合预期;真实生成模型的回答仍要添加实际答案检查,尤其是需要综合多段材料的问题。
从一次失败继续改时,先保留输入:正确块没取回来,检查分块、编码与检索;正确块已经在输入里但结论错,检查提示和模型输出;回答正确却重复保存,则回到业务事务与恢复测试。这样每次修改都有对应依据。