Головна / Статті / Гібридний пошук для домашньої лабораторії: Qdrant RRF, BM25 і власний чанкінг

Гібридний пошук для домашньої лабораторії: Qdrant RRF, BM25 і власний чанкінг

Самостійно розгорнутий асистент для хоумлабу ріже на чанки нотатки, таблиці, діаграми Draw.io та логи Loki, а потім шукає за щільними й розрідженими векторами з RRF і переранжуванням крос-енкодером.

1386 слів

Перебирати пів сотні markdown-файлів або дивитися docker logs у трьох вікнах термінала заради однієї IP-адреси — виснажливий спосіб керувати хоумлабом. Нотатки — це добре. Миттєві відповіді від гібридного векторного пошуку — краще. У цьому розборі збирається повністю самостійно розгорнутий асистент, який індексує нотатки з Nextcloud, мережеві діаграми Draw.io і живі потоки логів Grafana Loki. Основна увага — механіці: як дані ріжуться на чанки, як поводяться ембединги з погляду математики і як Qdrant знаходить та об'єднує результати.

Парадокс документації

У цій лабораторії працюють багатовузловий гіпервізор Proxmox, файрвол OPNsense, що маршрутизує кілька VLAN, і десятки Docker-контейнерів на кшталт Jellyfin, Radarr, Prowlarr, Grafana та Home Assistant. З часом документація розповзлася по чотирьох місцях:

  1. Мережеві нотатки й сторінки-інструкції в Nextcloud Collectives, що зберігаються як файли .md.
  2. Інвентаризація заліза й таблиці IP-адрес у Excel-файлах .xlsx.
  3. Топологічні схеми, намальовані в Draw.io і збережені як файли .drawio.
  4. Живі логи контейнерів, які збирає Grafana Loki.

Будь-який інцидент чи зміна моста означали ручну рутину: відкрити Nextcloud, знайти потрібну нотатку, відкрити діаграму, щоб перевірити підключення інтерфейсів, прогорнути таблицю, а потім запустити docker logs і переглянути помилки контейнерів. Дратує, і довго.

Публічні чат-боти тут не допоможуть. Вони нічого не знають про приватні підмережі на кшталт 10.27.30.2, мости Proxmox на кшталт vmbr1 чи прив'язки мережевих карт на кшталт igc0, а на запитання про приватну інфраструктуру впевнено вигадують нісенітниці. Тож знання мають лишатися локальними й підтягуватися в момент запитання.

Чанкінг кожного формату за його правилами

Стандартні RAG-схеми ріжуть текст кожні кілька сотень символів чи слів. Для даних хоумлабу це згубно. Фраза на кшталт «Proxmox Host 1 під'єднано до порту 2» може розійтися по двох чанках, і зв'язок між сервером і портом зникне. Тому кожен формат має свою стратегію.

Markdown: розбиття за заголовками

Документи ріжуться за структурними межами, тобто за #, ## і ###. Nextcloud Collectives зберігає сторінки в Markdown, тож заголовки вже на місці. Розбиття за ними зберігає шлях розділу, і чанк — це не відірваний текст, а фрагмент з усією ієрархією:

Document: Home Server Setup | Section: Network > Firewalls > OPNsense
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OPNsense VM 101 is configured with passthrough interface igc0…

Таблиці: рядки як речення

За допомогою openpyxl кожен рядок із залізом витягується й переписується в явне твердження «ключ — значення». Сирі таблиці втрачають заголовки стовпців, коли наївний чанкер перетворює їх на текстові блоки, а переписаний рядок самодостатній:

Row 4: Device: Proxmox Host 1 | Port: Port 2 | IP: 10.27.10.3 | Connection: Airtel Router

every single row becomes an independently searchable record in vector storage.

Кожен рядок стає окремим записом у векторному сховищі, за яким можна шукати.

Draw.io: прихований XML

Щоб читати мережеві діаграми, не потрібні дорогий OCR чи комп'ютерний зір: файли .drawio — це просто стиснуті XML-рядки. Перший крок робить zlib із Python: декодуємо base64, потім розпаковуємо через zlib.decompress(data, -15). З отриманого XML-дерева елементів <mxCell> видобуваються вузли, пристрої та ребра з'єднань, які записуються так:

## Nodes
- Node: Proxmox Host 1 (10.27.10.3)
- Node: OPNsense Firewall VM (101)
## Connections
- Connection: Airtel WAN Router -> Port 2 (igc0) -> OPNsense VM (101)

З'єднання виходять точними — без помилок розпізнавання і без навантаження на GPU.

Логи Loki: два рівні

Якщо залити сирі рядки Docker-логів із Loki у векторне сховище, воно потоне в мільйонах безглуздих INFO-повідомлень. Цього дає змогу уникнути адаптивна дворівнева схема:

  • Рівень 1 — інциденти. Сплески критичних помилок на кшталт ETIMEDOUT, HTTP 500, збоїв і падінь контейнерів ріжуться на чанки з точними мітками часу, мітками контейнерів на кшталт jellyfin чи radarr і стектрейсами.
  • Рівень 2 — рутинна активність. Звичайний «пульс» згортається у зведення по 15 хвилин.
[Tier-1 Incident Event]
Timestamp: 2026–09–14 18:30:12 UTC | Service: jellyfin | Container: jellyfin
Log Event: ERR_FR_REDIRECTION_FAILURE attempting to connect to 10.27.30.2:8096

Два простори ембедингів

Навіщо довіряти одній моделі ембедингів, якщо можна поєднати два математичні простори?

Щільні вектори на all-MiniLM-L6-v2

Це шестишаровий трансформер MiniLM, що видає 384-вимірні вектори. Релевантність вимірюється косинусною подібністю між вектором запиту A і вектором чанка B.

Щільні вектори вловлюють зміст. Модель розуміє, що firewall — концептуальний сусід OPNsense, а сховище пов'язане з пулом ZFS. Їхнє слабке місце — числа, які згладжуються. 10.27.30.2 і 10.27.30.3 опиняються майже в одній точці, і для IP-адрес це дає помилки точності.

Розріджені вектори на FastEmbed BM25

Розріджена частина використовує частоту терміна, зважену оберненою документною частотою, і оцінює збіги ключових слів у колекції документів D для термінів запиту q.

Тут сила — в точності. Технічні токени на кшталт IP-адрес (10.27.30.2), інтерфейсів (igc0, vmbr1), портів (8096) і кодів помилок (ETIMEDOUT) збігаються ідеально. Ціна в тому, що синонім замість буквального слова нічого не знаходить.

Зберігання, злиття і переранжування

Обидва представлення потрапляють до бази Qdrant на localhost:6333, в одну колекцію з назвою homelab. Запит запускає гібридний пошук.

Reciprocal Rank Fusion

Щільні косинусні оцінки (скажімо, 0,85) і розріджені оцінки BM25 (скажімо, 14,2) живуть у зовсім різних шкалах, тож додавати їх напряму безглуздо. Натомість Qdrant об'єднує ранжовані списки кандидатів методом Reciprocal Rank Fusion.

RRF винагороджує згоду. Документ, що стоїть першим у розрідженому пошуку завдяки точному збігу з 10.27.30.2 і другим у щільному завдяки сильній смисловій близькості, після злиття злітає нагору.

Переранжування крос-енкодером

Бі-енкодери на кшталт MiniLM під час індексації кодують запит і документ окремо. Щоб відсіяти хибні спрацювання, десять найкращих кандидатів після RRF проходять через крос-енкодер ms-marco-MiniLM-L-6-v2, який пропускає пару «запит — документ» через self-attention трансформера разом.

Чанки з оцінкою нижче MIN_RERANK_SCORE = -2.0 відкидаються. Якщо шум вибив би всі чанки, конвеєр акуратно відкочується: спекулятивний каскад залишає кандидата з найкращою оцінкою.

Сирий логіт крос-енкодера (z) перетворюється на зрозумілу ймовірність релевантності через сигмоїду.

Різні значення логіта дають приблизно такі ймовірності:

За z = -2.0 імовірність, що чанк релевантний, близько 11,9%. Усе, що вище, несе корисний контекст або хоча б слабкий тематичний перетин, який може допомогти мовній моделі. Усе, що нижче, швидко падає до нуля — за z = -4.0 це лише 1,8% — і зазвичай означає забруднення з іншої галузі, наприклад сплески сирих Docker-логів, домішані до запитання про підмережі.

Query: "What is the OPNsense management IP?"

[Top Retrieved Chunks]
 Chunk 1: "OPNsense Firewall VM 101 IP: 10.27.10.1..."    ──► Logit: +4.8 (Keep)
 Chunk 2: "Proxmox host bridge vmbr0 LAN 10.27.10.3..."  ──► Logit: +0.4 (Keep)
 Chunk 3: "Grafana Loki log error: connection refused"  ──► Logit: -4.2 (Filtered out!)

Без цього порогу третій чанк потрапив би в контекст моделі, і помилка з логу могла б збити її на хибну відповідь. Поріг -2.0 працює як автоматичний вишибала біля дверей контекстного вікна.

Перевірка

Автоматичний регресійний скрипт tests/run_regression.py проганяє систему тестовим набором із 17 запитань про хоумлаб:

Ran 3 tests in 1.307s
OK
[Qdrant Connected] Vector DB ready at http://localhost:6333 (Collection: 'homelab').
[Regression Assertion] Hit@1 Accuracy Rate: 100.0% | MRR: 1.0000
```
* **Hit@1 Accuracy Rate**: **100.0%**
* **MRR (Mean Reciprocal Rank)**: **1.0000**
* **Time-to-First-Token (TTFT)**: **640.8 ms**

Усе разом

Багатоформатний чанкінг (заголовки Markdown, рядки Excel, розпаковані XML-графи Draw.io і дворівневі логи Loki), щільні ембединги MiniLM і розріджені BM25, злиття рангів у Qdrant і переранжування крос-енкодером разом дають приватного асистента на власному залізі. Він може відповісти на запитання про будь-який кабель, IP-адресу й помилку Docker-контейнера в стійці.