Дрейф чанкинга и конфига индекса
Индекс строился с одним чанкингом (chunk_size=512, overlap=64,
text-embedding-3-small), а запросы уходят с другим — кто-то поправил конфиг
и передеплоил query-сервис, не переиндексировав базу. Формально ничего не
падает: векторы той же размерности, поиск работает, но соседи в выдаче теперь
мусорные. Это один из самых незаметных способов угробить качество RAG.
1. Записываем конфиг сборки внутрь индекса
Заголовок раздела «1. Записываем конфиг сборки внутрь индекса»При переиндексации кладём отпечаток конфига в метаданные коллекции (или в служебную точку). Это единственный источник правды о том, «чем построено»:
import os, json, hashlib, requests
def build_config(): return { "chunk_size": int(os.environ["CHUNK_SIZE"]), "chunk_overlap": int(os.environ["CHUNK_OVERLAP"]), "embed_model": os.environ["EMBED_MODEL"], "embed_dim": int(os.environ["EMBED_DIM"]), "splitter": os.environ.get("SPLITTER", "recursive"), }
def config_fingerprint(cfg: dict) -> str: return hashlib.sha256(json.dumps(cfg, sort_keys=True).encode()).hexdigest()[:12]
def save_index_config(): cfg = build_config() # ваш клиент: сохранить в метаданные коллекции vector_db_set_meta("index_config", json.dumps(cfg)) vector_db_set_meta("index_config_fp", config_fingerprint(cfg))2. Сверяем query-конфиг с build-конфигом при старте
Заголовок раздела «2. Сверяем query-конфиг с build-конфигом при старте»Query-сервис на старте (и раз в час) читает отпечаток из индекса и сравнивает со своим. Расхождение — немедленный push:
def check_config_drift(): build_fp = vector_db_get_meta("index_config_fp") query_fp = config_fingerprint(build_config())
if build_fp and build_fp != query_fp: build_cfg = json.loads(vector_db_get_meta("index_config") or "{}") query_cfg = build_config() diff = [f"{k}: индекс={build_cfg.get(k)} → запрос={query_cfg.get(k)}" for k in query_cfg if build_cfg.get(k) != query_cfg.get(k)] notify("🧩 Дрейф конфига RAG", "Конфиг сборки индекса ≠ конфиг на запросе:\n" + "\n".join(f"• {d}" for d in diff) + "\n\nЛибо переиндексируйте, либо откатите query-конфиг.", priority=9) return False return True
def notify(title, message, priority): requests.post(f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5)Проверка на старте ловит расхождение сразу после деплоя — до того, как первый пользователь получит мусорную выдачу.
3. Особый случай: разъехалась модель embeddings
Заголовок раздела «3. Особый случай: разъехалась модель embeddings»Смена embed_model — самая опасная: при совпадающей размерности поиск даже
не сломается, просто вернёт бессмысленных соседей. Модель выносим в
отдельную явную проверку с максимальным приоритетом:
if build_cfg.get("embed_model") != query_cfg.get("embed_model"): notify("🧩 Разошлась модель embeddings", f"Индекс: {build_cfg.get('embed_model')} → " f"запрос: {query_cfg.get('embed_model')}. Ретривал вернёт мусор.", priority=10)Что положить в текст алёрта
Заголовок раздела «Что положить в текст алёрта»- поля, которые разошлись, с обеими сторонами (индекс → запрос);
- отпечатки конфигов;
- имя коллекции;
- когда индекс собирался в последний раз (см. устаревший индекс).
Связанные рецепты
Заголовок раздела «Связанные рецепты»- Смена версии embeddings — когда модель меняет провайдер, а не вы.
- RAG-ретривал возвращает пустоту — частое следствие дрейфа.
- Устаревший индекс знаний — соседний сигнал качества индекса.