Spaces:
Sleeping
Sleeping
Download src/embeddings/embedder.py from Harshavard21/FinRAG: direct link, hf CLI and curl.
- Browser
- Download file 4.98 kB
-
https://huggingface.co/spaces/Harshavard21/FinRAG/resolve/main/src/embeddings/embedder.py
- Command line
-
hf download hf://spaces/Harshavard21/FinRAG/src/embeddings/embedder.py
-
curl -L -o embedder.py https://huggingface.co/spaces/Harshavard21/FinRAG/resolve/main/src/embeddings/embedder.py
4.98 kB
| """ | |
| src/embeddings/embedder.py | |
| =========================== | |
| Embedding engine using BAAI/bge-large-en-v1.5. | |
| Why BGE-large? | |
| - Free, runs locally on your RTX 3050 | |
| - Top of MTEB leaderboard for retrieval tasks (among free models) | |
| - 768-dimensional output β good balance of quality vs. memory | |
| - Trained with specific instruction prefix for queries vs. documents | |
| BGE-specific usage: | |
| Documents β embed as-is (no prefix) | |
| Queries β add "Represent this sentence: " prefix | |
| This asymmetric approach improves retrieval accuracy significantly. | |
| L2 Normalization: | |
| All embeddings are L2-normalized before storage. | |
| This makes cosine similarity = dot product β faster ANN search in Qdrant. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from typing import Union | |
| from tqdm import tqdm | |
| # config import first β sets USE_TF=0 before sentence_transformers loads | |
| from config.settings import settings | |
| from src.utils.logger import logger | |
| # Now safe to import | |
| from sentence_transformers import SentenceTransformer | |
| class Embedder: | |
| """ | |
| Singleton-pattern embedding engine. | |
| Loads BAAI/bge-large-en-v1.5 once and reuses it. | |
| GPU-accelerated on RTX 3050 automatically (via settings.get_device()). | |
| """ | |
| _instance: "Embedder | None" = None | |
| def __new__(cls): | |
| """Enforce singleton β only load the model once.""" | |
| if cls._instance is None: | |
| cls._instance = super().__new__(cls) | |
| cls._instance._initialized = False | |
| return cls._instance | |
| def __init__(self): | |
| if self._initialized: | |
| return | |
| self._load_model() | |
| self._initialized = True | |
| def _load_model(self): | |
| """Load the embedding model onto GPU/CPU.""" | |
| model_name = settings.embedding_model | |
| device = settings.get_device() | |
| logger.info(f"Loading embedding model: {model_name} β device: {device}") | |
| logger.info("(First load downloads ~1.3GB β subsequent loads use cache)") | |
| self.model = SentenceTransformer(model_name, device=device) | |
| self.model.max_seq_length = 512 # BGE-large max input length | |
| self.dim = settings.embedding_dim # 768 | |
| logger.info(f"Embedding model loaded | dim={self.dim} | device={device}") | |
| # ---------------------------------------------------------------- # | |
| # Public API | |
| # ---------------------------------------------------------------- # | |
| def embed_documents(self, texts: list[str]) -> np.ndarray: | |
| """ | |
| Embed a list of document chunks (corpus side). | |
| No instruction prefix needed for documents with BGE. | |
| Args: | |
| texts: List of text strings to embed | |
| Returns: | |
| np.ndarray of shape (len(texts), 768), L2-normalized | |
| """ | |
| if not texts: | |
| return np.array([]) | |
| logger.info(f"Embedding {len(texts)} documents in batches of {settings.embed_batch_size}...") | |
| embeddings = [] | |
| batch_size = settings.embed_batch_size | |
| for i in tqdm(range(0, len(texts), batch_size), desc="Embedding", unit="batch"): | |
| batch = texts[i: i + batch_size] | |
| batch_emb = self.model.encode( | |
| batch, | |
| batch_size=batch_size, | |
| show_progress_bar=False, | |
| normalize_embeddings=True, # L2 normalize in-place | |
| convert_to_numpy=True, | |
| ) | |
| embeddings.append(batch_emb) | |
| result = np.vstack(embeddings) | |
| logger.info(f"Embedding complete: shape={result.shape}") | |
| return result | |
| def embed_query(self, query: str) -> np.ndarray: | |
| """ | |
| Embed a single search query (query side). | |
| BGE models benefit from an instruction prefix on queries. | |
| Args: | |
| query: The user's question | |
| Returns: | |
| np.ndarray of shape (768,), L2-normalized | |
| """ | |
| # BGE instruction prefix β improves retrieval accuracy | |
| prefixed_query = f"Represent this sentence for searching relevant passages: {query}" | |
| embedding = self.model.encode( | |
| prefixed_query, | |
| normalize_embeddings=True, | |
| convert_to_numpy=True, | |
| show_progress_bar=False, | |
| ) | |
| return embedding | |
| def embed_queries(self, queries: list[str]) -> np.ndarray: | |
| """ | |
| Embed multiple queries (for multi-query retrieval expansion). | |
| Args: | |
| queries: List of question strings | |
| Returns: | |
| np.ndarray of shape (len(queries), 768) | |
| """ | |
| prefixed = [ | |
| f"Represent this sentence for searching relevant passages: {q}" | |
| for q in queries | |
| ] | |
| embeddings = self.model.encode( | |
| prefixed, | |
| normalize_embeddings=True, | |
| convert_to_numpy=True, | |
| show_progress_bar=False, | |
| ) | |
| return embeddings | |
| def embedding_dim(self) -> int: | |
| return self.dim | |