RAG-Anything на 65 кнігах пра віно: дзе графавы пошук выйграе і дзе памыляецца
Практычны тэст графа ведаў RAG-Anything на 65 PDF пра віно: выбар парсера, кошт, адказы графа супраць вектарных і сляпая пляма з ігрыстымі вінамі.
Найбольш адказны спосаб ацэніць фрэймворк — гэта стварыць чыгунны продукт з яго, выявіць яго меры і пераканацца, што будзе, замест таго каб паверыць у стандартныя тэсты чыста README-файл. Сюды належыць RAG-Anything. Гэта адкрыты праект HKU, які стверджвае, што можа автаматычна ствараць граф знанняў з мультымадальных дакументаў, а матэрыялам для тэстав служыць персональная бібліятэка кніг пра вайну, асцярожаная чалавекам, які скончыў курсы і экзамены WSET і проста любіць гэтую тэму.
Чаму вайну можна выкорыстоўваць як хорашы тэст на стрэс
Віньярна праця — галузь, якая паўнастаўна залежыць ад атмасферы сувязей. Таніны вплываюць на тое, насколькі добра віньярны напой старэецца, кіслотнасць взаімадзейвае з танінамі, а дрожжы спрыяюць ферментацыі, якая стварае спецыфічныя ароматычныя складовы. Віноград асоціюецца з рэгіонамі, рэгіоны — з кліматамі, а кліматы — з стылямі віньярных напоў. Калі структураваная экстракція сувязей межаў можа перамагаць звычную вектарную пашуковую сістэму па ўсіх аспектах, то ёй трэба гэта зрабіць і тут. Задача, якая перацэнюецца: чы рэшэння RAG-Anything стварае корыстную графіку знанняў на адной з рэальных кніг у фармаце PDF, і чы яно можа адпавядаць на запытанні пра віньярныя напоў лепшая, чым простая вектарная пашуковая сістэма?
У сутнасці RAG-Anything ўключае LightRAG. Да яго додаюцца мультымадальная парсацыя (PDF, DOCX, адпраўкі), аўтаматычная выкалекаванне ентытэў і звязкаў у вяртэк, а таксама пяць рэжымаў запытанняў, якія збалансавана співаюць праходжэнне па вяртэку і пошук за вектарамі. Серэд парсероў выкорыстоўваюцца MinerU, Docling і PaddleOCR; працуюць таксама кілька бэкендоў LLM, а табелі, адпраўкі і рэвнянні апрацоўваюцца ў момент уводу дадзенаў. Заводзячы пад 15 000 зірак у GitHub, ён прывлекае сэрйёзную увагу, хоць колькасць зірак — гэта зусім не першы показнік, які трэба бачыць. Далей: практычныя рэзультаты, адлагоджымы код і адпаведнае адгукі пра тое, калі графічны падход RAG ефектыўны, а калі — ні.
Архітэктура
Існуе два этапы: увод дадзенаў і запытання. Увод дадзенаў прабівае PDF праз гэты пайплайн, пачынаючы з парсера дакумента — або MinerU (на базе VLM), або простышы і дэшавейшы Docling:
PDF Document
|
v
[Document Parser] ─── MinerU (VLM-based) or Docling (lighter/cheaper)
|
v
[Content Extraction] ─── text, tables, images, equations
|
v
[Text Chunking] ─── split into manageable pieces
|
v
[LLM Entity/Relation Extraction] ─── LLM extracts entities + relationships
|
├──> [Knowledge Graph] ─── entities as nodes, relations as edges (GraphML)
└──> [Vector Embeddings] ─── chunks embedded for similarity search (JSON)
LLM чытае кожны фрагмент і выявляе такі ентытэты, як „Cabernet Sauvignon“, „malolactic fermentation“ і „Bordeaux“, а таксама зв’язкі між ямі: Cabernet Sauvignon расце ў Борды, а працэс malolactic fermentation зменшуе кіслотнасць. Рэзультаты зберагаюцца як вузлы і рэшткі ў стацыонарным графе GraphML.
Запыткі падтрымваюць пяць рэжымаў, кожны з якіх мае сваю стратэгію пошуку. У табліцы пераказана, што шукае кожны рэжым, для чаго ён падходзіць і ў чым яго слабасці:
+--------+------------------------------+-------------------------------+---------------------------------+
| Mode | What It Searches | Best For | Weakness |
+--------+------------------------------+-------------------------------+---------------------------------+
| naive | Vector similarity only | Factoid questions, robustness | Misses relational structure |
| local | Graph neighborhood traversal | Entity-specific deep dives | Blind to entities not extracted |
| global | Community-level summaries | Broad thematic questions | Less specific, slower |
| hybrid | local + global | Balanced depth and breadth | No vector fallback |
| mix | Graph + vector together | General-purpose (recommended) | Slowest mode |
+--------+------------------------------+-------------------------------+---------------------------------+
Што стосуецца модэляў, GPT-4o-mini адпавядае за генераванне тэксту, што дазволяе залічваць сотні вызываў LLM пад час выкарыстоўвання для выкарыстоўвання элементаў за прыемным цэнам. text-embedding-3-small стварае эмбедынгі, а апрацоўка зяўроў выключаецца, каб зменшыць вітраты:
from openai import AsyncOpenAI
from lightrag.utils import EmbeddingFunc
from raganything import RAGAnythingConfig
aclient = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Text LLM - handles entity extraction and answer synthesis
async def llm_model_func(prompt, system_prompt=None, history_messages=None, **kwargs):
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
if history_messages:
messages.extend(history_messages)
messages.append({"role": "user", "content": prompt})
response = await aclient.chat.completions.create(
model="gpt-4o-mini", messages=messages, temperature=0.0,
)
return response.choices[0].message.content
# Embeddings - 1,536 dimensions, 8K token window
async def _embed_texts(texts, **kwargs):
response = await aclient.embeddings.create(model="text-embedding-3-small", input=texts)
return np.array([item.embedding for item in response.data])
embedding_func = EmbeddingFunc(embedding_dim=1536, max_token_size=8192, func=_embed_texts)
# Configuration - Docling parser, tables enabled, images disabled for cost
rag_config = RAGAnythingConfig(
working_dir="./rag_storage",
parser="docling",
enable_image_processing=False, # skipping images - valid for my use-case
enable_table_processing=True,
enable_equation_processing=True,
)
Цена — гэта справжняя прагматычная дыялегія. Усе старонкі ўсіх кніг аналізуюцца, раздзеляюцца на часткі і праходзяць праз GPT-4o-mini для выкарыстоўвання інфармацыі, а дадзенне можлівасці апрацоўкі зялёных фартакоў прызвела бы да дадатковых вызоў да GPT-4o для аналізу зялёных фартакоў. Для гэтага эксперымента былі дастатнія тэксты і табліцы.
Корпус і аналізаторы
У калекцыю увайшло 65 PDF-файлаў, выбраных, каб пакрыць разныя аспекты. Кнігі WSET — гэта структураваны навчальны матэрыял з табліцамі і тэхнічным слоўнікам, тады як іншыя — гэта розмовныя тэксты, складзеныя з парадам або історыям пра вырабоцтва вайн. Гэтая сумешанасць дапамагае пераканацца, чы можа граф вырашаць задачы, калі мова йдэ пра розныя стылі пісьма і рывень складнасці.
MinerU, стандартны пасэр, выкарыстоўвае мадэль візыі-мовы для чытання структуры сторанкі. Ён робіць рэшытны аналіз, але дзейнаць повольна. Першы раз для завантажэння VLM трэба або-і 7 хвілін, а кожная сторанка аналізуецца прыблізна 15 секунд на CPU; такім чынам аналіз кнігі з 50 сторанакікоўкваўся або-і 20 хвілін прычыму пачатку выкарыстоўвання дакументаў. Кнігі з 300 сторанакімаўкваўліяўся цэлкам, нават калі яны былі раздзелены на часткі по 50 сторанак, таму што OCR выканаў тайма-аут.
Docling, легкішы пасэр, які не выкарыстоўвае VLM, справіўся з той самай кнігай без працэсаў завершэння, і весь працэс выкарыстоўвання дакументаў завершыўся, таму я ўжо быў выкарыстоўваны для рэшты кніг. Практычны вывод такі: MinerU дае болей дакладнае розумеўство структуры, напрыклад, лепша выяўляе ілустрацыі і точней разлічвае загалоўкі ад основнага тэксту, тады як Docling — гэта рэалістычны выбар для сістэм, якія выкарыстоўваюць толькі CPU. Плануецца праба з выкарыстоўванням локальнага GPU.
Код для прыемкі дакументаў ўсьмохніцельны, пры чым для кожнага дакумента выкарыстоўваецца адна вызовка:
from raganything import RAGAnything
rag = RAGAnything(
config=rag_config,
llm_model_func=llm_model_func,
embedding_func=embedding_func,
)
for pdf_path in sorted(Path("./data").glob("*.pdf")):
file_start = time.time()
await rag.process_document_complete(
file_path=str(pdf_path),
output_dir="./output",
)
print(f"Completed {pdf_path.name} in {time.time() - file_start:.1f}s")
await rag.finalize_storages()
Эта праметка внутршні ўрабляе парсінг, разбіўку на часткі, выкарыстоўванне ентытэў, стварэнне графа і вектарные эмбеддынгі. API добра спраектаваны, практычна да ступеня пераспростаўлення.
Усі кнігі былі выкарыстоўваны за 6 гадзін, у сярэжнем тэчасе 30 стораніцаў за хвіліну. Парсінг не ўтварае бутылочнаг горла. Сотні вызоў LLM для выкарыстоўвання ентытэў такі, калі кожная частка праходзіць праз GPT-4o-mini для выявлення ентытэў і зв’язкаў. Усё-такі, расходы на LLM без зображэнняў былі невялікія — адпаведна $5 за весь эксперымент, уключаючы тэставання. Табліцы пасля апрацоўкі падсумоўвае размер графа:
+----------------------------+------------------------------+
| Metric | Value |
+----------------------------+------------------------------+
| Entities (graph nodes) | 37,132 |
| Relations (graph edges) | 47,650 |
| Text chunks | 6,247 |
| Storage on disk | ~185 MB (all JSON + GraphML) |
| Indexed documents | 65 |
| Load time at query startup | ~10 seconds |
+----------------------------+------------------------------+
Не выкарыстоўваецца жадных зовнішніх баз дадзеных. Граф утрымваецца у файле GraphML, а даны вектарных сховішчаў і даны тыпу ключ-значэнне — у файлах JSON. Весь база знання помешчаецца ў образе Docker без якой-лібо дапаможнай інфраструктуры.
Граф проты вектараў
У цэнтральным эксперыменце было адправлена 6 запыткаў праз два рэжымы: local (толькі праходжэнне па графе) і naive (толькі сэмантычная супарадніснасць вектараў), пасля чаго былі паўтарна перакананы адпаведныя адпаведзі. Запыткі былі выбраны для пераканання рэляцыйнага, компаратыўнага, каузальнага і междокументнага аналізу.
async def compare_modes(rag, query):
"""Compare different retrieval modes on the same query."""
for mode in ["local", "naive"]:
result = await rag.aquery(query, mode=mode)
print(f"[{mode}] {len(result)} chars, {elapsed:.1f}s")
Рэзультаты кожнага запытку, з часамі выканання, паказаны ніжэй.
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
| Query | Graph (local) | Vector (naive) | Winner |
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
| How does soil type influence wine character? | 32.5s / 3,464 chars | 28.2s / 2,531 chars | Tie |
| Relationship between tannins, acidity, and aging? | 24.8s / 2,266 chars | 25.8s / 2,289 chars | Graph (structure) |
| Compare red vs. white winemaking | 32.9s / 3,119 chars | 42.0s / 3,423 chars | Graph (speed + structure). |
| What role does yeast play in fermentation? | 27.8s / 2,587 chars | 26.8s / 2,403 chars | Tie |
| How do fortified wines differ from table wines? | 28.5s / 2,635 chars | 23.1s / 2,597 chars | Tie |
| Sparkling wine production methods? | 10.7s / 48 chars | 48.4s / 2,900 chars | Vector (graph fails) |
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
Таніны, кіслотна сільскарбніца і працэс старэння. Класычны пытанне, якое выклікае адносіны межаў. У графе ёсць явныя рэшткі з танінаў на ўмэнне старэння, з кіслотнасці на свежасць, а таксама з обох на якасць. Праследаванне гэтых рэштакоў дае структураваны апіс, як таніны і кіслотнасць взаімаюцца, што інакша значыць спалучэнне фрагментаў, якія можаць знаходзіцца далека адзін ад другога ў аднаго ісходным тэксце.
Вырабаткі чырвонага і белага вайна. Граф выражае гэты контраст за дапамогою звязкаў межаў між ентытамі: вырабаткі чырвонага і белага вайна кожны з’яўляецца у звязку з аднаковымі працэсамі (ферментацыя, прасуванне, старэння) праз разныя рэшткі адносінаў. Гэта было адпаведзана за 32,9 секунды, тады як вектарны пошук зайняў 42,0 секунды, пры чым рэзультат быў болей арганізаваным.
Укрэпленыя і звычныя вайны. Для гэтага трэбае сінтэз дакументаў з калькі кніг, і граф павольна з’ўязваў ентыты за межамі дакументаў.
Па простыях пытаннях на асэнцыях, такіх як тое, як грунт вплывае на характэр віна чым займаецца дрожжы пад ферментацыі, оба рэжымы далі парадоксальныя адказы. Фрагменты маюць гэту інфармацыю безпосерэдна, таму метод вектарнай сэмлівенасці выкарыстоўвае ўсе тыя ж фрагменты настолькі ж эфектыва, як і метод пераскоку.
Найболей значныя недастаткі
На пытанне пра методы вырабаткі шампанскага рэжым графа адказаў, што у яго няма достатнько інфармацыі. Гэты адказ складаўся з 48 сімваў і быў створаны за 10,7 секунд. Рэжым вектараў за 48,4 секунды даў дэталізаваны адказ дыяграматычнай дужыной 2,900 сімваў, які охопіў традыцыйны, Шармат, пераказны і абачныя методы.
Экстракцыя элементаў працяглася без урахоўвання ключоўых паняць квасоўых вайн. Не існавало вузлаў для тэрмінаў "methode champenoise", "tank method" чы "riddling", таму запыт графа па працэсе вырабаткі квасоўай вайны не знайшоў нічога для аналізу. Граф не могаў ведаць пра гэтыя тэмы. Векторны пошук проста знаходзіў фрагменты тэксту, якія мялі гэтыя тэрміны, і перадаваў іх ШМ, пры чым ён толеруе неудачы экстракцыі, адтолькі што працюе з необработаным тэкстам, а не з падготавленым наборам элементаў.
Основныя абмежэнні заключаюцца ў следзэм: калі экстракцыя працяглася без урахоўвання паняці, запыты графа не можуць яе пазначыць, таму граф ўсё толькі настолькі ўжоўполнены, насколькі ўжоўполнена сама процедура экстракцыі. У векторнага пошуку немае такога спосабу неудачы. Самэлькі існуе рэжым mix, і самае по сабе выборка local ёсць слабым варыянтом.
Таксамо варта атрымуць увагу часавая складоўка. Час выканання запытанняў да графа вырашваўся ад 10,7 секунд (колі запыт не знаходзіў нічога і шырока шукаў) да 32,9 секунд, а запытанняў да вектараў — ад 23,1 да 48,4 секунд. У обох случаях домінуе сінтэза за дапамойю LLM, а не выкарыстоўванне інформацыі з базы дадзенаў, а кэшаванне робіць павтаральныя запытання на аднаковыя тэмы значна шырэйшымі.
Аналіз графа
Ёнколі працэс перагляду самога графа, інтэрактыўная візуалізацыя, створаная з NetworkX і pyvis, паказала 120 аб’ектаў-центроў з найбольшай колькасцю звязоў:
import networkx as nx
from pyvis.network import Network
G = nx.read_graphml("rag_storage/graph_chunk_entity_relation.graphml")
# Focus on the most connected nodes (hubs)
degree_dict = dict(G.degree())
top_nodes = sorted(degree_dict, key=degree_dict.get, reverse=True)[:120]
subG = G.subgraph(top_nodes).copy()
# Categorize nodes by wine domain keywords
def categorize(name):
lower = name.lower()
if any(k in lower for k in ["cabernet", "merlot", "pinot", "riesling", ...]):
return "grape" # Red nodes
if any(k in lower for k in ["bordeaux", "california", "champagne", ...]):
return "region" # Blue nodes
if any(k in lower for k in ["fermentation", "aging", "maceration", ...]):
return "process" # Green nodes
if any(k in lower for k in ["port", "sherry", "sparkling", ...]):
return "wine_type" # Orange nodes
return "general" # Purple nodes
Зялёнка паказвае кантэкты вузлаў па катэгорыях (чырвоны — для вінограду, сіні — для рэгіёнаў, зелены — для процэсаў, апранкавы — для тыпаў віна, фіялетавы — для загальных паняцэй) і вырашае размер вузлаў па колькасці кантэктаў.
15 аб’ектаў з наібольшым колікствам кантэктаў паказваюць, што граф адлучыў добра. „Віна“, як загальны аб’ект, лідуе з 2 136 кантэктамі:
+--------------------+-------------+-----------+
| Entity | Connections | Category |
+--------------------+-------------+-----------+
| Wine | 2,136 | General |
| Wine Production | 1,344 | General |
| Italian Wines | 768 | General |
| Bordeaux | 442 | Region |
| Cabernet Sauvignon | 420 | Grape |
| Riesling | 412 | Grape |
| Champagne | 348 | Region |
| California | 321 | Region |
| Grapes | 287 | Grape |
| Port | 243 | Wine Type |
+--------------------+-------------+-----------+
Візуабельныя законамернасці включаюць ўжоўтканы кластэр процэсаў ферментацыі, сорты вінограду, якія выходзяць на рэгіоны, а таксама падкластэр алкагольных віноградных вайн (Порт, Шэрры, Мадэйра) з іхнімі саецьбамі працы. Рэдкія зоны таксама даюць важлівую інфармацыю. Канцэпцыі газавых віноградных вайн практычна не прэдставлены, што пояснюе неудачную запытку. У калекцыі таксама мало кніг пра газавыя віноградныя вайны, таму гэты прытлумленне можа часткова выйшаць з самых дадзеных.
Маленькі фронтэнд-дэман
Для аднароджэння і нетэхнічных дэманаў інтэрфейс запыткі можа быць абгорнуты ў Gradio:
import gradio as gr
def query_wine(question, mode):
t0 = time.time()
result = _run_async(_query(question, mode))
elapsed = time.time() - t0
return result, f"**Mode:** {mode} | **Time:** {elapsed:.1f}s"
with gr.Blocks(title="Wine Knowledge RAG") as demo:
question = gr.Textbox(label="Ask a wine question", lines=2)
mode = gr.Radio(
choices=["mix", "local", "global", "hybrid", "naive"],
value="mix", label="Retrieval Mode",
)
submit_btn = gr.Button("Ask", variant="primary")
stats = gr.Markdown("")
answer = gr.Markdown(label="Answer")
submit_btn.click(fn=query_wine, inputs=[question, mode], outputs=[answer, stats])
Аплікацыя завантажае загадзя створаную графіку за апошнія 20 секунд і не патрэбуе GPU для выканання запыткаў, адколі весь вычысленні выкананы праз вызовы API да OpenAI.
Што паказвае эксперымент
Графіка RAG явна павышае якасць адпаведзей на тры тыпы запыткаў:
- Запыткі, якія стосуюцца адносаў між рэчамі, калі пытаюцца, як адна рэч вплывае на іншую: рэшткі між ентытамі значаюць, што для адпаведзю не патрэбна спяльная частка тэксту. Аднос між танінамі, кіслотнасцю і працэсам выдзейвання выступіў як явны прыклад успеху.
- Порэвананні двух рэчаў: контрасты самі сабою арганізуюцца. У выдзейванні чырвонага і белага вайна вікорыстоўваліся аднаковыя вузлы процэсаў праз разныя рэшткі адносаў, што дало адчытныя адпаведзі парадульна.
- Сінтэз з кантэнту калькі кніг: векторны пошук таксама можа справіцца з гэтым, але графіка чытка паказвае зв’язкі і не залежыць ад таго, чы рэзультаты пошуку знаходзяцца поблізу адзін ад другога.
Выдзеляюця тры слабасі:
- Экстракцыя сутнасцей ўсё ж застаецца слабым месам. Методы, прызначаныя для аналізу віна, былі ў неапранутым тэксце, але так і не сталі элементамі графа, што ўскладнюе будзь-які падход, базаваны на экстракцыі.
- Запіты на факты не выклікаюць патрэбы ў графе. Запіты на кшталт «Пад якой тэмпературой трэба зберагаць віно?» можна рашыць за дапамогою вектарнага пошуку, а вікорыстанне графа толькі збільшуе навантажэння без жадных дадзеных выгод.
- Косты і затрымкі ёсць рэальнымі проблемамі. Для апрацоўкі дадзеных трэба сотні вызоваў LLM, а затрымкі у 25–30 секунд вызваны саме працэсам сінтэзы дадзеных LLM.
Матрыца прыняцця рашэння супарабатоўвае тыпы запітаў з трыма варыянтамі: вектарны (наўатывны), графавы (локальны) і сумешаны, пры чым рэкамендуецца сумешаны падход:
+----------------------------+----------------+---------------+-------------------+
| Query Type | Vector (naive) | Graph (local) | Mix (recommended) |
+----------------------------+----------------+---------------+-------------------+
| Factoid lookup | Good | Good | Good |
| Relational ("X affects Y") | OK | Best | Best |
| Comparative ("A vs B") | OK | Best | Best |
| Cross-document synthesis | OK | Good | Best |
| Topic with extraction gaps | Best | Fails | Good |
+----------------------------+----------------+---------------+-------------------+
Чатыры практычныя рэкамендацыі:
- Заўсёды выкорыстоўваеце рэжым
mix. Не трэба пакладацца на пошук толькі на аснове графа, і відсутнае шырокое пакрыцце пацэлкавае гэта. - Для рэалізацыі толькі на CPU выбірайце Docling заместо MinerU, адколькі VLM у MinerU ўсеохопны, але кранічна чувасты.
- Звярніце увагу на час, неабходны для апрацоўкі дадзеных, больш заўсёды для большых корпусаў.
- Аналізуйце граф. Пераканаецеся, што ключовыя концэпцыі домэнаа дзейсна выступаюць як ентытаты, і пераканаецеся ў структурных памэтрыках графа пры пакладанні на яго.
Вывад
RAG-Anything адрысваецца сваёй галоўнай обяцанні. Ён выкарыстоўвае структураваныя веды з PDF-файлаў, ператвараючы іх у граф, што значна павышае якасць запытанняў, заснованых на зв’язках і паўтарэннях: аналіз даваў болей арганізаваныя адпаведзі на запытанні пра таніны, кіслотнасць і працэс старэння, паўтарэнне запытанняў пра ружовыя і белыя вінограды выконвалася быстрэй і з болей чыстымі рэзультатамі, а таксонамія з матэрыяламі з разных выдавецкіх будов працавала. Гэта перспектывнае рашэння, але ён яшчэ молады; надзор за надзеяйнасцю парсера і абсягам выкарыстоўваных дадзеных ёсць ў прыорытэ. Кожны, хто расследвае яго для практычнага выкарыстоўвання, павінен парабаваць гэты эксперымент на савых дадзеных, адколькі документ README не пакажае, дзе ён можа зламацца.
Спадневаная літэратура
- Порэванаўанне 16 архітэктураў RAG: каторы патэрн падходзіць для каторай проблемы — Аналіз шанзацоў RAG у чатырох катэгорыях: ад стандартнага і гібрыднага адзысквання да агентных, графавых і федэравых дыяграм, з практычным планам разработкі месцам за месцам.
- Магчымасці адзысквання інформацыі для корпаратыўных AI-агентаў за дапамогою Langfuse і OCI — Чаму логі, метрыкі і трэйсы не падходзяць для агентаў на базе GenAI, і як адсочваць запросы, вызовы інструментаў, токены і сесіі за дапамогою адкрытага кода Langfuse на OCI Agents.