首页 / 文章 / 实用提示:我不再使用向量数据库构建RAG系统:PageIndex

实用提示:我不再使用向量数据库构建RAG系统:PageIndex

《实用笔记》操作指南:我为何不再使用向量数据库构建RAG系统:为采用该模式的团队提供的PageIndex、校验项以及可直接使用的代码片段。

2265 词

以下笔记为“I Stopped Using Vector Databases for RAG : PageIndex Vectorless RAG”提供了一条实用的实施路径。重点在于契约定义、校验机制以及可直接插入的代码占位符,而非激励性表述。 在完成概览阶段时,首先明确契约内容:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续代码修改的规范性。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误消息处理都是产品本身的组成部分,而非后续需要补充的功能。

PageIndex 到底是什么?

当将 PageIndex 阶段视为可度量的对象时,其效果最佳。在扩大范围之前,先记录一份理想的处理结果、一个失败案例以及回滚说明。 相较于庞大的脚本,应优先选择小型且可测试的单元。当某个步骤失败时,故障应能指向单一责任模块,而非复杂的流程链。 应将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

向量 RAG 的问题(人们往往忽视)

将“向量阶段”的问题视为可测量的表面来处理时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 将这一阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,绝不允许默默地仅完成部分工作。 将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

输入PageIndex——基于推理的检索

将“Enter PageIndex-按阶段检索”视为可度量的对象时,其效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个故障案例以及回滚说明。 在功能结果旁同时记录处理时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境过渡到共享环境时出现意外账单。 为每轮操作和每次会话设定令牌预算。智能工具往往会大量消耗上下文资源,设置上限能防止演示环境变成令人意外的费用来源。 将“Enter PageIndex-按阶段检索”视为可度量的对象时,其效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个故障案例以及回滚说明。 需同时记录正常处理路径和异常恢复路径。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要补充的内容。

实际运作方式

在“实际工作原理”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一的责任主体,而非复杂的流程链。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分是虚假信息还是索引缺失所致。

Annual Report 2023
├── Business Overview
│   ├── Products and Services
│   └── Market Position
├── Risk Factors
│   ├── Financial Risks
│   └── Operational Risks
├── Financial Statements
│   ├── Balance Sheet
│   │   ├── Assets
│   │   └── Liabilities
│   └── Income Statement
└── Notes to Financial Statements
    ├── Note 1: Accounting Policies
    └── Note 12: Long-term Debt

PageIndex的架构

在PageIndex阶段的设计中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 必须引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失的问题。

开始编码

在“让我们编码”阶段,应在修改代码之前明确输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及令牌或查询成本。提前显示成本信息,可避免在从演示环境过渡到共享环境时出现意外费用。需注明实际作为答案依据的段落;没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

步骤1:解析文档

在第一步“解析阶段”中,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审核。 需引用实际作为答案依据的段落。如果没有引用,操作人员就无法区分是虚假信息还是索引缺失导致的错误。

import fitz  # pip install pymupdf

def parse_pdf(pdf_path: str) -> list[dict]:
    doc = fitz.open(pdf_path)
    pages = []
    for i, page in enumerate(doc):
        text = page.get_text().strip()
        if text:
            pages.append({"page_num": i + 1, "text": text})
    doc.close()
    return pages
def group_pages_into_sections(pages, per_section=3):
    sections = []
    for i in range(0, len(pages), per_section):
        batch = pages[i : i + per_section]
        section_id = f"S{str(i // per_section + 1).zfill(3)}"
        combined_text = "\n\n".join(p["text"] for p in batch)
        sections.append({
            "section_id": section_id,
            "start_page": batch[0]["page_num"],
            "end_page": batch[-1]["page_num"],
            "text": combined_text,
        })
    return sections

第二步:构建树形索引(基于大语言模型)

在“第二步:构建阶段”中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 当下一步操作为代码编写或工具调用时,应优先使用具有结构化格式且经过模式验证的输出,而非自由形式的文字描述。

from google import genai
from google.genai import types

client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
def index_section(section: dict) -> dict:
    preview = section["text"][:1500]
    prompt = f"""Read this section from a document and summarize it.
Section pages: {section['start_page']} to {section['end_page']}
Text:
{preview}
Respond with ONLY valid JSON:
{{
  "title": "short descriptive title (5-8 words)",
  "summary": "2-3 sentence summary of what this section covers",
  "key_topics": ["topic1", "topic2", "topic3"]
}}"""
    response = client.models.generate_content(
        model="gemini-2.0-flash",
        contents=prompt,
        config=types.GenerateContentConfig(temperature=0.0),
    )
    parsed = json.loads(response.text.strip())
    return {
        "node_id": section["section_id"],
        "title": parsed["title"],
        "pages": f"{section['start_page']}-{section['end_page']}",
        "summary": parsed["summary"],
        "key_topics": parsed["key_topics"],
    }
def build_tree_index(sections):
    nodes = [index_section(s) for s in sections]
    return {
        "title": "Your Document Title",
        "total_sections": len(nodes),
        "nodes": nodes,
    }
# Save for reuse
with open("tree.json", "w") as f:
    json.dump(tree, f, indent=2)

第三步:树形搜索(基于推理,而非相似性)

在第三阶段的树形搜索环节中,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任主体,而非复杂的流程链。若后续步骤为代码或工具调用,相比自由形式的文字描述,结构化且经过模式验证的输出更为合适。

def retrieve_sections(tree: dict, query: str) -> dict:
    # Build a compact text representation of the tree
    tree_text = f"Document: {tree['title']}\n\n"
    for node in tree["nodes"]:
        tree_text += f"[{node['node_id']}] Pages {node['pages']} | {node['title']}\n"
        tree_text += f"  Summary: {node['summary']}\n"
        tree_text += f"  Topics: {', '.join(node['key_topics'])}\n\n"
        prompt = f"""You are a document retrieval expert.
        Given this document tree, identify which sections most likely answer the question.
        Think step by step about where a human expert would look.
        {tree_text}
        QUESTION: {query}
        Respond with ONLY valid JSON:
        {{
          "reasoning": "your step-by-step reasoning about where to look",
          "selected_ids": ["S001", "S004"],
          "confidence": "high/medium/low"
        }}"""
            response = client.models.generate_content(
                model="gemini-2.0-flash",
                contents=prompt,
                config=types.GenerateContentConfig(temperature=0.0),
            )
            return json.loads(response.text.strip())

第四阶段:内容检索

在第四步“内容检索”阶段,修改代码之前需明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 必须引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失的问题。

def retrieve_content(selected_ids: list, sections: list) -> str:
    section_map = {s["section_id"]: s for s in sections}
    context_parts = []
    for sid in selected_ids:
            if sid in section_map:
                sec = section_map[sid]
                context_parts.append(
                    f"--- Pages {sec['start_page']}-{sec['end_page']} ---\n"
                    + sec["text"][:3000]
                )
        return "\n\n".join(context_parts)

第五步:答案生成

在第五步答案生成阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应在功能结果旁记录执行时间以及令牌或查询成本。提前显示成本可避免在流程从演示环境转向共享环境时出现意外费用。 需注明实际作为答案依据的段落。没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。 在第五步答案生成阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理措施都是产品本身的组成部分,而非后续需要补充的内容。

def generate_answer(query: str, context: str) -> str:
    prompt = f"""Answer the question using only the provided context.
Be specific. Include exact numbers, technical terms, and cite page numbers.CONTEXT:
{context}
QUESTION: {query}
ANSWER:"""
    response = client.models.generate_content(
        model="gemini-2.0-flash",
        contents=prompt,
        config=types.GenerateContentConfig(temperature=0.1),
    )
    return response.text.strip()

PageIndex与传统向量RAG的对比

在研究PageIndex与传统向量技术时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续代码修改的规范性。 应优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤出现故障时,故障应指向单一责任模块,而非复杂的流程链。 在调整提示词之前,需先使用固定的问题集来评估召回率。仅仅更换提示词往往无法解决检索效果不佳的问题。

何时应使用PageIndex?

在处理“何时应使用”这一阶段时,首先写下相关契约:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功判定标准,杜绝无声的半完成状态。 在调整提示词之前,先使用固定的问题集来测试召回率。仅仅更换提示词往往无法解决检索效果不佳的问题。

最后思考

在完成“最终思考”阶段时,首先需写下相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境过渡到共享环境时出现意外费用。 在调整提示词之前,先使用固定的问题集测试系统的召回率。仅仅更换提示词往往无法改善较差的检索效果。 在完成“最终思考”阶段时,首先需写下相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 需同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理措施都是产品本身的组成部分,而非后续需要补充的功能。

入门指南

在“入门阶段”,将其视为可度量的对象会更为有效。在扩大范围之前,先记录一份优秀的测试用例、一个失败案例以及回滚说明。相比庞大的脚本,应优先选择小型且可测试的单元。当某一步骤失败时,故障应指向单一责任点,而非复杂的流程链。应将分块策略与检索策略分开处理;当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

操作检查清单

在“操作检查清单阶段”,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏的状态。

将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审核。

请注明支撑该答案的具体段落。没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

编写一份简短的操作手册:说明如何轮换密钥、如何清空队列、以及如何回滚上一次的导入操作。

同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及死信处理都是产品不可或缺的部分,而非后续才需要补充的功能。

请注明支撑该答案的具体段落。没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

在升级整个系统之前,先冻结现有版本,为关键流程保存一份标准操作记录,并确认好回滚步骤。共享环境需要设置速率限制、进行租户身份验证,同时还要明确负责密钥轮换的人员。与其展示花哨的一次性演示,不如追求扎实可靠的性能。

e54dedbe364e的批处理说明:不要将提供者密钥放入代码仓库,为每个会话设置令牌上限,并将转录内容存储在评估测试用例的旁边,以便后续更换模型时仍能保持可比性。