Міркування над деревом документів замість пошуку векторів
PageIndex створює ієрархічний індекс, який дозволяє ШІ навігувати в ньому, оминаючи процеси розділення на частини та використання ембеддингів. Як це працює, результати в тесті FinanceBench та коли його використовувати.
Джерела: I Threw Out My Vector Database. RAG Got Way Better With PageIndex
Мало що є настільки ніяковим, як коли модель впевнено цитує неправильний розділ 200-сторінкового щорічного звіту під час прямої демонстрації. Після цього зазвичай настає знайомий сценарій: команда „виправляє“ проблему шляхом кращого розділення інформації, розумнішого перекриття даних та більш ретельно налаштованої моделі вбудовування — це звичайний цикл оновлень, через який проходить кожен інженер RAG, переконаний, що наступна корекція нарешті спрацює. Потім система відповідає на запитання про прогнози доходів на 2023 рік абзацом з плану капітальних витрат на 2021 рік. Обидва розділи використовували схожу фінансову термінологію, тож моделі вбудовування вважали їх пов’язаними. Насправді вони такими не були.
Такий тип невдачі ставить закономірне запитання щодо того, чи не є вся ця методологія пошкодженою з самого початку.
Чому подібність є недостатньою
Кожен посібник з RAG пояснює однаковий алгоритм. Документ розділяють на фрагменти по 500 слів, вбудовують їх, знаходять найближчі вектори до кожного запитання та передають ці фрагменти моделі LLM. Це звучить елегантно — семантичний пошук у поєднанні з генерацією мови.
Однак цей алгоритм не враховує того, що схожість не означає релевантності. База даних векторів знаходить текст, семантично близький до запитання, а не текст, який на нього відповідає. Параграф про фактори ризику ліків та параграф про його ефективність знаходяться поруч у просторі векторів, проте лише один з них стосується запитання про фінансові ризики компанії.
Розділення на частини створює ще одну проблему. Якщо розділити 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 розглядає документ таким, яким він є: структурованим продуктом, призначеним для навігації здатним читачем.
Старший аналітик, який читає щорічний звіт, не починає з першої сторінки. Він переглядає зміст, переходить до тієї частини, яка його цікавить, перевіряє цифри за примітками та повертається до розділу MD&A. Штучний інтелект у PageIndex робить те саме.
Саме тому цей підхід застосовується й у спеціалізованих сферах. Звіт про фармацевтичні випробування має іншу ієрархію, ніж юридичний контракт чи технічна специфікація, проте в кожному з них автор дотримується внутрішньої логіки, якою користуються досвідчені читачі. PageIndex фіксує цю логіку та допомагає знаходити потрібну інформацію.
Інша діагностика
Песимістичний висновок полягає у тому, що роки зусиль могли бути спрямовані на неправильну проблему. Увага приділялася стратегіям розділення на частини, тоді як справжня проблема полягала у руйнуванні структури з самого початку. Моделі вбудовування налаштовувалися, хоча семантична схожість ніколи не була правильним сигналом для пошуку. Для покращення якості пошуку додавалися засоби переранжування, тоді як сам метод пошуку був вузьким місцем.
Екосистема RAG створила складну механіку, щоб компенсувати початкові недоліки розділення на частини. PageIndex пропонує альтернативу: без частин, без вбудовування, без пошуку за схожістю. Створіть індекс, який відтворює справжню структуру документа, та працюйте з ним шляхом логічних міркувань.
Це не відповідає на все. Векторний пошук все ще є кращим для семантичного пошуку серед мільйонів коротких, неструктурованих документів. Проте для широкого спектру запитань щодо довгих, структурованих документів, які охоплюють більшість потреб корпорацій, ієрархічне дерево заслуговує на серйозну увагу. Запустіть ще раз демо-версію, яка двічі зазнала невдачі, поставивши запит про прогнози доходів на 2023 рік, і система тепер переходить у правильний розділ, завантажує потрібні сторінки та надає відповіді з точними посиланнями. Жодного неправильного року, жодного неправильного розділу, жодної хибної впевненості.
PageIndex є програмним забезпеченням з відкритим кодом, яке можна самостійно розмістити на GitHub, а також доступні інтерфейс чату та документація API з посиланнями на REST та MCP для тестування на власних документах.
Пов’язана література
- 30 запитань для інтерв’ю щодо RAG та векторних баз даних із стислими відповідями — Тридцять запитань для інтерв’ю про RAG та векторний пошук із короткими відповідями: гібридний пошук, HNSW проти IVF, чанкінг, переранкінг, метрики оцінки та безпека для кількох користувачів.
- RAG без векторів: як PageIndex читає документи без ембеддингів — Векторний пошук розділяє довгі документи на чанки, що призводить до втрати контексту. Дізнайтеся, як індекс у вигляді дерева, заснований на логічних міркуваннях, знаходить відповіді в складних звітах, разом із кодом.