Перейти к содержимому

Дрейф чанкинга и конфига индекса

Индекс строился с одним чанкингом (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)
  • поля, которые разошлись, с обеими сторонами (индекс → запрос);
  • отпечатки конфигов;
  • имя коллекции;
  • когда индекс собирался в последний раз (см. устаревший индекс).