Spaces:
Sleeping
Sleeping
Changelog
[2.2.0] - 2024-12-20 - Model Upgrade & Critical Fixes
🔥 Критические исправления
Новая модель:
ai-forever/ru-en-RoSBERTa- Оптимизирована для русского языка
- Размерность: 768 (вместо 384)
- Лучшее качество для semantic matching
Нормализация эмбеддингов
model.encode( texts, batch_size=32, normalize_embeddings=True, # КРИТИЧНО для cosine similarity! ... )- pgvector + cosine (
<=>) ожидает нормализованные векторы - Без нормализации similarity "плывёт" и хуже ранжирование
- pgvector + cosine (
Унифицированная кэш-логика
- Новая функция
encode_single_async_with_flag()возвращает(embedding, cached) - Исправлены двойные
CACHE_MISSES - Корректный флаг
cachedво всех ответах
- Новая функция
MAX_CONCURRENT_REQUESTS = 6 (было 4)
- Оптимально для 8-16 vCPU
⚠️ Breaking Changes
- Размерность эмбеддингов изменилась: 384 → 768
- Необходимо переиндексировать все объекты!
- SQL миграция:
ALTER TABLE leads DROP COLUMN embedding; ALTER TABLE leads ADD COLUMN embedding vector(768); -- Пересоздать индекс CREATE INDEX ON leads USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
[2.1.0] - 2024-12-19 - Production-Ready Release
🚀 Основные улучшения
Полная переработка сервиса для production-ready статуса по рекомендациям экспертов.
Что было (v2.0.0) vs Что стало (v2.1.0)
1. Асинхронность и CPU/IO разграничение
Было:
# Синхронный вызов, блокирует event loop FastAPI
embedding = model.encode(request.text, convert_to_numpy=True)
Стало:
# Асинхронный вызов через ThreadPoolExecutor
async def encode_async(texts: List[str]) -> np.ndarray:
loop = asyncio.get_event_loop()
result = await asyncio.wait_for(
loop.run_in_executor(
executor,
lambda: model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
),
timeout=ENCODE_TIMEOUT_SECONDS
)
return result
Влияние на индексацию:
- ✅ Сервис остаётся отзывчивым при параллельных запросах
- ✅ Таймаут 30 секунд предотвращает "зависание" запросов
- ✅ До 4 параллельных encode операций (настраивается через
MAX_CONCURRENT_REQUESTS)
2. Валидация входных данных
Было:
- Нет ограничений на размер текста
- Нет ограничений на размер батча
- Возможность DoS-атаки через огромные запросы
Стало:
MAX_BATCH_SIZE = 128 # Максимум элементов в батче
MAX_TEXT_LENGTH = 10000 # Максимум символов в тексте
MAX_CONCURRENT_REQUESTS = 4 # Параллельные encode операции
ENCODE_TIMEOUT_SECONDS = 30 # Таймаут на encode
class EmbedRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=MAX_TEXT_LENGTH)
class BatchRequest(BaseModel):
items: List[BatchItem] = Field(..., max_length=MAX_BATCH_SIZE)
Влияние на индексацию:
- ✅ Защита от перегрузки сервиса большими запросами
- ✅ Понятные 400 ошибки при превышении лимитов
- ✅ Предсказуемое время ответа
3. Prometheus метрики
Было:
- Нет метрик
- Невозможно отследить производительность
- "Слепой полёт" в production
Стало:
# Endpoint /metrics возвращает:
embedding_requests_total{endpoint="/embed", status="success"} 150
embedding_request_latency_seconds_bucket{endpoint="/embed", le="0.1"} 120
embedding_batch_size_bucket{le="10"} 45
embedding_encode_failures_total{reason="timeout"} 2
embedding_model_loaded 1
embedding_cache_hits_total 89
embedding_cache_misses_total 61
embedding_active_requests 3
Влияние на индексацию:
- ✅ Мониторинг в Grafana: requests/s, latency, batch sizes
- ✅ Алерты на encode_failures и model_loaded
- ✅ Отслеживание cache hit rate для оптимизации
4. Rate Limiting
Было:
- Нет ограничений на частоту запросов
- Возможность перегрузки сервиса одним клиентом
Стало:
RATE_LIMIT = "100/minute" # Для одиночных запросов
RATE_LIMIT_BATCH = "20/minute" # Для батчей
@app.post("/embed")
@limiter.limit(RATE_LIMIT)
async def embed_text(request: Request, body: EmbedRequest):
...
Влияние на индексацию:
- ✅ Защита от перегрузки
- ✅ Справедливое распределение ресурсов между клиентами
- ✅ HTTP 429 при превышении лимита
5. In-Memory кэширование
Было:
- Каждый запрос генерирует эмбеддинг заново
- Повторные запросы тратят CPU
Стало:
CACHE_ENABLED = True
CACHE_TTL_SECONDS = 3600 # 1 час
CACHE_MAX_SIZE = 10000 # 10k эмбеддингов
# Автоматическое кэширование:
cache_key = hashlib.sha256(text.encode()).hexdigest()
if cache_key in embedding_cache:
return embedding_cache[cache_key] # Мгновенно!
Влияние на индексацию:
- ✅ До 100x ускорение для повторных запросов (0.1-0.5s → <1ms)
- ✅ Экономия CPU для часто запрашиваемых объектов
- ✅ TTL автоматически инвалидирует устаревший кэш
- ✅ Статистика:
GET /cache/stats, очистка:POST /cache/clear
6. Версионирование модели
Было:
- Невозможно отследить какая модель использовалась
- Проблемы при обновлении модели
Стало:
# Каждый ответ содержит:
{
"embedding": [...],
"model_version": "2.1.0",
"model_checksum": "a1b2c3d4e5f6" # MD5 от model_name:dimensions
}
Влияние на индексацию:
- ✅ Go Backend может хранить model_checksum вместе с эмбеддингом
- ✅ При обновлении модели можно переиндексировать только устаревшие записи
- ✅
/model-infoпоказывает время загрузки модели
7. Structured Logging (JSON)
Было:
Loading embedding model: sentence-transformers/...
Model loaded. Dimensions: 384
Стало:
{"event": "model_loading", "model": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", "timestamp": "2024-12-19T10:30:00Z"}
{"event": "model_loaded", "dimensions": 384, "checksum": "a1b2c3d4e5f6", "load_time_seconds": 12.5, "timestamp": "2024-12-19T10:30:12Z"}
{"event": "batch_process", "total": 50, "successful": 50, "cached": 12, "timestamp": "2024-12-19T10:35:00Z"}
Влияние на индексацию:
- ✅ Интеграция с ELK/Loki/CloudWatch
- ✅ Поиск и анализ логов
- ✅ Трейсинг запросов
8. Улучшенная батч-обработка
Было:
# Все тексты генер��руются заново
embeddings = model.encode(texts, convert_to_numpy=True)
Стало:
# Сначала проверяем кэш
for item in body.items:
cache_key = get_cache_key(prepared)
if cache_key in embedding_cache:
# Мгновенно из кэша!
cached_count += 1
continue
texts_to_encode.append(prepared)
# Только некэшированные идут в model.encode
if texts_to_encode:
embeddings = await encode_async(texts_to_encode)
Влияние на индексацию:
- ✅ Смешанный батч (кэш + compute) обрабатывается оптимально
- ✅ Ответ содержит
cached_countдля аналитики - ✅ Каждый
BatchResultItemимеет флагcached: true/false
9. Graceful Error Handling
Было:
- 500 при любой ошибке
- Нет информации о причине
Стало:
# Таймаут
except asyncio.TimeoutError:
ENCODE_FAILURES.labels(reason="timeout").inc()
raise HTTPException(status_code=503, detail=f"Encoding timeout after {ENCODE_TIMEOUT_SECONDS}s")
# Ошибка модели
except Exception as e:
ENCODE_FAILURES.labels(reason="error").inc()
raise HTTPException(status_code=500, detail=f"Encoding error: {str(e)}")
Влияние на индексацию:
- ✅ 503 для временных проблем (клиент может повторить)
- ✅ 400 для ошибок валидации (клиент должен исправить запрос)
- ✅ Метрики для алертов на ошибки
10. Новые endpoints
| Endpoint | Описание |
|---|---|
GET /metrics |
Prometheus метрики |
GET /cache/stats |
Статистика кэша |
POST /cache/clear |
Очистка кэша |
Конфигурация (переменные окружения)
| Переменная | По умолчанию | Описание |
|---|---|---|
EMBEDDING_MODEL |
paraphrase-multilingual-MiniLM-L12-v2 |
Модель эмбеддингов |
MAX_BATCH_SIZE |
128 |
Максимум элементов в батче |
MAX_TEXT_LENGTH |
10000 |
Максимум символов в тексте |
MAX_CONCURRENT_REQUESTS |
4 |
Параллельные encode |
ENCODE_TIMEOUT_SECONDS |
30 |
Таймаут на encode |
RATE_LIMIT |
100/minute |
Rate limit для одиночных |
RATE_LIMIT_BATCH |
20/minute |
Rate limit для батчей |
CACHE_ENABLED |
true |
Включить кэш |
CACHE_TTL_SECONDS |
3600 |
TTL кэша (1 час) |
CACHE_MAX_SIZE |
10000 |
Максимум записей в кэше |
ALLOWED_ORIGINS |
* |
CORS origins |
Оценка готовности к production
| Критерий | v2.0.0 | v2.1.0 |
|---|---|---|
| Асинхронность | ❌ Блокирует event loop | ✅ ThreadPoolExecutor |
| Валидация | ❌ Нет лимитов | ✅ Batch/text limits |
| Метрики | ❌ Нет | ✅ Prometheus |
| Rate limiting | ❌ Нет | ✅ slowapi |
| Кэширование | ❌ Нет | ✅ TTLCache |
| Версионирование | ❌ Нет | ✅ checksum в ответах |
| Логирование | ❌ print() | ✅ structlog JSON |
| Таймауты | ❌ Нет | ✅ 30s timeout |
| Error handling | ❌ Базовый | ✅ Graceful 503/400 |
Рейтинг: 5/10 → 8/10 ✅
Следующие шаги (roadmap для 9/10)
- Redis кэширование — для распределённого кэша
- OpenTelemetry tracing — trace_id propagation
- API Key авторизация — уже подготовлено (
API_KEYenv) - Background workers — для длинных reindex-batch (Celery/RQ)
- ONNX Runtime — для ускорения инференса
- Health check с warmup — pre-load model weights
Миграция с v2.0.0
- Обновить
requirements.txt - Обновить
main.py - Обновить
Dockerfile(опционально) - Настроить Prometheus scraping на
/metrics - Добавить переменные окружения (опционально)
Breaking changes: Нет. Все endpoints совместимы.