Расчеты над деревом документов вместо поиска векторов
PageIndex создаёт иерархический индекс, позволяющий большой языковой модели навигировать по нему, избегая процессов разбиения на части и использования эмбеддингов. Как это работает, результаты тестирования на FinanceBench и когда его следует использовать.
Источники: I Threw Out My Vector Database. RAG Got Way Better With PageIndex
Мало что может быть столь неловким, как когда модель с уверенностью цитирует неверный раздел 200-страничного годового отчета прямо во время прямой демонстрации. Затем следует знакомая ситуация: команда «исправляет» проблему путем улучшения структурирования данных, более эффективного использования перекрытия информации и более тщательно настроенной модели встраивания — обычный цикл обновлений, через который проходит каждый инженер RAG, уверенный, что следующая корректировка наконец сработает. Однако система отвечает на вопрос о прогнозах доходов на 2023 год абзацем из плана капитальных расходов на 2021 год. Оба раздела использовали схожую финансовую терминологию, поэтому модели встраивания считали их связанными. На самом деле это не так.
Подобные сбои заставляют задаться вопросом о том, не сломана ли вся эта подходка изначально.
Почему сходство оказывается недостаточным
Каждый учебник по технологии RAG объясняет один и тот же алгоритм: документ разбивается на фрагменты по 500 слов, они встраиваются в систему, для каждого вопроса находятся наиболее близкие векторы, после чего эти фрагменты передаются в нейросеть. Это звучит элегантно — сочетание семантического поиска и генерации текста.
Однако этот алгоритм упускает из виду тот факт, что сходство векторов не всегда означает релевантность. База данных векторов находит текст, семантически близкий к вопросу, но не обязательно текст, который на него отвечает. Параграф о факторах риска лекарства и параграф об его эффективности находятся рядом друг от друга в пространстве векторов, но только один из них касается вопроса о финансовых рисках компании.
Разбиение на части создаёт ещё одну проблему. Если разделить 200-страничный юридический документ на блоки по 500 токенов, всё, что тщательно создал автор, исчезает: разделы, внутренние ссылки, логическая структура. Остаётся куча фрагментов, не имеющих никакого понимания того, откуда они взялись и как между собой связаны. Лучшее разбиение на части немного облегчает ситуацию, но никогда не решает коренную проблему, поскольку утраченная структура не может быть восстановлена.
Человеческий аналитик никогда бы не читал в таком формате. Он бы просмотрел оглавление, перешёл к нужному разделу, прочитал его, зная содержание соседних глав, и проверил сноски. Он воспринимает документ как целостный документ.
Дерево вместо векторов
Команда VectifyAI задалась вопросом, как выглядела бы система RAG, если бы она работала именно так, и создала PageIndex. Как только эта идея сформулируется вслух, она кажется почти неожиданно очевидной. Не нужно преобразовывать документы в векторы и искать по сходству. Вместо этого следует создать иерархический деревянный индекс документа, фактически машинночитаемый оглавление, и позволить большой языковой модели следовать по этому дереву к соответствующему материалу.
Это означает отсутствие векторной базы данных, отсутствие разбиения на части и отсутствие оценок сходства — только структурированный индексирование и интеллектуальное навигирование.
Первый шаг: создание индекса
PageIndex не разделяет предоставленный документ на части. Он анализирует его естественную структуру — разделы, подразделы, заголовки и логические группировки — и формирует иерархию, соответствующую способу организации контента, который выбрал бы эксперт. Представьте себе автоматически сгенерированный оглавление, только более подробное. Каждый узел содержит не только название, но и краткое описание того, что охватывает его ветвь, вопросы, на которые он может дать ответ, а также информацию о своей связи с соседними и родительскими узлами.
Техническое описание объемом 100 страниц может привести к формированию дерева глубиной три-четыре уровня, при этом основные функциональные области находятся на верхнем уровне, а более узкие темы — в подузлах ниже. Логическая иерархия документа сохраняется в том виде, в котором ее устроил автор. Эта процедура выполняется один раз для каждого документа в начале работы, после чего индекс сохраняется, при этом оригинальный контент остается нетронутым и доступным.
Второй шаг: анализ дерева
Когда поступает запрос, PageIndex не встраивает его и не выполняет поиск ближайшего соседа. Вместо этого большая языковая модель навигирует по дереву индекса. Она читает узлы верхнего уровня и определяет, какая ветвь с наибольшей вероятностью содержит ответ, затем следует по этой ветви, читает узлы следующего уровня и снова анализирует ситуацию. Она продолжает спускаться вниз, пока не найдет соответствующие разделы.
Именно это и меняет ситуацию. Затем модель загружает эти полные разделы из исходного документа без каких-либо фрагментаций и использует их в качестве контекста для формирования ответа.
Процесс поиска предназначен для объяснимости. Можно точно увидеть, какие узлы прошла большая языковая модель и на каких страницах исходного документа она остановилась. Нет никаких «черных ящиков» в виде коэффициентов косинуса, а цепочка логических рассуждений видна.
Попробовать
Настройка требует минимум усилий. Начните с клонирования репозитория и установки его зависимостей:
git clone https://github.com/VectifyAI/PageIndex.git
cd PageIndex
pip3 install --upgrade -r requirements.txt
Затем создайте файл .env в корневой директории, в котором будет храниться ключ API:
CHATGPT_API_KEY=your_openai_api_key_here
Чтобы индексировать PDF, укажите скрипту путь к нему. Хорошим примером может служить финансовый отчет, поскольку именно в этой области инструмент проявляет себя наилучшим образом:
python3 run_pageindex.py --pdf_path /path/to/annual_report.pdf
В документах Markdown используется другой флаг:
python3 run_pageindex.py --md_path /path/to/technical_spec.md
На этом настройка завершена. Нет необходимости настраивать векторную базу данных, выбирать модель вкладки или размышлять над параметрами разбиения на части.
Обработка документа приводит к созданию структуры индекса примерно в таком виде, упрощённом здесь:
{
"document": "Apple Inc. Annual Report 2023",
"index": {
"title": "Apple Inc. Annual Report 2023",
"summary": "Comprehensive financial and operational report covering revenue, product segments, risks, and strategic outlook",
"children": [
{
"title": "Business Overview",
"summary": "Company description, product lines, and market position",
"pages": [1, 8],
"children": [...]
},
{
"title": "Financial Results",
"summary": "Revenue, operating income, EPS, and segment performance for fiscal 2023",
"pages": [45, 72],
"children": [
{
"title": "Revenue by Product Category",
"summary": "iPhone, Mac, iPad, Wearables, and Services revenue breakdown",
"pages": [46, 52]
},
{
"title": "Geographic Revenue Distribution",
"summary": "Americas, Europe, Greater China, Japan, Rest of Asia Pacific",
"pages": [53, 58]
}
]
},
{
"title": "Risk Factors",
"summary": "Operational, market, regulatory, and competitive risks",
"pages": [89, 110]
}
]
}
}
Представьте, что аналитик запрашивает информацию о доходах от iPhone за финансовый год 2023. Вместо поиска похожих фрагментов текста большая языковая модель читает указатель, делает вывод, что раздел «Финансовые результаты > Доходы по категориям продуктов» является нужным направлением, переходит туда и получает полностью страницы с 46 по 52. Ответ сопровождается точным указанием страницы, и поскольку модель никогда не видела раздела «Факторы риска», ничто лишнее не мешает его точности.
Упрощённая версия поиска на основе логических рассуждений выглядит так в коде:
import json
from openai import OpenAI
client = OpenAI()
def navigate_index(query: str, index_node: dict, depth: int = 0) -> list[dict]:
"""
Recursively navigate the document index using LLM reasoning.
Returns list of relevant leaf nodes with page references.
"""
children = index_node.get("children", [])
if not children:
# Leaf node: return this section as relevant
return [index_node]
# Ask the LLM which branches are relevant to the query
children_summary = "\n".join([
f"[{i}] {child['title']}: {child['summary']}"
for i, child in enumerate(children)
])
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": (
"You are navigating a document index to find sections relevant "
"to a query. Select the index numbers of sections that are likely "
"to contain the answer. Return a JSON array of selected indices."
)
},
{
"role": "user",
"content": (
f"Query: {query}\n\n"
f"Available sections:\n{children_summary}\n\n"
f"Which sections should I look into? Return JSON array of indices only."
)
}
],
temperature=0,
response_format={"type": "json_object"}
)
selected = json.loads(response.choices[0].message.content).get("indices", [])
relevant_nodes = []
for idx in selected:
if idx # Recurse into selected branches
relevant_nodes.extend(
navigate_index(query, children[idx], depth + 1)
)
return relevant_nodes
def answer_with_pageindex(query: str, index: dict, document_pages: dict) -> str:
"""
Full PageIndex retrieval and answer generation.
"""
# Navigate the index to find relevant sections
relevant_nodes = navigate_index(query, index)
# Retrieve full text from identified pages
context_parts = []
citations = []
for node in relevant_nodes:
pages = node.get("pages", [])
if pages:
page_start, page_end = pages[0], pages[1]
for page_num in range(page_start, page_end + 1):
if page_num in document_pages:
context_parts.append(document_pages[page_num])
citations.append(f"p.{page_num}")
context = "\n\n".join(context_parts)
# Generate answer with full, unchunked context
answer_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": (
"Answer the question based on the provided document sections. "
"Be precise. If the answer involves numbers or dates, quote them exactly."
)
},
{
"role": "user",
"content": f"Document sections:\n{context}\n\nQuestion: {query}"
}
],
temperature=0
)
answer = answer_response.choices[0].message.content
citation_str = ", ".join(set(citations))
return f"{answer}\n\n**Source:** {citation_str}"
Посмотрите, чего нет: эмбеддинги, косинусное сходство, векторные базы данных и логика разбиения на части. Структурированное обработка информации на основе индекса документов заменяет весь процесс поиска.
Данные из финансового тестового случая
FinanceBench собирает вопросы, касающиеся реальных финансовых документов, таких как заявления в SEC, годовые отчеты и конференции по финансовым результатам. Ответы должны быть числово точными, разделы — точно перекрываться между собой, а примечания — тщательно изучаться. Методы RAG на основе векторов здесь, как правило, сталкиваются с трудностями, поскольку финансовые документы содержат множество похожих по звучанию формулировок на разные темы.
Mafin 2.5, основанный на PageIndex, достиг точности 98,7% на тестовой платформе FinanceBench. Типичные векторные системы RAG на сопоставимых финансовых тестах показывают результаты от 65 до 80%, в зависимости от настроек. Эта разница существенна: она отделяет инструмент, которому инвестиционный аналитик может действительно доверять, от того, который генерирует правдоподобную чушь, требующую проверки каждой строки.
Причина кроется в структуре документов. Именно в финансовых документах иерархия имеет наибольшее значение. Консолидированная балансовая ведомость, примечания к отчетам и документы MD&A всегда имеют четкую, продуманную структуру, на которую постоянно опираются аналитики. PageIndex сохраняет и использует эту структуру, тогда как векторное разбиение на части ее разрушает.
Когда выбирать тот или иной инструмент
PageIndex не всегда может заменить векторные системы RAG. Правильный выбор зависит от конкретного проекта.
Он хорошо справляется в следующих случаях:
- У документов есть четкая иерархия, как и у отчетов, руководств, контрактов и спецификаций.
- Точность и правильные цитаты являются абсолютно необходимыми.
- Сфера применения специализирована, например, финансы, юриспруденция, медицина или регулирование.
- Для ответов на вопросы требуются перекрестные ссылки между несколькими разделами одного документа.
- Важна объяснимость, и система должна показывать, почему она дала такой результат.
Традиционный векторный RAG по-прежнему имеет смысл в следующих случаях:
- Корпус состоит из тысяч коротких, независимых документов, таких как заявки на поддержку или отзывы на продукты.
- У документов нет единообразной структуры.
- Цель — приблизительный семантический поиск в большом корпусе.
- Основной проблемой является задержка, а процесс разбиения на фрагменты уже оптимизирован.
Большинство сценариев использования корпоративных документов, при которых система должна быть действительно полезной, а не просто впечатляющей на демонстрации, больше похожи на первый список, чем на второй.
Для производственного использования без дополнительной нагрузки на инфраструктуру PageIndex также предлагает REST API и возможность интеграции с MCP. Настройка для разработчика выглядит следующим образом:
import requests
PAGEINDEX_API_KEY = "your_api_key"
BASE_URL = "https://api.pageindex.ai/v1"
def upload_document(file_path: str) -> str:
"""Upload a document and get back a document_id."""
with open(file_path, "rb") as f:
response = requests.post(
f"{BASE_URL}/documents",
headers={"Authorization": f"Bearer {PAGEINDEX_API_KEY}"},
files={"file": f}
)
return response.json()["document_id"]
def query_document(document_id: str, question: str) -> dict:
"""Query an indexed document and get a cited answer."""
response = requests.post(
f"{BASE_URL}/query",
headers={
"Authorization": f"Bearer {PAGEINDEX_API_KEY}",
"Content-Type": "application/json"
},
json={
"document_id": document_id,
"question": question
}
)
return response.json()
# Example usage
doc_id = upload_document("q3_earnings_report.pdf")
result = query_document(doc_id, "What was total revenue in Q3?")
print(result["answer"])
print(f"Sources: {result['citations']}")
При загрузке API автоматически создаёт индекс, а при запросе возвращает структурированные ответы с указанием страниц, по которым можно вернуться к исходному тексту.
Более глубокая концепция
Помимо показателей эффективности существует нечто более существенное. Стандартные технологии векторного RAG рассматривают документ как набор связанных фрагментов текста, игнорируя структуру, созданную автором. Каждый заголовок, разделитель и переходная ссылка являются сигналами, способом автора указать читателю, куда обратиться, но такие технологии уничтожают всю эту структуру, прежде чем попытаться восстановить релевантность из ничего. PageIndex рассматривает документ таким, каков он есть: структурированным объектом, предназначенным для навигации способным читателем.
Старший аналитик, читающий годовой отчет, не начинает с первой страницы. Он смотрит оглавление, переходит к той части, которая его интересует, сверяет цифры с примечаниями и возвращается к разделу с информацией о рисках и коррективах. ИИ-модель в PageIndex делает то же самое.
Именно поэтому это принцип применим и в специализированных областях. Отчет о фармацевтических испытаниях имеет другую структуру, чем юридический контракт или техническое описание, но в каждом случае автор следует внутренней логике, которую используют эксперты-читатели. PageIndex захватывает эту логику и помогает находить нужную информацию на ее основе.
Другой диагноз
Печальный вывод заключается в том, что годы усилий могли быть направлены на неправильную проблему. Внимание уделялось стратегиям разбиения на фрагменты, в то время как на самом деле именно первоначальный дефект разрушал структуру. Модели встраивания настраивались, хотя семантическое сходство никогда не было подходящим сигналом для поиска. Для улучшения качества поиска применялись специальные инструменты ранжирования, тогда как сам метод поиска и являлся узким местом.
Экосистема RAG создала сложные механизмы, чтобы компенсировать первоначальный недостаток разбиения на фрагменты. PageIndex предлагает альтернативу: без фрагментов, без моделей встраивания, без поиска по сходству. Необходимо создать индекс, отражающий реальную структуру документа, и перемещаться по нему с помощью логических рассуждений.
Это не отвечает на все вопросы. Векторный поиск по-прежнему превосходит себя в семантическом поиске среди миллионов коротких, неструктурированных документов. Однако для широкого спектра вопросов, связанных с длинными, структурированными документами, которые покрывают большинство потребностей корпораций, иерархическая структура заслуживает серьезного внимания. Перезапустите демо-версию, которая дважды выдавала ошибку при запросе о прогнозах доходов на 2023 год — теперь система переходит в правильный раздел, загружает нужные страницы и отвечает с точными цитатами. Ни неверного года, ни неверного раздела, ни чрезмерной уверенности.
PageIndex — это программное обеспечение с открытым исходным кодом, которое можно самостоятельно разместить на GitHub; также доступен интерфейс чата и документация API с примерами использования REST и MCP для тестирования на собственных документах.
Связанные материалы
- 30 вопросов к кандидатам на работу в области RAG и векторных баз данных с краткими ответами — Тридцать вопросов о технологиях RAG и векторного поиска с короткими ответами: гибридный способ поиска, сравнение алгоритмов HNSW и IVF, разбиение документов на части, переранжирование результатов, показатели оценки и механизмы безопасности для нескольких пользователей.
- RAG без векторов: как PageIndex читает документы без использования векторных представлений — Векторный поиск разбивает длинные документы на части, что приводит к потере контекста. Узнайте, как деревянный индекс, основанный на логических рассуждениях, находит ответы в сложных отчетах, с примерами кода.