Галоўная / Артыкулы

Бязвектарны RAG з PageIndex: пошук праз разважанне замест вектараў

PageIndex замяняе эмбедынгі і разбіўку на LLM, якая ідзе па дрэве дакумента. Кароткі эскіз на Python і выпадкі, калі вектарны RAG усё ж лепшы.

Выдавец: REACTAPP.TOP9 хв чытання2090 слоў

Крыніцы: **I Stopped Using Vector Databases for RAG : PageIndex Vectorless RAG**

Кожны, хто ужо дзеяе з сістэмамі RAG якіясь час, знае гэты раздражэнне. Вы вітрачаеце гадзіны на стварэнне эмбедынгаў, налаштовванне розмера фрагментаў, выбір базы вектарных дадзеных, а сістэма все равно дае некарэктны адказ. Яна знаходзіць штось падобнае, пры тым што „падобнае“ і „рэлевантнае“ — гэта не адно і тое ж.

Фінансавыя звясткі яскрава паказваюць гэта. Возьмімо 120-сторонній документ 10-K. Сістэма на адной вектарнай базе RAG продовжае браць фрагменты з канцэптуальнага падсумку, тады калі справжні адказ знаходзіцца ў прымечанні на 87-й стороне. Оба фрагменты маюць спакойныя словы і аднаковы значэнне, але толькі ў аднам з іх ёсць неабходная цыфра.

PageIndex была створана самэ для борьбы з гэтым.

Што такое PageIndex

PageIndex — гэта фрэймворк RAG адаптаванага ад адкрытага кода ад команды VectifyAI. Яго цэнтральная ідэя простая і адрозніцца ад большай часткі іншых раўняючыхся раўняў. У працоўцы замест таго, каб ператварыць дакумент у вектары і адрабатваць пошук найбліжэйшага суседа, ён стварае іерархічны дрэва з дакумента, і LLM сам вырашае, як дасягнуць адпаведнай адпаведзі.

Ён абоўсумова прыменшвае вікорыстоўванне эмбедынгаў, хранення вектараў і ўдзелення дакументаў на часткі.

Праект называе гэта «vectorless RAG», і гэтыя апісанні досыць точныя. Ён з’явіўся у верасні 2025 года і быстра прывернуў увагу, галоўная прычына — адзін паказаны показнік: 98,7% точнасці на FinanceBench, проты прыблізна 50% для традыцыянальнага vector RAG на тым жа тэсце. Ставіцеся з асторогай да показнікаў, паказаных віробнікамі, і тэставаце на своіх сабскайных дакументах, але калі гэтыя разлікі застаюцца, хоць часткова, то гэта зусім іншы ранг.

Дзе выконваецца vector RAG

Асэнцыйныя падазрыцці вектарнага RAG заключаюцца у тым, што тэкст, які мае найбольшую сэмантычную супараднасць з запытам, ў той жа час ёсць і найболей рэлевантным. У практыцы гэтыя падазрыцці ўсё часы не падтрымваюцца.

Якшо запытаць: «Калькуюцься якія загальныя боргі компаніі у 2023 годзе?», то фрагменты з найвышым балам можу быць такімі:

  • ліст генеральнага дырэктара, у яком обгаваруецца стратэгія кантролю боргаў,
  • раздзіл пра рызыкі, прысвечаны правілам боргавых зобавэсцяў,
  • ступенек у гласарыі, які дае вялікія боргі.

Усе тры фрагменты маюць блізкі значэнне да запытання, але ніхто з іх не ёсць адказам — адказам являецца конкрэтная цыфра ў балансе на сторанцы 64.

Да гэтага дадаюцца ўсё большыя проблемы:

  1. Разбіўка на часткі разрушае кантэкст. Разліквідаванне 100-сторанковага дакумента на фрагменты по 500 токенав ведае да втраты яго структуры. Рэчэнца, якая гаворыць "як згадана ў Тэбліце 3.2", стае аддзеўнанай ад Тэбліцы 3.2, калі яны опінуюцца ў разных частках, і логіка ўзаемных звязкаў дакумента ігнаруецца.
  2. Вектары не можу следаваць за перакіднымі ссылкамі. У дакументах пішацца "дывіцеся Дадатак G" або "зверніцеся да паказнікаў пакананнага года ў Раздзеле 4", але алгорытмы пошуку на адпоўненасці не маюць можлівасці следаваць за гэтымі паказаннямі. Гэта проста фрагменты тэксту без жадных звязкаў з ўсімі цілямі, на якія воны натыкаюцца.
  3. Запіты выражаюць намер, а не змест. Пользоватэль опісвае, што ён хочаць дазнацца, а не як будзе выглядаць адпаведны адказ. Адказ можа вжываць іншы слоўнік, і алгорытмы на адпоўненасці сталквуюцца з труднасцямі, таму што яны паўставляюць тэкст запіту з тэкстам адказу.

Пошук па логіцы

Змена падходу заключаецца ў наступным. Уместо таго, каб спытацца, якія часткі дакладна падобнаеся да запіту, PageIndex спытаецца, дзе чалавек-эксперт стаў бы шукаць у гэтым докуменце, каб даў адпаведны адказ. Гэта іншая стратэгія адналёгчэння, і яна набліжэй падходзіць да структураваных, складных документав.

Як гэта працюе

Існуе два этапы.

Этап 1: стварэнне дрэва індэкса. Пры отрыманні документа 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

Дакумент большэй не ў форме плоскага аркуша. У ёму ёсць структура, і LLM можа пераходзіць праз яе так, як чалавек перагледвае кнігу.

Этап 2: пошук у дрэве на аднойчыннай аснове. Калі прыходзіць запіт, нічога не падае ў базу дадзеных у вектарнай форме. LLM прыме запіт і верхні ўрадзень дрэва, і яму прасяць аднойчынна вырашыць, дзе можа знаходзіцца адпаведна адказ. Ён можа выйсці да вывару, што запіт пра давялегалегкі борг належыць у роздзеле Фінансовых выкладнікаў, верагодна ў прымітках, і вернуць ID вузла.

PageIndex пасля чаго розгледвае гэты вузел, паказваючы наступны ўрадзень дэталяў, а LLM продовжае глыбокае дзеянне, пакуль не дасягне тыяўніка, якая адпавядае на запіт.

Чалавечы аналітык працуе так сама. Ніхто не чытае 200-сторонній атлас з першай да апошней старонкі. Вы пераглядаеце змест, знаходзіце правы роздзел і ідзеце туды без перапытак. PageIndex робіць гэта з шыроцай швалёй машыны.

За выключэннем апошнага запрашоўвання кантэнту, кожны этап являецца процэсам разумовага аналізу модэлю. Нічога не падходзіць за дапамою вектараў; LLM сама выбірае, дзе шукаць.

Маленькае рэалізаванне

У наступным практычным прыкладзе прадстаўлена спрасцаваная версія гэй ідэі на Python, дзе як LLM выступае Gemini.

Крок 1: аналіз дакумента

Спачатку неабходна выкарыстоўваць функцыі для выкарыстоўвання неапранутага тэксту сторынка за сторынкай. PyMuPDF эфектыва караце з PDF-файламі.

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

Шаг 2: стварэнне індэкса дрэва за дапамою LLM

У гэтым і знаходзіцца сэрцо методу. Эмбеддынгі не выкарыстоўваюцца, а мадэль чытае адглед кожнага раздзела і піша структураваны падсумак. Рэзультатам яе працы стае вялікое дрэва у фармаце JSON, у кожнай вёсцы якога ёсць заглавле, короткі падсумак і спіс ключоўых тэм.

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"],
    }

Заўёршыце процэс для всіх раздзелаў, і цэлы дрэва будзе запісаны як файл JSON.

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)

Шаг 3: пошук у дрэве, керованы выважванням

У гэты момент гэты падход абсалютна адзінаковы ад вектарнага RAG. Прыходзіць запіт, і замест таго, каб шукаць у базе дадзеных, вы даеце LLM усе дрэва і запытваеце, якія ўзелкі з наібольшай верагатыбайцю маюць адпаведны адказ. Ён паводзіцца як аналітык, які працуе з зместам кнігі.

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())

Поле аргументаў у адпаведзі ўжо не проста дэкоратыў. Яно фіксуе, чаму мадэль выбрала тыя часткі, і гэта і ёсць значэнне пояснюемасці тут. Вы можэце пераглядзець рашэнне пра выбіранне інфармацыі, чаго не можа запроста зробіць вектарны пошук.

Крок 4: запрашаць контэнт

Калі у вас ўжо ёсць ID выбранных частак, запрашайце неапдытаваны тэкст тых частак. Не трэба вектарных пошукоў і вычыслення эмбедынга — достатнья толькі перагляд у словніку частак, якія вы вялікі час аналізавалі.

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)

Шаг 5: стварэнне адказу

Зявлены тэкст і первасны запыт прыходзяць да Gemini, і мадэль складае точны адказ з цітатамі на рэвэлі.

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 протыў vector RAG

Этыя два падходы разлічаюцца за некалькома аспектамі. Vector RAG ўзлётны па швальнасці, можа працаваць з вялікімі колекцыямі дакументаў і выкарыстоўвае эмбеддынгі і індэксы. PageIndex не патрабуе нічога з гэтага, але выконвае калькі последовальных вызоў LLM за кожны запіт; у якім выніку ён дае вышэю точнасць у працэ са структураванымі дакументамі і адпаведныя паследованасці рашэнняў.

Калі выбіраць PageIndex

Ён не ўніверсальны заменнік для vector RAG. Гэта аднае інструмент для адной задачы.

Выбірайце PageIndex, калі:

  • Вы працуеце з аднім довгім, структураваным дакументам, дзе точнасць важнейшая за швальнасць: фінансавыя звясткі, прававыя контракты, норматыўныя дакументы, тэхнічныя інструкцыі, акадэмічныя працы. Будзь-што з значымія іерархіямі, дзе «дастатнька блізкасць» недастатня.
  • Патрэбна можлівасць адказаў з поясненням, таму што корыстувальнікі аб заінтэрэсаваныя стороны павінны точна бачыць, звядзе гэта адказ. Вы отрымваеце след аб разумовых кроках з апісанням раздзелаў і номераў сторанак.
  • Вы ствараеце рашэнні для рэгулююцыхся сектароў, такіх як фінансы, права і охорона здароў’я, дзе некарэктны адказ мае рэальныя наследкі, і важна точнасць адпаведных даных.

Выбірайце vector RAG, калі:

  • Вы шукаеце сярод вялікага калекцыі дакументаў і павінны выявіць, якія з іх ёсць рэлевантнымі. Vector RAG чырпае ў гэтым, тады як PageIndex сфокусаваны на адзінам дакуменце і не прызначаны для выбору правільнага з тыячы.
  • Для вас важны праўоцэннасць і час адпаведзення. Колькісныя запыты да LLM для кожнага запіту стаюць дорогімі і повольнымі, калі паспрабоўкі зростаюць.
  • Матэрыял ёсць короткім, слабка арганізаваным або напісаным у фармаце дыялогу. Сіла методу заключаецца ў навігацыі па іерархіі, а без іерархіі яго прынадзы знікаюць.

Спяюце іх разам. Іспользуйце вектарны пошук, каб знайсці правільны дакумент у вашай колекцыі, а пасля іспользуйце PageIndex, каб выявіць точны адказ усерэдзіні яго. Такі гібрыд выкарыстоўвае адзіныя з лепшых якостэй обох падходаў.

Большая ідэя

PageIndex на самай справе спрыяе пошуку як проблеме разумавання, а не проблеме сэмабільнасці. Гэта змена мае значэнне не толькі для дакументаў. Аналізы паказваюць, што Claude Code перэстаў павяржацца на пошук на адной основе эмбедынгаў для пошуку коду і цяпер апіраецца на пошук, керованы разумаванням. Шаблон стае яшчэ ясней: калі задача выкалікае патрэбу разумець структуру і следаваць логіцы, разумаванне перамагае сэмабільнасць.

Базы дадзэнайў вектарных даных не знікнуць. Яны застаюцца шырокаіснымі, магчымаеі пашырэнне і ідэальнымі для сэмантычнага пошуку ў вялікіх калекцыях. Але для складнай задачы пошуку абоўсцю правільных дазвялей у сложаным дакуменце падход, які спачатку аналізуе логіку, выглядае як крашчая альтэрнатыва.

PageIndex ўзнаходзіцца сярод найлепшых рэалізацый гэтай ідэі сёння. Ён ў фармаце адкрытага кода, мае чысты API, а праект публікуе хмарную службу і сервер MCP, якія дазволяюць інтэграцыю з Claude або іншымі агентскімі рабочымі практыкамі без неабяжнага напісання коду для пошуку. Прыклады з офіцыяльнага кулінарнага кнігі ўсё ж такі ёсць разумным пачатком і дазволяюць запусціць працюючую сістэму за апоўну паловы час. Калі вашы корыстувальнікі ставяць точныя запытанні пра 200-сторонніяя звярненні, гэта варта спробаваць.

Супаўзвязаныя матэрыялы

  • Усё працэўнае па векторных базах дадзеных для RAG: хранэнне частак, HNSW, IVF і пераранжаванне — Як векторныя базы дадзеных RAG храняць частакі, шукаюць мільйоны вектараў за дапамогою HNSW і IVF, дадаюць гібрыдны спосаб шукання і пераранжавання, а таксама порушаюць меры сэмантычнай супадання.
  • Гібрыдны спосаб адналёгчэння дадзеных для RAG: BM25, злучэнне рангаў і пераранжаванне працэйшыя за вектары — Чаму чыстая векторная шукання не знаходзіць точныя тэрміны ў RAG, і як BM25 у поўнай суме з сэмантычной шуканням, злучэннем рангаў і крос-кодэрам для пераранжавання паднялі точнасць адналёгчэння дадзеных да 91%.