Содержание
- Что такое PageIndex и какую задачу он закрывает
- Архитектура и дерево документа вместо векторного индекса
- Из чего состоит узел дерева
- Два режима индексации, flash и standard
- Принцип работы двухшагового поиска через tree search
- Чем reasoning-поиск отличается от векторного RAG
- Ограничения, о которых стоит знать заранее
- Сценарии использования, когда брать и когда не стоит
- Практика, строим дерево и ищем по нему
- Заключение
- Референсные ссылки
PageIndex это движок поиска по документам для LLM-приложений, который обходится без векторной базы и без нарезки текста на чанки. Вместо эмбеддингов он строит из документа иерархическое дерево (document tree), похожее на оглавление, и заставляет модель ходить по этому дереву рассуждением. Разработку выпустила компания Vectify AI в сентябре 2025 года, авторы фреймворка Mingtian Zhang (Минтянь Чжан) и Yu Tang (Юй Тан). Идея простая — эксперт, которому дали годовой отчёт на четыреста страниц, не ищет в нём похожие абзацы, а открывает содержание и идёт в нужный раздел. Пакет открыт под лицензией MIT.
Что такое PageIndex и какую задачу он закрывает
Классический RAG (Retrieval-Augmented Generation) работает на сходстве. Корпус режется на фрагменты, каждый превращается в вектор, вопрос тоже превращается в вектор, дальше из базы достаются ближайшие соседи. Схема отлично закрывает вопросы вида «что написано про X», где ответ лежит в одном абзаце. Работает это быстро и дёшево. Про то, как устроен этот контур целиком, есть подробный разбор в статье RAG-приложения и Neo4j: поддержка векторного индекса для LLM.
Проблема начинается на профессиональных документах: отчётности, регуляторике, технических регламентах, медицинских протоколах. Там похожий текст и нужный текст расходятся. Расходятся сильно. Формулировка «риски ликвидности» встречается в документе двенадцать раз, а ответ на вопрос лежит ровно в одном разделе, и отличает его не лексика, а место в структуре. Авторы PageIndex формулируют это так: сходство не равно релевантности, а релевантность требует рассуждения.
Отсюда и решение. PageIndex убирает из контура векторную базу, приближённый поиск ближайших соседей и саму нарезку на чанки. Документ остаётся документом со своей иерархией разделов, а поиск превращается в обход дерева, где решение на каждом шаге принимает языковая модель. Такой поиск называют reasoning-based retrieval, а весь подход vectorless RAG. Векторов в нём нет вообще.
Архитектура и дерево документа вместо векторного индекса
Единственная структура данных в PageIndex это дерево. Корень соответствует документу, узлы разделам и подразделам, вложенность повторяет реальную иерархию исходника. Никакого отдельного хранилища под это не нужно: дерево целиком помещается в JSON и живёт рядом с документом. Отдельный сервис поднимать не надо.
Из чего состоит узел дерева
Узел это не кусок текста, а ссылка на диапазон страниц плюс метаданные для навигации. Состав узла разберём по полям.
- title. Заголовок раздела ровно в том виде, в каком он стоит в документе.
- node_id. Идентификатор узла, по которому модель называет свой выбор.
- start_index и end_index. Границы раздела в страницах, нумерация с единицы. Именно они делают ответ проверяемым: систему всегда можно спросить, откуда она это взяла.
- summary. Краткая выжимка раздела, её пишет модель на этапе индексации. Поле необязательное.
- nodes. Вложенные узлы, то есть подразделы.
Из этого набора видно главное отличие от чанка: узел знает своё место в документе, а чанк нет. Текст узла в дереве не дублируется. Он достаётся из исходника по номерам страниц.
Два режима индексации, flash и standard
Дерево строится двумя разными способами, и разница между ними принципиальная. Режим standard отдаёт документ модели: она читает страницы, находит оглавление, определяет границы разделов и собирает иерархию. Режим flash работает эвристикой по типографике страниц и встроенным закладкам PDF, модель тут нужна только для необязательных выжимок. В локальном клиенте пакета 0.2.10 режимом по умолчанию идёт именно flash. Ключи для него не нужны.
Практическая разница измеряется секундами против минут. На усечённом годовом отчёте Федеральной резервной системы в пятьдесят страниц flash собрал дерево из семи разделов верхнего уровня за 2.39 секунды и не сделал ни одного обращения к модели. Плата за скорость честная: там, где текстового слоя нет вообще, брать структуру неоткуда, и остаётся только standard.
ИИ-агенты для оптимизации бизнес-процессов
Код курса
AGENT
Ближайшая дата курса
26 октября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000
Принцип работы двухшагового поиска через tree search
Поиск в PageIndex распадается на два обращения к модели. В каждом она видит принципиально разные вещи.
На первом шаге модель получает только дерево: заголовки, пути от корня, диапазоны страниц и выжимки, если они есть. Текста документа в промпте нет. Только карта. Задача формулируется как выбор: в каком узле, скорее всего, лежит ответ на вопрос. Модель отвечает идентификатором узла и обоснованием выбора, и это обоснование остаётся в логах, что и делает поиск объяснимым.
На втором шаге по границам выбранного узла достаётся текст соответствующих страниц, и уже он вместе с вопросом уходит в модель за ответом. В контекст попадает раздел, а не весь документ и не набор разрозненных фрагментов из разных мест. Границы раздела при этом остаются в ответе.
В агентском варианте шагов больше: модель спускается по дереву постепенно, при необходимости возвращается на уровень выше и берёт соседний узел. Отсюда и название tree search, и прямая отсылка авторов к AlphaGo, где перебор вариантов тоже шёл по дереву.
Чем reasoning-поиск отличается от векторного RAG
Различия удобнее свести в таблицу. Расходятся два подхода почти по всем осям сразу.
| Свойство | Векторный RAG | PageIndex |
|---|---|---|
| Что хранится | Чанки и их эмбеддинги в векторной базе | Дерево разделов в JSON рядом с документом |
| Как выбирается контекст | Ближайшие соседи по метрике сходства | Решение модели на каждом узле дерева |
| Стоимость индексации | Векторизация всего корпуса | Разбор структуры, в режиме flash без модели |
| Стоимость запроса | Один поиск по индексу, дёшево и быстро | Несколько вызовов LLM, дороже и медленнее |
| Объяснимость | Ранг и расстояние, интерпретируются плохо | Путь по дереву, номера страниц, текст обоснования |
| Слабое место | Похожее не значит нужное | Документ без внятной структуры |
Резюме простое: векторный поиск выигрывает на объёме и скорости, PageIndex на точности и проверяемости внутри одного сложного документа. Другой способ уйти от чистого сходства разбирается в статье Не только векторные БД: графовый RAG для LLM и агентского ИИ, и подходы эти друг друга не отменяют.
Ограничения, о которых стоит знать заранее
Заявленные 98.7% точности на бенчмарке FinanceBench получены в облачном контуре Vectify AI с сильной моделью и собственным OCR. Открытый пакет использует обычный разбор PDF, и результат у него скромнее. Цифру из README на свой стенд переносить не стоит. Дальше идут ограничения, которые всплывают на первом же прогоне.
- Каждый запрос стоит вызовов модели. Векторный поиск после индексации почти бесплатен, tree search платит за каждый вопрос. На потоке запросов это заметная статья расходов.
- Качество упирается в структуру документа. Договор с нумерованными разделами разбирается прекрасно, слитая презентация или скан без текстового слоя не разбирается никак.
- Мелкие модели путаются в формате. Шаг выбора узла возвращает JSON, и семимиллиардная модель периодически оборачивает его пояснениями. Разбор ответа приходится делать устойчивым к мусору вокруг.
- Локальный сервер не любит параллель. Режим standard шлёт запросы по страницам пачкой, а Ollama выполняет их по одному. На прогоне двенадцатистраничного PDF очередь упёрлась в таймаут LiteLLM на 600 секунд, и индексация не завершилась.
Ни одно из этих ограничений не является приговором, но все они означают, что PageIndex это инструмент под конкретный класс задач, а не замена векторной базе вообще.
Сценарии использования, когда брать и когда не стоит
Подход хорошо ложится на длинные документы со внятной иерархией, где цена ошибки высока, а объём корпуса умеренный. Это отчётность и раскрытия по ценным бумагам, регуляторные документы, юридические договоры, технические регламенты и руководства по эксплуатации, научные монографии и учебники. Общее у них одно: длинные и с оглавлением. Отдельный сценарий это работа ИИ-агента: дерево отдаётся ему как инструмент, и агент сам решает, в какой раздел заглянуть. Проектированию таких систем посвящён курс ИИ-агенты для оптимизации бизнес-процессов.
Брать PageIndex не стоит там, где корпус состоит из миллионов коротких неструктурированных текстов: переписка, тикеты поддержки, товарные карточки, посты. У таких данных нет иерархии, по которой можно рассуждать, и векторный поиск здесь и быстрее, и уместнее. Не подойдёт он и там, где важна миллисекундная задержка ответа. Несколько вызовов модели в такой бюджет не помещаются.
Архитектура ML-систем
Код курса
ARML
Ближайшая дата курса
12 октября, 2026
Продолжительность
24/32 ак.часов
Стоимость обучения
76 800
Практика, строим дерево и ищем по нему
По традиции весь код используемый в статье выкладываем на наш GitHub репозиторий
Прогон сделан на macOS 26.5.2 с Python 3.12.13, пакетом pageindex 0.2.10 и локальной моделью qwen2.5:7b через Ollama 0.32.13. Документом взят усечённый годовой отчёт Федеральной резервной системы на пятьдесят страниц из примеров репозитория. Первый скрипт, build_tree.py, строит дерево вообще без обращений к модели. Ollama на этом шаге не нужна.
# pageindex 0.2.10, прогон на macOS 26.5.2, Python 3.12.13, вывод снят 2026-08-24
"""Строит дерево документа из PDF режимом flash: без LLM, без эмбеддингов."""
import json
import time
from pageindex import page_index_flash
PDF = "annual_report.pdf"
def print_tree(nodes, depth=0):
"""Печатает дерево с отступами: заголовок узла и диапазон страниц."""
for node in nodes:
pages = f'{node["start_index"]}-{node["end_index"]}'
print(f'{" " * depth}[{pages:>6}] {node["title"][:70]}')
print_tree(node.get("nodes", []), depth + 1)
if __name__ == "__main__":
start = time.time()
# summary=False и optimize=False отключают все обращения к модели:
# структура извлекается по типографике страницы и закладкам PDF
result = page_index_flash(PDF, summary=False, optimize=False)
elapsed = time.time() - start
structure = result["structure"]
print(f'Документ: {result["doc_title"]}')
print(f"Дерево построено за {elapsed:.2f} с, без единого вызова LLM")
print(f"Узлов верхнего уровня: {len(structure)}")
print("-" * 60)
print_tree(structure)
# дерево сохраняется в JSON, дальше по нему идёт reasoning-поиск
with open("tree.json", "w", encoding="utf-8") as f:
json.dump(structure, f, ensure_ascii=False, indent=2)
print("-" * 60)
print("Дерево сохранено в tree.json")
Вывод показывает иерархию с диапазонами страниц. Вложенность повторяет структуру отчёта.
Второй скрипт, tree_search.py, реализует те самые два шага. Дерево разворачивается в плоский список с путями от корня, модель выбирает узел, после чего в контекст уходит текст только выбранных страниц.
# pageindex 0.2.10, litellm 1.98.0, Ollama 0.32.13, модель qwen2.5:7b, прогон 2026-08-24
"""Reasoning-поиск по дереву PageIndex: модель выбирает узел, потом отвечает по нему."""
import json
import re
import time
import litellm
import pypdfium2 as pdfium
MODEL = "ollama/qwen2.5:7b"
PDF = "annual_report.pdf"
QUESTION = "What did the Federal Reserve do about crypto-asset supervision?"
def flatten(nodes, out=None, path=""):
"""Разворачивает дерево в плоский список узлов с путём от корня."""
out = [] if out is None else out
for node in nodes:
full = f'{path} > {node["title"]}' if path else node["title"]
out.append({
"node_id": f'{len(out):04d}',
"title": full,
"start": node["start_index"],
"end": node["end_index"],
})
flatten(node.get("nodes", []), out, full)
return out
def ask(prompt):
"""Один синхронный вызов локальной модели через LiteLLM."""
response = litellm.completion(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
num_ctx=8192,
timeout=900,
)
return response.choices[0].message.content
def pages_text(start, end):
"""Достаёт текст диапазона страниц PDF, страницы нумеруются с единицы."""
doc = pdfium.PdfDocument(PDF)
chunks = []
for page_no in range(start - 1, min(end, len(doc))):
chunks.append(doc[page_no].get_textpage().get_text_range())
return "\n".join(chunks)
if __name__ == "__main__":
with open("tree.json", encoding="utf-8") as f:
nodes = flatten(json.load(f))
# шаг 1: модель видит только оглавление, а не текст документа
toc = "\n".join(f'{n["node_id"]}: {n["title"]} (pages {n["start"]}-{n["end"]})'
for n in nodes)
select_prompt = (
f"Here is the table of contents of a document.\n\n{toc}\n\n"
f'Question: "{QUESTION}"\n'
"Which single section most likely contains the answer? "
'Reply with JSON only: {"node_id": "0000", "reason": "..."}'
)
start = time.time()
raw = ask(select_prompt)
print("Ответ модели на шаге выбора узла:")
print(raw.strip())
chosen_id = re.search(r'"node_id"\s*:\s*"?(\d+)"?', raw).group(1).zfill(4)
node = next(n for n in nodes if n["node_id"] == chosen_id)
print(f'\nВыбран узел {chosen_id}: {node["title"]} '
f'(страницы {node["start"]}-{node["end"]})')
print(f"Шаг выбора занял {time.time() - start:.1f} с")
# шаг 2: в контекст уходят только страницы выбранного узла
text = pages_text(node["start"], node["end"])
print(f"В контекст ушло {len(text)} символов вместо всего документа")
start = time.time()
answer = ask(f"Answer the question using only this text.\n\n{text}\n\n"
f"Question: {QUESTION}\nAnswer in three sentences.")
print(f"\nОтвет (получен за {time.time() - start:.1f} с):")
print(answer.strip())
На вопрос про надзор за криптоактивами модель прошла три уровня иерархии и остановилась на нужном разделе. Промахов не было. В контекст ушло 6442 символа вместо полусотни страниц.
Обратите внимание на строку с обоснованием выбора. Это и есть та самая объяснимость, которой нет у векторного поиска: система прямо говорит, куда пошла и почему, а номера страниц позволяют проверить ответ руками. Исходники и разбор архитектуры лежат в репозитории PageIndex.
Заключение
PageIndex переносит поиск с уровня похожести текста на уровень структуры документа. Дерево разделов заменяет векторный индекс, обход дерева заменяет поиск ближайших соседей, а решение на каждом шаге принимает языковая модель и объясняет его. Взамен приходится платить вызовами модели за каждый запрос и мириться с тем, что документ без внятной иерархии такому поиску не поддаётся. На длинных профессиональных документах обмен выходит выгодным. На потоке коротких неструктурированных текстов нет.
Референсные ссылки
- Репозиторий PageIndex, исходный код и README, ветка main на август 2026
- Документация PageIndex, описание SDK, режимов индексации и облачного контура
- Разбор фреймворка PageIndex в блоге Vectify AI, авторское описание дерева и tree search
- Результаты Mafin 2.5 на FinanceBench, методика и цифры бенчмарка




