Update 2 files

- /rag/services/file_service.py
- /rag/services/embedding_service.py
This commit is contained in:
Markov Andrey
2026-06-30 14:45:46 +00:00
parent ab8cac768b
commit c00af40977
2 changed files with 114 additions and 117 deletions

View File

@@ -7,6 +7,7 @@
import asyncio
import logging
import time # используется только в синхронной функции _embed_batch_sync
from functools import lru_cache
from typing import List, Optional, Any
@@ -37,12 +38,10 @@ class EmbeddingService:
self.cache_size = cache_size
self.verify_ssl = verify_ssl
self._giga = None
# Кэширование одиночных эмбеддингов (синхронная обёртка с lru_cache)
self.embed_sync = lru_cache(maxsize=cache_size)(self._embed_sync_impl)
self._sparse_model = None
def _get_client(self) -> GigaChat:
"""Ленивое создание клиента GigaChat (с нужными параметрами)."""
if self._giga is None:
self._giga = GigaChat(
credentials=self.api_key,
@@ -62,12 +61,10 @@ class EmbeddingService:
# ---------- Синхронное получение эмбеддинга (с обрезкой) ----------
def _embed_sync_impl(self, text: str) -> Optional[List[float]]:
"""Синхронный вызов GigaChat API для одного текста. Результат кэшируется."""
if not text or not text.strip():
logger.warning("Попытка получить эмбеддинг пустого текста")
return None
# Обрезка до 800 символов (безопасно для лимита 514 токенов GigaChat)
MAX_CHARS = 800
if len(text) > MAX_CHARS:
text = text[:MAX_CHARS]
@@ -75,7 +72,6 @@ class EmbeddingService:
client = self._get_client()
try:
# Совместимость с разными версиями SDK: пробуем input=[text] или просто [text]
try:
response = client.embeddings(input=[text])
except TypeError:
@@ -94,7 +90,6 @@ class EmbeddingService:
raise
async def embed(self, text: str, retries: int = 3, delay: float = 1.0) -> Optional[List[float]]:
"""Асинхронная обёртка с повторными попытками."""
if not isinstance(text, str):
logger.error(f"embed получил не строку, а {type(text)}: {text!r}")
return None
@@ -112,7 +107,7 @@ class EmbeddingService:
f"Попытка {attempt+1}/{retries} получить эмбеддинг не удалась: {e}. "
f"Повтор через {wait:.1f} сек."
)
await asyncio.sleep(wait)
await asyncio.sleep(wait) # <-- исправлено: await asyncio.sleep
logger.error(f"Не удалось получить эмбеддинг после {retries} попыток")
return None
@@ -143,7 +138,7 @@ class EmbeddingService:
if attempt < 4:
wait = 2 ** attempt # 1,2,4,8 секунд
logger.warning(f"Rate limit, повтор через {wait} секунд")
time.sleep(wait) # синхронный sleep, так как мы в синхронной функции
time.sleep(wait) # оставляем, так как функция синхронная и выполняется в отдельном потоке
else:
logger.error("Превышено количество попыток при 429")
return None
@@ -163,22 +158,20 @@ class EmbeddingService:
vectors = await loop.run_in_executor(None, self._embed_batch_sync, batch)
if vectors is None:
for attempt in range(retries):
await asyncio.sleep(delay * (2 ** attempt))
wait = delay * (2 ** attempt)
await asyncio.sleep(wait) # <-- исправлено: await asyncio.sleep
vectors = await loop.run_in_executor(None, self._embed_batch_sync, batch)
if vectors is not None:
break
else:
return None
results.extend(vectors)
# ДОБАВИТЬ: пауза между батчами
await asyncio.sleep(0.5) # 500 мс
await asyncio.sleep(0.5) # <-- исправлено: await asyncio.sleep
return results
async def embed_sparse_batch(self, texts: List[str]) -> List[Optional[Any]]:
"""Возвращает список объектов SparseEmbedding для каждого текста"""
model = self._get_sparse_model()
if model is None:
# Возвращаем список из None такой же длины, чтобы не нарушать индексацию
return [None] * len(texts)
loop = asyncio.get_running_loop()
embeddings = await loop.run_in_executor(None, list, model.embed(texts))