Главная / Статьи / Практические советы: масштабирование RAG до 10 миллионов документов. Часть 2: оптимизация

Практические советы: масштабирование RAG до 10 миллионов документов. Часть 2: оптимизация

Пошаговое руководство по практическим рекомендациям: масштабирование RAG до 10 миллионов документов. Часть 2: оптимизация — контракты, проверки и слоты для вставки кода для команд, использующих эту схему.

1904 слов

Используйте это как переработанную версию идей из статьи «Масштабирование RAG до 10 миллионов документов, часть 2: Оптимизация поиска и генерации», ориентированную на операторов: четкие этапы, упорядоченные блоки кода и записи для восстановления, сохраняющиеся при передаче задачи. Этап обзора работает наилучшим образом, если рассматривать его как измеримую основу. Соберите один эталонный пример вывода, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи.

1. Многоэтапный фильтр поиска

Для первого этапа многоэтапной системы поиска необходимо заранее определить входные данные, ответственного за выполнение этапа и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность повторно запустить этап, исходя из известной точки контроля, без необходимости угадывать скрытое состояние. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение стоимости на ранней стадии предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

[ 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: Реляционная предфильтрация (строгие ограничения)

Для этапа предфильтрации в реляционной модели первой стадии необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Указывайте те участки текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

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: Гибридный поиск (слияние плотных и разреженных данных)

Для этапа гибридного поиска второй стадии необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующими улучшениями. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации. Для этапа гибридного поиска второй стадии необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Укажите названия элементов, определите критерии успеха и не допускайте молчаливого частичного выполнения задачи.

Этап 3: Переупорядочивание с использованием Cross-Encoder

При работе над этапом переупорядочивания с использованием Cross-Encoder на третьем этапе сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает избегать ошибок при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость обработки токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Перед настройкой подсказок измерьте показатель воспроизводимости на фиксированном наборе вопросов. Частая смена подсказок редко помогает улучшить качество поиска.

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. За пределами простого RAG: оркестрация множества агентов и циклы обратной связи

При работе над третьим этапом «Beyond simple RAG» сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте как успешный, так и восстановительный пути работы системы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Измеряйте уровень воспроизведения информации на фиксированном наборе вопросов перед настройкой подсказок — частая смена подсказок редко помогает улучшить качество поиска. При работе над третьим этапом «Beyond simple RAG» сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как договор между входными данными и проверенными результатами. Дайте названия всем элементам, определите критерии успеха и не допускайте безответственного частичного выполнения задач.

                  [ 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

Самокоррекция и циклы обратной связи

Этап циклов обратной связи для самокоррекции работает наилучшим образом, если рассматриваться как измеримая поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Разделяйте политику разбиения данных на части и политику их поиска. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

4. Непрерывная оценка

Этап непрерывной оценки №4 работает наилучшим образом, если рассматривать его как измеримую величину. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

┌──► 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?)

6. Практическое применение от начала до конца: полный цикл извлечения и генерации данных

На этом этапе «6 End-to-End Hands-On» лучше всего работает, когда он рассматривается как измеримая поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Документируйте как успешный путь выполнения, так и путь восстановления одновременно. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не последующими доработками. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества. На этом этапе «6 End-to-End Hands-On» лучше всего работает, когда он рассматривается как измеримая поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи.

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 в производстве

В заключение на этапе создания RAG необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

Чек-лист операций

При работе над чек-листом операций сначала запишите условия работы: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист обеспечивает прозрачность последующих изменений в коде.

Лучше использовать небольшие, тестируемые единицы вместо обширных скриптов. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций.

Оценивайте уровень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить слабую систему поиска информации.

Заморозьте эталонный набор данных до изменения подсказок или моделей. Изменение как системы, так и критериев оценки маскирует возможные ухудшения.

При наличии бюджета добавляйте тесты на работоспособность критических участков в процессе интеграционного тестирования с использованием фикстур, а не реальных платных API.

Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код.

Перед внедрением данной стек-технологии необходимо заморозить версии, сгенерировать эталонный отчет для критической цепочки операций и уточнить шаги возврата к предыдущему состоянию. В совместных средах требуются ограничения на частоту запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, даже если она кажется менее привлекательной, чем красивые одноразовые демонстрации.

Примечание для записи c42ae29c43bc: не храните ключи поставщика в репозитории, установите лимит токенов на одну сессию и сохраняйте отчеты рядом с фиксами для оценки, чтобы последующие замены моделей оставались сопоставимыми.