nomic-embed-text на русском не работал: в замере «велосипедная камера» набирала мультиметру 0.704 — больше, чем верный ответ на «паяльник» (0.691). Зазор −0.013, то есть мусор систематически обгонял правильное и порогом это не лечилось. bge-m3 на том же наборе: верные не ниже 0.537, мусор не выше 0.460, зазор +0.077. Префиксы задачи ему не нужны, поэтому обнулены. Порог откалиброван на настоящей базе, а не на придуманном наборе, и картина там честнее: абстрактное «чем посветить» набирает лампам 0.411, а посторонняя «детская коляска» — 0.535. Полностью не разделяются, поэтому SEMANTIC_MIN выставлен так, чтобы отсечь весь мусор ценой слабых догадок — уверенно показанное не то хуже честного «не нашёл». Точных запросов это не касается: они находятся по словам и тегам. Добавлен tools/reindex.py: смена эмбеддера требует полного пересчёта векторов, иначе смешанная база тихо портит поиск. Скрипт делает копию перед работой. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Пересчёт векторов после смены модели эмбеддера.
|
||
|
||
Векторы разных моделей несовместимы: если половина базы посчитана одной
|
||
моделью, а половина другой, косинус между ними — шум, и поиск тихо портится,
|
||
ничем себя не выдавая. Поэтому при смене EMBED_MODEL нужен полный пересчёт.
|
||
|
||
Запуск (сервер лучше остановить, чтобы не писал в базу одновременно):
|
||
venv\\Scripts\\python.exe tools\\reindex.py
|
||
"""
|
||
|
||
import io
|
||
import os
|
||
import sys
|
||
import time
|
||
import shutil
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
try:
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||
except Exception:
|
||
pass
|
||
|
||
from hlamingo import store, search, ollama, config # noqa: E402
|
||
|
||
|
||
def main():
|
||
if not config.DB_FILE.exists():
|
||
print("База пуста — пересчитывать нечего.")
|
||
return 0
|
||
if not ollama.check():
|
||
print("Ollama недоступна (%s) — без неё векторы не посчитать." % config.OLLAMA_HOST)
|
||
return 1
|
||
|
||
db = store.load()
|
||
total = len(db["items"])
|
||
print("Модель эмбеддера: %s" % config.EMBED_MODEL)
|
||
print("Вещей в базе: %d" % total)
|
||
if not total:
|
||
return 0
|
||
|
||
# Своя копия рядом с автоматическими: пересчёт трогает каждую запись.
|
||
backup = config.DATA_DIR / "backups" / ("db-before-reindex-%s.json"
|
||
% time.strftime("%Y%m%d-%H%M%S"))
|
||
backup.parent.mkdir(parents=True, exist_ok=True)
|
||
shutil.copy2(str(config.DB_FILE), str(backup))
|
||
print("Копия: %s\n" % backup.name)
|
||
|
||
t0 = time.time()
|
||
done = 0
|
||
for item in db["items"].values():
|
||
item["emb"] = None
|
||
for item in db["items"].values():
|
||
vector = ollama.embed(search.item_text(item))
|
||
if not vector:
|
||
print(" не удалось посчитать: %s" % item.get("name"))
|
||
continue
|
||
item["emb"] = vector
|
||
done += 1
|
||
print(" [%d/%d] %s" % (done, total, item.get("name")))
|
||
|
||
store.save(db)
|
||
print("\nГотово: %d из %d за %.1f с." % (done, total, time.time() - t0))
|
||
if done < total:
|
||
print("Часть векторов не посчиталась — запусти ещё раз, когда Ollama ответит.")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|