文档层级建模、多粒度 embedding 和父子上下文扩展。模块化、可恢复、可解释的 RAG 库, 使用 LanceDB 作为向量数据库.
========================================================================
🏆 EasyRAG 评测综合指标汇总 (Overall Metrics | 样本数: 150)
========================================================================
• 执行状态: 检索成功 150/150 | 生成成功 148/150 | 失败 2
• 数据集 SHA256: 8fbec02b8dc1bb2869d3401223401e86573ef400c44ab346ae70bc220f4ca35f
• 索引 SHA256: 7df478c3ed5e887f4402c686a8298c3f8d0b4a08322e97ea74dcac903fe714c5
【1. 检索召回评测指标 (Retrieval Quality)】
• Hit Rate@1: 68.99% | 首条命中率
• Hit Rate@3: 79.84% | Top-3 命中率
• Hit Rate@5: 85.27% | Top-5 命中率
• Hit Rate@K: 87.60% | Top-K 总体命中率
• MRR: 0.7483 | 平均倒数排名 (Mean Reciprocal Rank)
• NDCG@10: 0.7074 | 归一化折损累积增益 (相关度加权)
• Precision@10: 11.40% | 检索精确率
• Recall@10: 71.60% | 检索召回率
【2. 回答生成与裁判指标 (Generation Quality)】
• Correctness: 71.40% | 语义正确性与事实一致度
• Faithfulness: 92.93% | 上下文忠实度 (无幻觉率)
• Relevance: 88.07% | 回答针对性与切题度
• Refusal Accuracy: 57.14% | 不可回答问题正确拒答率
• False Refusal Rate: 6.20% | 可回答问题错误拒答率 (过度拒答率)
• 成功裁判样本口径: Correctness=72.36% | Faithfulness=94.19% | Relevance=89.26% (148 条)
------------------------------------------------------------------------
问题类型 (Type) | 样本 | Hit@K | MRR | Correctness | Faithfulness
------------------------------------------------------------------------
comparison | 21 | 95.2% | 0.726 | 67.1% | 88.1%
multi_hop_fact | 22 | 95.5% | 0.826 | 74.5% | 95.5%
multi_hop_reasoning | 21 | 95.2% | 0.790 | 73.3% | 100.0%
single_hop_fact | 22 | 100.0% | 0.964 | 100.0% | 99.5%
single_hop_reasoning | 22 | 90.9% | 0.879 | 90.9% | 100.0%
summary | 21 | 47.6% | 0.286 | 34.3% | 95.2%
unanswerable | 21 | 0.0% | 0.000 | 57.1% | 71.4%
========================================================================
# 1. 配置环境变量
cp .env.example .env
# 2. 安装项目依赖
uv sync以下命令以当前默认配置为准:默认使用 two_stage 两阶段检索,并自动启用问题类型路由。
# 建库:增量索引(首次使用或文档变更后执行)
uv run main.py index tests/docs/
# 查看检索结果与完整 trace
uv run main.py search "你的问题" --detail
# 直接问答(默认流式输出)
uv run main.py query "你的问题"
# 仅评估检索质量,适合快速比较参数
uv run eval.py run -d data/eval_dataset.json -o data/eval_report_retrieval.json --mode retrieval --reranker two_stage
# 综合评测:检索 + 回答生成 + Judge
uv run eval.py run -d data/eval_dataset.json -o data/eval_report.json --mode all --reranker two_stage
# 与旧版 Frequency + Diversity 链路做基线对照
uv run eval.py run -d data/eval_dataset.json -o data/eval_report_baseline.json --mode retrieval --reranker both评测时建议先运行 --mode retrieval 做检索链路 A/B,再运行 --mode all 检查回答质量。all 模式会额外调用回答模型和评判模型,耗时与 API 消耗更高。
纯本地离线运行,不调用外部 API,用于执行文档解析、断句分段、四层建树以及效果预览校验。
| 功能 | 执行命令 | 参数与说明 | API / Token 消耗 |
|---|---|---|---|
| 单文件预处理 | uv run main.py preprocess tests/docs/太白金星有点烦.txt |
解析文件、切分分块并打印各层级节点生成统计 | ❌ 零消耗 (纯本地) |
| 单文件预处理并导出 JSON | uv run main.py preprocess tests/docs/太白金星有点烦.txt --output-dir data/processed/ |
--output-dir:将结构化文档树导出为 JSON 文件 |
❌ 零消耗 (纯本地) |
| 批量目录预处理 | uv run main.py preprocess data/ --output-dir data/processed/ |
递归扫描目录内所有文档并批量导出结构化分块数据 | ❌ 零消耗 (纯本地) |
| 基础树状结构预览 | uv run main.py preview tests/docs/太白金星有点烦.txt --limit 2 |
--limit 2:每个层级最多展示 2 个子节点,折叠超长内容 |
❌ 零消耗 (纯本地) |
| 完整树状结构展开 | uv run main.py preview tests/docs/太白金星有点烦.txt --limit 0 |
--limit 0:展开所有章节、段落与叶子句子 |
❌ 零消耗 (纯本地) |
| 章节/段落大纲预览 | uv run main.py preview tests/docs/太白金星有点烦.txt --max-depth 3 |
--max-depth 3:仅展示至段落级,不展开底层句子 |
❌ 零消耗 (纯本地) |
| 终端直接输出 JSON | uv run main.py preview tests/docs/太白金星有点烦.txt --json |
--json:直接在终端打印可序列化的节点树 JSON |
❌ 零消耗 (纯本地) |
执行完整的向量化计算、LanceDB 向量入库与 SHA-256 哈希增量同步。
| 功能 | 执行命令 | 参数与说明 | API / Token 消耗 |
|---|---|---|---|
| 创建/索引单文件 | uv run main.py index tests/docs/太白金星有点烦.txt |
解析分块、计算向量并登记入库(文件未改动则自动跳过,--force重新执行) |
🟢 仅新文件消耗 Embedding |
| 自定义文档标题索引 | uv run main.py index tests/docs/太白金星有点烦.txt --title "太白金星" |
--title:指定知识库中的文档标题 |
🟢 仅新文件消耗 Embedding |
| 批量增量索引目录 | uv run main.py index tests/docs/ |
递归扫描目录,根据哈希只索引新增/修改的文件 | 🟢 仅增量变更消耗 |
| 强制全量重建索引 | uv run main.py index tests/docs/ --force |
--force:无视哈希缓存,强制重新切块并重新计算向量 |
|
| 查看已索引文档清单 | uv run main.py list |
列出当前知识库已入库的文档 ID、分块数、文件大小 | ❌ 零消耗 |
| 查看知识库统计信息 | uv run main.py stats |
查看文档总数、分层节点总数、存储路径与所用模型 | ❌ 零消耗 |
| 删除指定文档 | uv run main.py delete <doc_id> |
同步从 LanceDB 和注册表中清理该文档及所有分层节点 | ❌ 零消耗 |
| 清空重置知识库 | uv run main.py reset |
清空 LanceDB 向量表及 doc_registry.json 元数据,允许-y参数 |
❌ 零消耗 |
索引会同时写入
index_manifest.json,记录 Embedding 模型、维度、分块格式和段落向量模式。非空索引缺少清单或与当前配置不一致时,系统会明确拒绝检索/增量写入,避免把维度错误静默表现成“未检索到资料”。首次升级到该版本时,旧索引需要执行uv run main.py reset -y后重新索引。
both(双重重排序)将 频次共识重排(Frequency Consensus) 与 层级多样性重排(Diversity & Deduplication) 串联在一起,兼顾了相关度置信度与信息覆盖面。🚀 Small-to-Big 分层上下文追溯(
--parent-depth/--child-depth): 检索时以最细粒度的 句子 (Sentence) 参与语义与关键词匹配;召回后通过ContextGraph自动沿parent_id向上回溯至 完整段落 (Paragraph) 或 章节 (Section),或通过child_depth向下递归聚合子节点内容,实现“精细召回,完整呈现”。
flowchart TD
Raw[初始召回的候选节点列表 Matches] --> Step1{是否启用了 Multi-Query?}
Step1 -- 是 --> Freq[第 1 阶:FrequencyReranker 频次共识重排,按命中子查询次数从大到小排序,取 top_k*2]
Step1 -- 否 (单查询) --> Bypass[跳过频次统计]
Freq --> Div[第 2 阶:DiversityReranker 层级多样性打散,按 parent_id 限制同段落最多 2 句,最终取 top_k]
Bypass --> Div
Div --> Graph[第 3 阶:ContextGraph 分层图扩展,按 parent_depth 向上回溯段落/章节,按 child_depth 向下聚合]
Graph --> Output[最终精排上下文片段 Snippets]
| 阶段 | 解决的核心痛点 | 具体做法 |
|---|---|---|
第 1 阶段:频次共识 (Frequency) |
避免单一角度检索产生的偶然偏差和语义遗漏 | 在开启多查询(--multi-query)时,LLM 会扩展出 3 个不同问法并发检索。被多个子查询同时命中的段落,说明置信度极高,会优先排在最前面。 |
第 2 阶段:层级多样性 (Diversity) |
避免检索结果被同一段话里的连续句子霸屏 | 限制来自同一个父段落(parent_id)的句子最多只保留 2 个(可通过 --diversity-max-per-parent 调整),把名额留给其他段落。 |
第 3 阶段:分层上下文延展 (ContextGraph) |
避免句子碎片孤立无上下文,或段落太长导致向量匹配不准 |
Small-to-Big 扩展: • --parent-depth 1(默认):句子 • --parent-depth 2:句子 • --parent-depth 0:不延展,仅输出匹配的单句。 |
| 功能 | 执行命令 | 参数与说明 | API / Token 消耗 |
|---|---|---|---|
| 默认两阶段检索 (Hybrid + Rerank) | uv run main.py search "太白金星 李长庚" --top-k 5 |
Dense/BM25 → RRF → 段落聚合 → Cross-Encoder → Coverage/MMR → Small-to-Big;默认 two_stage |
🟢 Embedding + Reranker API |
| 纯向量检索 (Dense) | uv run main.py search "太白金星" --no-hybrid |
--no-hybrid:仅使用向量余弦相似度匹配 |
🟢 消耗 1 次 Query Embedding |
| 多查询意图扩展检索 | uv run main.py search "李长庚的坐骑" --multi-query |
--multi-query:使用 LLM 生成同义搜索词并发召回,并通过频次共识重排 |
🟢 消耗 LLM 生成 + Embedding |
| 向上延展至完整章节 | uv run main.py search "织女" --parent-depth 2 |
--parent-depth 2:由句子命中向上回溯并聚合整个章节的上下文 |
🟢 消耗 1 次 Query Embedding |
| 纯句子级精准命中 (不延展) | uv run main.py search "老鹤" --parent-depth 0 |
--parent-depth 0:关闭向上回溯,直接展示命中的单句 |
🟢 消耗 1 次 Query Embedding |
| 指定仅多样性重排序 | uv run main.py search "太白金星" --reranker diversity --diversity-max-per-parent 1 |
--reranker diversity:限制单父节点命中上限,防止同段落垄断 |
🟢 消耗 1 次 Query Embedding |
| 禁用重排序 (纯相似度截断) | uv run main.py search "太白金星" --reranker none |
--reranker none:跳过频次与多样性重排,按原始分数排序截断 |
🟢 消耗 1 次 Query Embedding |
执行端到端完整 RAG 问答,支持流式生成、精准引用溯源(Citations)与终端交互多轮聊天。
| 功能 | 执行命令 | 参数与说明 | API / Token 消耗 |
|---|---|---|---|
| 默认流式单次提问 | uv run main.py query "孙悟空对李长庚说:很多事须怪不到你头上,而李长庚后来反复琢磨这句话,这暗示了什么?" |
默认开启流式打字机输出,文末自动打印精准引用溯源 | 🟢 消耗 Embedding + LLM |
| 查看详细检索上下文 | uv run main.py query "孙悟空对李长庚说:很多事须怪不到你头上,而李长庚后来反复琢磨这句话,这暗示了什么?" --detail |
--detail:打印参与 Prompt 组装的上下文原始片段及相似度 |
🟢 消耗 Embedding + LLM |
| 延展至章节上下文提问 | uv run main.py query "织女下凡的经过" --parent-depth 2 |
--parent-depth 2:将命中句子向上延展至所属完整章节作为 Prompt 上下文 |
🟢 消耗 Embedding + LLM |
| 纯句子级窄上下文提问 | uv run main.py query "老鹤的名字是什么?" --parent-depth 0 |
--parent-depth 0:仅使用命中的精简句子回答,节省 Prompt Token |
🟢 消耗 Embedding + LLM |
| 多查询扩展 + 多样性问答 | uv run main.py query "孙悟空对李长庚说:很多事须怪不到你头上,而李长庚后来反复琢磨这句话,这暗示了什么?" --multi-query --reranker diversity --diversity-max-per-parent 1 |
开启多意图扩展,并严格控制同段落仅出 1 句 | 🟢 消耗 Embedding + LLM |
| 非流式单次提问 | uv run main.py query "问题内容" --no-stream |
--no-stream:等待模型完整生成后一次性打印输出 |
🟢 消耗 Embedding + LLM |
| 终端交互式多轮对话 | uv run main.py chat |
进入终端连续问答模式(支持 --parent-depth 等所有检索参数,输入 exit 或 q 退出) |
🟢 按每次问答消耗 |
| 参数类别 | 命令行参数 | 类型 / 默认值 | 作用与说明 |
|---|---|---|---|
| 问答输出 | question |
字符串 (必填) | 用户提问内容(chat 模式下无需输入)。 |
--stream / --no-stream |
布尔值 (默认 --stream) |
是否开启打字机流式输出。 | |
--detail |
布尔开关 (默认关闭) | 在回答下方打印检索到的详细候选上下文片段与得分。 | |
| 检索召回 | --top-k |
整数 (默认 5) |
最终注入 LLM 上下文的片段数量 (Top-K)。 |
--hybrid / --no-hybrid |
布尔值 (默认 --hybrid) |
是否启用 Dense 向量 + BM25 混合检索。 | |
--rule-query-expansion / --no-rule-query-expansion |
布尔值 (默认开启) | 是否启用本地规则查询扩展;与 LLM 多查询独立控制。 | |
--multi-query |
布尔开关 (默认关闭) | 是否开启 LLM 多查询意图拆解并发检索。 | |
| 重排序 | --reranker |
two_stage | both | diversity | frequency | none (默认 two_stage) |
两阶段检索默认执行段落聚合、Cross-Encoder 重排和 Coverage/MMR 去重。 |
--diversity-max-per-parent |
整数 (默认 2) |
每个父段落最多保留的候选句子数。 | |
| 上下文延展 | --parent-depth |
整数 (默认 1) |
向上回溯父级深度:0 为仅句子,1 为段落,2 为章节。 |
--child-depth |
整数 (默认 0) |
向下展开子级深度:递归聚合子节点文本内容。 |
flowchart TD
subgraph S1["阶段 1:数据预处理与文档建模"]
A["输入文档 (.txt / .md / .pdf)"] --> B["Factory 路由解析器 + MetadataExtractor"]
B --> C["HeadingDetector 标题与层级检测"]
C --> D["HierarchicalChunker 四级建树<br>Doc -> Section -> Paragraph -> Sentence"]
end
subgraph S2["阶段 2:向量存储与增量同步注册表"]
D --> E["DocRegistry 文件 SHA-256 哈希校验"]
E -->|未变更| Skip["跳过索引"]
E -->|新增/变更| F["EmbeddingPropagator 向量化<br>仅向量化叶子句子,向上均值池化聚合段落/章节/文档"]
F --> G["LanceDBNodeStore 持久化节点表 + 内存快速缓存"]
G --> H["更新 doc_registry.json 状态"]
end
subgraph S3["阶段 3:多路召回、重排序与分层上下文延展"]
Q["用户 Query"] --> MR{"是否启用 Multi-Query?"}
MR -- 是 --> MQ["MultiQueryRetriever LLM 扩展 3 个子查询"]
MR -- 否 --> HR["HybridRetriever 混合检索"]
MQ --> HR
HR --> DR["Dense 向量余弦检索"] & BR["BM25 倒排关键词检索"]
DR & BR --> RRF["RRF 倒数排名融合评分"]
RRF --> FR["FrequencyReranker 频次共识重排"]
FR --> DVR["DiversityReranker 基于 parent_id 多样性打散"]
DVR --> CG["ContextGraph 上下文图追溯<br>Small-to-Big 沿 parent_id 向上回溯段落/章节"]
end
subgraph S4["阶段 4 & 5:上下文组装、LLM 生成与交互问答"]
CG --> CB["ContextBuilder 组装带 [ref_N] 标记的 Prompt"]
CB --> LLM["LLMClient 流式 / 非流式推理生成"]
LLM --> EX["extract_citations 正则解析引用标签"]
EX --> OUT["Rich 终端流式输出答案 + 精准溯源清单"]
end
-
多格式解析分发 (
src/parsers/)-
parsers/factory.py根据文件后缀自动分发到对应解析器(TextParser、MarkdownParser、PDFParser)。 -
MetadataExtractor提取文件元数据(大小、修改时间)并计算文件 SHA-256 哈希作为唯一内容版本指纹。
-
-
章节与层级识别 (
HeadingDetector)- 通过正则表达式和排版规则识别 Markdown 标题(
#)、中文章节(第X章/节)及数字编号(1.1),划分逻辑章节Section并记录完整标题路径(heading_path面包屑)。
- 通过正则表达式和排版规则识别 Markdown 标题(
-
四层文档树构建 (
HierarchicalChunker)- 将文档自顶向下切分为四级拓扑树:
DOCUMENT(文档根)$\to$ SECTION(章节)$\to$ PARAGRAPH(段落 / 图片附件)$\to$ SENTENCE(句子叶子)。 -
text_splitter.py依据中英文标点将段落递归切分为句子。 - 为每个节点生成确定性层级 ID(
doc_id:sec{N}:p{N}:s{N}),并在节点间绑定parent_id与children双向拓扑引用。
- 将文档自顶向下切分为四级拓扑树:
-
增量比对与注册表 (
DocRegistry)- 维护本地
data/doc_registry.json元数据索引库。 - 索引前校验文件 SHA-256 哈希:内容未变更时直接跳过;若文件已更新或删除,自动级联清理 LanceDB 中的旧节点与元数据记录。
- 文档身份由规范化文件路径生成,因此相同内容的不同文件不会互相覆盖;内容更新后文档 ID 保持稳定。
- 更新采用“新节点 upsert → 清理过期节点 → 提交注册表”的顺序,注册表提交失败时恢复上一版节点。
- 维护本地
-
多粒度 Embedding 向上聚合传播 (
EmbeddingPropagator/EmbeddingManager)-
叶子计算:仅将最底层的
SENTENCE句子节点批量提交给 Embedding API 计算向量,大幅减少 Token 消耗与 API 请求耗时。 -
向上汇聚:通过树的递归后序遍历,
PARAGRAPH、SECTION、DOCUMENT等父级节点的向量由其直属子节点向量经均值池化(Mean Pooling)与 L2 归一化自底向上合成(段落节点亦可配置为直接全量 Embedding 或混合模式)。
-
叶子计算:仅将最底层的
-
LanceDB 向量持久化与内存缓存 (
LanceDBNodeStore)- 将整棵树展平为
StoredNode,批量持久化写入 LanceDB 向量表。 - 节点类型和
document_id条件会在 ANN Top-K 前由 LanceDB 预过滤;底层索引异常会显式抛出,不会伪装成空结果。 - 建立内存字典缓存(
_cache),保证检索后的父子节点图回溯具备$\mathcal{O}(1)$ 的查找性能。
- 将整棵树展平为
-
多路混合召回 (
src/retrievers/)-
稠密向量检索 (
DenseRetriever):调用 LanceDB 对 Query Embedding 进行余弦相似度匹配,以细粒度句子为主要匹配单元。 -
稀疏关键词检索 (
BM25Retriever):基于内存倒排索引计算词频权重,精准召回专有名词与精确术语。 -
RRF 倒数排名融合 (
HybridRetriever):使用 Reciprocal Rank Fusion 算法($\text{Score} = \sum w \cdot \frac{1}{k + \text{rank}}$)融合 Dense 与 BM25 的排名结果并归一化。 -
多查询扩展 (
MultiQueryRetriever):可选通过 LLM 将用户问题拆解/扩展为多个不同视角的子查询并发检索,拓宽召回边界。
-
稠密向量检索 (
-
两阶段级联重排序 (
src/rerankers/)-
候选归并 (
ParagraphCandidateAggregator):把 Dense/BM25 命中的多个句子合并为唯一段落,同时保留命中子节点与查询来源。 -
语义重排 (
CrossEncoderReranker):默认将前 30 个段落交给Qwen/Qwen3-Reranker-0.6B做 Query-Document 联合打分;接口失败时保留融合排序,不中断问答。 -
覆盖度与去重 (
CoverageMMRSelector):先覆盖不同查询意图,再以 Reranker 相关性和向量相似度执行 MMR,最终返回 8~12 个上下文(默认 10)。 - 原有
FrequencyReranker与DiversityReranker仍可通过--reranker both用于基线对照。
-
候选归并 (
-
Small-to-Big 分层上下文追溯 (
ContextGraph)-
parent_depth表示语义目标层:0为句子、1为段落、2为最近章节;不会因起始节点类型不同而意外多升一级。 - 去重合并后生成包含完整上下文语义的
ContextSnippet,并保留触发该片段的全部叶子节点 ID。
-
- 上下文结构化组装 (
ContextBuilder)- 将
ContextSnippet转义后放入明确标记为“不可信外部数据”的<reference>块,为每个片段分配唯一引用标记(如[ref_1],[ref_2])。 - 系统提示明确禁止执行参考资料中的指令、角色切换、工具调用或提示词泄露要求,降低知识库 Prompt Injection 风险。
- 将
- LLM 推理与流式输出 (
LLMClient)- 封装 OpenAI 兼容接口,支持非流式完整生成(
acomplete)与异步迭代器打字机流式生成(acomplete_stream)。
- 封装 OpenAI 兼容接口,支持非流式完整生成(
- 可核验引用解析 (
extract_citations)- 提取
[ref_X]标签后,对标签前的陈述与来源执行实体、术语、数值词法支撑校验;无支撑的装饰性或错误引用不会进入结构化引用清单。
- 提取
- 统一引擎驱动与 CLI 交互 (
engine.py/main.py)EasyRAG门面类提供统一的高层 API(index、retrieve、query、chat、preview等)。- 多轮问答会把近期 user/assistant 历史加入检索查询,用于消解“他/它/后来”等指代和省略。
从原始知识库文档中采样真实内容,并基于采样内容自动生成评测问题。
原始文档
↓
采样文档 / Chunk
↓
生成问题
↓
基于原文生成 Reference Answer
↓
记录对应 Evidence
↓
生成结构化 Evaluation Dataset
生成器默认启用质量闭环:
- 先过滤寒暄、转场、短反问等低信息段落;多跳与对比题通过实体桥梁 + BM25 选择“主题相关但信息不同”的段落,不再随机硬拼。
- 总结题自顶向下读取完整章节(超长章节按
EVAL_SECTION_MAX_CHARS顺序截断)。 - Evidence 的 quote 必须逐字存在于本次出题上下文,node ID 必须与真实句子一致;无法核验的样本直接废弃,不生成兜底证据。
- 独立 Critic 只读取 Evidence 反向答题;不可回答题会先在完整段落库检索最相关候选,再确认全库确实缺少答案。建议通过
EVAL_CRITIC_MODEL配置不同于出题模型的 Critic。 - 断点续传前会重新核对已有 Evidence 的节点 ID、文档 ID 和原文;语料、分块或文档身份规则变化时拒绝混合续跑,并提示使用
--force全量重建。 - 正式评测会在检索和模型调用前执行相同的索引兼容性预检,旧评测集不会再产生整组虚假 0 分。
每条评测数据同时保存:
id: idquestion:评测问题reference_answer:基于原文生成的参考答案evidence:答案对应的原始证据difficulty:问题难度question_type:问题类型answerable:当前知识库是否能够回答该问题
这样一份数据既可以用于 RAG 检索评测,也可以用于 最终回答质量评测。
ok = {
"id": "qa_001",
"question": "李长庚为什么心疼老鹤?",
"reference_answer": "因为老鹤寿元将尽,羽毛不容易重新长出。",
"evidence": [
{
"document_id": "xxx",
"node_id": "xxx:sec1:p3:s1",
"quote": "这鹤太老了,再想长出新羽可不容易。",
"relevance": 3,
},
],
"difficulty": "easy",
"question_type": "single_hop_reasoning",
"answerable": True,
}not_ok = {
"id": "qa_002",
"question": "李长庚第一次见到老鹤是在什么时候?",
"reference_answer": None,
"evidence": [],
"difficulty": "easy",
"question_type": "unanswerable",
"answerable": False,
}single_hop_fact:单个 Evidence 即可直接找到答案。single_hop_reasoning:基于单个或连续上下文,需要简单推理才能得到答案。multi_hop_fact:需要组合多个 Evidence 中的事实才能回答。multi_hop_reasoning:需要组合多个 Evidence,并进一步进行推理。summary:需要总结一段或多段内容。comparison:需要比较两个或多个对象、事件或观点。unanswerable:现有知识库中不存在足够证据回答。
0=irrelevant(无关)1=weakly relevant(弱相关 / 背景补充)2=supporting evidence(关键支撑依据)3=direct / necessary evidence(直接 / 必要证据)
| 功能阶段 | 执行命令 | 参数与说明 | API / Token 消耗 |
|---|---|---|---|
| 自动生成评测集 | uv run eval.py generate -i tests/docs/太白金星有点烦.txt -o data/eval_dataset.json -n 100 |
--force重跑,从文档中分层采样并生成 20 条覆盖 7 种题型的评测样本 |
🟢 消耗 LLM 生成 |
| 从知识库生成评测集 | uv run eval.py generate -o data/eval_dataset.json -n 100 |
直接从当前 LanceDB 已索引知识库中采样 | 🟢 消耗 LLM 生成 |
| 生成指定题型样本 | uv run eval.py generate -i tests/docs/太白金星有点烦.txt -t single_hop_fact,multi_hop_fact,unanswerable -n 15 |
-t / --types:指定生成的题型子集 |
🟢 消耗 LLM 生成 |
| 查看评测集统计分布 | uv run eval.py stats -d data/eval_dataset.json |
查看样本数、可回答/拒答分布、题型与难度统计 | ❌ 零消耗 |
| 查看评测集样本明细 | uv run eval.py inspect -d data/eval_dataset.json -n 5 |
-n / --limit:预览前 5 条样本及其精确证据引用 |
❌ 零消耗 |
| 筛选特定题型查看 | uv run eval.py inspect -d data/eval_dataset.json -t unanswerable |
-t / --type:按题型过滤查看样本 |
❌ 零消耗 |
| 运行全链路综合评测 | uv run eval.py run -d data/eval_dataset.json -o data/eval_report.json |
执行检索(Hit@K, MRR, NDCG)与回答质量(LLM-Judge)综合评测 | 🟢 消耗 Embedding + LLM |
| 仅运行检索质量评测 | uv run eval.py run -d data/eval_dataset.json --mode retrieval --top-k 5 |
--mode retrieval:跳过回答生成与裁判,仅评估召回率与排序 |
🟢 仅消耗 Embedding |
| 关闭规则扩展做消融 | uv run eval.py run -d data/eval_dataset.json --mode retrieval --no-rule-query-expansion |
独立关闭本地规则扩展,不影响 LLM 多查询开关 | 🟢 仅消耗 Embedding |
| 多查询扩展 + 重排序评测 | uv run eval.py run -d data/eval_dataset.json --multi-query --reranker both |
评测在开启意图扩展与双重重排下的综合召回表现 | 🟢 消耗 Embedding + LLM |
评测报告默认保存 Dense、BM25、RRF、重排和最终上下文各阶段的候选与得分;可用 --no-retrieval-trace 关闭。生成指标同时报告“失败样本保留在分母中的端到端口径”和“仅成功裁判样本口径”。索引 SHA256 是逻辑内容指纹:由文件哈希、节点身份/文本及 Embedding 配置组成,不包含每次重建都会变化的 indexed_at。因此相同文件和配置重新建库后指纹保持稳定;重建时间仍会单独保留在文档元数据中。
aretrieve/query 会先识别问题类型并选择检索链路:事实题走 Hybrid → Cross-Encoder → Small-to-Big;比较题拆成两个实体子查询并交错保留双方证据;多跳题拆解后分别召回,候选不足时自动补充检索;章节总结优先返回章节节点并补充段落;全库主题总结以章节节点作为 map 单元。路由结果写入 retrieval trace 的 query_route 字段。