Галоўная / Артыкулы

RAG на дзесяці мільёнах дакументаў падчас запыту: варонкі, маршрутызатары, агенты і ацэнкі

RAG на дзесяці мільёнах дакументаў: варонка пошуку менш за 200 мс з SQL-фільтрамі, гібрыдным пошукам і пераранжаваннем, а таксама маршрутызацыя запытаў, ацэнкі і скрыпт для Qdrant.

Выдавец: REACTAPP.TOP8 хв чытання1776 слоў

Крыніцы: Scaling RAG to 10 Million Documents Part 2: Optimizing retrieval and generation

Занесенне дзесяці мільйонаў дакументаў у векторную базу дадзенаў — гэта толькі палова работы. Першая частка гэтай тэмы была прысвечана працэсу вводу дадзенаў: прыстроям для керавання дакументамі, такім як Docling, Unstructured і Apache Tika, роздзеленню дакументаў на часткі з урахоўваннем ўзора, індэксаванню з вікорыстаннем методу MinHash, а таксама дызайну з двума рэзервуарамі, які дозволяе індэксавацыю рэальных файлаў без парадкі дадзенаў і занадто вялікіх вітатаў.

Другая частка прысвечана часу выканання запітаў. Мільйоны частак дакументаў не маюць ніякога значэння, калі ўзяцце дакументы заўсёды трэба 8 секунд, запіты зупіняюцца праз певныя коды каштоўкаў, апавяргаюцься конфідэнцыйныя даны заробкі, або ж запіты паддаюцца вплыву з боку файлаў з паўстарэлым фарматам. У наступным раздэле рассматрываецца механізм выкарыстоўвання запітаў, які дае адпаведзь за менш чым 200 мс, розумная сортавання запітаў, коордынацыя дзеяння розных прыстроў, стацыонарная ацэнка эфектывасці і полны скрыпт.

1. Багатаступенчаты механізм выкарыстоўвання запітаў

У стандартных інструкціях радзяць уключыць запыт корыстніка і адкрыць пошук сэроднечы на аснове top_k=5 у всій базе дадзеных. Пры дзесяці мільйонах дакументаў такой падход стварае два большыя проблемы.

  1. Інтэнсівныя эмбеддынгі добра фіксуюць абстрактны значэнне, але не справляюцца з точнымі ключовымі словамі, алфавітно-чыслаўскімі номерамі дзялянак і кодамі адказаў. Пошук Error 504 Gateway Timeout можа вернуць загальныя матэрыялы пра працяванне па часе, а не саму старонку з рашэнням проблемы.
  2. Оцэнка кожнага кандыдата за дапамою складнага модэлю рэлевантнасці ёсць занадта медленной для інтэрактыўнага выкарыстоўвання.

Таму сістэмы для прыменення ў рэальных умовах ставяцца да процэсу адналёгчэння кандыдатаў як да працэсу, які пашагоўна звяршчаеся:

[ 10,000,000 Total Document Chunks ]
                 │
                 ▼
     [ Step 1: SQL Pre-Filter ] ────────► Filter by Tenant / Dept / Role / Region
                 │
                 ▼
       [ ~50,000 Candidates ]
                 │
                 ▼
     [ Step 2: Hybrid Search ] ─────────► Dense Vectors (Qdrant) + Sparse BM25
                 │
                 ▼
        [ Top 100 Candidates ]
                 │
                 ▼
       [ Step 3: Cross-Encoder ] ───────► Cohere Rerank / BGE-Reranker
                 │
                 ▼
       [ Final Top 5 Chunks ] ──────────► Passed to LLM Context Window

Этап 1: прадзеянне на адносінах

Перш чым прыменяцца якія-небудзь высака-вымяроўваныя матэматычныя методы, фільтры метадаў па ролі карыстальніка, аддзелу чы статусу дакумента зменшаюць прастор пошуку. Скорачэнне колькасці элементаў з дзесяці мільйонаў да, можа, 50 000 скарочвае час адпаведзення і адночасна забезпечвае кантроль даступу:

from qdrant_client.models import Filter, FieldCondition, MatchValue

# Restrict search space by user session permissions before distance scoring
user_access_filter = Filter(
    must=[
        FieldCondition(key="department", match=MatchValue(value="Engineering")),
        FieldCondition(key="is_active", match=MatchValue(value=True))
    ]
)

Этап 2: гібрыдныя пошукі

Рэальныя пошукі бываюць двух відменнасцяў, і важна зрозумець аба яныя пры аналізе алгорытма ўзаўмнага спалучэння.

Жыццёздатны або семантычны пошук ператварае тэкст у эмбеддынгі ў векторнай базе дадзенаў, размешчаючы словы з аднаковым значэннем поблізу адзінаго ад другога. Ён добра справляецца з парафразамі, сінонімамі і фразамі, якія вжываюцца пад час размовы, хоць можа працягнуць точнае ключавае слова і прадаст дакументы, якія проста раздзеляюць аднаковую ідэю.

Рэдкісны або ключавы пошук, з алгорытмамі на кшталт BM25 або старэйшага TF-IDF, оцінюе тэрмін па тым, як часта ён з’являецца ў дакуменце у стацыянарныя паўстановкі да таго, насколькі ён рэдкісны ў всім корпусе. Ён надзеянна выловляе точныя словы, але не мае жадных ведамаў пра ўзначэнне.

Парадоксальным адаптаваннем ёсць запуск обох методаў і сумаванне ўсіх ўзятых у расчытанні балоў, і самэ гэта ёсць ўвёсам для методу Reciprocal Rank Fusion. RRF з’едынае кальколькі рангаваных спісоў у адны аб’еднаны спіс без патрэбы нормалізаваць первісныя балы. Дакумент, які знаходзіцца пад высокім рангам у обох спісах, паднімаецца на першае месца ў сумаваным спісе.

Этап 3: перарангаванне за дапамою крос-кодэра

Гібрыдны пошук быстра даўа або штоўцо кандыдатаў, але ўпорадкуванне іх выключна за дапамою вектарнай адстані можа ўсё ж прынесці нерэлевантныя рэзультаты. Два спосабы кодавання паяснююць гэтыя проблемы.

Бі-кодэр — гэта спосаб, які вікорыстоўваецца у звычайным вектарным пошуку. Дакументы ператвараюцца на вектары пад час ўваходжэння дадзеных, запіт корыстніка таксама стае вектарам у момент запіту, а простыя геаметрычныя метрыкі, такія як косінусная схожасць або скалярны добутак, выкорыстоўваюцца для ўпорадкування. Гэты спосаб ёсць шырока распространеным, але мае толькі сяроднюю точнасць.

Крос-энкодэр чытае запіт і дакумент адночасова і выдае рэйтынг па тое, насколькі дакумент ўзгодны з тым запітам. Ён ўсё разгледзае, таму і працуе медленна, адколі оцэнка мільйона дакументаў для аднаго пошуку займе гадзіны. Рашэння — сумаваць іх. Бі-энкодэр выбірае 100 найлепшых кандыдатаў, а крос-энкодэр пасля таго пераранжуе ўсё толькі іх у 10 найлепшых, што займае меней чым 100 мс.

2. Умовны рутэр запытаў

Большасць інтэрфейсаў чату — гэта простая палатаця для тэксту, таму корыстувальнікі можаць пісаць што завгодна. Паведамленні на кшталт "падсумаваць нашу паўтарную розмову" або "пераклаць рэзультаты на нямецкі" зовсім не трэбуюць пошуку дакументаў, а ўсунуць іх праз весь працэс займае час і грошы.

Умовны рутэр знаходзіцца на входзе як швырак класыфікатор, які вяршыць, куды пацягнуць кожнае паведамленне:

User Query
    │
    ▼
[ Intent Classifier / Router ]
    │
    ├── Simple Math / Logic ──────────► Direct Calculator / Python REPL
    ├── Conversational / Follow-up ───► Direct LLM Memory Context
    └── Domain Knowledge Request ─────► Full Hybrid RAG Pipeline

Канцэптуальна, маленькі запрос прасіць мадэль класіфікація запіту ў адну з кальколька можлівасцей, такіх як адзысканне інфармацыі з внутраніх дасягліваў або прымітная адпаведзь:

# Conceptual Router Pattern
def route_query(user_query: str) -> str:
    prompt = f"""Classify the user query into one of these routes:
    - RETRIEVE: Needs internal company documentation/database lookup.
    - COMPUTE: Pure math, calculation, or logic.
    - DIRECT: Conversational, greetings, or basic language rewrites.

    Query: {user_query}
    Classification:"""

    # Run a fast, lightweight classifier (or small local SLM)
    decision = fast_classifier(prompt).strip()
    return decision

3. За межамі адзінаго пошуку: оркестрацыя і адзынакоўка

Багатыя рэальныя запыткі выклікаюць патрэбу калькuluвання ў калькuluваннях. Узглянем прыклад запытку прыравняць показнікі прыбылі ў рэгіоне APAC за 2025 год з правіламі салідарнасці за 2024 год і выявіць разніцы. Ён дзеліцца на тры падзаданні: знаходзіць і выкарыстоўваць даны прыбылі ў рэгіоне APAC за 2025 год, атрымліваць правілы салідарнасці за 2024 год, а таксама шукаць суперсечанні между ўсімі гэтымі данымі.

Сістэмы, якія працуюць толькі адзін раз, тут не падходзяць, таму што яны намагаюцца адпаведзець на усё ў аднам запытку. Агентныя сістэмы RAG рашаюць гэтыя проблемы за дапамогою фрэймворкаў оркестрацыі, такіх як LangGraph чыра CrewAI, якія дзеліць задачу на скоордынаваныя спецыялістскія падзаданні. Планавальнік можа стварыць калькuluвання агентаў, кожны з якіх выкарыстоўвае той самы механізм атрымлівання інформацыі, каб збіраць сваю частку доказаў:

                  [ Orchestrator / Planner ]
                              │
            ┌─────────────────┴─────────────────┐
            ▼                                   ▼
    [ Researcher Agent ]                [ Compliance Agent ]
    • Retrieves 2025 Sales Data         • Retrieves 2024 Regulations
    • Extracts regional tables          • Parses policy constraints
            │                                   │
            └─────────────────┬─────────────────┘
                              ▼
                      [ Synthesis Agent ]
                      • Reconciles numbers
                      • Validates output consistency
                              │
                    Confidence Score Check
                       │             │
              [ Low Confidence ]     [ High Confidence ]
                       │                     │
                       ▼                     ▼
              Loop back & refine     Final Guardrail Validation

Самаактуалізацыя

Калі сістэма адзыскання вяртае фрагменты з нізкімі рэйтынгамі сэмплярства, або калі агент, які сінтазуе інфармацыю, з’являе прытвар, ён павінен рэагаваць. Ён адхоўчыць слабы тэкст, перапісвае запіт, расшырваючы скраценні чыстаўшы сінонімы, і зноў запускае адзысквальны механізм, у замест на тое, каб датыць вымышлены, універсальны адказ. Агенты керуюць і прымяюць рашэнні, але без моцнага прайсупутньага каналу RAG у іх няма нічога для работы.

4. Пастаяцельная ацэнка

Сістэма для практычнага выкарыстоўвання павінна адбываць стаячую ацэнку за дапамой автаматызаваных канструкцый, такіх як Ragas, TruLens чыстаўшы DeepEval. Яны беруць у ўвагу калькі параметраў:

┌──► Faithfulness (Is the answer grounded in the retrieved chunks?)
RAG Evaluation ─┼──► Answer Relevance (Did it actually answer the user's prompt?)
                ├──► Context Recall (Did retrieval find all necessary reference chunks?)
                └──► System Latency & Token Cost (Is it cost-effective at scale?)

Парада ўсіх метрыкаў кантролі, кроме тых, якія стосуюцяся аператыўных показнікаў, існуюць таксама паказнікі рэвалюцыі адпаведнасці службы, якія ацэнююць эфектывасць процэсу выкарыстоўвання інформацыі:

  • «Адпаведнасць» — гэта частка тэксту адпаведзі, якую падтрымвае выкарыстоўаны кантэкст; гэта служыць для пераканання, чы не выдумваецца інформацыя.
  • «Точнасць кантэкста» — гэта частка выкарыстоўваных фрагментаў сярод усіх тых, якія былі даны ў запыт, якія на самай працоўцы ёсць рэлевантнымі.
  • «Працоўнае запам’ятаванне кантэкста» — гэта перакананне, чы всі факты, неабходныя для адпаведзі на запыт, былі выкарыстоўаны.
  • «Затрымка» — гэта час, які прабіваецца ад початку да канца, а таксама на кожны ўзлок, уключаючы рутэры, фільтры, гібрыдны пошук, перэранкіраўчыя прыемы і процэс стварэння адпаведзі.

Кожны з гэтых паказнікаў патрабуе пэўнага цэльвог базовага значэння, каб было можна пераканацца ў адпаведнасці якосьці і працоўнасці.

5. Полны скрыпт выкарыстоўвання інформацыі і стварэння адпаведзі

Наведзены ў ніжыні скрыпт на Python абараняе аспект выканання. Ён выконвае прадзефлектараванне метаданых, запошчае вектары з Qdrant, складае кантэкст і запошчае локальны сервер LM Studio для аналізу:

from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue

# 1. Initialize Clients
# Pointing to local LM Studio running on port 8080
ai_client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="lm-studio")
qdrant_client = QdrantClient(url="http://localhost:6333")
COLLECTION_NAME = "enterprise_knowledge_base"
EMBEDDING_MODEL = "nomic-ai/nomic-embed-text-v1.5"
def retrieve_and_generate(user_query: str, user_department: str) -> str:
    print(f"\n🔍 Processing query: \"{user_query}\" for department: [{user_department}]")

    # 2. Vectorize the User Query
    query_resp = ai_client.embeddings.create(
        input=[user_query],
        model=EMBEDDING_MODEL
    )
    query_vector = query_resp.data[0].embedding
    # 3. Stage 1 & 2: SQL Pre-Filter + Vector Search
    # Filter by user department and active document status
    access_filter = Filter(
        must=[
            FieldCondition(key="department", match=MatchValue(value=user_department))
        ]
    )
    search_results = qdrant_client.search(
        collection_name=COLLECTION_NAME,
        query_vector=query_vector,
        query_filter=access_filter,
        limit=3
    )
    if not search_results:
        return "No relevant or authorized documents found."
    # 4. Context Assembly with Breadcrumbs
    context_blocks = []
    for hit in search_results:
        breadcrumb = hit.payload.get("breadcrumb", "General")
        text = hit.payload.get("text", "")
        context_blocks.append(f"[{breadcrumb}]\n{text}")
    full_context = "\n\n---\n\n".join(context_blocks)
    # 5. Generation via Local LLM
    system_prompt = (
        "You are an enterprise technical assistant. "
        "Answer the user query strictly using the provided context. "
        "If the context does not contain the answer, explicitly state that you do not know.\n\n"
        f"Context:\n{full_context}"
    )
    completion = ai_client.chat.completions.create(
        model="local-model",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_query}
        ],
        temperature=0.1
    )
    return completion.choices[0].message.content
# Example Execution
if __name__ == "__main__":
    response = retrieve_and_generate(
        user_query="How do I enable TLS 1.3 in config.yaml?",
        user_department="Engineering"
    )
    print("\n🤖 Final Answer:\n", response)

Форма архітэктуры вырабатчыка

Пераход з пратотыпа да сістэмы, якая обслужвае мільйоны корпаратыўных дакументаў, значыць трэба ставіцца да RAG як да інжынернай дысцыпліны на всіх етапах, а не як да адной простай вызову бібліятэкі. Чатыры прыкметнікі паўтараюць гэта:

  • Працаваць чыста. Структура важлівейшая за хітрасць, таму трэба вжываць спецыялізаваныя парсеры, зберагаць інфармацыю пра структуру і ухіліцца ад перыіндэксавання незмененых файлаў за дапамогою дэльта-хэшаў.
  • Фільтрацыя рана. Аспекты безпекі і рэляцыйныя метаданы трэба обрабоцваць у кроку SQL, які выконваецца раней за будзь-яяі дорогія вектарная вычысленні.
  • Спалучэнне стратэгій пошуку. Спаўніць ўжыце ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўжо ўж
  • Чаму RAG яшчэ дае некарэктныя адказы: гібрыдны пошук, перэранкіраванне і мэтрыка Recall@K — Чыстыя фрагменты тэксту не гарантуюць хорашых адказоў. Дазнаёмся, дзе паспелае аблікаванне вектараў, і як фюзыя, крос-кодэр для перэранкіравання, HyDE і мэтрыка Recall@K гэта выправляюць.