О себе Портфолио Кейсы Услуги Блог Контакт 🎙 Поговорить с AI
EN DE RU
🎙 Поговорить с AI
October 26, 2026 · 5 min read

RAG в проде тормозит из-за зоопарка индексов: как закрыть код, логи, PDF и CSV одним локальным MCP-поиском

Я — Denis Shokhirev, Agentic AI Systems Architect из Freiburg im Breisgau, Germany; в DennisCraft AI Studio я собираю production AI-системы на Claude, Supabase, n8n, Doppler и self-hosted Postgres. Самая частая причина, почему RAG в проде отвечает медленно и нестабильно, у меня не в модели и не в embeddings, а в том, что данные распиханы по 4–6 отдельным индексам: репозиторий кода ищется одним способом, логи другим, PDF третьим, CSV вообще через временный ETL. Пока команда лечит latency подборо

Denis Shokhirev
Denis Shokhirev
Agentic AI Systems Architect
Telegram LinkedIn

Я — Denis Shokhirev, Agentic AI Systems Architect из Freiburg im Breisgau, Germany; в DennisCraft AI Studio я собираю production AI-системы на Claude, Supabase, n8n, Doppler и self-hosted Postgres. Самая частая причина, почему RAG в проде отвечает медленно и нестабильно, у меня не в модели и не в embeddings, а в том, что данные распиханы по 4–6 отдельным индексам: репозиторий кода ищется одним способом, логи другим, PDF третьим, CSV вообще через временный ETL.

Пока команда лечит latency подбором модели, реальная проблема остается: агенту приходится угадывать, куда идти за фактом. Это увеличивает число запросов, плодит дубликаты контекста и ломает трассировку. В regulated среде это еще и аудитная проблема: трудно объяснить, почему один и тот же вопрос вчера ходил в vector store, сегодня в SQL, а завтра в ad hoc parser на воркере.

Почему index sprawl ломает production RAG

В демо отдельные индексы выглядят логично. У кода одна схема chunking, у PDF другая, у CSV третья. Но в проде вопрос пользователя редко уважает эти границы. Он спрашивает: «Почему агент отправил не тот invoice?» Ответ почти всегда живет сразу в 3 местах: код бизнес-правила, лог выполнения и табличные данные.

Что я вижу на реальных внедрениях

Обычно появляются 4 симптома. Первый — растет latency, потому что оркестратор дергает несколько retrieval-путей последовательно. Второй — качество ответа скачет: разные индексы ранжируют документы по разным правилам. Третий — растет стоимость поддержки: у каждого коннектора свой ingestion, свой retry и свои схемы обновления. Четвертый — наблюдаемость почти исчезает, потому что retrieval-путь становится ветвящимся.

Anthropic в документации по tool use рекомендует давать модели четкие инструменты с понятными контрактами, а не размытые возможности поиска по разным источникам: docs.anthropic.com, 2024. Это хорошо совпадает с моим опытом: если агент видит один локальный search tool с единым интерфейсом, поведение стабильнее, чем при выборе между 5 почти одинаковыми retriever-инструментами.

ПодходЧто происходит в демоЧто ломается в продеЧто я делаю вместо этого
Отдельный vector index на каждый тип данныхБыстрый стартМного роутинга и рассинхрон обновленийЕдиный MCP search tool поверх локальных адаптеров
SQL только для таблицХорошо для CSVКод и PDF выпадают из одного ответаНормализую метаданные и общий контракт поиска
Парсер PDF как отдельный сервисРаботает на тестахСложно аудировать путь ответаЛокальный вызов через один сервер инструментов

Один локальный MCP search layer вместо зоопарка

Под MCP-поиском я имею в виду не новый бренд и не еще один индекс, а паттерн: один локальный сервер инструментов, который отдает модели единый search API, а внутри сам решает, как искать по коду, логам, PDF и CSV. Модель не знает о внутреннем зоопарке. Для нее есть один инструмент: search(query, source_types, limit).

Почему локально? Потому что в DACH B2B я часто работаю с данными, которые нельзя гонять через лишние внешние прослойки. Локальный MCP-сервер рядом с Postgres, файловым storage и логами дает меньше сетевой болтовни и предсказуемее аудит.

Минимальный контракт инструмента

from typing import List, Optional
from pydantic import BaseModel

class SearchRequest(BaseModel):
    query: str
    source_types: Optional[List[str]] = None
    limit: int = 8

class SearchHit(BaseModel):
    source_type: str
    uri: str
    title: str
    snippet: str
    score: float

def search_local(req: SearchRequest) -> List[SearchHit]:
    filters = req.source_types or ["code", "logs", "pdf", "csv"]
    hits = []
    hits.extend(search_code(req.query, req.limit) if "code" in filters else [])
    hits.extend(search_logs(req.query, req.limit) if "logs" in filters else [])
    hits.extend(search_pdf(req.query, req.limit) if "pdf" in filters else [])
    hits.extend(search_csv(req.query, req.limit) if "csv" in filters else [])
    return sorted(hits, key=lambda x: x.score, reverse=True)[:req.limit]

Ключевое здесь не Python, а единый выходной формат. Если лог, кусок кода и строка CSV приходят в одном типе ответа с метаданными, следующий шаг агента проще: он сравнивает факты, а не пытается привести 4 разных схемы к общему виду на лету.

Как закрыть 4 источника без лишней магии

Код

Для кода мне не нужен отдельный «AI-индекс» на старте. Достаточно пройтись по репозиторию, вытащить функции, классы, README и конфиги, сохранить чанки и метаданные в Postgres. Перед подачей в RAG я почти всегда прогоняю semgrep, bandit и gitleaks, чтобы не утащить в контекст секреты и опасные паттерны. OWASP в LLM Top 10 2025 отдельно указывает prompt injection и sensitive information disclosure как реальные риски для retrieval-цепочек: genai.owasp.org.

Логи

Логи часто дают самый точный ответ, но только если их нормально нормализовать. Я храню timestamp, service, correlation_id, severity и message как обязательные поля. Тогда агент может искать не просто по тексту, а по комбинации текста и исполнения конкретного workflow. Для n8n это особенно полезно, когда надо связать шаг агента, SQL-вставку и ошибку внешнего API.

PDF

PDF не надо романтизировать. В проде это обычно договор, спецификация, счет или policy. Я извлекаю текст, сохраняю page number, section heading и checksum файла. Если документ обновился, старые чанки инвалидируются по checksum, а не по имени файла. Это убирает типичную ошибку, когда агент цитирует прошлую версию SLA.

CSV

CSV лучше воспринимать как табличный факт-слой, а не как plain text. Я храню имя файла, строку, колонки и легкий текстовый рендер строки. Тогда один и тот же поиск может вернуть как точную строку таблицы, так и документ, который ее объясняет.

python ingest_code.py --repo /srv/app --out postgres
python ingest_logs.py --input /var/log/app.jsonl --out postgres
python ingest_pdf.py --input /srv/docs --out postgres
python ingest_csv.py --input /srv/data --out postgres

psql "$DATABASE_URL" -c "create index if not exists idx_docs_type on docs(source_type);"
psql "$DATABASE_URL" -c "create index if not exists idx_docs_uri on docs(uri);"
psql "$DATABASE_URL" -c "analyze docs;"

Где здесь реальный выигрыш

Первый выигрыш — latency. Вместо того чтобы агент сам выбирал между 4 инструментами и делал несколько раундов tool use, он вызывает один поиск и получает уже объединенный top-k. Второй — предсказуемость. Если поиск централизован, вы можете логировать запрос, фильтры, hits и дальнейший ответ модели в одной трассе. Третий — управление доступом. Проще один раз ограничить локальный search tool по путям, таблицам и типам документов, чем держать одинаковые правила в нескольких retrieval-сервисах.

OpenAI Cookbook в материалах по retrieval и tool usage в 2024 последовательно показывает ценность простых интерфейсов между моделью и инструментами: cookbook.openai.com. В production это ощущается еще сильнее: чем меньше у модели вариантов «куда сходить», тем меньше лишних токенов и ложных маневров.

tools:
  - name: local_search
    description: Search code, logs, pdf and csv from local sources
    input_schema:
      type: object
      properties:
        query: { type: string }
        source_types:
          type: array
          items: { type: string }
        limit: { type: integer, default: 8 }
      required: [query]

Что не делать

Не начинайте с 4 vector database подряд, если у вас еще нет стабильного контракта поиска. Не заставляйте модель выбирать между code_search, pdf_search, csv_search и logs_search без крайней причины. Не храните метаданные в одном месте, а сырой контент в другом без общего идентификатора. И не тащите секреты из репозитория в retrieval: gitleaks на ingestion стоит дешевле, чем разбор инцидента.

FAQ

MCP обязателен, чтобы сделать такой слой?

Нет. Сначала важен единый контракт инструмента и локальная реализация. MCP удобен как стандартный способ дать этот инструмент агенту, но сам паттерн работает и без него.

Нужен ли vector search для всех типов данных?

Нет. Для логов и CSV часто хватает keyword + metadata filtering. Я добавляю embeddings там, где это реально поднимает recall, а не потому что это модно.

Можно ли все хранить в Postgres?

Да, на многих B2B-нагрузках этого достаточно. Если schema и индексы сделаны аккуратно, Postgres закрывает ingestion, метаданные, аудит и часть retrieval без лишних сервисов.

Как это связано с безопасностью?

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

Когда отдельные индексы все-таки оправданы?

Когда источники очень разные по нагрузке, retention и правам доступа. Но даже тогда я стараюсь оставить один внешний search contract для модели и спрятать сложность внутри.

Где у вас сейчас теряется больше времени в production RAG: на раздельном retrieval по источникам, на нормализации метаданных или на объяснимости ответа для аудита? Я правда хочу это понять на реальных стеках, а не на презентациях.

I run a free 30-min stack audit for DACH founders building AI in regulated markets. DM me on LinkedIn or write to @ger_dennis_ai.

Читать дальше
Один агент упёрся в лимит, второй потерял контекст: как собрать команду Claude Code + Codex без ручных перезапусков
Всегда-on агенты уже здесь: как не превратить Claude Duet в источник утечек, ложных действий и хаоса в доступах
LiteLLM снова скомпрометировали: как не потерять SSH-ключи и API-секреты через AI gateway в проде
Если вашему AI-агенту нужен веб-доступ, MCP уже стал новым стандартом: что реально решает Firecrawl MCP в проде
Все статьи →
Где это применяется
Услуги — что мы делаем
Поговорить с голосовым агентом
Кейсы
Готовы к следующему шагу?

Превратить процесс в систему, которая работает

Продакшн-качество, а не демо.

Обсудить проект → ← Все статьи