This commit is contained in:
@@ -130,6 +130,13 @@
|
||||
- Убраны избыточные логи из `precache_topics_followers`
|
||||
- Более чистое и информативное логирование процесса кеширования
|
||||
|
||||
### 🚨 Исправлено
|
||||
- **Запуск приложения**: Исправлена блокировка при старте из-за SentenceTransformers
|
||||
- Переведен импорт `sentence_transformers` на lazy loading
|
||||
- Модель загружается только при первом использовании поиска
|
||||
- Исправлена ошибка deprecated `TRANSFORMERS_CACHE` на `HF_HOME`
|
||||
- Приложение теперь запускается мгновенно без ожидания загрузки ML моделей
|
||||
|
||||
## [0.9.13] - 2025-08-27
|
||||
|
||||
### 🗑️ Удалено
|
||||
|
||||
@@ -209,11 +209,6 @@ class MockInfo:
|
||||
}
|
||||
self.field_nodes = [MockFieldNode(requested_fields or [])]
|
||||
|
||||
# Патчинг зависимостей
|
||||
@patch('storage.redis.aioredis')
|
||||
def test_redis_connection(mock_aioredis):
|
||||
# Тест логики
|
||||
pass
|
||||
```
|
||||
|
||||
### Асинхронные тесты
|
||||
|
||||
@@ -57,7 +57,7 @@ dependencies = [
|
||||
# https://docs.astral.sh/uv/concepts/dependencies/#development-dependencies
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"fakeredis[aioredis]",
|
||||
"fakeredis",
|
||||
"pytest",
|
||||
"pytest-asyncio",
|
||||
"pytest-cov",
|
||||
@@ -68,7 +68,7 @@ dev = [
|
||||
]
|
||||
|
||||
test = [
|
||||
"fakeredis[aioredis]",
|
||||
"fakeredis",
|
||||
"pytest",
|
||||
"pytest-asyncio",
|
||||
"pytest-cov",
|
||||
|
||||
@@ -15,8 +15,10 @@ def get_models_cache_dir() -> str:
|
||||
"""Определяет лучшую папку для кеша моделей"""
|
||||
# Пробуем /dump если доступен для записи
|
||||
dump_path = Path("/dump")
|
||||
print(f"🔍 Checking /dump - exists: {dump_path.exists()}, writable: {os.access('/dump', os.W_OK) if dump_path.exists() else 'N/A'}")
|
||||
|
||||
print(
|
||||
f"🔍 Checking /dump - exists: {dump_path.exists()}, writable: {os.access('/dump', os.W_OK) if dump_path.exists() else 'N/A'}"
|
||||
)
|
||||
|
||||
if dump_path.exists() and os.access("/dump", os.W_OK):
|
||||
cache_dir = "/dump/huggingface"
|
||||
try:
|
||||
|
||||
@@ -9,11 +9,12 @@ from typing import Any, Dict, List
|
||||
|
||||
import muvera
|
||||
import numpy as np
|
||||
from sentence_transformers import SentenceTransformer
|
||||
|
||||
from settings import MUVERA_INDEX_NAME, SEARCH_MAX_BATCH_SIZE, SEARCH_PREFETCH_SIZE
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
# Отложенный импорт SentenceTransformer для избежания блокировки запуска
|
||||
SentenceTransformer = None
|
||||
primary_model = "paraphrase-multilingual-MiniLM-L12-v2"
|
||||
|
||||
|
||||
@@ -22,7 +23,9 @@ def get_models_cache_dir() -> str:
|
||||
"""Определяет лучшую папку для кеша моделей"""
|
||||
# Пробуем /dump если доступен для записи
|
||||
dump_path = Path("/dump")
|
||||
logger.info(f"🔍 Checking /dump - exists: {dump_path.exists()}, writable: {os.access('/dump', os.W_OK) if dump_path.exists() else 'N/A'}")
|
||||
logger.info(
|
||||
f"🔍 Checking /dump - exists: {dump_path.exists()}, writable: {os.access('/dump', os.W_OK) if dump_path.exists() else 'N/A'}"
|
||||
)
|
||||
|
||||
if dump_path.exists() and os.access("/dump", os.W_OK):
|
||||
cache_dir = "/dump/huggingface"
|
||||
@@ -41,13 +44,28 @@ def get_models_cache_dir() -> str:
|
||||
|
||||
|
||||
MODELS_CACHE_DIR = get_models_cache_dir()
|
||||
os.environ.setdefault("TRANSFORMERS_CACHE", MODELS_CACHE_DIR)
|
||||
# Используем HF_HOME вместо устаревшего TRANSFORMERS_CACHE
|
||||
os.environ.setdefault("HF_HOME", MODELS_CACHE_DIR)
|
||||
|
||||
# Global collection for background tasks
|
||||
background_tasks: List[asyncio.Task] = []
|
||||
|
||||
|
||||
def _lazy_import_sentence_transformers():
|
||||
"""🔄 Lazy import SentenceTransformer для избежания блокировки старта приложения"""
|
||||
global SentenceTransformer # noqa: PLW0603
|
||||
if SentenceTransformer is None:
|
||||
try:
|
||||
from sentence_transformers import SentenceTransformer as SentenceTransformerClass
|
||||
|
||||
SentenceTransformer = SentenceTransformerClass
|
||||
logger.info("✅ SentenceTransformer импортирован успешно")
|
||||
except ImportError as e:
|
||||
logger.error(f"❌ Не удалось импортировать SentenceTransformer: {e}")
|
||||
SentenceTransformer = None
|
||||
return SentenceTransformer
|
||||
|
||||
|
||||
class MuveraWrapper:
|
||||
"""🔍 Real vector search with SentenceTransformers + FDE encoding"""
|
||||
|
||||
@@ -60,42 +78,10 @@ class MuveraWrapper:
|
||||
self.documents: Dict[str, Dict[str, Any]] = {} # Simple in-memory storage for demo
|
||||
self.embeddings: Dict[str, np.ndarray | None] = {} # Store encoded embeddings
|
||||
|
||||
# 🚀 Инициализируем реальную модель эмбедингов с локальным кешом
|
||||
try:
|
||||
logger.info(f"💾 Using models cache directory: {MODELS_CACHE_DIR}")
|
||||
|
||||
# Проверяем наличие основной модели
|
||||
is_cached = self._is_model_cached(primary_model)
|
||||
if is_cached:
|
||||
logger.info(f"🔍 Found cached model: {primary_model}")
|
||||
else:
|
||||
logger.info(f"🔽 Downloading model: {primary_model}")
|
||||
|
||||
# Используем многоязычную модель, хорошо работающую с русским
|
||||
self.encoder = SentenceTransformer(
|
||||
primary_model,
|
||||
cache_folder=MODELS_CACHE_DIR,
|
||||
local_files_only=is_cached, # Не скачиваем если уже есть в кеше
|
||||
)
|
||||
logger.info("🔍 SentenceTransformer model loaded successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load primary SentenceTransformer: {e}")
|
||||
# Fallback - простая модель
|
||||
try:
|
||||
fallback_model = "all-MiniLM-L6-v2"
|
||||
is_fallback_cached = self._is_model_cached(fallback_model)
|
||||
if is_fallback_cached:
|
||||
logger.info(f"🔍 Found cached fallback model: {fallback_model}")
|
||||
else:
|
||||
logger.info(f"🔽 Downloading fallback model: {fallback_model}")
|
||||
|
||||
self.encoder = SentenceTransformer(
|
||||
fallback_model, cache_folder=MODELS_CACHE_DIR, local_files_only=is_fallback_cached
|
||||
)
|
||||
logger.info("🔍 Fallback SentenceTransformer model loaded")
|
||||
except Exception:
|
||||
logger.error("Failed to load any SentenceTransformer model")
|
||||
self.encoder = None
|
||||
# 🚀 Откладываем инициализацию модели до первого использования
|
||||
logger.info("🔄 MuveraWrapper инициализирован - модель будет загружена при первом использовании")
|
||||
self.encoder = None
|
||||
self._model_loaded = False
|
||||
|
||||
def _is_model_cached(self, model_name: str) -> bool:
|
||||
"""🔍 Проверяет наличие модели в кеше"""
|
||||
@@ -128,6 +114,60 @@ class MuveraWrapper:
|
||||
logger.debug(f"Error checking model cache for {model_name}: {e}")
|
||||
return False
|
||||
|
||||
def _ensure_model_loaded(self) -> bool:
|
||||
"""🔄 Убеждаемся что модель загружена (lazy loading)"""
|
||||
if self._model_loaded:
|
||||
return self.encoder is not None
|
||||
|
||||
# Импортируем SentenceTransformer при первой необходимости
|
||||
sentence_transformer_class = _lazy_import_sentence_transformers()
|
||||
if sentence_transformer_class is None:
|
||||
logger.error("❌ SentenceTransformer недоступен")
|
||||
return False
|
||||
|
||||
try:
|
||||
logger.info(f"💾 Using models cache directory: {MODELS_CACHE_DIR}")
|
||||
|
||||
# Проверяем наличие основной модели
|
||||
is_cached = self._is_model_cached(primary_model)
|
||||
if is_cached:
|
||||
logger.info(f"🔍 Found cached model: {primary_model}")
|
||||
else:
|
||||
logger.info(f"🔽 Downloading model: {primary_model}")
|
||||
|
||||
# Используем многоязычную модель, хорошо работающую с русским
|
||||
self.encoder = sentence_transformer_class(
|
||||
primary_model,
|
||||
cache_folder=MODELS_CACHE_DIR,
|
||||
local_files_only=is_cached, # Не скачиваем если уже есть в кеше
|
||||
)
|
||||
logger.info("🔍 SentenceTransformer model loaded successfully")
|
||||
self._model_loaded = True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load primary SentenceTransformer: {e}")
|
||||
# Fallback - простая модель
|
||||
try:
|
||||
fallback_model = "all-MiniLM-L6-v2"
|
||||
is_fallback_cached = self._is_model_cached(fallback_model)
|
||||
if is_fallback_cached:
|
||||
logger.info(f"🔍 Found cached fallback model: {fallback_model}")
|
||||
else:
|
||||
logger.info(f"🔽 Downloading fallback model: {fallback_model}")
|
||||
|
||||
self.encoder = sentence_transformer_class(
|
||||
fallback_model, cache_folder=MODELS_CACHE_DIR, local_files_only=is_fallback_cached
|
||||
)
|
||||
logger.info("🔍 Fallback SentenceTransformer model loaded")
|
||||
self._model_loaded = True
|
||||
return True
|
||||
except Exception:
|
||||
logger.error("Failed to load any SentenceTransformer model")
|
||||
self.encoder = None
|
||||
self._model_loaded = True # Помечаем как попытка завершена
|
||||
return False
|
||||
|
||||
async def async_init(self) -> None:
|
||||
"""🔄 Асинхронная инициализация - восстановление индекса из файла"""
|
||||
try:
|
||||
@@ -153,7 +193,12 @@ class MuveraWrapper:
|
||||
|
||||
async def search(self, query: str, limit: int) -> List[Dict[str, Any]]:
|
||||
"""🔍 Real vector search using SentenceTransformers + FDE encoding"""
|
||||
if not query.strip() or not self.encoder:
|
||||
if not query.strip():
|
||||
return []
|
||||
|
||||
# Загружаем модель при первом использовании
|
||||
if not self._ensure_model_loaded():
|
||||
logger.warning("🔍 Search unavailable - model not loaded")
|
||||
return []
|
||||
|
||||
try:
|
||||
@@ -194,7 +239,8 @@ class MuveraWrapper:
|
||||
|
||||
async def index(self, documents: List[Dict[str, Any]], silent: bool = False) -> None:
|
||||
"""🚀 Index documents using real SentenceTransformers + FDE encoding"""
|
||||
if not self.encoder:
|
||||
# Загружаем модель при первом использовании
|
||||
if not self._ensure_model_loaded():
|
||||
if not silent:
|
||||
logger.warning("🔍 No encoder available for indexing")
|
||||
return
|
||||
|
||||
6
uv.lock
generated
6
uv.lock
generated
@@ -400,7 +400,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "discours-core"
|
||||
version = "0.9.14"
|
||||
version = "0.9.18"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "ariadne" },
|
||||
@@ -492,7 +492,7 @@ requires-dist = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "fakeredis", extras = ["aioredis"] },
|
||||
{ name = "fakeredis" },
|
||||
{ name = "mypy" },
|
||||
{ name = "playwright" },
|
||||
{ name = "pytest" },
|
||||
@@ -506,7 +506,7 @@ lint = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
test = [
|
||||
{ name = "fakeredis", extras = ["aioredis"] },
|
||||
{ name = "fakeredis" },
|
||||
{ name = "playwright" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
|
||||
Reference in New Issue
Block a user