Source code for corerec.embeddings.text

"""
Text Embeddings

Encode text into dense vectors using transformer models.
"""

from typing import Any, List, Optional, Union
import numpy as np


[docs] class TextEncoder: """ Encode text into embeddings using sentence transformers or similar. Wraps sentence-transformers or other text encoding models with a simple interface. Example: encoder = TextEncoder("sentence-transformers/all-MiniLM-L6-v2") # single text emb = encoder.encode("red running shoes") # batch embs = encoder.encode(["red shoes", "blue jacket", "green hat"]) """
[docs] def __init__( self, model: Optional[Union[str, Any]] = None, device: str = "cpu", normalize: bool = True, ): """ Args: model: model name or pre-loaded model instance device: 'cpu' or 'cuda' normalize: whether to L2-normalize embeddings """ self.model_name = model if isinstance(model, str) else None self._model = None if isinstance(model, str) else model self.device = device self.normalize = normalize self._dim: Optional[int] = None
@property def model(self): """Lazy load model on first use.""" if self._model is None: self._model = self._load_model(self.model_name) return self._model @property def embedding_dim(self) -> int: """Get embedding dimension.""" if self._dim is None: _ = self.model # ensure loaded if hasattr(self._model, 'get_sentence_embedding_dimension'): self._dim = self._model.get_sentence_embedding_dimension() else: # probe by encoding something test = self.encode("test") self._dim = len(test) return self._dim def _load_model(self, model_name: str): """Load the embedding model.""" if model_name is None: model_name = "sentence-transformers/all-MiniLM-L6-v2" try: from sentence_transformers import SentenceTransformer return SentenceTransformer(model_name, device=self.device) except ImportError: raise ImportError( "sentence-transformers required. Install: pip install sentence-transformers" )
[docs] def encode( self, texts: Union[str, List[str]], batch_size: int = 32, show_progress: bool = False, ) -> np.ndarray: """ Encode text(s) into embeddings. Args: texts: single string or list of strings batch_size: batch size for encoding show_progress: show progress bar Returns: numpy array of shape (n, dim) or (dim,) for single text """ single = isinstance(texts, str) if single: texts = [texts] embeddings = self.model.encode( texts, batch_size=batch_size, show_progress_bar=show_progress, convert_to_numpy=True, ) if self.normalize: norms = np.linalg.norm(embeddings, axis=1, keepdims=True) norms = np.maximum(norms, 1e-10) embeddings = embeddings / norms if single: return embeddings[0] return embeddings
[docs] def similarity( self, text_a: Union[str, np.ndarray], text_b: Union[str, np.ndarray], ) -> float: """ Compute similarity between two texts. Args: text_a: text or embedding text_b: text or embedding Returns: cosine similarity score """ if isinstance(text_a, str): emb_a = self.encode(text_a) else: emb_a = text_a if isinstance(text_b, str): emb_b = self.encode(text_b) else: emb_b = text_b # cosine similarity (already normalized if self.normalize=True) return float(np.dot(emb_a, emb_b))
def __repr__(self) -> str: return f"TextEncoder(model={self.model_name}, dim={self._dim})"