fix(admin): покрытие L3 мерить по индексу, а не по отметкам в базе
Проверка на живых данных вскрыла, что панель отладки врала в мою же пользу: показывала 154 046 «документов с эмбеддингом» (87%), тогда как в FAISS реально 93 053 вектора (52.5%). Колонка documents.faiss_id для этого непригодна — отметка остаётся после пересоздания индекса (смена модели: 768 → 1024) и после сбоев worker-gpu. Выборочная проверка: у 8 из 20 «отмеченных» вектора нет. - gpu.index_stats — новая задача, отдаёт реальное содержимое активного векторного бэкенда; API спрашивает её для панели отладки; - панель показывает «векторов в индексе» и отдельно предупреждает о ложных отметках (их 60 993), потому что такие документы молча выпадают из L3: в индексе их нет, а на пересчёт они не попадут — reembed ищет faiss_id IS NULL; - scripts/ops/faiss_reconcile.py — сверяет отметки с индексом и обнуляет ложные, после чего reembed_missing.py отправляет их на пересчёт. Проверен вживую (dry-run на проде: 93 053 в индексе, 60 993 ложных отметок). Документация: зафиксирована реальная глубина корпуса — полный текст только у 27.5% документов, у 79% меньше 500 отпечатков (уровень аннотации). Система ловит списывание из того, что есть целиком; это граница, а не поломка. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -784,6 +784,20 @@ async def upload_documents(files: list[UploadFile]) -> dict:
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# ОТЛАДКА
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
def _vector_index_stats() -> dict:
|
||||
"""Реальное наполнение векторного индекса — спросить у worker-gpu.
|
||||
|
||||
Колонка `documents.faiss_id` для этого не годится: пометка остаётся и когда
|
||||
вектор в индекс не попал, и когда индекс пересоздали после смены модели
|
||||
эмбеддингов. Отладка, показывающая покрытие L3 по ней, завышает его в разы.
|
||||
"""
|
||||
try:
|
||||
result = celery_app.send_task("gpu.index_stats", queue="queue.gpu")
|
||||
return result.get(timeout=10) or {}
|
||||
except Exception as e:
|
||||
return {"error": str(e)[:200] or type(e).__name__}
|
||||
|
||||
|
||||
def _celery_snapshot() -> dict:
|
||||
"""Живое состояние воркеров через Celery inspect (блокирующий вызов)."""
|
||||
try:
|
||||
@@ -827,8 +841,10 @@ async def debug_snapshot(db: AsyncSession = Depends(get_db)) -> dict:
|
||||
именно сейчас крутит, что копится в очередях, как наполняется корпус,
|
||||
какие прогоны идут и на чём падали последние.
|
||||
"""
|
||||
# Воркеры — блокирующий Celery inspect, в пул потоков, чтобы не вешать луп
|
||||
# Воркеры и статистика индекса — блокирующие вызовы Celery, в пул потоков,
|
||||
# чтобы не вешать событийный цикл
|
||||
celery_state = await run_in_threadpool(_celery_snapshot)
|
||||
vector_stats = await run_in_threadpool(_vector_index_stats)
|
||||
|
||||
rmq = await _rabbitmq_queues()
|
||||
queues = (
|
||||
@@ -915,8 +931,10 @@ async def debug_snapshot(db: AsyncSession = Depends(get_db)) -> dict:
|
||||
"queues": queues,
|
||||
"corpus": {
|
||||
"documents": docs_total,
|
||||
"documents_embedded": docs_embedded,
|
||||
"documents_without_embedding": max(docs_total - docs_embedded, 0),
|
||||
# Пометка в БД и реальное содержимое индекса расходятся — показываем
|
||||
# обе величины, иначе покрытие L3 выглядит лучше, чем оно есть
|
||||
"documents_marked_embedded": docs_embedded,
|
||||
"vector_index": vector_stats,
|
||||
"fingerprints_estimate": int(fingerprints_est),
|
||||
"elasticsearch_documents": es_docs,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user