如何构建自定义代码 RAG

构建一个自定义检索增强生成(RAG)系统,以便在大规模代码库中实现更快速、更具成本效益的代码搜索。本指南专为需要跨所有用户统一索引代码或包含自定义逻辑的高级用户准备。

第一步:如何选择嵌入模型

如果可能,我们建议使用 voyage-code-3,它能为现有的代码嵌入模型提供最准确的答案。您可以在此处获取 API 密钥。由于其 API 与 OpenAI 兼容,您可以直接替换 URL 来使用任何 OpenAI 客户端。

第二步:如何选择向量数据库

目前有许多可用的向量数据库,但由于大多数向量数据库都能高效处理大型代码库,我们建议选择一个易于设置和实验的数据库。
LanceDB 是一个不错的选择,因为它可以在 Python 和 Node.js 的内存中运行。这意味着初期您可以专注于代码编写,而无需配置基础设施。如果您已经选择了其他向量数据库,使用它替代 LanceDB 也是完全可以的。

第三步:如何选择“分块”策略

大多数嵌入模型一次只能处理有限的文本量。为了解决这个问题,我们需要将代码“分块”为较小的部分。
如果您使用 voyage-code-3,它的最大上下文长度为 16,000 个 token,足以容纳大多数文件。这意味着初期您可以采用简单的策略,即直接截断超过限制的文件。以下是三种分块策略(按从最简单到最全面排列):
  1. 文件超出上下文长度时进行截断:在这种情况下,每个文件始终只有一个分块。
  2. 将文件拆分为固定长度的分块:从文件头部开始,向当前分块添加行直到达到限制,然后开始一个新的分块。
  3. 使用基于抽象语法树(AST)的递归策略:这是最精确但也最复杂的方法。在大多数情况下,使用 (1) 或 (2) 即可获得高质量结果。如果您想尝试此方法,可以在我们的代码分块器LlamaIndex 中找到参考示例。
和往常一样,我们建议从那些以 20% 的努力获得 80% 收益的策略开始。

第四步:如何编写索引脚本

索引过程(即将代码插入向量数据库以供检索)分为三个步骤:
  1. 分块
  2. 生成嵌入
  3. 插入向量数据库
使用 LanceDB,我们可以同时执行步骤 2 和 3,正如其文档所示。例如,如果您使用 Voyage AI,配置方式如下:
from lancedb.pydantic import LanceModel, Vectorfrom lancedb.embeddings import get_registrydb = lancedb.connect("/tmp/db")func = get_registry().get("openai").create(    name="voyage-code-3",    base_url="https://api.voyageai.com/v1/",    api_key=os.environ["VOYAGE_API_KEY"],)class CodeChunks(LanceModel):    filename: str    text: str = func.SourceField()    # 1024 is the default dimension for `voyage-code-3`: https://docs.voyageai.com/docs/embeddings#model-choices    vector: Vector(1024) = func.VectorField()table = db.create_table("code_chunks", schema=CodeChunks, mode="overwrite")table.add([    {"text": "print('hello world!')", filename: "hello.py"},    {"text": "print('goodbye world!')", filename: "goodbye.py"}])query = "greetings"actual = table.search(query).limit(1).to_pydantic(CodeChunks)[0]print(actual.text)
如果您要索引多个存储库,最好将它们存储在不同的“表”(LanceDB 使用的术语)或“集合”(某些其他向量数据库使用的术语)中。通过添加“存储库”字段然后进行过滤的替代方案效率较低。
无论您选择哪种数据库或模型,脚本都应遍历所有需要索引的文件,对它们进行分块,为每个分块生成嵌入,然后将所有分块插入向量数据库。

第五步:如何运行索引脚本

在理想的生产环境中,您需要构建“自动增量索引”,这样每当文件发生更改时,仅该文件会被自动重新索引。这具有保持嵌入完美同步且成本更低的优势。
话虽如此,我们强烈建议在尝试此操作之前先构建并测试工作流。除非您的代码库在频繁且彻底地重写,否则对索引进行增量刷新通常就足够且成本合理。
此时,您已经编写了索引脚本并测试了从向量数据库查询的功能。现在,您需要一个运行索引脚本的计划。
初期,您可能需要手动运行它。一旦确认自定义 RAG 能提供价值并准备好长期使用,您可以设置定时任务(cron job)定期运行。由于代码库在短时间内通常变动不大,每天重新索引一次就足够了,甚至每周或每月一次也可能足够。

第六步:如何设置 MCP 服务器

要将您的自定义 RAG 系统与 Continue 集成,您需要创建一个 MCP(模型上下文协议)服务器。MCP 为 AI 工具访问外部资源提供了一种标准化的方式。

创建您的 MCP 服务器

以下是一个使用 Python 查询向量数据库的参考实现:
"""Custom RAG MCP server for code retrieval"""
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import lancedb

# Initialize your vector database connection
db = lancedb.connect("/path/to/your/db")
table = db.open_table("code_chunks")

app = Server("custom-rag-server")

@app.tool()
async def search_codebase(query: str, limit: int = 10) -> list[TextContent]:
    """
    Search the codebase using vector similarity.
    
    Args:
        query: The search query
        limit: Maximum number of results to return
    """
    # Query your vector database
    results = table.search(query).limit(limit).to_list()
    
    # Format results for Continue
    formatted_results = []
    for result in results:
        formatted_results.append(TextContent(
            type="text",
            text=f"File: {result['filename']}\n\n{result['text']}"
        ))
    
    return formatted_results

@app.tool()
async def get_file_context(filename: str) -> list[TextContent]:
    """
    Get all chunks from a specific file.
    
    Args:
        filename: The name of the file to retrieve
    """
    results = table.where(f"filename = '{filename}'").to_list()
    
    return [TextContent(
        type="text",
        text="\n".join([r['text'] for r in results])
    )]

if __name__ == "__main__":
    stdio_server(app).run()

配置 Continue 以使用您的 MCP 服务器

将您的 MCP 服务器添加到 Continue 配置中。
config.yaml
mcpServers:
  - name: custom-rag
    command: python
    args:
      - /path/to/your/mcp_server.py
    env:
      VOYAGE_API_KEY: ${VOYAGE_API_KEY}
config.json
{
  "mcpServers": [
    {
      "name": "custom-rag",
      "command": "python",
      "args": ["/path/to/your/mcp_server.py"],
      "env": {
        "VOYAGE_API_KEY": "${VOYAGE_API_KEY}"
      }
    }
  ]
}

第七步(进阶):如何设置重排序(Reranking)

如果您想提高结果质量,添加重排序是一个很好的第一步。这涉及先从向量数据库检索较大的初始结果池,然后使用重排序模型按相关性对它们进行排序。这种方法有效,因为重排序模型可以在一小部分顶部结果上执行计算成本稍高的运算,从而比必须在数据库所有条目中搜索的相似度搜索提供更准确的排序。
例如,如果您希望为每个查询返回 10 个结果,那么您应该:
  1. 使用相似度搜索从向量数据库检索约 50 个结果。
  2. 将这 50 个结果连同查询一起发送到重排序 API,以获取每个结果的相关性得分。
  3. 按相关性得分对结果进行排序,并返回前 10 名。
我们建议使用 Voyage AI 的 rerank-2 模型,此处有其使用示例 链接