English translation is not available yet. Showing Russian content.

Реализовать graceful degradation при отказе vector DB

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать graceful degradation при отказе vector DB

1. Цель задачи

Разработать механизм graceful degradation для RAG-агента, который при недоступности векторной базы данных (vector DB) продолжает отвечать пользователю, последовательно используя fallback: сначала — кэш ранее найденных документов, затем — вызов LLM без контекста (zero-shot). Это обеспечивает отказоустойчивость: система не падает с ошибкой, а деградирует с контролируемой потерей качества.

Ключевой результат Рабочий код fallback-цепочек (кэш → без контекста) с тестами, демонстрирующими, что система отвечает при отказе vector DB, а качество ответов падает не более чем на 30% по метрике ответа (например, по оценке LLM-as-judge).

2. Исходные данные

Перед началом необходимо иметь:

Что нужноОткуда взять
Рабочая RAG-система (минимальный прототип)Пет-проект или пример из документации LangChain/LlamaIndex
Vector DB (Qdrant / FAISS / Pinecone)Docker-образ Qdrant (qdrant/qdrant), либо FAISS локально
Кэш (Redis)Docker-образ redis:7-alpine
LLM API (OpenAI / Anthropic / local через Ollama)API-ключ или модель через Ollama (llama3)
Python 3.11+ и менеджер пакетовУстановлены

Если нет реального инструмента — симулируем:

  1. Эмуляция отказа vector DB
    • Оборачиваем вызов vector DB в функцию, которая может бросать исключение VectorDBUnavailable по сигналу (env var SIMULATE_VECTOR_DB_FAILURE=true).
    • Имитируем timeout (например, sleep(30) и сбой) или HTTP 503.
  2. Эмуляция кэша
    • Если Redis недоступен, используем in-memory dict с тем же интерфейсом (для локальных тестов).
  3. Synthetic тестовый набор
    • 10–20 вопросов с известными ответами (ground truth) для оценки деградации.

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.11+Реализация логики
Vector DBQdrant (Docker) / FAISSХранение и поиск эмбеддингов
КэшRedis 7 (Docker) / dictХранение результатов предыдущих retrieval
LLMOpenAI API / Ollama (llama3)Генерация ответа (с контекстом и без)
ФреймворкFastAPI / LangChainПостроение endpoint и цепочек
КонтейнеризацияDocker, docker-composeПоднятие vector DB и Redis
Тестированиеpytest + pytest-asyncioЮнит- и интеграционные тесты
Мониторинг (опционально)Prometheus + GrafanaМетрики отказов (fallback_count)

4. Этапы выполнения

Этап 1: Подготовка окружения и базовая RAG-система (1–1.5 часа)

Действия

  1. Создать проект с структурой папок:
graceful-degradation-lab/
├── docker-compose.yml
├── src/
│   ├── __init__.py
│   ├── vector_store.py
│   ├── cache.py
│   ├── llm_client.py
│   ├── rag_pipeline.py
│   └── fallback_manager.py
├── tests/
│   ├── __init__.py
│   ├── test_fallback.py
│   └── conftest.py
├── data/
│   └── sample_docs.txt
├── requirements.txt
└── .env
  1. Запустить vector DB и Redis через docker-compose:
version: '3.8'
services:
  qdrant:
    image: qdrant/qdrant
    ports:
      - "6333:6333"
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  1. Реализовать минимальный класс VectorStore (на примере Qdrant):

    • Инициализация клиента (через grpc или rest).
    • Метод search(query_embedding, top_k=5) -> List[Document].
    • Добавить инъекцию сбоя: при SIMULATE_VECTOR_DB_FAILURE=true бросать VectorDBUnavailable.
  2. Реализовать класс LLMClient:

    • Метод generate(prompt: str) -> str (с контекстом = RAG, без контекста = просто вопрос).
  3. Реализовать базовую RAG pipeline:

    • embed_query → vector_store.search → format_context → llm.generate.

Ожидаемый результат этапа Докер-контейнеры запущены, базовая RAG даёт ответы на тестовые вопросы. Есть простой endpoint /ask (FastAPI) или CLI-команда.

Этап 2: Реализация кэша на Redis (45–60 минут)

Действия

  1. Реализовать класс Cache:

    • Интерфейс: get(key: str) -> Optional[Document] и set(key: str, doc: Document, ttl: int = 3600).
    • Использовать Redis (через redis.asyncio).
    • Ключ = хеш вопроса (или embedding вопроса), значение = сериализованный JSON с текстом документа и метаданными.
  2. Интегрировать кэш в RAG pipeline:

    • Перед вызовом vector_store.search проверить кэш по вопросу (или его embedding).
    • Если найден – сразу вернуть документ (увеличить cache_hit).
    • Если нет – после успешного поиска в vector DB сохранить результат в кэш (для будущих запросов).
  3. Добавить тайм-ауты и обработку ошибок Redis:

    • Если Redis недоступен, пропустить кэш (логировать warning).

Ожидаемый результат этапа При повторном вопросе ответ берётся из кэша (значительное снижение latency). Кэш работает при отказе vector DB (если документ уже был закэширован ранее).

Этап 3: Реализация graceful degradation (fallback chain) (1–1.5 часа)

Действия

  1. Создать класс FallbackManager:
class FallbackManager:
    def __init__(self, vector_store, cache, llm):
        self.vector_store = vector_store
        self.cache = cache
        self.llm = llm

    async def answer(self, query: str) -> dict:
        # Этап 1: попробовать векторную БД
        try:
            docs = await self.vector_store.search(query)
            if docs:
                context = self._format_context(docs)
                answer = await self.llm.generate(context + query)
                return {"source": "vector_db", "answer": answer, "quality_score": 0.9}
        except VectorDBUnavailable:
            pass

        # Этап 2: fallback на кэш
        try:
            cached_docs = await self.cache.get(query)
            if cached_docs:
                context = self._format_context(cached_docs)
                answer = await self.llm.generate(context + query)
                return {"source": "cache", "answer": answer, "quality_score": 0.7}
        except CacheUnavailable:
            pass

        # Этап 3: fallback на LLM без контекста
        answer = await self.llm.generate(query)  # zero-shot
        return {"source": "zero_shot_llm", "answer": answer, "quality_score": 0.5}
  1. Логировать каждый fallback-шаг (через logging или метрики Prometheus):

    • Счётчик fallback_vector_db_to_cache.
    • Счётчик fallback_cache_to_zero_shot.
  2. Настроить тайм-ауты для каждого шага:

    • Например, ждать векторную БД не более 5 секунд (настраиваемо), иначе считать её недоступной.
  3. Обработка ошибок на каждом уровне:

    • Если все fallback упали – вернуть сообщение об ошибке (но это крайний случай, задача этого избежать).

Ожидаемый результат этапа При симуляции отказа vector DB система сначала пробует кэш, при провале кэша – LLM без контекста. Ответ всегда есть.

Этап 4: Тестирование и симуляция отказа (1–1.5 часа)

Действия

  1. Написать интеграционные тесты (pytest):

    • Тест 1: Нормальная работа vector DB (базовый сценарий) – ответ из vector DB.
    • Тест 2: Отказ vector DB, кэш пуст – ответ из LLM без контекста.
    • Тест 3: Отказ vector DB, кэш содержит документ – ответ из кэша.
    • Тест 4: Отказ vector DB, отказ Redis (кэша) – ответ из LLM без контекста.
  2. Реализовать фикстуры для симуляции отказов:

@pytest.fixture
def vector_db_failure(monkeypatch):
    monkeypatch.setenv("SIMULATE_VECTOR_DB_FAILURE", "true")
    yield
    monkeypatch.delenv("SIMULATE_VECTOR_DB_FAILURE", raising=False)
  1. Измерить качество ответов:

    • Подготовить 10 вопросов с golden answer.
    • Для каждого уровня fallback оценить ответ с помощью LLM-as-judge (например, попросить GPT-4 оценить по шкале 1-5).
    • Убедиться, что средний score:
      • vector_db > 4.0
      • cache > 3.5
      • zero_shot > 3.0
  2. Проверить метрики:

    • Убедиться, что при отказе vector DB не возникает исключений, возвращающих 500.

Ожидаемый результат этапа Все тесты проходят. Система отказоустойчива.

Этап 5: Документация и финальная проверка (30 минут)

Действия

  1. Написать README.md:

    • Описание архитектуры fallback chain.
    • Инструкция по запуску, симуляции отказов.
    • Примеры вывода каждого уровня.
  2. Проверить, что код соответствует PEP8 (прогнать flake8 / black).

  3. Запустить полный цикл

    • docker-compose up,
    • SIMULATE_VECTOR_DB_FAILURE=true python main.py "вопрос"
    • Убедиться в работе.

Ожидаемый результат этапа Готовый репозиторий с чистой документацией.

5. Критерии приемки (Definition of Done)

  • Реализован класс FallbackManager с цепочкой: vector DB → cache → zero-shot LLM.
  • При отказе vector DB (симуляция) ответ всегда возвращается (источник: cache или zero-shot).
  • При отказе vector DB и пустом кэше ответ приходит от LLM без контекста (zero-shot).
  • Кэш на Redis корректно сохраняет и возвращает документы (с TTL).
  • Написаны минимум 4 интеграционных теста (pytest), покрывающих основные сценарии отказов.
  • Все тесты проходят без ошибок.
  • Добавлены метрики/логи, позволяющие отслеживать количество fallback-срабатываний.
  • README содержит описание архитектуры и инструкцию по симуляции отказов.
  • Код соответствует PEP8, проект структурирован.

6. Ожидаемый результат

Основной артефакт Репозиторий на GitHub (или zip-архив) с кодом, тестами и документацией.

Содержание

  • src/rag_pipeline.py — основная RAG логика.
  • src/fallback_manager.py — механизм graceful degradation.
  • tests/test_fallback.py — интеграционные тесты.
  • docker-compose.yml — окружение (Qdrant, Redis).
  • README.md — документация.

Опционально

  • Дашборд Grafana с метриками fallback (через Prometheus).
  • Скрипт для оценки качества (LLM-as-judge) и сравнения трёх уровней.

7. Возможные сложности и их решение

СложностьРешение
Трудно симулировать отказ vector DB в тестахИспользовать unittest.mock или env-флаг SIMULATE_VECTOR_DB_FAILURE; проверять в рантайме.
Redis может быть недоступен в CIВ тестах использовать redis-mock или in-memory dict, если Redis нет.
Оценка качества ответов субъективнаИспользовать LLM-as-judge с чёткой шкалой (1-5) и одним и тем же промптом для всех вариантов.
Время ожидания vector DB (timeout) может привести к ложно-положительному fallbackНастроить разумный тайм-аут (например, 3-5 секунд) и добавить retry (1-2 попытки) перед fallback.
Кэш может содержать устаревшие документыИспользовать TTL, инвалидацию по версии данных.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка окружения и базовая RAG1–1.5 часа
Этап 2: Реализация кэша на Redis45–60 минут
Этап 3: Реализация graceful degradation1–1.5 часа
Этап 4: Тестирование и симуляция отказа1–1.5 часа
Этап 5: Документация и финальная проверка30 минут
Итого4–5 часов

Примечание: Для первого раза возможно увеличение до 6 часов (из-за отладки docker-compose и Redis).

9. Связанные вопросы из базы знаний

ВопросТема
23Кэширование результатов инференса LLM
47Обработка ошибок в RAG pipeline
82Graceful degradation микросервисов
134Интерфейсы vector store и их альтернативы
211Метрики качества ответов LLM (LLM-as-judge)
278Circuit breaker pattern в AI-агентах
315Использование Redis для кэша в AI-системах
451Timeout и retry стратегии при работе с LLM API
567Симуляция отказов внешних сервисов в тестах
689Zero-shot и few-shot промпты: плюсы и минусы

10. Чек-лист самопроверки

  • Я последовательно реализовал fallback chain, начиная от vector DB, затем кэш, затем zero-shot.
  • Я проверил, что при симуляции отказа vector DB (установка env-переменной) система не падает с 500, а возвращает ответ из fallback источника.
  • Я написал минимум один тест, который проверяет сценарий, когда vector DB недоступна, кэш пуст – должен использоваться zero-shot LLM.
  • Я убедился, что время отклика при fallback на zero-shot не превышает reasonable limit (например, 10 секунд с учётом LLM).
  • Я зафиксировал метрики (через логи или Prometheus), чтобы в production было видно, как часто срабатывают fallback.