Retriever 检索 替代 方案 拆解 文档
编写日期:2026年 6月 28日
此目录是为了更易阅读而重新划分的文档集合,来源于 Retriever 检索 替代 ChatGPT 原文 备注。
原文备注保持原样。此 README 汇总了共同上下文、当前方式、模型候选、选择指南。子文档仅包含各替代方案的文档。
综合比较表
这些表格并非最终选择,而是 6 月 28 日用于在同一页面判断需要比较的检索替代方案的观察表。标准是 “把代码 chunk 原样放入 Retriever 时,会出现哪些 JSONL 候选,以及这些候选能否在 Qwen 验证阶段直接被认可为依据”。
一目了然的结论
| 替代方案 | 位置 | 预期角色 | 可直接使用程度 | 核心优势 | 核心风险 | 当前判断 |
|---|---|---|---|---|---|---|
| A. PostgreSQL full-text 维护 | 当前 /api/retrieve 基准线 |
检查现有实现的检索能达到什么程度 | 高 | 已经集成且日志可直接查看 | 当长代码 chunk 成为查询时,相关文档可能被遗漏 | 作为基准线保留,但不确定为最终检索方式 |
| B. 仅 BM25 | 首次检索候选 | 查找直接包含代码/API 字符串的文档 | 中 | 对 KinematicBody2D、move_and_slide、AnimatedSprite2D 等直接字符串表现强 |
当出现 2D/3D 等宽泛词汇时会产生 false positive | 作为首个 PoC 最适合观察 |
| C. 仅 embedding | 基于语义的 recall 扩展 | 即使字符串不完全相同也能找到相关说明文档 | 中 | 捕获句子意义、教程说明、相似使用上下文 | 单独使用在版本/API 精确度判断上风险大 | 作为辅助 recall 使用,而非单独使用 |
| D. BM25 + embedding | 现实的中间方案 | 同时收集字符串依据和语义依据 | 中 | 兼具 BM25 的准确性和 embedding 的 recall | 需要分数融合、去重、上位候选标准 | 接近实际使用的最小方案 |
| E. Qwen query profile | 检索辅助实验 | 将代码 chunk 的检索意图转为 JSON | 低 | 人类可读的 query profile 易于阅读 | 在检索前 LLM 可能生成不存在的 API/意图 | 作为实验/辅助,而非首要检索核心 |
| F. BM25 + embedding + reranker + validator | 最终候选结构 | 从候选生成、重新排序到直接依据验证全链路 | 低 | 质量和可观察性最佳 | 成本、延迟、步骤多 | 作为最终目标候选 |
PoC 仿真结果比较
| 替代方案 | 输入 chunk | 检索候选产生方式 | 相关 JSONL 上线可能性 | 不相关 JSONL 混入可能性 | Qwen 验证前的预期状态 | Qwen 验证后的预期状态 |
|---|---|---|---|---|---|---|
| A | chunkText 全部 |
使用 plainto_tsquery('simple', chunkText) 生成长 query |
低或不稳定 | 可能低,但若为 0 则无候选可观察 | 可能没有候选或仅返回部分 | 在直接依据验证前可能被遗漏 |
| B | chunkText 全部 |
按 token 的 BM25 分数对文档排序 | 高 | 中 | 可能出现 Input.is_action_pressed、AnimatedSprite2D、clamp 等文档 |
若缺少直接字符串则被淘汰 |
| C | chunkText 全部 |
code/document embedding 相似度 | 中 | 高 | “movement”、 “2D”、 “player” 等说明文档会广泛出现 | 若 JSONL 中无直接字符串则被淘汰 |
| D | chunkText 全部 |
合并 BM25 候选和 embedding 候选 | 高 | 中 | 同时看到字符串候选和语义候选 | 仅保留有直接依据的候选 |
| E | chunkText 全部由 Qwen 解析 |
使用 Qwen 生成的检索 profile 进行检索 | 若 profile 匹配则为中等 | 高 | 可能混入 profile 中未出现的 API | 需要验证 profile 本身是否与实际代码匹配 |
| F | chunkText 全部 |
BM25 + embedding 候选经 reranker 重新排序 | 高 | 低 | 即使候选很多,上位也会被整理 | Qwen 以 “是/否” 直接留下有依据的结果 |
演示判定中显现的差异
| 观察情形 | 最初的简单提示 | 改进后的直接依据提示 | 对检索结构的意义 |
|---|---|---|---|
| Godot 3 代码 + 相关 JSONL | 可能得到 “是” | 必须得到 “是” | 若相关依据与实际字符串/API 对应则通过 |
| Godot 3 代码 + 无关 JSONL | 由于 Godot 词汇宽泛可能得到 “是” | 应该得到 “否” | 即使检索候选出现,缺少直接依据验证仍有风险 |
KinematicBody2D 代码 + 3D Spatial 映射 |
可能因 “Godot migration” 这种宽泛意义被视为相关 | 正确答案应为 “否” | 仅 embedding 或宽松的 LLM 判断会产生 false positive |
yield(...) 代码 + await 原型 |
若有直接模式则为 “是” | “是” | label_prototypes 可作为捕捉整体使用模式变化的依据 |
AnimatedSprite2D.play() 代码 + 普通 animation 文档 |
语义上可能看似相关 | 若 JSONL 中无直接字符串/模式则为 “否” | docs_chunks 在最终判定时仍需直接依据确认 |
候选返回情况比较
| 替代方案 | 上位容易出现的候选 | 易被遗漏的候选 | 易被错误混入的候选 | 目视检查要点 |
|---|---|---|---|---|
| A | 在同一行中拥有大量 query token 的文档 | 当相关 token 分散在多篇文档时 | 若 query 过于严格几乎没有结果 | 为什么为 0,哪些 token 导致匹配失败 |
| B | 直接包含 API 名称、函数名、节点名的行 | 表述被改写的说明文档 | 其他系统中出现相同词汇的文档 | match_terms 与实际 source chunk 字符串是否一致 |
| C | 语义相近的教程/说明文档 | 短小的 exact API 映射 | Godot 版本或 2D/3D 不同的文档 | 除了 cosine 分数,是否有直接字符串依据 |
| D | exact 候选 + semantic 候选 | 在分数融合中被压制的候选 | 两种检索都仅弱匹配的候选 | 候选是来源于 BM25 还是 embedding |
| E | 与 Qwen 选出的意图匹配的候选 | Qwen profile 未包含的实际代码线索 | Qwen 想象的 API/意图相关候选 | profile 各字段是否真实存在于 chunk 中 |
| F | reranker 认为与 chunk 匹配的候选 | reranker 未识别的 Godot 特殊案例 | reranker 分数高但 JSONL 无直接依据的候选 | rerank 分数与 validator 判定不一致的点 |
False Positive / False Negative 比较
| 方案 | false positive 示例 | false negative 示例 | 减少方法 |
|---|---|---|---|
| A | 虽然少见,但 query 过宽时会混入一般 movement 文档 | 相关文档分散在多行时为 0 条 | 改变 query 生成方式或添加 BM25 候选 |
| B | 因为 movement、player、2d 导致混入其他 movement 文档 |
API 名称仅在文档中以说明形式出现时会遗漏 | 停用词/弱 token 分离,直接对 API token 加权 |
| C | 意义相近的 3D/其他版本文档混入 | 短的 API rename 行未被捕获 | 与 BM25 并行使用并附加 validator |
| D | BM25 与 embedding 两侧弱匹配的候选混入 | fusion 标准错误时一侧候选被压制 | 保留 union source 标记和 direct evidence 日志 |
| E | Qwen 未包含的 migration_intent 生成导致候选被污染 |
Qwen 实际代码线索在 profile 中缺失 | 在搜索前不信任 profile,仅作辅助使用 |
| F | reranker 过度评价合理的解释 | 候选生成阶段根本未获取到文档 | 充分获取候选数量并将 Qwen 验证放在最后 |
各表适用度
| 方案 | docs_chunks |
api_mapping |
label_prototypes |
备注 |
|---|---|---|---|---|
| A | 长说明文档中不稳定 | exact mapping 行太短可能会漏掉 | pattern 行太短可能会漏掉 | 确认当前方式局限性的基准线 |
| B | 对 API/heading/token 搜索友好 | 对 source/target API 的 exact 搜索友好 | 对 source/target pattern 的搜索友好 | 三表都适合作为首次搜索候选观察 |
| C | 对说明型文档 recall 好 | 对像 API rename 这种短行弱 | 使用模式意义相似时可作辅助 | docs_chunks 辅助最自然 |
| D | 同时获取说明/字符串候选 | 同时获取 exact mapping 与语义候选 | 大幅获取整体使用模式候选 | 三表统一结构最通用 |
| E | query profile 良好时可搜索说明文档 | 出现幻觉 API 时风险大 | 可生成 prototype 意图但可能被污染 | 更像观察/辅助实验而非核心搜索 |
| F | 对最终说明依据的精炼有帮助 | 对 migration 依据的精炼有帮助 | 对使用方式变更依据的精炼有帮助 | 三表在同一流水线中处理最方便 |
必须通过日志查看的内容
| 方案 | 搜索前日志 | 搜索中日志 | 搜索后日志 | 失败原因确认点 |
|---|---|---|---|---|
| A | 原始 chunkText、生成的 tsquery |
search_tsv @@ query 命中数、ts_rank_cd |
返回行、score | query 是否过长,哪些 token 在行中不存在 |
| B | 原始 chunkText、token 列表 |
词频、匹配的 term、BM25 分数 | 各表 top‑k | 强 token 与弱 token 是否被区分 |
| C | 原始 chunkText、embedding 模型 |
向量距离/余弦分数 | 语义 top‑k | 是否为仅凭意义匹配、无实际字符串的候选 |
| D | BM25 top‑k、embedding top‑k | union、duplicate 合并、score 融合 | 带 source 标记的候选列表 | 只来自单一搜索的候选为何存活 |
| E | 发送给 Qwen 的原始 chunk | Qwen query profile JSON | 基于 profile 的搜索结果 | profile 字段是否真实存在于 source 中 |
| F | 全部 BM25/embedding 候选 | rerank 分数、rerank 顺序 | Qwen “是/否”、reject reason | 通过 reranker 的候选为何在直接依据验证中被淘汰 |
实现复杂度与运营成本
| 方案 | 实现量 | DB 变更 | 外部模型费用 | latency | 调试难度 | 失败后修复难易度 |
|---|---|---|---|---|---|---|
| A | 低 | 几乎无 | 无 | 低 | 低 | 低。仅是结构性性能上限可能存在 |
| B | 中等 | 需选择 BM25 搜索引擎/索引 | 无或低 | 低 | 低 | 高。可调 token/权重 |
| C | 中等 | 需 embedding 列/索引 | 有 | 中 | 中 | 中。需调模型/维度/阈值 |
| D | 中等~高 | BM25 与 vector 均需 | 有 | 中 | 中 | 高。可分离哪条搜索路径失败 |
| E | 中等 | profile 存储为可选 | 有 | 高 | 高 | 低。LLM profile 错误难复现 |
| F | 高 | 需 BM25 + vector + rerank 日志 | 有 | 高 | 高 | 高。有分阶段日志时易定位问题 |
Qwen 放置位置比较
| 位置 | 优点 | 风险 | 当前更合适的使用方式 |
|---|---|---|---|
| 搜索前 query 生成 | 可在无需硬编码的情况下构造搜索意图 | 可能制造不存在的线索污染搜索 | 仅用于实验 |
| 搜索候选后直接依据验证 | 可过滤搜索结果是否与实际代码匹配 | 调用成本高 | 最重要的环节 |
| 最终答案生成 | 可生成代码说明/迁移响应 | 依据错误则答案也错 | 放在已验证的 JSONL 候选之后 |
| 整体判断整合 | 可生成对人友好的结论 | 可能隐藏中间失败 | 在日志足够累积后再审查 |
选择优先级
| 优先级 | 选项 | 理由 | 本阶段需确认的问题 |
|---|---|---|---|
| 1 | 以 A 为基准线测量 | 必须了解当前实现在哪儿失效 | 现行方式实际返回多少条 |
| 2 | 将 B 作为独立 PoC 接入 | 代码/API 字符串搜索最具解释性 | KinematicBody2D、yield、move_and_slide 能否找到正确行 |
| 3 | 用 C 作为辅助 recall 对比 | 可扩大 docs_chunks 说明型候选 | 语义候选在直接依据验证中被淘汰的比例 |
| 4 | 用 D 并行生成候选 | 现实的最小结构 | 合并 BM25 与 embedding 候选后质量是否提升 |
| 5 | 用 F 添加 reranker/validator | 最终质量确认 | Qwen 直接依据判断无关的 JSONL 是否能被稳妥剔除 |
| 保留 | E | 搜索前 LLM 污染风险大 | profile 是否仅基于实际代码字符串生成 |
PoC 检查清单
| 检查项目 | 需要查看的界面/日志 | 成功标准 |
|---|---|---|
将相同的 chunkText 放入多个方案 |
Web 调试器的 chunk 输入区域 | 输入在各方案之间保持不变 |
| 各表搜索按钮 | docs_chunks、api_mapping、label_prototypes 按钮 |
同一 chunk 在各表的候选结果出现差异 |
| raw 候选查看 | 返回的 JSONL payload | 能看到与 source chunk 中实际字符串对应的字段 |
| Qwen 验证确认 | prompt + chunk + jsonl 调试区域 |
相关候选显示 “是”,无关候选显示 “否” |
| reject reason 确认 | validator 结果日志 | 仅匹配到 Godot、migration、2D 等宽泛词汇的候选被剔除 |
| false positive 收集 | 被剔除的候选列表 | 可用于后续 query/token/validator 规则的改进 |
| 各方案比较保存 | 观察日志或结果 JSON | 同一输入的 A-F 差异得以保留 |
方案文档
- 方案 A:PostgreSQL 全文检索维护
- 方案 B:仅 BM25
- 方案 C:仅 embedding
- 方案 D:BM25 + embedding 并行
- 方案 E:生成 Qwen 查询配置文件
- 方案 F:BM25 + embedding + reranker + validator
当前前提
当前基准如下。
1. zip 中包含 markdown 原始文件、模式、调试器、设计文档。
2. 本地已经有 markdown → JSONL → DB 的转换版本。
3. 现在可以看到的是转换前的 markdown 和 DB 模式/搜索流程。
4. PoC 需要将 zip 中的 markdown 像 JSONL chunk 那样拆分,并模拟可能进入本地 DB 的搜索目标。
5. 所需的不是代码框架,而是追踪当 chunk 进入时,各种搜索方式如何生成分数以及为何成功/失败。JSONL/DB 基准
文档基准产出如下。
work/godot_rag/jsonl/docs_chunks.jsonl
work/godot_rag/jsonl/api_mapping.jsonl
work/godot_rag/jsonl/label_prototypes.jsonl
work/godot_rag/jsonl/ingest_report.jsonl数据库不是将整行 JSONL 放入 payload jsonb,而是将 JSONL 的字段展开为表的列来理解。
docs_chunks
chunk_id
doc_version
source_url
source_file
source_sha256
doc_type
symbol
section_path
heading
content
code_blocks
language_tags
godot_version_tags
api_symbols
token_count
metadata
embedding
search_tsvapi_mapping
mapping_id
source_api
target_api
change_type
godot_from
godot_to
confidence
evidence_chunk_ids
match_terms
notes
negative_patternslabel_prototypes
prototype_id
label
task_type
input_pattern
expected_finding
recommended_action
evidence_mapping_ids
evidence_chunk_ids
severity
validator_rules
embedding
search_tsv基准块
在替代方案比较中作为基准的块是以下的 Godot 代码。
func _process(delta):
var velocity = Vector2.ZERO
if Input.is_action_pressed(&"move_right"):
velocity.x += 1
if Input.is_action_pressed(&"move_left"):
velocity.x -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)强令牌:
animatedsprite2d
is_action_pressed
move_left
move_right
normalized
clamp
screen_size
vector2
zero弱令牌:
func
var
if
else
velocity
position
delta
x
0
1搜索输入保持为原始的 chunkText。在搜索之前必需提取 Godot API signal 并将其转换为单独的 query 的做法,并不是基本设计的核心。如果有需要,只在以后作为辅助评分进行审查。
当前 /api/retrieve 方式
当前项目的 /api/retrieve 并非 BM25。
流程接近如下。
chunkText
-> plainto_tsquery('simple', chunkText)
-> search_tsv @@ query
-> ts_rank_cd(...)PostgreSQL的 ts_rank_cd 是全文检索排名函数。它不同于像 BM25 那样将词频、逆文档频率、字段长度归一化组合在一起的搜索引擎式 BM25 本身,需要另作区分。
plainto_tsquery(raw_chunk) 会从输入的 chunk 中提取标记并生成 query。基准 chunk 可以像下面这样成为一个很长的 query。
func & process & delta & var & velocity & vector2 & zero
& input & is_action_pressed & move_right & move_left
& length & normalized & speed & animatedsprite2d
& play & stop & position & clamp & screen_size这种方式可能在一行中要求太多的标记。
如果官方文档的块被划分成如下所示,就会出现问题。
chunk A:
Input.is_action_pressed
velocity.normalized
AnimatedSprite2D.play/stop
chunk B:
AnimatedSprite2D 说明
position.clamp
screen_size两者都相关,但可能并非所有令牌同时出现在同一行中。
在原始备注中,认为严格原始 AND 性质的搜索可能会像下面这样失败。
strict raw AND hits: 0当前方式可以作为 PoC 的基准线,但作为最终搜索策略维持下来较为薄弱。下一阶段需要将当前方式与 BM25 分离后进行比较。
模型候选
voyage-code-3
被提及为最符合当前项目的 embedding 候选。
原因:
query 不是自然语言问题,而是 GDScript 代码块。
搜索目标 JSONL 中也混杂了 code_blocks、API 名称、文档说明。优点
- 对代码检索能力强。
- 适合处理长块(chunk)。
- 适用于 code → docs 的搜索。
缺点
- 依赖外部 API。
- 成本较高。
- 不是专门针对 Godot 的模型。
基于内存的推荐
voyage-code-3 1024 floatOpenAI text-embedding-3-large
通用高质量 embedding 候选。
优点:
- 在通用语义检索方面表现强劲。
- 对文档说明搜索稳定可靠。
- 易于与 OpenAI 生态系统集成。
缺点:
- 并非专用于代码检索。
- 基础维度较大,可能导致存储/索引成本上升。
- 对于 GDScript 块 → JSONL 的搜索,可能不如 voyage-code-3 直接。
基于内存的推荐度:
第二优先级Gemini Embedding
通用/多语言语义检索候选。
优点:
- 适用于文档说明型检索。
- 有维度缩减的选项。
- 在多语言文档搜索方面可能具有优势。
缺点:
- 不是专用于代码检索的选项。
- 更侧重语义相似性,而非 API 代码的准确性。
- 在判断 Godot 迁移是否精确时,单独使用存在风险。
Jina embeddings v4
在复杂文档、跨模态、视觉丰富的文档检索方面具有优势的候选。
优点:
- 文档搜索范围广泛。
- 对跨模态/复合文档表现出色。
- 也可以考虑代码适配器系列。
缺点:
- 当前项目以 markdown/代码为中心。
- 图像/表格检索不是核心需求。
- 在现阶段可能是过度的选择。
基于备注的推荐度:
目前优先级较低rerank-2.5
reranker 候选人被提及。
角色:
BM25和 embedding 带来的候选项会与原始块再次比较。
根据相关度得分对候选项重新排序。在此项目中预期的效果:
- 降低 2D movement 和 3D movement 的误报率。
- 更好地反映
AnimatedSprite2D、Vector2、screen_size、position.clamp等直接上下文。 - 将 BM25 和 embedding 各自得到的候选项整理为最终候选。
注意:
- reranker 不是 validator。
- 直接依据的验证需要由 Qwen direct‑evidence validator 重新进行。
选项摘要
| 方案 | 流程 | 优点 | 风险 |
|---|---|---|---|
| A | 保持 PostgreSQL 全文检索 | 已实现 | 在长 chunk 中可能为 0 条,非 BM25 |
| B | 仅使用 BM25 | 透明,对字符串/API 强 | 3D movement 等误报 |
| C | 仅使用 embedding | 可进行语义检索 | API/版本准确性较弱 |
| D | BM25 + embedding | 现实的平衡 | 需要对分数组合进行调优 |
| E | Qwen 查询配置文件 | 减少硬编码,可概括意图 | 幻觉、成本、检索前污染 |
| F | BM25 + embedding + reranker + validator | 质量至上 | 成本、延迟、步骤多 |
当前临时判断
原始备忘的结论接近于以下内容。
输入仅保留一个 chunkText。
第一次搜索使用 BM25。
原因:在代码/API 字符串搜索中透明且强大。
第二次搜索使用 voyage-code-3 嵌入。
原因:因为查询是代码块,适合代码检索模型。
第三次排序使用 rerank-2.5。
原因:在 BM25 和嵌入得到的相似候选中,将真正的 chunk 对应的 JSONL 提升到上方。
最后验证使用 Qwen direct-evidence validator。
原因:如果 JSONL 中没有与 chunk 直接匹配的字符串/模式依据,就必须舍弃。一行摘要:
raw chunk 原样输入
-> BM25 + code embedding 并行候选生成
-> reranker 重新排序
-> Qwen 直接依据验证接下来实际比较的内容
- 当前 PostgreSQL 全文检索方式在基准 chunk 中返回多少条记录
- BM25 是否真的把
first_2d_game排到前面 - 在仅使用 BM25 时,3D movement 的误报会混入多少
- 仅使用 embedding 时会额外检索到哪些说明性文档
- 在 BM25 + embedding 联合时,如何合并重复项和分数
- reranker 是否能正确重新排序 2D/3D 候选项
- Qwen direct-evidence 验证器是否会把无关的 JSONL 实际上标记为
아니오并丢弃