RAG без векторов: как PageIndex читает документы без использования векторных представлений
Vectorless RAG с PageIndex: почему поиск векторов с разбиением на части теряет контекст в длинных отчетах, и как индекс в виде дерева, основанный на логических рассуждениях, находит ответ, с примером кода.
Источники: I Deleted My Vector Database and My RAG System Got Better
Если спросить кого-то, что происходит в главе 7, он не станет перечитывать всю книгу. Он откроет оглавление, перейдёт к главе 7 и прочитает только эту часть. Это быстро и точно.
Большинство систем поиска документов на основе ИИ работают совсем не так. Они разбивают текст на произвольные фрагменты, преобразуют каждый фрагмент в числа и сопоставляют вопрос с теми числами, которые находятся ближе всего. Иногда это работает нормально. Иногда ответ располагается между границами фрагментов, и система его не замечает. Более современный подход под названием vectorless RAG отказывается от использования чисел и заставляет модель воспринимать структуру документа так же, как это делает человек. Сообщается, что он достиг 98,7% на сложном финансовом тесте, в то время как традиционные методы с трудом достигали 50%. Далее будет показано, как на основе принципов можно добиться такого результата.
Что делает RAG
RAG — это аббревиатура от retrieval-augmented generation. На практике это означает предоставление модели доступа к вашим собственным документам, чтобы её ответы основывались на ваших данных, а не только на материалах обучения.
Это важно, поскольку такие модели, как GPT или Claude, имеют предел знаний. Они никогда не видели внутренних отчётов, юридических контрактов или медицинских записей конкретной организации. RAG заполняет этот пробел, извлекая соответствующие фрагменты и предоставляя их модели непосредственно перед тем, как она даст ответ. Обещание простое: задайте вопрос, и вы получите ответ, основанный на ваших материалах. На деле всё оказывается сложнее.
Версия с применением brute-force
Самый очевидный подход заключается в вставке всего документа в запрос вместе с вопросом и позволении модели самой всё обработать. Никаких баз данных, никаких сложных механизмов.
from openai import OpenAI
import PyPDF2
client = OpenAI(api_key="your-api-key")
# Extract all text from a PDF
def extract_pdf_text(pdf_path):
reader = PyPDF2.PdfReader(pdf_path)
full_text = ""
for page in reader.pages:
full_text += page.extract_text() + "\n"
return full_text
document_text = extract_pdf_text("annual_report.pdf")
user_question = "What was the total revenue in 2024?"
response = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": "Answer based on the provided document."},
{"role": "user", "content": f"Document:\n{document_text}\n\nQuestion: {user_question}"}
]
)
print(response.choices[0].message.content)
Это подходит для пятистраничного PDF. Однако 200-страничный годовой отчет быстро сталкивается с такими ограничениями.
- Ограничения по объему текста. Модели могут обрабатывать только определенное количество текста за раз, а 200-страничный документ может содержать до 300 000 токенов, что превышает возможности даже крупнейших моделей.
- Падение качества ответов. Даже если весь текст поместится, ответы становятся расплывчатыми. Просьба к модели найти одну иглу среди всего сена заставляет её терять фокус.
- Затраты. Провайдеры берут плату за каждый токен, поэтому отправка 300 000 токенов на каждый вопрос, даже если ответ находится на одной странице, быстро приводит к значительным расходам.
RAG был создан именно для преодоления этих ограничений.
Как работает векторный RAG
Идея довольно проста: обработать документ один раз, сохранить его умные представления в базе данных и при поступлении вопроса извлекать только соответствующие части. Процесс состоит из пяти этапов:
- Разбиение на части. Документ разделяется на более мелкие фрагменты — например, каждые 500 слов или по одному абзацу.
- Вкладка. Специализированная модель преобразует каждый фрагмент в список чисел, вектор, который отражает его смысл. Схожие идеи получают схожие числовые значения.
- Хранение. Векторы сохраняются в специально созданных базах данных, таких как ChromaDB, Pinecone или Weaviate. Другими распространёнными вариантами являются Milvus, Qdrant и расширение PGVector для PostgreSQL.
- Поиск. Вопрос также преобразуется с помощью вкладки, и база данных возвращает те фрагменты, векторы которых наиболее близки к вектору вопроса. Это и есть поиск по сходству.
- Генерация. Полученные фрагменты вместе с вопросом передаются в модель, что позволяет ей работать на основе сфокусированного контекста.
Полная рабочая версия с использованием ChromaDB и OpenAI выглядит следующим образом:
from openai import OpenAI
import chromadb
import PyPDF2
client = OpenAI(api_key="your-api-key")
# Step 1: Extract and chunk the document
def extract_and_chunk(pdf_path, chunk_size=500):
reader = PyPDF2.PdfReader(pdf_path)
full_text = ""
for page in reader.pages:
full_text += page.extract_text() + "\n"
words = full_text.split()
chunks = []
for i in range(0, len(words), chunk_size):
chunk = " ".join(words[i:i + chunk_size])
chunks.append(chunk)
return chunks
chunks = extract_and_chunk("annual_report.pdf")
# Step 2 and 3: Embed and store in ChromaDB
chroma_client = chromadb.Client()
collection = chroma_client.create_collection("my_documents")
collection.add(
documents=chunks,
ids=[f"chunk_{i}" for i in range(len(chunks))]
)
# Step 4: Search for relevant chunks
user_question = "What was the total revenue in 2024?"
results = collection.query(
query_texts=[user_question],
n_results=5
)
relevant_chunks = "\n\n".join(results["documents"][0])
# Step 5: Generate answer with focused context
response = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": "Answer based only on the provided context."},
{"role": "user", "content": f"Context:\n{relevant_chunks}\n\nQuestion: {user_question}"}
]
)
print(response.choices[0].message.content)
Сегодня большинство компаний используют что-то похожее. Это решение хорошо справляется с простыми вопросами о простых документах, но сталкивается с ограничениями, когда документы становятся более сложными.
Три причины неудачи
Фрагменты, разделяющие идею пополам
Представьте абзац отчета, в котором говорится, что операционная прибыль за 3-й квартал составила 4,2 миллиарда долларов, что на 12% больше, чем в предыдущем году, причем основную долю в этом вкладали облачные услуги, принесшие 2,8 миллиарда долларов постоянных доходов (подробности в Приложении C). Если окно длиной 500 слов случайно заканчивается посередине этого предложения, один фрагмент сохранит цифру, но потеряет объяснение, а другой — объяснение, но потеряет цифру. Ни один из них не передает всю картину.
Эту проблему легко воспроизвести:
paragraph = """The company's Q3 operating income was $4.2 billion,
representing a 12% increase over the prior year period. This growth
was primarily driven by the expansion of cloud services, which
contributed $2.8 billion in recurring revenue as detailed in the
segment breakdown in Appendix C."""
# Simulating a chunk boundary at word 20
words = paragraph.split()
chunk_1 = " ".join(words[:20])
chunk_2 = " ".join(words[20:])
print("Chunk 1:", chunk_1)
print("---")
print("Chunk 2:", chunk_2)
Если спросить, что стало причиной роста операционной прибыли в третьем квартале, поиск может вернуть раздел о облачных сервисах без цифр или раздел с цифрами, но без объяснения причины. В любом случае ответ будет неполным.
Разделение на абзацы не решает проблему. Некоторые абзацы состоят из двух предложений, другие занимают половину страницы, а три последовательных абзаца могут рассказывать единую историю, понятную только при совместном прочтении.
Ссылки, которые никуда не ведут
В контрактах и финансовой документации часто используются указания вроде «это регулируется разделом 4.2.1» или «подробные таблицы приведены в Приложении G». Вопрос о чем-то на странице 12, содержащем ссылку на данные на странице 87, скорее всего, приведет на страницу 12, поскольку ключевые слова совпадают, но система векторного поиска не будет следовать по этой ссылке на страницу 87, где находятся цифры.
Краткая симуляция демонстрирует эту проблему:
# Simulating a cross-reference failure
documents = {
"page_12": "The company's debt-to-equity ratio improved significantly in 2024. For a full breakdown of long-term obligations, see Appendix G on page 87.",
"page_45": "Marketing expenses increased by 15% due to new campaigns.",
"page_87": "Appendix G: Long-term debt stands at $12.4B. Senior notes: $8.1B. Credit facility: $4.3B. Maturity schedule: 2026-2034."
}
# Vector RAG would likely return page_12 for a debt question
# but miss page_87 where the actual numbers live
# because "debt-to-equity ratio" is more similar to the query
# than "Senior notes" and "Credit facility"
Это часто встречается в контрактах, регуляторных документах и научных статьях. Ответ часто находится одновременно в двух местах, и система, не способная их связать, всегда будет возвращать неполные результаты.
Лексика, не совпадающая
Метод векторного сходства во многом зависит от того, насколько формулировки пользователя похожи на формулировки в документе. Если в отчете указано «non-GAAP adjusted EBITDA», а пользователь спрашивает о скорректированной прибыли, векторы могут оказаться недостаточно схожими, чтобы выделить нужный фрагмент. Люди не знают точной терминологии документа, и им не следует её знать.
В технических областях ситуация ухудшается. Врач, ищущий протоколы лечения инфаркта миокарда, может пропустить фрагменты, описанные как «методы лечения инфаркта миокарда». Значение одинаковое, но векторы могут находиться на достаточном расстоянии друг от друга, из-за чего нужные фрагменты так и не появляются.
Это не редкие крайние случаи. В длинных структурированных документах в реальных условиях они возникают регулярно.
Появление безвекторного RAG
В сентябре 2025 года команда из VectifyAI выпустила фреймворк под названием PageIndex, который выбирает другой подход. Он не разделяет документы на части и не сопоставляет номера страниц. Вместо этого он создаёт структурированную карту документа, позволяя ИИ-модели самостоятельно находить нужную информацию.
Идея основана на том, как на самом деле работают эксперты. Аналитику передают 200-страничный отчёт; тот, кому нужна одна цифра, не читает все страницы. Он смотрит оглавление, выбирает раздел, в котором с наибольшей вероятностью находится ответ, и внимательно его читает. Если там указано «см. Приложение G», он переходит к Приложению G.
PageIndex заставляет модель вести себя одинаково. Ключевая идея заключается в том, что наиболее похожий текст не всегда является наиболее релевантным. Векторная база данных возвращает то, что наиболее похоже на вопрос, однако ответ иногда находится в разделе, который совсем не похож на него. Необходимо рассуждать о том, где должен находиться ответ, а не просто искать совпадения шаблонов.
Система работает в два этапа.
Первый этап: дерево индекса
Вместо разбиения на части и генерации векторов документ передаётся модели для рассуждений, которая представляет его в виде дерева. Представьте себе талантливого стажёра, который читает весь отчёт и составляет многоуровневый оглавление, в котором каждая запись содержит название, краткое резюме, диапазон страниц и ссылки на свои подзаписи. Финансовый отчёт может выглядеть примерно так:
Root: Annual Report 2024
|-- Executive Summary (pages 1-3)
| Summary: "Overview of company performance, key metrics..."
|-- Financial Statements (pages 15-45)
| |-- Income Statement (pages 15-20)
| |-- Balance Sheet (pages 21-30)
| +-- Cash Flow Statement (pages 31-45)
|-- Risk Factors (pages 46-60)
+-- Appendices (pages 80-120)
|-- Appendix A: Segment Data (pages 80-95)
+-- Appendix G: Detailed Tables (pages 96-120)
Никаких векторов и эмбеддингов — только логическая структура, соответствующая способу организации документа. Модель использует свои рассуждения для обнаружения естественных границ, таких как разделы на главы, смена тем и новые разделы, вместо того чтобы делить текст на фиксированное количество слов.
Второй шаг: поиск на основе рассуждений
Во время поиска модели показывают только дерево структуры, а не весь документ. Она сканирует заголовки и краткое описание, применяет собственные знания в данной области и выбирает узлы, которые, по её мнению, с наибольшей вероятностью содержат ответ.
Рассмотрим вопрос о годовом изменении маржи операционной прибыли. Логика модели выглядит примерно так: маржи являются финансовым показателем, поэтому важна отчетность о доходах, но для сравнения с прошлым годом могут понадобиться и более ранние данные, возможно, из приложения. Модель отвечает идентификаторами узлов обеих частей, система загружает исходный текст только для этих двух разделов, и окончательный ответ формируется на основе этого узкого контекста.
Для демонстрации этой концепции достаточно простых вызовов OpenAI:
import json
from openai import OpenAI
import PyPDF2
client = OpenAI(api_key="your-api-key")
# Extract text page by page
def extract_pages(pdf_path):
reader = PyPDF2.PdfReader(pdf_path)
pages = {}
for i, page in enumerate(reader.pages):
pages[i + 1] = page.extract_text()
return pages
pages = extract_pages("annual_report.pdf")
all_text = "\n".join([f"--- Page {k} ---\n{v}" for k, v in pages.items()])
# Step 1: Build the tree index using AI reasoning
tree_prompt = f"""You are a document analyst. Read this document and create
a hierarchical table of contents as a JSON tree. Each node should have:
- "title": section name
- "summary": 2-3 sentence description of what this section covers
- "pages": [start_page, end_page]
- "children": array of child nodes (or empty array)
Document:
{all_text[:80000]}
Return ONLY valid JSON. No other text."""
tree_response = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": tree_prompt}],
response_format={"type": "json_object"}
)
document_tree = json.loads(tree_response.choices[0].message.content)
print("Document tree built successfully!")
print(json.dumps(document_tree, indent=2)[:500])
# Step 2: Reasoning-based search over the tree
user_question = "What was the year-over-year change in operating margin?"
search_prompt = f"""You are a retrieval expert. Given a user question and
a document's table of contents tree, identify which sections are most
likely to contain the answer.
Think step by step:
1. What kind of information does the question ask for?
2. Which sections would a human expert check first?
3. Are there sections that might cross-reference each other?
Document tree:
{json.dumps(document_tree, indent=2)}
Question: {user_question}
Return JSON with:
- "reasoning": your step-by-step thought process
- "relevant_pages": list of page numbers to retrieve"""
search_response = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": search_prompt}],
response_format={"type": "json_object"}
)
search_result = json.loads(search_response.choices[0].message.content)
print("Reasoning:", search_result["reasoning"])
# Step 3: Fetch only relevant pages and generate answer
relevant_text = ""
for page_num in search_result["relevant_pages"]:
if page_num in pages:
relevant_text += f"\n--- Page {page_num} ---\n{pages[page_num]}"
answer_response = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": "Answer precisely based on the document context provided."},
{"role": "user", "content": f"Context:{relevant_text}\n\nQuestion: {user_question}"}
]
)
print("\nAnswer:", answer_response.choices[0].message.content)
Всё секретное решение сводится к одному предложению: пропустите хранилище векторов, пропустите эмбеддинги, пропустите настройку размера блоков, и пусть модель изучит структуру, определит, где должен находиться ответ, и загрузит только эти разделы.
Использование фреймворка PageIndex
Вариант самостоятельной реализации подходит для обучения. Для реальных проектов PageIndex берёт на себя всю тяжёлую работу: он создаёт оптимизированные деревья, поддерживает более сложные стратегии поиска и доступен в виде облачного сервиса или варианта с собственным хостингом. Основное использование выглядит следующим образом:
from pageindex import PageIndexClient
import time
# Initialize the client
pi_client = PageIndexClient(api_key="your-pageindex-api-key")
# Upload and process your document
result = pi_client.submit_document("./annual_report.pdf")
doc_id = result["doc_id"]
# Wait for processing to complete
while True:
status = pi_client.get_document(doc_id)["status"]
if status == "completed":
print("Document processed!")
break
time.sleep(5)
# Inspect the generated tree structure
tree_result = pi_client.get_tree(doc_id)
if tree_result.get("status") == "completed":
tree = tree_result["result"]
for node in tree:
print(f"[{node['node_id']}] {node['title']} - Page {node['page_index']}")
# Ask questions using the Chat API
response = pi_client.chat_completions(
messages=[{"role": "user", "content": "What was total revenue in FY2024 vs FY2023?"}],
doc_id=doc_id
)
print(response["choices"][0]["message"]["content"])
Сгенерированная структура имеет следующий вид:
{
"title": "Financial Stability",
"node_id": "0006",
"page_index": 21,
"text": "The Federal Reserve maintains financial stability...",
"nodes": [
{
"title": "Monitoring Financial Vulnerabilities",
"node_id": "0007",
"page_index": 22,
"text": "The Federal Reserve's monitoring focuses on..."
},
{
"title": "Domestic and International Cooperation",
"node_id": "0008",
"page_index": 28,
"text": "In 2023, the Federal Reserve collaborated..."
}
]
}
У каждого узла есть уникальный идентификатор, указывающий на исходный документ, заголовок, сам текст и дочерние узлы для подразделов. Модель просматривает дерево, читая краткое содержание, и решает, какие ветви следует развернуть дальше.
В одном запросе можно сравнить несколько документов:
response = pi_client.chat_completions(
messages=[{"role": "user", "content": "Compare the results across these two reports."}],
doc_id=["pi-abc123def456", "pi-abc123ghi789"]
)
Разработчики, создающие агентов с помощью LangChain, CrewAI или OpenAI Agents SDK, могут подключить PageIndex через Model Context Protocol (MCP), что позволяет агенту использовать его в качестве инструмента поиска данных без необходимости написания специального кода для интеграции.
Критерии оценки, лежащие в основе утверждения
FinanceBench — это тестовая среда, привлекшая внимание к подходу RAG без использования векторов. Вопросы в ней взяты из реальных документов, подаваемых в SEC (отчеты 10-K и 10-Q), и требуют точных ответов: цифр из таблиц, расчетов с несколькими шагами, а также перекрестных ссылок между различными разделами. Это совсем не упрощенная модель для тестов.
Традиционные решения на основе векторов в подходе RAG показывают на этом тесте результаты примерно от 30% до 50%. PageIndex, использующий движок рассуждений Mafin 2.5, достиг результата 98,7%, что относится к совершенно другой категории и представляет собой значительное преимущество. Чаще всего выделяют три причины этого:
- Последовательное обращение по перекрестным ссылкам. Когда в документе указано «см. Приложение G», модель рассуждений перемещается по структуре к Приложению G и извлекает соответствующие данные. Хранилище векторов не имеет представления о необходимости следования по этому указателю.
- Сохранение целостности формата. Финансовая таблица содержит заголовки, вложенные заголовки, сноски и ссылки между ячейками. Структура в виде дерева сохраняет всё это в рамках четких границ, тогда как разбиение на фиксированные блоки приводит к их разделению.
- Многократные операции рассуждений. Чтобы определить годовое изменение операционной маржи, необходимы данные из двух разделов плюс результат расчета. Модель рассуждений понимает, что ей нужны оба вида данных, и извлекает их вместе, в то время как поиск по сходству, скорее всего, выберет только тот блок, который наиболее близок к формулировке вопроса.
Одно важное замечание: по состоянию на начало 2026 года команда PageIndex ещё не опубликовала подробных данных о скорости и пропускной способности. Для обработки каждого запроса требуется несколько последовательных вызовов модели для анализа и вывода решений, поэтому скорость и стоимость обработки одного запроса выше, чем при использовании простого поиска в векторном формате. Для задач с высоким объёмом данных и низкой задержкой такой компромисс может оказаться неэффективным.
Выбор между ними
Решения типа RAG без векторов не являются универсальной заменой векторным базам данных — это специализированный инструмент.
Решения типа Vector RAG подходят, когда ответы должны поступать менее чем за секунду, когда корпус данных состоит из множества коротких документов, таких как база FAQ или сборник статей, когда стоимость обработки запроса превышает значение идеальной точности, или когда чат-боту потребителя достаточны ответы приемлемого качества.
Решение RAG без векторов подходит для длинных, структурированных профессиональных документов: годовых отчетов, контрактов, регуляторных заявлений и технических руководств. Оно также эффективно используется в ситуациях, когда критически важна точность, когда запросы должны опираться на перекрестные ссылки, или когда необходимо четкое отслеживание источников информации для составления ответа.
Для промышленного применения стоит рассмотреть гибридный подход. Легкие метаданные или фильтры по ключевым словам позволяют отсеять нерелевантные документы, а затем алгоритмы на основе деревьев находят точные разделы внутри них. Это обеспечивает скорость традиционного поиска при первом проходе и точность анализа при втором.
Компромисс между затратами и качеством действительно существует. Модели, основанные на логических рассуждениях, стоят дороже за один вызов по сравнению с моделями с встраиваемыми представлениями, а просмотр дерева означает необходимость множества последовательных вызовов и более высокую задержку. Скорость и стоимость заменяются точностью и объяснимостью. В регулируемых сферах, таких как финансы, право и здравоохранение, где неверные ответы имеют серьезные последствия, такой компромисс обычно оправдан.
Каково будущее
Переход от поиска на основе сходства к поиску на основе логических рассуждений только набирает обороты, но направление ясно. PageIndex уже поддерживает вариант, основанный на обработке изображений, который работает непосредственно с изображениями страниц PDF без необходимости извлечения текста или использования OCR. Вводятся изображения страниц, и модель анализирует увиденное. Это помогает справляться с сложными макетами, отсканированными страницами и встроенными диаграммами, с которыми методы извлечения текста справляются плохо.
Также ведутся работы над агентным поиском информации. Вместо однократного просмотра модель продолжает исследование: она следует за ссылками, сравнивает разделы, задаёт себе дополнительные вопросы и совершенствует свой ответ в ходе нескольких этапов рассуждений, что и составляет разницу между беглым просмотром документа и его тщательным изучением.
Любой, кто хочет поэкспериментировать, может воспользоваться репозиторием PageIndex с открытым исходным кодом на GitHub, в котором имеются запускаемые Jupyter-ноутбуки как для стандартных, так и для основанных на визуальном анализе методов обработки данных. После запуска проекта в сентябре 2025 года он набрал 20 000 звёзд, а его сервер Discord активен.
Базы данных векторов останутся, и они хорошо справляются с большим количеством рабочих нагрузок. Однако приложения, требующие детального анализа длинных и сложных документов, становятся всё более распространёнными, и метод разбиения текста на фиксированные фрагменты с последующей попыткой найти подходящий с помощью поиска по сходству теряет эффективность в таких случаях. Контракты, юридические документы и технические спецификации — это именно те документы, которые предприятия ценят больше всего, и именно здесь наиболее эффективен поиск на основе логических рассуждений.
Разумный способ оценки заключается в том, чтобы сначала протестировать самостоятельную версию алгоритма на документе, который уже хорошо понятен, а затем попробовать PageIndex на том же файле и посмотреть, как дерево структуризации компенсирует потери, вызванные разбиением на фрагменты. Лучшая система поиска — это не та, что использует самые современные технологии, а та, которая находит правильный ответ.
Как попробовать
- Vector RAG — хорош для изучения основ: установите
chromadbиopenai, запустите пример ChromaDB на коротком документе, затем попробуйте его на более длинном документе и обратите внимание, в каких моментах ответы становятся неточными. - Реализация без векторов вручную — хороша для понимания концепции: запустите простой пример OpenAI с использованием алгоритма дерева и логических рассуждений на том же документе и сравните полученные ответы с ответами версии с векторами.
- Применение без векторов в производственных условиях: скачайте бесплатный ключ API PageIndex с официального сайта, установите Python-пакет
pageindexи следуйте примерам SDK. Загрузите реальный документ и задавайте вопросы, на которые уже известны ответы — это самый быстрый способ оценить эффективность такого подхода.
Во всех примерах используется Python 3.10 или новее вместе с SDK OpenAI, ChromaDB и PyPDF2, устанавливаемыми командой pip install openai chromadb PyPDF2 pageindex. Требуется ключ API OpenAI, а также ключ PageIndex для примеров с использованием PageIndex.
Связанная литература
- Семантический поиск в PostgreSQL с использованием pgvector и Node.js: без необходимости в LLM — Создание поиска на основе смысла внутри PostgreSQL с помощью pgvector, вкладок MiniLM и Node.js: токены, фрагменты, векторы, индекс IVFFlat, операторы расстояний и способы чтения оценок.
- Снимки памяти агента, навыки, затухание данных и ограничение действия памяти по веткам — Четыре дополнения к слою памяти встроенной векторной БД для кодирующих агентов: переносимые снимки с возможностью слияния, активация навыков, затухание данных с проверкой конфликтов и ограничение действия памяти по веткам.