Гибридный поиск: Совмещение RAG и длинного контекста

50 минут Урок 20

Введение: Дилемма «Окна» и «Базы»

Коллеги, добро пожаловать в четвертый модуль. До этого момента мы рассматривали RAG (Retrieval-Augmented Generation) и длинный контекст как две конкурирующие парадигмы. Обычно дискуссия строится так: «Что лучше: использовать векторную базу данных для поиска кусочков информации или просто загрузить всю книгу в промпт?».

С выходом Gemini 3 и моделей с контекстным окном в миллионы токенов этот вопрос стал неактуальным. Ответ — и то, и другое. Мы вступаем в эру Гибридного поиска.

Традиционный RAG (Chunking + Vector Search) страдает от проблемы «фрагментации». Когда вы разбиваете сложный документ на куски по 500 токенов, вы теряете глобальный нарратив и связи между удаленными частями текста. С другой стороны, «Грубая сила» (Brute Force Long Context), когда мы пытаемся запихнуть в модель гигабайты данных, сталкивается с проблемами latency (задержки) и стоимости.

В этом уроке мы научимся строить архитектуру, которая использует RAG для грубой фильтрации, а длинный контекст Gemini — для глубокого анализа и синтеза информации. Это «золотая середина» для Enterprise-решений.

Концепция: RAG как пре-фильтр для Long Context

Давайте переосмыслим роль RAG. В классической схеме мы ищем ответ или параграф с ответом. В гибридной схеме мы используем RAG, чтобы найти область интереса.

Сравнение подходов:

  • Классический RAG: Найти 5 чанков (кусков текста) по 512 токенов, наиболее похожих на запрос. Скормить модели ~2500 токенов.
    Проблема: Если ответ требует понимания всей главы, модель ошибется, так как видит только вырванные из контекста фразы.
  • Чистый Long Context: Загрузить 100 книг (10 млн токенов). Задать вопрос.
    Проблема: Дорого, долго, риск «галлюцинаций» из-за переизбытка шума.
  • Гибридный подход (Context RAG): Использовать векторный поиск, чтобы найти 5-10 наиболее релевантных документов целиком (не чанков!). Загрузить эти документы в контекстное окно (скажем, 200,000 токенов).
    Результат: Модель имеет полный контекст необходимых материалов, но не перегружена лишней информацией из других тем.

Gemini 3 идеально подходит для этого благодаря своей способности удерживать внимание на огромных массивах данных (needle-in-a-haystack) и высокой скорости обработки кэшированного контекста.

python
import google.generativeai as genai
import os

# Представим, что у нас есть эмуляция векторного поиска
# В реальности здесь был бы вызов к ChromaDB, Pinecone или pgvector

class MockVectorDB:
    def search(self, query, top_k=3):
        # Возвращает ID документов, а не просто чанки
        # Логика: RAG находит релевантные документы целиком
        print(f"Searching vector DB for: {query}...")
        return ["doc_id_101", "doc_id_204", "doc_id_333"]

class DocumentStore:
    def get_full_content(self, doc_ids):
        # Извлекаем ПОЛНЫЙ текст документов
        # Это ключевое отличие от обычного RAG
        docs = {
            "doc_id_101": "Полный текст технической спецификации А... (20k токенов)",
            "doc_id_204": "Полный текст протокола испытаний Б... (15k токенов)",
            "doc_id_333": "Полный текст инцидента В... (10k токенов)"
        }
        return [docs[id] for id in doc_ids if id in docs]

# Инициализация Gemini 3 (предполагаем наличие API ключа)
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-pro-latest') # или gemini-3-ultra

def hybrid_search_query(user_query):
    db = MockVectorDB()
    store = DocumentStore()
    
    # Шаг 1: Грубая фильтрация через RAG
    relevant_ids = db.search(user_query, top_k=3)
    
    # Шаг 2: Загрузка полного контекста
    full_docs = store.get_full_content(relevant_ids)
    
    # Объединяем контент. Для Gemini это "легкий вес" (всего ~45k токенов)
    combined_context = "\n\n---\n\n".join(full_docs)
    
    # Шаг 3: Генерация с глубоким пониманием
    prompt = f"""
    Ты - аналитик. Используй предоставленные ниже документы как ЕДИНСТВЕННЫЙ источник правды.
    Проанализируй их полностью, чтобы ответить на вопрос пользователя. 
    Обрати внимание на детали и связи между документами.
    
    КОНТЕКСТ:
    {combined_context}
    
    ВОПРОС: {user_query}
    """
    
    response = model.generate_content(prompt)
    return response.text

# Пример вызова
# print(hybrid_search_query("Какие риски описаны в спецификации А в контексте инцидента В?"))

Паттерн: Иерархическое сжатие (Hierarchical Summarization)

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

  1. Уровень 1 (Метаданные и Саммари): Вы создаете краткие выжимки (summary) для каждого документа и индексируете их векторами.
  2. Уровень 2 (Поиск): Когда пользователь задает вопрос, вы ищете по саммари. Это быстро и дешево.
  3. Уровень 3 (Развертывание): Найдя релевантные саммари, система подтягивает оригинальные полные документы, к которым эти саммари относятся.
  4. Уровень 4 (Инференс): Полные документы отправляются в Gemini.

Зачем это нужно? Векторный поиск по полному тексту часто дает сбои (шум, смешивание контекстов). Поиск по качественным саммари гораздо точнее определяет тему документа. А Gemini уже разбирается с деталями внутри полного текста.

Роль Context Caching

В Gemini API доступна функция Context Caching. Это game-changer для гибридного поиска. Если у вас есть набор «золотых стандартов» (например, Трудовой Кодекс, Внутренний Регламент, Брендбук), которые используются в 80% запросов, вы можете:

  1. Загрузить их в кэш Gemini (они становятся «горячей» памятью).
  2. Использовать RAG только для поиска переменных данных (свежие новости, тикеты пользователей за сегодня).
  3. Объединять кэшированный (статичный) и найденный (динамический) контекст в одном запросе.

Это снижает стоимость токенов ввода до 90% и значительно ускоряет ответ модели.

Упражнение

Спроектируйте функцию `smart_context_builder`. Она должна принимать запрос пользователя и список доступных категорий документов (например, ['tech_specs', 'hr_policies', 'meeting_notes']). <br><br>Логика:<br>1. Если запрос касается технических деталей, загружать ВСЕ документы из 'tech_specs' (предполагаем, что их объем влезает в окно).<br>2. Если запрос общий, использовать симуляцию векторного поиска для выбора топ-5 документов из всех категорий.<br>3. Возвращать итоговый промпт.

Оптимизация промптов для большого контекста

Когда вы подаете в Gemini 100,000+ токенов текста, структура промпта меняется. Вот ключевые правила для гибридного режима:

  • Инструкции в конце (Post-prompting): В моделях с длинным контекстом феномен Lost in the Middle (потеря середины) минимизирован, но эффект Recency Bias (внимание к последнему) сохраняется. Всегда дублируйте сам вопрос и инструкции ПОСЛЕ блока с контекстом.
  • Явная разметка (Delimiters): Используйте XML-теги. Gemini отлично понимает структуру вроде:
    <documents> ... </documents>
    <user_query> ... </user_query>. Это помогает модели отделить данные от инструкций.
  • Chain-of-Thought (CoT): При работе с большим объемом данных требуйте от модели сначала «найти релевантные цитаты», а потом «сформулировать ответ». Это заставляет модель физически пройтись по загруженному контексту.

Пример эффективной структуры:

Вы - эксперт по финансовому анализу.


[Здесь 50 страниц отчетов, найденных через RAG]


Твоя задача: Сравнить показатели Q3 и Q4.
Инструкция:
1. Выпиши все цифры прибыли за Q3 из документов.
2. Выпиши цифры за Q4.
3. Сделай таблицу сравнения.

Вопрос пользователя: Как изменилась прибыль во второй половине года?

Вопрос

В каком случае использование Гибридного подхода (RAG фильтр + Long Context) НАИБОЛЕЕ оправдано по сравнению с обычным RAG (Retrieval of Chunks)?