- 人工智能
- 搜索引擎
- 本地部署
- 后端
- MCP 服务
【免费下载链接】xerj
XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.
2026-10-06 的一次 reference coding 实测暴露了xerj code --mode hybrid在混合 mapping 语料上的一个隐蔽缺陷:bm25模式能返回 20 条命中,而hybrid模式只返回 1 条,输出中的@mode备注却声称 BM25 腿覆盖了全部索引。本文基于该现场报告(field report),完整复现问题路径,并结合当前仓库源码讲清混合检索的管线结构、缺陷根因与 PR #1177 修复方案的实现原理。读完后,你可以理解 XERJ 的服务端 RRF 融合契约、混合语料(mixed-mapping corpus)上的降级逻辑,以及"honest miss"这类诚实性设计是如何落到退出码和输出备注上的。
现场环境:一次 reference coding 的完整闭环
报告使用的场景是 XERJ 的 reference coding 工作流(reference-coding toolkit):克隆已含正确实现的仓库、用 XERJ 建索引、写代码前先检索参考段落。整个循环自 issue #977 起全部内建于xerj二进制,核心命令只有三条:
xerj corpus add xerj-storage https://github.com/spacejam/sled … # 克隆参考仓库(一次性) xerj corpus index xerj-storage # 走 xerj autoindex 建索引 xerj code xerj-storage "how does sled fsync its WAL segment on rotation?" # 写前检索报告中的复现目标是一个"混合映射"语料:一个 30 个文件的 Python git 仓库外加其corpus.json,在干净的--data-dir下建出61 条记录、2 个索引——其中一个索引的body字段映射为semantic_text(支持向量检索),另一个只是普通text索引。这个"同语料、两种 mapping 混排"的形态正是触发 #1146 的必要条件。
实测数字(来自报告):
| 操作 | 结果 |
|---|---|
xerj code tinya "retry connection timeout handler" -k 20 --json,bm25 | 20 条命中 |
同一查询,hybrid(修复前,用 curl 重放旧请求) | 1 条命中 |
同一查询,hybrid(修复后) | 20 条命中 |
xerj corpus index建索引 | 31 个文件,耗时 29.5s |
cargo build --release -p xerj-server | 7 分 08 秒 |
报告特别记录了一个操作层面的坑:当时机器上安装的二进制是 rc.74,根本没有xerj code子命令(unknown argument: code),因此无法直接拿到"同版本修复前"的对照,只能改用 curl 重放旧请求来验证"before"。这说明该能力是随版本引入的,阅读本文结论时以当前仓库源码为准。
corpus add/corpus index每完成一步都会打印下一条命令,xerj-done给出明确的结束行,整个驱动过程很顺滑。报告的结论很直接:这个 bug 属于"诚实性缺陷"——hybrid 静默地只返回 1 条命中而 bm25 返回 20 条,如果当时没有做 bm25 对照,这个结果是很容易被直接信任的。
--mode hybrid的完整管线:从台账到融合
要理解为什么会出现"1 条 vs 20 条",先看xerj code在 hybrid 模式下实际做了什么。核心管线是纯函数run_code_query,按顺序执行:台账读取 → 30 天陈旧度检查 → 节点存活索引计数 → mapping 发现 → 查询 → 段落选择 → 许可证标注 → 渲染。与本文直接相关的分支如下。
1. 先读 mapping,发现"向量能力集"。查询前会先GET /{prefix}*/_mapping,由semantic_capable挑出body字段映射为semantic_text的索引。这一步不是优化,而是硬性约束:一个semantic查询打到body为普通text的索引上,返回的不是"少几条结果",而是让整个搜索以 HTTP 400 失败,把通配符里的其他索引全部拖下水。因此向量腿只能瞄准能力集(capable set)。
2. BM25 size-1 预检决定"诚实性"。发出融合请求前,先发一条 size 为 1 的 BM25 预检。若词法腿找不到任何匹配,直接报告 miss(退出码 1),而不是继续跑向量腿——因为 lexical embedder 对任意输入都会返回"自信的最近邻",向量近邻不能作为"匹配"的证据。这是该工具"宁可报无,不可乱报"的核心设计。
3. 发出原生顶层hybrid查询,融合在服务端完成。请求体由hybrid_body构造,形状就是一个原生hybrid查询:
{ "size": 20, "query": { "hybrid": { "queries": [ { "query": { "multi_match": { "query": "…", "fields": ["body", "defs", "title"] } } }, { "query": { "semantic": { "field": "body", "query": "…", "k": 20 } } } ], "fusion": { "method": "rrf", "k": 60 } } } }客户端不做任何 rank 合并。常量RRF_K = 60定义在 xccode/mod.rs,注释明确写着它是 Cormack, Clarke & Buettcher(SIGIR 2009)论文的原值,"在这里并没有调过参"。融合本身发生在引擎侧:run_hybrid把每条子查询当作独立搜索并发执行,各子列表按max(50, size + from)深度取回,再由fuse_rrf按weight_i / (k + rank_i)累加融合分,输出统一的全局排序"融合分降序 → seq_no 升序 → _id 升序"(#940)。
4. 每次 hybrid 运行都以一条"arms-ran 备注"开场。形如[hybrid RRF(k=60) — BM25 over 2 index(es), vector over 1 of 2]。没有可运行的向量臂时,答案会明确标注BM25 only — …,绝不假装是 hybrid。独立--mode semantic下 mapping/传输失败则直接退出码 2(没有 BM25 结果可回退)。
缺陷根因:能力集瞄准丢掉了词法索引的 BM25 腿
回到 #1146。管线第 1 步的能力集瞄准本身是对的,但修复前存在一个漏洞:当能力集小于整个语料时,融合请求只发往能力集(capable.join(",")),其余词法索引被整体排除在请求之外——它们的 BM25 命中根本没有任何机会进入结果。于是:
- 修复前,hybrid 只返回能力集索引上的命中(本例 1 条),而 bm25 模式对全通配符查询能拿到 20 条;
- 更糟的是
@mode备注仍打印BM25 over 2 index(es),声称 BM25 腿覆盖了两个索引——备注与实际发出的请求不一致,这正是"诚实性缺陷"的准确描述。
单元测试把这条缺陷路径钉死为回归测试hybrid_keeps_lexical_only_indices_in_the_bm25_leg:两个索引(一个semantic_text、一个text),断言修复前"hybrid 只回 1 条命中而--mode bm25回 20 条,且备注声称覆盖 2 个索引"的行为必须被消除。
修复方案:第二个单腿 hybrid 请求 + 按_score合并
修复的关键洞察来自引擎的融合语义:引擎是按索引做 per-index RRF 的,索引之间则按_score合并。因此,一个只带 BM25 腿的hybrid请求,其命中的_score与完整双腿请求的 RRF 分数处在同一个1/(k+rank)量纲上——两个响应按分数合并,就是引擎自己的跨索引合并步骤,而不是"第二次融合"。
实现分三步(xccode/mod.rs):
- 第二个请求仍是原生
hybrid,只是只剩 BM25 腿。hybrid_bm25_leg_body复用同一个hybrid_request封装、同一份{"fusion":{"method":"rrf","k":60}},发往词法索引集合。测试断言两个请求的fusion字段完全一致、size同为 overfetch 深度(#1137的 10×k 过取,默认 50)。 - 按
_score降序合并两个响应。merge_by_score的排序规则与引擎多索引搜索的合并方式完全一致:分数降序、稳定排序(同分时各响应保持自身 rank 顺序)、_index打破跨响应平局(结果页不依赖哪个请求先发);hits.total取两者之和,其余元数据沿用第一份响应。 - 备注如实描述分工。arms-ran 行追加一段说明,例如:
BM25 over 2 index(es), vector over 1 of 2; lexical-only (BM25 leg only, merged by score): xc-kv-b1-001,明确点名哪些索引走了"仅 BM25 腿"、如何合并。
修复后,报告的复现命令逐字生效:hybrid 返回与 bm25 相同的 20 条命中。对照引擎侧实现可以看出这套契约是自洽的:run_hybrid里每条子列表独立取深、RRF 在列表内累加、索引间按分数归并;客户端的merge_by_score正是把这个"索引间归并"步骤在跨请求维度上复刻了一遍,量纲由引擎的 per-index 打分管(测试数据里直接出现1.0/61.0、2.0/61.0这样的 RRF 分数)保证一致。
混合模式何时该用:实测对照
需要强调,hybrid 从来不是默认模式,--mode bm25才是(Mode 默认值)。仓库的 SKILL.md 给出了两个语料、12 条人工标注查询的实测:
| rust-text top-1 | rust-text top-3 | kv-oss (C) top-1 | kv-oss (C) top-3 | combined top-1 | combined top-3 | |
|---|---|---|---|---|---|---|
| bm25 | 3/6 | 6/6 | 6/6 | 6/6 | 9/12 | 12/12 |
| hybrid | 5/6 | 6/6 | 2/6 | 4/6 | 7/12 | 10/12 |
| semantic | 4/6 | 4/6 | 2/6 | 3/6 | 6/12 | 7/12 |
top-3 才是工作指标(Agent 读的是 k 条段落而不是一条),而 bm25 在它上面是满分且两个语料上都不是最差臂,同时快约 5 倍(没有向量往返、没有 mapping 查找)。hybrid 曾在rust-text单语料上因 top-1 占优而被设为默认,那是语料特定的结果:在 C 语料上向量臂只能触达 407 个索引中的 5 个(#173),融合等于把一个像样排序和一个看不见 98.8% 材料的排序搅在一起。结论可以概括为:当语料的semantic_text覆盖广、且你确实关心 top-1 时再上--mode hybrid,并且先测量再信任。
可验证的依据与延伸阅读
本文所有实现细节均可在当前仓库内复核:
- 管线与修复主体:xccode/mod.rs(hybrid 分支)、merge_by_score、hybrid_bm25_leg_body;
- #1146 回归测试:hybrid_keeps_lexical_only_indices_in_the_bm25_leg;
- 能力集发现:semantic_capable;
- 引擎侧 RRF 融合与全序定义:run_hybrid、fuse_rrf;
- 原始现场报告与全部实测数字:field report;
- 退出码契约(0 命中 / 1 无匹配 / 2 用法或传输 / 3 语料未加载)与 MEATL 输出格式:tools/xerj-code/README.md、SKILL.md。
这个案例的价值不在 hybrid 检索本身,而在它示范了一种可复用的工程模式:当"请求必须瞄准能力子集"与"备注必须描述全部工作"冲突时,与其放宽瞄准(引入 400 风险),不如补发同量纲的第二腿请求、按引擎自己的合并语义归并结果,并把分工写进输出——让每一个声称都有对应的请求记录可查。
- 人工智能
- 搜索引擎
- 本地部署
- 后端
- MCP 服务
【免费下载链接】xerj
XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.
相关推荐
修复 XERJ 中 knn 与 query 并置时的静默丢弃:825 树重写修复实录
修复 XERJ 中 knn 与 query 并置时的静默丢弃: 825 树重写修复实录 本篇文章基于 XERJ 仓库中的一份 AI 现场报告( user fee
人工智能搜索引擎本地部署后端MCP 服务BabyAI核心功能解析:从GoToObj到Synth的25个任务级别详解
BabyAI核心功能解析:从GoToObj到Synth的25个任务级别详解 BabyAI平台是一个用于训练智能体理解和执行语言命令的测试平台,提供了丰富多样的任
人工智能强化学习机器学习深度学习NLPPostHog `event_dropped_too_old` 摄入警告:诊断与修复事件因超龄被策略丢弃的静默数据丢失
PostHog event_dropped_too_old 摄入警告:诊断与修复事件因超龄被策略丢弃的静默数据丢失 本篇指南围绕 PostHog 数据摄入管道的
逆向工程MCP 服务AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考