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 подбором модели, реальная проблема остается: агенту приходится угадывать, куда идти за фактом. Это увеличивает число запросов, плодит дубликаты контекста и ломает трассировку. В 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.

Read more

Computer-Use-Agenten scheitern an Pixeln: wie Sie auf stabile UI-Referenzen über Accessibility Trees umstellen und fehleranfällige Desktop-Automatisierung nicht mehr ständig reparieren

von Denis Shokhirev, Enterprise AI Architect aus Freiburg im Breisgau Ich bin Denis Shokhirev, Agentic AI Systems Architect in Freiburg im Breisgau, Germany, und bei DennisCraft AI Studio setze ich produktive KI-Systeme für DACH-B2B-Kunden auf Basis von Claude, Supabase, n8n, Doppler und selbst betriebenem Postgres um. Die teuersten Fehler in

By Denis Shokhirev

Компьютерные агенты ломаются на пикселях: как перейти на stable UI refs через accessibility tree и перестать чинить flaky desktop automation

Я Denis Shokhirev, Agentic AI Systems Architect из Freiburg im Breisgau, Germany, и в DennisCraft AI Studio я собираю production AI-системы для DACH B2B на Claude, Supabase, n8n, Doppler и self-hosted Postgres. Самая дорогая поломка в desktop automation, которую я вижу у клиентов, почти никогда не связана с моделью: агент

By Denis Shokhirev