Files
medtech-hackathon/etl/normalize/embedding.py
T
admins 1de66eaa85 feat(etl): нормализация на Gemini-эмбеддингах (API), Vision подключён
- эмбеддинги переведены с локального torch на Gemini API (лёгкий контейнер)
  GeminiEmbedder: gemini-embedding-001, 768d, RETRIEVAL query/document
- фильтр настоящих названий: коды/числа не уходят в сопоставление
- порог косинуса 0.70 → ~73% автонормализации (цель ТЗ ≥70%), остальное в unmatched
- Vision (gemini-2.5-flash, новый SDK) подключён в диспетчер для скан/нулевых PDF;
  проверено: Клиника 5 — 34 чистые позиции со страницы
- зависимости: убран torch/sentence-transformers, добавлен google-genai+numpy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:17:06 +05:00

60 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Эмбеддинги через Gemini API (SDK google-genai) — без локального torch.
Лёгкая зависимость вместо sentence-transformers: контейнер на маленьком VPS не
раздувается и не держит модель в RAM. Размерность 768 выбрана, чтобы вектор лез в
HNSW-индекс pgvector. Требуется переменная окружения GEMINI_API_KEY.
"""
from __future__ import annotations
import os
import numpy as np
EMBED_DIM = 768
class GeminiEmbedder:
"""Обёртка над Gemini-эмбеддингами с интерфейсом `.encode()` (как у sentence-transformers)."""
def __init__(
self,
model: str = "gemini-embedding-001",
dim: int = EMBED_DIM,
api_key: str | None = None,
batch: int = 100,
):
from google import genai
self._client = genai.Client(api_key=api_key or os.environ["GEMINI_API_KEY"])
self.model = model
self.dim = dim
self.batch = batch
def encode(
self,
texts,
normalize_embeddings: bool = True,
task_type: str = "RETRIEVAL_DOCUMENT",
show_progress_bar: bool = False,
):
"""Векторизовать список строк. Возвращает numpy-массив (n, dim) float32.
Для справочника используем RETRIEVAL_DOCUMENT, для запросов —
RETRIEVAL_QUERY: так несвязанные названия расходятся сильнее, чем при
SEMANTIC_SIMILARITY, и порог отсечения работает надёжнее.
"""
from google.genai import types
items = [t if t and t.strip() else " " for t in texts]
config = types.EmbedContentConfig(task_type=task_type, output_dimensionality=self.dim)
vectors: list[list[float]] = []
for start in range(0, len(items), self.batch):
chunk = items[start : start + self.batch]
response = self._client.models.embed_content(model=self.model, contents=chunk, config=config)
vectors.extend(embedding.values for embedding in response.embeddings)
array = np.array(vectors, dtype=np.float32)
if normalize_embeddings:
array = array / np.clip(np.linalg.norm(array, axis=1, keepdims=True), 1e-9, None)
return array