← Лендинг Hermes Agent

Опубликовано

Что не пишут в статьях типа RAG за 5 минут. Часть вторая

Автор: Гусев Николай [портфолио]

Дата обновления не указана в исходнике.

Темы: rag, prompting

Что не пишут в статьях типа "RAG за 5 минут". Часть вторая

В первой части описан один RAG: CRUD-индексация, адаптивные пороги, CRAG с веб-фоллбэком, content-hash дедупликация, отдельная коллекция сессий. 127 тестов. Корпус - 2000 документов, 750 сессий.

Но это была одна из двух систем. Вторая работала параллельно - на другом сервере, с другой архитектурой.

Два RAG, два подхода

На HQ: 92 тысячи чанков из 27 тысяч markdown-файлов. Три источника - wiki, почта (IMAP), Telegram-история (state.db). Автоиндексация каждые 30 минут через cron с flock. Плагин с pre_llm_call hook - RAG контекст встраивается в каждый ответ LLM автоматически. Восемь категорий чанков с фильтрацией на уровне базы: email, tg, concept, entity, raw, verified, llm-wiki, wiki.

На Autolycus: 2.5 тысячи чанков. CRUD-индексация (delete-before-upsert), content-based дедупликация, детерминистичные ID. Отдельная коллекция сессий (906 чанков). Source boost для приоритизации источников. Конфигурация через переменные окружения.

Оба работали независимо. Каждый обслуживал своего агента.

Сравнение

Сильные стороны HQ:

  • Категоризация: 8 категорий, фильтрация на уровне ChromaDB. На Autolycus категорий нет.

  • Автоиндексация: 3 cron-задачи с flock. Новые данные в поиске через 15 минут. На Autolycus - ручная индексация.

  • Плагин: pre_llm_call hook, автоматическое обогащение каждого ответа LLM. На Autolycus - только CLI-скрипты.

  • Объём: 92K чанков из 27K файлов.

Сильные стороны Autolycus:

  • CRUD-индексация: delete-before-upsert, cleanup удалённых файлов, детерминистичные ID (MD5 от source+heading+seq). На HQ - полная переиндексация при изменениях, 650+ ошибок при сбое.

  • Content-based dedup: content_hash на каждом чанке. На HQ - дубликаты при каждой переиндексации.

  • Sessions collection: отдельная коллекция (906 чанков), свой embedding prefix. На HQ - всё в одной коллекции.

  • Source boost: concepts/ +0.05, adr/ +0.03, manuals/ +0.03. На HQ - все чанки равны.

  • Конфигурация: через env vars. На HQ - хардкод.

Модели

На Autolycus - LM Studio с тремя моделями:

  • e5-large-instruct - эмбеддинги (1024d, cosine similarity)

  • Qwen 2.5 7B - grading, reranking, query rewriting

  • Gemma 4B - классификация запросов, fallback при неуверенности основного классификатора

Основной классификатор на обоих узлах - TF-IDF+SVM (96.4% accuracy, <1ms). Gemma подключается при confidence < 0.9.

Всё локальное.

Структурное ограничение

При проектировании объединения проведён Prism-анализ. Результат: автономность × полнота × простота ≤ 2. Три свойства одновременно получить нельзя.

  • Автономность + Полнота = сложность (SSH тоннель, синхронизация, мониторинг)

  • Полнота + Простота = рассинхронизация (категории расходятся, схемы несовместимы)

  • Автономность + Простота = потеря полноты (каждый узел изолирован)

Выбраны: Автономность + Полнота. Сложность принята как данность.

Premortem

Проведён премортем на горизонте один месяц. Восемь причин провала, четыре критические:

  • SSH тоннель падает → autossh + systemd + health check каждые 5 минут

  • Плагин конфликтует → тестирование на тестовом инстансе до установки

  • Категории расходятся → единый config.yaml, git-синхронизация

  • Self-RAG threshold не откалиброван → калибровка на реальных запросах по F1-score

ADR обновлён. Чеклист: 9 пунктов. Мониторинг - обязателен до запуска.

Результат унификации

Из Autolycus в HQ: CRUD-индексация, content dedup, детерминистичные ID, source boost, sessions collection, env vars.

Из HQ в Autolycus: категоризация, автоиндексация, плагин.

Каждый узел остался автономным. Данные не перемещаются. Поиск маршрутизируется: эвристика + fallback на оба узла. Происхождение фактов сохраняется - метка узла в каждом чанке.

Итоговая архитектура

Компоненты

Хранилище: ChromaDB, коллекции wiki и sessions. Пространство cosine. Каждый чанк содержит метаданные: source, heading, category, content_hash, node.

Эмбеддинги: e5-large-instruct (1024d). Локально через LM Studio.

Классификатор запросов: TF-IDF+SVM (основной), Gemma 4B (fallback при confidence < 0.9).

Grading и reranking: Qwen 2.5 7B. Batch-оценка релевантности чанков.

Индексация: CRUD (delete-before-upsert), content-hash дедупликация, cleanup удалённых файлов. Детерминистичные ID (MD5 от source+heading+seq).

Категории: email, tg, concept, entity, raw, verified, llm-wiki, wiki, session. Авторасширение из путей файлов с валидацией.

Маршрутизация: эвристика по ключевым словам + fallback на оба узла.

Self-RAG: адаптивные пороги (factual 0.75, analytical 0.65, synthesis 0.60). Детектор шума. CRAG fallback на DuckDuckGo.

Мониторинг: JSONL-логи, 4 алерта (avg_relevance, elapsed_seconds, chunks_found, SSH tunnel).

Варианты развёртывания

Вариант A: Полный (GPU + LM Studio)

  • e5-large-instruct - эмбеддинги

  • Qwen 2.5 7B - grading, reranking

  • Gemma 4B - классификация

  • TF-IDF+SVM - основной классификатор

Требуется: GPU с 24GB+ VRAM или LM Studio на отдельном сервере.

Вариант B: Без GPU, с LM Studio на удалённом сервере

  • То же, что вариант A, но LM Studio на удалённом хосте

  • Подключение через HTTP API (RAG_EMBEDDING_URL, RAG_LLM_URL)

  • Задержка: +5-15ms на каждый вызов

Вариант C: Без LM Studio (облачный fallback)

  • Эмбеддинги: sentence-transformers/all-MiniLM-L6-v2 (локальный, CPU, 384d) вместо e5-large-instruct

  • Классификация: только TF-IDF+SVM (без Gemma)

  • Grading: cosine threshold без LLM (адаптивные пороги по типу запроса)

  • CRAG: DuckDuckGo (без изменений)

Требуется: только CPU. Качество ниже, но работает.

Вариант D: Минимальный (один узел, без федерации)

  • Одна коллекция ChromaDB

  • TF-IDF+SVM классификатор

  • Cosine threshold без адаптивных порогов

  • Без CRAG, без sessions collection

  • Без плагина (только CLI)

Требуется: Python 3.10+, ChromaDB, scikit-learn. Работает на любом сервере.

Конфигурация (config.yaml)

`# ChromaDB
chroma_path: ~/.cache/chroma
collection_name: wiki
session_collection_name: sessions

# Embedding
embedding_model: e5-large-instruct  # или all-MiniLM-L6-v2 для CPU
embedding_dim: 1024  # или 384 для MiniLM
embedding_url: http://localhost:1234/v1/embeddings

# LLM (grading, reranking)
llm_model: qwen2.5-7b-instruct
llm_url: http://localhost:1234/v1/chat/completions

# Классификация
classifier: tfidf_svm  # основной
fallback_classifier: gemma4b  # при confidence < 0.9

# Self-RAG пороги
threshold_factual: 0.75
threshold_analytical: 0.65
threshold_synthesis: 0.60

# Индексация
chunk_size: 2000
chunk_overlap: 100
batch_size: 64

# Категории (авторасширение из путей)
category_rules:
  .email_cache/: email
  .tg_history/: tg
  concepts/: concept
  entities/: entity
  raw/: raw
  verified/: verified
  sessions/: session

# Источники
wiki_paths:
  - ~/wiki
  - ~/llm-wiki
  - ~/wiki/.email_cache
  - ~/wiki/.tg_history

# Cron (автоиндексация)
cron_schedule: "0 * * * *"  # каждый час
cron_lock: /tmp/rag-index.lock

# Мониторинг
log_path: ~/.cache/chroma/search.log
alert_avg_relevance: 0.3
alert_elapsed_seconds: 5
alert_chunks_found_zero_ratio: 0.3`

Воспроизведение

  1. Установить зависимости: pip install chromadb scikit-learn requests pyyaml 2. Скопировать config.yaml, указать пути к данным 3. Запустить индексацию: python3 rag_indexer.py 4. Запустить автоиндексацию: добавить cron-задачу из config 5. Для поиска: python3 rag_search.py --query "запрос"

При отсутствии LM Studio - использовать вариант C (sentence-transformers + cosine threshold).