Эмбеддер bge-m3: смысловой поиск на русском наконец разделяет смыслы
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>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# -*- 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())
|
||||
Reference in New Issue
Block a user