feat/knowledge-retrieval-core
main
为 Knowledge Core / Retrieval Core 增加一套可复现的 RAG 检索评测能力,量化检索质量并提供可复现基线,为后续检索调优(embedding / reranker / RRF 参数)提供依据。
本 PR 是对已关闭 #9 的重新提交,已落实 #9 审阅中的 3 项 P1 + 3 项 P2 问题。
rrf_k
rerank
rerank_candidates
score_threshold
SearchRequest
RetrievalEngine
rerank_candidates=None
index_meta
len(set(retrieved[:k]) & expected) / len(expected)
note_id
POST /api/benchmarks/rag/runs
202 queued
asyncio.Task
_DatasetMeta
citation_required
citation_required=true
expected_block_ids
modes
min_length=1
backend/data/benchmarks/*.json
app.retrieval.engine.search()
/api/benchmarks/*
backend/app/benchmarks/
metrics.py
datasets.py
rag.py
service.py
backend/app/contracts.py
backend/app/routes.py
backend/app/retrieval/engine.py
backend/app/retrieval/embedding.py
backend/app/retrieval/reranker.py
backend/data/benchmarks/rag-core-v1.json
backend/tests/test_benchmark.py
cd backend pytest -q # 113 passed pytest tests/test_benchmark.py -v # 21 passed
status: queued
GET /api/benchmarks/rag/runs/{run_id}
running
completed
metrics
POST /api/benchmarks/rag/runs/{run_id}/cancel
status: accepted
cancelled
GET /api/benchmarks/rag/runs/{run_id}/events/stream
验收笔记此前被误纳入 benchmark 提交,现摘除跟踪,文件保留在本地磁盘。 Co-Authored-By: Claude Code <noreply@anthropic.com>
- 检索调优参数(rrf_k/rerank/rerank_candidates/score_threshold)透传到引擎实际执行 - Recall 去重,避免同一 Note 多 Block 重复导致 Recall 超 1 - RAG 运行改为后台异步执行:创建即 queued + 202,支持取消与 SSE 实时事件 - 数据集元数据校验,坏文件隔离跳过;citation_required 语义修正 - modes 空/重复校验;配置快照记录模型版本与索引元信息 Co-Authored-By: Claude Code <noreply@anthropic.com>
暂不建议合并。
该分支能够与最新 main 自动合并,合并态后端 157 项测试、前端 29 项测试、TypeScript 类型检查和生产构建均通过。但针对性验证发现以下问题尚未被现有测试覆盖。
_remember() 超过 100 条记录时直接删除最旧 Run,没有判断其是否处于终态,同时删除对应的 Task、Event、Subscriber 和 Cancel Flag。
_remember()
复现时将上限设为 1,创建第二个 Run 后,第一个后台任务会因 _cancel_flags[run_id] 已被删除而抛出 KeyError。
_cancel_flags[run_id]
KeyError
建议:
failed
取消分支只更新 Run 和 Report,随后直接清除订阅者,没有发送 RunCancelled。
RunCancelled
后果:
queue.get()
RunStarted
建议增加 RunCancelled 事件,在移除订阅前发布,并将其加入 SSE 终止事件判断。
创建 Benchmark 时只把 index_meta 写入配置快照,没有验证:
实测空数据库仍会返回 completed,并生成全部为 0 的指标。这会把环境或索引错误误判成检索质量差。
建议在创建 Run 前验证索引兼容性,不满足时返回已有的:
BENCHMARK_INDEX_INCOMPATIBLE
聚合指标时只保留 error is None 的 Case。一个满分样本和一个执行失败样本最终仍会得到:
error is None
Hit@1 = 1.0 Recall@K = 1.0 MRR = 1.0
这会掩盖 Benchmark 的实际失败率。
total_cases
successful_cases
failed_cases
failure_rate
load_dataset() 遍历目录时直接解析每个文件。只要排序靠前的无关文件存在以下问题:
load_dataset()
就会提前抛出 422 或未映射的 AttributeError,导致后面的合法 Dataset 无法加载。
AttributeError
建议隔离无关损坏文件,并对 JSON 顶层对象类型进行稳定校验。只有与请求 ID 对应的数据集损坏时才返回 BENCHMARK_DATASET_INVALID。
BENCHMARK_DATASET_INVALID
后台异常通过 str(exc) 原样写入:
str(exc)
BenchmarkRun.error
RunFailed
BenchmarkReport.error
数据库、文件和 Adapter 异常可能包含绝对路径、SQL 信息或敏感参数。
建议统一转换为项目错误码和安全消息,详细异常只进入受控内部日志,不直接通过 HTTP 或 SSE 返回。
FTS 在数据库已经分页和计数后,才对当前页执行归一化和 score_threshold 过滤,但响应仍返回过滤前的 page.total。
page.total
实测可以出现:
items = [] page.total = 1
而且每页独立归一化会导致同一个阈值在不同页面具有不同含义。
建议在计数和分页前应用阈值,或重新定义阈值计算方式并返回过滤后的 total。
当前只接受:
?after_sequence=
没有像 Agent SSE 一样读取:
Last-Event-ID
这与第二阶段统一 SSE Contract 不一致,浏览器或通用客户端自动重连时无法恢复游标。
建议复用 Agent SSE 的游标解析逻辑,并明确查询参数优先级和非法游标错误码。
POST /api/benchmarks/agent/runs 已进入 OpenAPI,但始终调用 not_implemented()。
POST /api/benchmarks/agent/runs
not_implemented()
如果本 PR 只交付 RAG Benchmark,建议移除占位接口并在 PR 标题和说明中明确范围;如果 PR 声明 Benchmark 功能完成,则仍需补齐 Agent Benchmark Request、Runner 和测试。
本 PR 没有同步:
按照当前项目合并规范,代码、OpenAPI、接口文档和开发说明应在同一个 PR 中保持一致。
与最新 main 自动合并:通过 后端合并态测试:157 passed 前端测试:29 passed TypeScript 类型检查:通过 前端生产构建:通过 git diff --check:通过
生产构建只有项目既有的大 Chunk 警告。
请先修复上述问题并补充对应回归测试、契约和开发文档,再进行下一轮审阅。
No dependencies set.
The note is not visible to the blocked user.
概述
为 Knowledge Core / Retrieval Core 增加一套可复现的 RAG 检索评测能力,量化检索质量并提供可复现基线,为后续检索调优(embedding / reranker / RRF 参数)提供依据。
评审修复(对应 #9 审阅意见)
rrf_k/rerank/rerank_candidates/score_threshold已透传到SearchRequest并由RetrievalEngine实际执行,不再只写快照;rerank_candidates=None表示精排全部候选(保留原有检索行为)。配置快照同时补充 embedding / reranker 版本与索引元信息(index_meta)。len(set(retrieved[:k]) & expected) / len(expected),并补充重复note_id的单元测试。POST /api/benchmarks/rag/runs创建后立即返回202 queued,由受管asyncio.Task后台执行;Case 间检查取消标志;SSE 实时推送进度;取消接口真正生效。_DatasetMeta模型逐文件校验,损坏文件隔离跳过,列表接口不再整体 500。citation_required语义(P2):仅citation_required=true的样本计入 Citation Hit Rate;citation_required=true时强制要求存在expected_block_ids。modes校验(P2):modes加min_length=1,并拒绝重复模式。功能点
backend/data/benchmarks/*.json目录注册(dataset_id / kind / version / cases),加载时校验,内容 sha256 哈希保证可复现。app.retrieval.engine.search(),不旁路检索链路;逐 (mode, case, repeat) 采样,按 mode 聚合;单样本失败不中断整体。/api/benchmarks/*共 8 个(数据集列表、RAG 运行、Agent 运行占位、运行列表/详情/取消/事件/报告)。文件清单
backend/app/benchmarks/:metrics.py、datasets.py、rag.py、service.pybackend/app/contracts.py(Benchmark 契约 + SearchRequest 检索参数)、backend/app/routes.py(端点 + SSE + 取消)、backend/app/retrieval/engine.py(参数透传)、backend/app/retrieval/embedding.py、backend/app/retrieval/reranker.py(版本字段)backend/data/benchmarks/rag-core-v1.json(5 个中文检索 case)backend/tests/test_benchmark.py(21 个测试,覆盖指标去重 / 数据集校验 / 运行生命周期 / 取消)测试
可复现验证(http://127.0.0.1:8000/docs)
POST /api/benchmarks/rag/runs→ 立即返回status: queued。GET /api/benchmarks/rag/runs/{run_id}轮询 →running→completed,metrics含 hit_at_1 / recall_at_k / mrr / citation_hit_rate / p50 / p95。POST /api/benchmarks/rag/runs/{run_id}/cancel→status: accepted,运行转cancelled。GET /api/benchmarks/rag/runs/{run_id}/events/stream观察 SSE 实时推送。审阅结论
暂不建议合并。
该分支能够与最新
main自动合并,合并态后端 157 项测试、前端 29 项测试、TypeScript 类型检查和生产构建均通过。但针对性验证发现以下问题尚未被现有测试覆盖。必须修复
1. 活动 Benchmark Run 会被容量裁剪
_remember()超过 100 条记录时直接删除最旧 Run,没有判断其是否处于终态,同时删除对应的 Task、Event、Subscriber 和 Cancel Flag。复现时将上限设为 1,创建第二个 Run 后,第一个后台任务会因
_cancel_flags[run_id]已被删除而抛出KeyError。建议:
completed、failed、cancelled等终态 Run;2. 取消 Run 没有产生 SSE 终止事件
取消分支只更新 Run 和 Report,随后直接清除订阅者,没有发送
RunCancelled。后果:
queue.get();RunStarted,随后连接正常结束;建议增加
RunCancelled事件,在移除订阅前发布,并将其加入 SSE 终止事件判断。3. 空索引或不兼容索引仍会生成 completed 报告
创建 Benchmark 时只把
index_meta写入配置快照,没有验证:实测空数据库仍会返回
completed,并生成全部为 0 的指标。这会把环境或索引错误误判成检索质量差。建议在创建 Run 前验证索引兼容性,不满足时返回已有的:
4. 失败样本被排除,汇总指标会虚高
聚合指标时只保留
error is None的 Case。一个满分样本和一个执行失败样本最终仍会得到:这会掩盖 Benchmark 的实际失败率。
建议:
total_cases、successful_cases、failed_cases和failure_rate;5. 损坏的数据集文件会阻断其他合法 Dataset
load_dataset()遍历目录时直接解析每个文件。只要排序靠前的无关文件存在以下问题:就会提前抛出 422 或未映射的
AttributeError,导致后面的合法 Dataset 无法加载。建议隔离无关损坏文件,并对 JSON 顶层对象类型进行稳定校验。只有与请求 ID 对应的数据集损坏时才返回
BENCHMARK_DATASET_INVALID。6. 原始异常直接进入公开响应
后台异常通过
str(exc)原样写入:BenchmarkRun.errorRunFailedSSE EventBenchmarkReport.error数据库、文件和 Adapter 异常可能包含绝对路径、SQL 信息或敏感参数。
建议统一转换为项目错误码和安全消息,详细异常只进入受控内部日志,不直接通过 HTTP 或 SSE 返回。
需要修复
7. FTS 阈值过滤与分页 total 不一致
FTS 在数据库已经分页和计数后,才对当前页执行归一化和
score_threshold过滤,但响应仍返回过滤前的page.total。实测可以出现:
而且每页独立归一化会导致同一个阈值在不同页面具有不同含义。
建议在计数和分页前应用阈值,或重新定义阈值计算方式并返回过滤后的 total。
8. Benchmark SSE 不支持 Last-Event-ID
当前只接受:
没有像 Agent SSE 一样读取:
这与第二阶段统一 SSE Contract 不一致,浏览器或通用客户端自动重连时无法恢复游标。
建议复用 Agent SSE 的游标解析逻辑,并明确查询参数优先级和非法游标错误码。
9. Agent Benchmark 接口仍固定返回 501
POST /api/benchmarks/agent/runs已进入 OpenAPI,但始终调用not_implemented()。如果本 PR 只交付 RAG Benchmark,建议移除占位接口并在 PR 标题和说明中明确范围;如果 PR 声明 Benchmark 功能完成,则仍需补齐 Agent Benchmark Request、Runner 和测试。
文档问题
本 PR 没有同步:
按照当前项目合并规范,代码、OpenAPI、接口文档和开发说明应在同一个 PR 中保持一致。
验证结果
生产构建只有项目既有的大 Chunk 警告。
请先修复上述问题并补充对应回归测试、契约和开发文档,再进行下一轮审阅。
Pull request closed