Расчунак над дрэвам дакументаў у замену на пошук вектараў
PageIndex ствараяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя
Крыніцы: I Threw Out My Vector Database. RAG Got Way Better With PageIndex
Мало чаго ўскладнюе сітуацыю так, як калі мадэль з упэўненасцю цітае некарэктны абзац з 200-сторонньага годовага звятыя ў падзеі прыжывой дамастры. Пасля гэтага зазвычай настае такі ж сценарый: каманда «вылечвае» прычыну за дапамогою лепшага разбівання тэксту, розумнейшага перакрыцча і адносна лепшая налаштаванага модэлю эмбедынгу — тое сама процедура апдэйта, якая пераходзіць кожны інжынер RAG, які верыць, што наступная паслядробка нарэшце запрацюе. Аднак сістэма на запыт пра прогназы дохода на 2023 год адпавядае абзацам з плану капітальных выдаткаў на 2021 год. У обох абзацах была схожая фінансавая тэрмінологія, таму модэлі эмбедынгу судзілі, што яны адносуюцца адно да другога. Аднак гэта не так.
Такія неудачы паднімаюць справядлівы вопыт пра тое, чы не ўся гэтая падход з самага початку ўражоны.
Чаму схожасць не ўстаёт на высоту
Кожныя навучальныя матэрыялы па RAG выкладваюць аднаковы прыем. Документ раздзеляюць на фрагменты по 500 слоў, якія пасля таго вставляюцца, для кожнага запіту шукаюцься найбліжэйшыя векторы, і гэтыя фрагменты перэдаюцца LLM. Гэта звучыць элегантна — семантычны пошук у поўнай злагодзе з генераваннем мовы.
Але гэты прыем не берае ў расчыт таго, што схожасць не значыць рэлевантнасць. База дадзеных на векторах знаходзіць тэксты, якія семантычна блізкія да запіту, а не тыя, якія на яго адпавядаюць. Абарон пра факторы рызыку леку і абарон пра яго эфектыўнасць знаходзяцца поблізу адзін ад другога ў просторэ вектораў, пры тым што толькі адні з іх адпавядае на запыт пра фінансавыя рызыкі компаніі.
Разбіўка на часткі створае другую проблему. Калі роздзеліць 200-сторонній юрыдычны дакумент на фрагменты по 500 токенав, усе, што рэчыпік стварыў з адказнасцю, знікае: раздзелы, перакіранні, логічны вывес. Заставаецца купа фрагментаў, без жаднага розумення таго, звядзе гэтыя фрагменты і якія межы межы ў ўзаінтэрэсаванні. Крашчая разбіўка трохі палегшвае сітуацыю, але ніколі не рашае асновнай проблемы, адколі структуру, якая была адкінута, немагчыма вярнуць.
Чалавечы аналітык ніколі не чытаў бы так. Ён бы прыглянуўся до зместу, перайшоў да правернага раздзелу, прачытаў яго, ведаючы пра суседнія раздзелы, і пераканаўся з прыміткамі. Ён навігуець дакументам як дакументам.
Дрэва замест вектараў
Команда VectifyAI запыталася, як бы выглядала сістэма RAG, якбы яна працавала такім чынам, і створыла PageIndex. Калі выгаварыць голасна, ключовая ідэя здаецца практычна явной. Не трэба ператвараць дакументы на вектары і шукаць за сэмплярнасцю. У замяну трэба стварыць іерархічны дрэва-індэкс дакументаў, фактычна табліцу зместу, чытальную для машыны, і дазволіць LLM працаваць па гэтым дрэве, каб знайсці рэлевантны матэрыял.
Гэта значыць, што няма базы дадзеных вектараў, няма чанкавання і няма балавання сэмплярнасці, толькі структураваны індэкс і розумна навігацыя.
Першы крок: стварэнне індэкса
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 годзе. У замяну пошуку супадзяючых фрагментаў тэксту, LLM чытае індэкс, вырашае, што раздзел «Фінансавыя рэзультаты > Выручка па катэгорыям продуктав» ёсць тым, што трэба пераглядзець, прыходзіць туды і запоўнюе всі сторункі 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 спрыяе дакументу як таму, кім ён ёсць: структураванам артэфакту, прызначанам для навігаціі здатным чытальнікам.
Старшы аналітык, які чытае годовы звятынак, не чытае яго з першай старонкі. Ён пераглядае змест, пераходзіць да таго раздзела, які яго цікавіць, пераканальвае цыфры за дапамою прымечанняў і зноў вяртаецца да раздзела з правіламі публікацыі. LLM, які знаходзіцца ў PageIndex, рабіць тое ж самае.
Самэ гэтая прычына, чаму гэты падход прыменяецца і ў спецыялізаваных сферах. Звятынак фармацэвтычных пробаў мае іншую іерархію, чым прававы контракт або тэхнічныя спецыфікацыі, пры тым у кожным з іх авар перашледжае внутранюю логіку, якую вікорыстоўваюць эксперты-чытальнікі. PageIndex фіксуе гэю логіку і дапамагае аднаходзіць неабходную інфармацыю.
Іншая дыягноза
Сумным вывадам ёсць тое, што гадзіны зусілляў моглі быць спрамаваны на неправыя проблемы. Увага была зосераджана на стратэгіях разбівання на часткі, тады калі асалідныя недагоды вельмі рана разрушалі структуру. Мадэлі вбудоввання налаштовваліся, хоча семантычная схожасць ніколі не была правым сігналам для пошуку. Для павышэння якосці пошуку прыкладваліся дадаткі для пераранжавання, тады калі сам метод пошуку быў вузькім месцам.
Экасыстэма RAG створыла складную схему, каб компенсаваць первачныя недагоды разбівання на часткі. PageIndex пропануе альтэрнатыўу: без частак, без вбудоввання, без пошуку за схожасцю. Неабходна стварыць індэкс, які будзе відпаваць рэальнай структуры дакумента, і працаваць з ям па прынцыпах логічных вывадоў.
Ён не адказвае на усё. Для сэмантычнага пошуку сярод мільйонав короткіх, неструктураваных дакументаў вектарны пошук застаецца лепшым. Аднак для шырокага класу запытанняў пра дугія, структураваныя дакументы, якія падпадаюць пад большасць патрэбаў падпрыемстваў, іерархічная сяброўка заслуговае на серйозную увагу. Паўтарыце дэман, які два разы зазнаў неудачы, задаючы запыт пра прогназы дохода на 2023 год, і сістэма цяпер пераходзіць у правыя раздзелы, выкарыстоўвае правыя сторанкі і адпавядае з точнымі цітатамі. Ніякога няправільнага года, ніякога няправільнага раздзела, ніякай непатрэбнай впэўненасці.
PageIndex — адкрыты код і можа быць самастойна размешаны на GitHub; таксама доступны інтэрфейс чату і дакументацыя API з апісамі REST і MCP, якія дапамагаюць пры тэставанні на савых дакументах.
Супаўзяныя матэрыялы
- 30 пытанняя для спыткаў з RAG і баз дадзеных вектараў з краткімі адказамі — Трыдцаць пытанняя для спыткаў пра RAG і пошук вектараў з короткімі адказамі: гібрыдны спосаб выкарыстоўвання дадзеных, HNSW проты IVF, разбіўка дадзеных на часткі, пераранжаванне, паметры ацэнкі і безпека для кальколяўкаў.
- RAG без вектараў: як PageIndex чытае дакументы без эмбедынгаў — Пошук вектараў разбівае дзейныя дакументы на часткі і тады втрачае кантэкст. Пазнакаміцеся, як індэкс на аднойчыні на аднойчынінах знаходзіць адказы ў складных звярненнях, з кодам.