跳转到内容

代码 RAG 管线:多引擎混合检索 + Reranker

代码 RAG 管线:多引擎混合检索 + Reranker

学习目标

本章要解决的核心问题:在代码库中搜索相关代码片段时,如何让搜索结果既精确(关键词匹配)又有语义理解能力(向量检索),还能感知用户的当前工作上下文?

你将学到:

  • Continue 的五路并行检索架构
  • Reranker 重排管线的工作流程
  • 代码分块策略(按语法结构而非纯文本)
  • 增量索引与内容寻址的优化

前置知识

本章涉及 RAG 的通用原理和上下文压缩策略,建议先阅读:

下文假设你已理解上述概念,直接聚焦 Continue 的具体实现。


项目实践

五路并行检索架构

Continue 的 RerankerRetrievalPipeline 同时从五个数据源检索相关代码块:

五路检索的职责分工:

检索路技术擅长场景示例
全文搜索 (FTS)SQLite FTS5精确关键词匹配搜索类名、函数名
向量检索 (Embeddings)LanceDB + 嵌入模型语义相似度”用户认证逻辑” → 找到 auth 相关代码
最近编辑时间戳排序上下文连续性用户刚改过的文件很可能相关
Repo MapLLM 智能选择全局感知查询需要跨多个文件的上下文
工具调用Agent 自主调用搜索工具交互式深度检索Agent 模式下深度探索代码库

检索管线代码结构

// RerankerRetrievalPipeline.ts 核心流程
private async _retrieveInitial(args): Promise<Chunk[]> {
let retrievalResults: Chunk[] = [];
// 第一路:全文搜索(始终执行)
const ftsChunks = await this.retrieveFts(args, nRetrieve);
// 第二路:向量检索(仅在配置了 embed 角色时)
const embeddingsChunks = config.selectedModelByRole.embed
? await this.retrieveEmbeddings(input, nRetrieve)
: [];
// 第三路:最近编辑文件
const recentlyEditedFilesChunks = await this.retrieveAndChunkRecentlyEditedFiles(nRetrieve);
// 第四路:Repo Map
const repoMapChunks = await requestFilesFromRepoMap(llm, config, ide, input, filterDirectory);
// 第五路:工具调用检索(可选实验性功能)
if (config.experimental?.codebaseToolCallingOnly) {
const toolBasedChunks = await this.retrieveWithTools(input);
}
// 合并 + 去重
return deduplicateChunks(retrievalResults);
}

Reranker 重排

检索到初步结果后,如果配置了 reranker 模型,执行重排:

五路检索结果(N=2×nFinal 个 Chunk)
每个 Chunk 与用户查询计算相关性分数
按分数排序,取 Top-nFinal
输出给上层(注入到 LLM 上下文)

Reranker 使用专门的 Rerank 模型(如 Cohere Rerank),相比直接用嵌入模型的 cosine 相似度,reranker 能更准确地判断”这个代码片段是否真的回答了用户的问题”。

代码分块策略

Continue 使用 ChunkCodebaseIndex 进行按代码结构的分块,而非简单的固定行数分割:

  • 按函数、类、方法等语法结构边界分块
  • 保留足够的上下文(函数签名、类定义)
  • 每块最大 384 tokens(可配置)
  • 分块结果供 LanceDB 向量检索使用

这与纯文本分块(如按 500 字符切分)的关键区别:代码结构的分块更有意义。一个函数的代码天然就是一个语义单元,按函数边界分块比按字符数截断更合理。

增量索引与内容寻址

Continue 的索引系统避免了重复索引的问题:

文件变更检测(时间戳对比)
与 SQLite 目录对比 → 分类:add / remove / update
对于 "add" 文件:检查内容 hash(cacheKey)是否已在索引中
如果已存在 → 只添加分支标签(不重新计算)
如果不存在 → 计算索引(chunk → embed → store)

这意味着在大型项目中:

  • 切换 Git 分支时,只索引新增的文件
  • 同一文件内容在不同分支只计算一次嵌入向量
  • 索引结果跨分支复用

问题与规避

陷阱 1:LanceDB 在 Linux 上的 CPU 兼容性

现象:在部分 Linux 机器上,向量检索不工作。

原因:LanceDB 的本地依赖对 CPU 指令集有要求(需要 AVX2 等),部分云服务器或旧 CPU 不兼容。

规避:Continue 在加载 LanceDB 前调用 isSupportedLanceDbCpuTargetForLinux() 进行检测。如果检测失败,LanceDB 不加载,RAG 管线回退到仅使用 FTS。

陷阱 2:嵌入模型缺失导致语义搜索降级

现象@codebase 只能找到关键词匹配的代码,找不到语义相关但关键词不同的代码。

规避:配置 embed 角色的模型。如果暂时没有,FTS 仍然可用,但语义搜索能力受限。

陷阱 3:索引过多或过慢

现象:首次打开大型项目时索引需要几分钟。

规避

  • pauseCodebaseIndexOnStart 配置可推迟索引
  • 使用 .continueignore 排除不需要索引的目录(如 node_modulesdist
  • 内容寻址机制确保切换分支时不需要全量重建索引

设计取舍

为什么用五路检索而非单一检索

方案优势代价
单一检索(仅 FTS)简单、快速、无依赖无法理解语义,“authentication” 搜不到 “auth”
单一检索(仅向量)语义理解强精确搜索弱,搜 “UserModel” 可能找不到同名字段
五路并行覆盖所有检索场景系统复杂,维护成本高

Continue 选择五路并行,因为代码搜索是一个多维度问题:有时你要找的是精确的名字(FTS),有时你要找的是相关逻辑(向量),有时你需要知道用户正在关注什么(最近编辑)。

Reranker vs 直接嵌入排序

  • 嵌入排序:计算查询向量和文档向量的 cosine 相似度。快速但精度有限。
  • Reranker:使用专门的交叉编码器模型,同时看查询和文档,给出更准确的相关性分数。更精确但更慢。

Continue 在配置了 reranker 时优先使用它,否则回退到嵌入排序。

内容寻址 vs 基于路径的索引

传统索引基于文件路径,同一文件在不同分支会被重复索引。Continue 使用内容 hash(cacheKey)作为去重键,同一内容的文件无论在哪里都只索引一次。这在多分支工作流中节省了大量计算。


参考来源