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 заповнює цю прогалину, завантажуючи релевантні уривки та розміщуючи їх перед моделлю безпосередньо перед тим, як вона дасть відповідь. Обіцянка проста: поставте запитання, і ви отримаєте відповідь, засновану на ваших даних. Однак насправді все є складніше.
Версія з примитивним підходом
Найочевидніший підхід — це додавати до запитання весь документ та дозволяти моделі самій його обробити. Жодних баз даних, жодних складних механізмів.
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)
Сьогодні більшість компаній використовують щось схоже. Він добре справляється з простими запитаннями щодо простих документів, але стикається з проблемами, коли документи ускладнюються.
Три причини його невдач
Фрагменти, які розділяють ідею навпіл
Уявіть абзац звіту, у якому сказано, що операційний прибуток у третьому кварталі склав 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)
Якщо запитати, що спричинило зростання операційного прибутку у 3-му кварталі, пошук може повернути розділ про хмарні послуги без цифр або розділ із цифрами без пояснення причини. У будь-якому разі відповідь є неповною.
Розділення на абзаци цю проблему не вирішує. Деякі абзаци складаються з двох речень, інші заповнюють півсторінки, а три послідовні абзаци можуть розповісти одну історію, яка зрозуміла лише при читанні разом.
Посилання, які нікуди не ведуть
У контрактах та фінансових звітах часто використовуються посилання нарізно, наприклад «Це регулюється розділом 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..."
}
]
}
Кожен вузол має унікальний ID, який вказує на оригінальний документ, заголовок, сам текст та дочірні вузли для підрозділів. Модель просувається по дереву, читаючи анотації та вирішуючи, які гілки розглядати детальніше.
У одному запиті можна порівняти кілька документів:
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 ще не оприлюднила детальних даних про швидкість та пропускну здатність. Кожен запит вимагає кількох послідовних викликів моделі для аналізу та міркувань, тому він є повільнішим та дорожчим за одиничний пошук у векторній базі даних. Для сценаріїв з великим обсягом даних та низькою затримкою такий компроміс може виявитися неефективним.
Вибір між ними
Vectorless RAG — це не універсальна заміна векторним базам даних. Це спеціалізований інструмент.
Vector RAG підходить тоді, коли відповіді мають надходити менш ніж за секунду, коли корпус складається з багатьох коротких документів, наприклад бази FAQ чи збірки статей, коли витрати на запит перевищують значення ідеальної точності, або коли чат-боту користувача достатньо достатньо якісних відповідей.
Vectorless RAG підходить для довгих, структурованих професійних документів: щорічних звітів, контрактів, регуляторних заявок та технічних посібників. Він також ідеально підходить у випадках, коли найважливішою є точність, коли запити мають ґрунтуватися на переліках посилань, або коли необхідно чітке слідування кроків для визначення джерела кожної частини відповіді.
Для серійної роботи варто розглянути гібридний підхід. Легкі метадані чи фільтри за ключовими словами дозволяють відсортувати релевантні документи, а потім алгоритми на основі дерева знаходять точні розділи всередині них. Це забезпечує швидкість традиційного пошуку під час першого проходження та точність аналізу під час другого.
Компроміс між вартістю та якістю є реальним. Моделі, які ґрунтуються на логічних міркуваннях, коштують більше за один виклик, ніж моделі з вбудованими данами, а перебір дерева означає кілька послідовних викликів та вищу затримку. Швидкість та ціна обмінюються на точність та здатність до пояснень. У регульованих сферах, таких як фінанси, право та охорона здоров’я, де неправильні відповіді мають наслідки, такий обмін зазвичай виправдовує себе.
Куди це прямує
Перехід від пошуку на основі схожості до пошуку на основі логічних міркувань ще на початковій стадії, але напрямок очевидний. PageIndex вже підтримує варіант, заснований на зоровому аналізі, який працює безпосередньо з зображеннями сторінок PDF, без вилучення тексту чи OCR. Зображення сторінок надходять, і модель аналізує те, що бачить. Це допомагає при роботі з складними макетами, відсканованими сторінками та вбудованими діаграмами, з якими вилучення тексту справляється погано.
Також триває робота над агентним пошуком інформації. Замість одноразового аналізу модель продовжує досліджувати матеріал: вона слідує за посиланнями, порівнює різні частини, ставить собі додаткові запитання та вдосконалює свою відповідь протягом кількох етапів міркувань, що й є різницею між швидким ознайомленням із документом та його ґрунтовним вивченням.
Усі, хто бажає експериментувати, можуть скористатися репозиторієм PageIndex з відкритим кодом на GitHub, який містить запускові Jupyter-ноутбуки як для стандартних, так і для базованих на візуальному аналізі підходів. Після запуску у вересні 2025 року проект набрав 20 000 „зірок“, а його сервер Discord залишається активним.
Бази даних векторів залишатимуться, і вони ефективно обробляють велику кількість завдань. Проте кількість додатків, які потребують детального аналізу довгих та складних документів, зростає, і підхід, який полягає у розділенні тексту на фрагменти фіксованого розміру з надією, що пошук схожостей знайде потрібний фрагмент, стає все менш ефективним. Контракти, звіти та технічні специфікації — це саме ті документи, які підприємства цінують найбільше, і саме тут найбільш ефективним є пошук, заснований на логічних міркуваннях.
Розумний спосіб оцінити це — запустити самостійну версію на документі, який вже добре зрозумілий, а потім спробувати PageIndex на тому самому файлі та подивитися, як деревоподібна структура компенсує втрати, спричинені частковим розділенням документа. Найкраща система пошуку — це не та, що має найсучасніші технології, а та, яка знаходить правильну відповідь.
Шлях для тестування
- Vector RAG – підходить для основ: встановіть
chromadbтаopenai, запустіть приклад ChromaDB на короткому документі, потім спробуйте його на довшому та спостерігайте, де відповіді стають неточними. - DIY без векторів – підходить для розуміння концепції: запустіть простий приклад OpenAI з використанням алгоритму дерева та міркувань на тому самому документі та порівняйте відповіді з версією з векторами.
- Продакшн без векторів: отримайте безкоштовний ключ API PageIndex з веб-сайту документації, встановіть пакет
pageindexдля Python та дотримуйтесь прикладу 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, оператори відстані та способи читання оцінок.
- Знімки пам’яті агента, навички, занепад якості та обмеження пам’яті за гілками — Чотири доповнення до шару пам’яті вбудованої векторної БД для агентів-програмістів: портативні знімки з можливістю об’єднання, активація навичок, занепад якості з перевіркою конфліктів та пам’ять, обмежена за гілками.