suggest-orgs / src /embeddings.py
amanydv2112's picture
Sync from GitHub via hub-sync
665ea76 verified
Raw
History Blame Contribute Delete
6.19 kB
"""Unified embedding interface supporting local, OpenAI, and Gemini backends."""
import os
import time
import numpy as np
from dotenv import load_dotenv
load_dotenv()
LOCAL_MODEL_NAME = "all-MiniLM-L6-v2"
OPENAI_MODEL_NAME = "text-embedding-3-small"
AZURE_OPENAI_MODEL_NAME = "text-embedding-3-large"
GEMINI_MODEL_NAME = "gemini-embedding-2"
BATCH_SIZE = 128
GEMINI_BATCH_SIZE = 100 # stay within Gemini rate limits
def get_provider() -> str:
return os.environ.get("EMBEDDING_PROVIDER", "local").lower()
def embeddings_filename(provider: str | None = None, multi: bool = False) -> str:
p = provider or get_provider()
suffix = ".multi" if multi else ""
return f"embeddings.{p}{suffix}.npy"
def default_min_score(provider: str | None = None) -> float:
p = provider or get_provider()
if p in ("openai", "azure", "gemini"):
return 0.45
return 0.35
class Embedder:
def __init__(self):
self.provider = get_provider()
if self.provider == "openai":
from openai import OpenAI
self._client = OpenAI()
self.dim = 1536
elif self.provider == "azure":
from openai import AzureOpenAI
self._client = AzureOpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-09-01-preview"),
)
self._azure_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", AZURE_OPENAI_MODEL_NAME)
self.dim = 3072
elif self.provider == "gemini":
from google import genai
self._client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
self.dim = 1536
else:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(LOCAL_MODEL_NAME)
self.dim = self._model.get_embedding_dimension()
def embed_batch(self, texts: list[str], show_progress: bool = True) -> np.ndarray:
if self.provider == "openai":
return self._embed_batch_openai(texts)
if self.provider == "azure":
return self._embed_batch_azure(texts)
if self.provider == "gemini":
return self._embed_batch_gemini(texts, show_progress)
return self._embed_batch_local(texts, show_progress)
def embed_query(self, query: str) -> np.ndarray:
if self.provider == "openai":
resp = self._client.embeddings.create(
model=OPENAI_MODEL_NAME, input=[query]
)
vec = np.array(resp.data[0].embedding, dtype=np.float32)
elif self.provider == "azure":
resp = self._client.embeddings.create(
model=self._azure_deployment, input=[query]
)
vec = np.array(resp.data[0].embedding, dtype=np.float32)
elif self.provider == "gemini":
resp = self._client.models.embed_content(
model=GEMINI_MODEL_NAME,
contents=query,
config={"output_dimensionality": 1536},
)
vec = np.array(resp.embeddings[0].values, dtype=np.float32)
else:
vec = self._model.encode(query, convert_to_numpy=True).astype(np.float32)
return vec / np.linalg.norm(vec)
def _embed_batch_azure(self, texts: list[str]) -> np.ndarray:
all_embeddings: list[list[float]] = []
total = len(texts)
for i in range(0, total, BATCH_SIZE):
batch = texts[i : i + BATCH_SIZE]
n = i // BATCH_SIZE + 1
total_batches = (total + BATCH_SIZE - 1) // BATCH_SIZE
print(f" Embedding batch {n}/{total_batches} ({len(batch)} texts) ...")
resp = self._client.embeddings.create(
model=self._azure_deployment, input=batch
)
all_embeddings.extend([d.embedding for d in resp.data])
return np.array(all_embeddings, dtype=np.float32)
def _embed_batch_openai(self, texts: list[str]) -> np.ndarray:
all_embeddings: list[list[float]] = []
total = len(texts)
for i in range(0, total, BATCH_SIZE):
batch = texts[i : i + BATCH_SIZE]
n = i // BATCH_SIZE + 1
total_batches = (total + BATCH_SIZE - 1) // BATCH_SIZE
print(f" Embedding batch {n}/{total_batches} ({len(batch)} texts) ...")
resp = self._client.embeddings.create(
model=OPENAI_MODEL_NAME, input=batch
)
all_embeddings.extend([d.embedding for d in resp.data])
return np.array(all_embeddings, dtype=np.float32)
def _embed_batch_gemini(self, texts: list[str], show_progress: bool) -> np.ndarray:
# The Gemini SDK returns one aggregated embedding for list inputs,
# so we embed one text at a time and rate-limit per GEMINI_BATCH_SIZE.
all_embeddings: list[list[float]] = []
total = len(texts)
for idx, text in enumerate(texts):
if show_progress and idx % GEMINI_BATCH_SIZE == 0:
batch_n = idx // GEMINI_BATCH_SIZE + 1
total_batches = (total + GEMINI_BATCH_SIZE - 1) // GEMINI_BATCH_SIZE
print(f" Embedding batch {batch_n}/{total_batches} ({min(GEMINI_BATCH_SIZE, total - idx)} texts) ...")
resp = self._client.models.embed_content(
model=GEMINI_MODEL_NAME,
contents=text,
config={"output_dimensionality": 1536},
)
all_embeddings.append(resp.embeddings[0].values)
# Brief pause every GEMINI_BATCH_SIZE texts to respect rate limits
if (idx + 1) % GEMINI_BATCH_SIZE == 0 and (idx + 1) < total:
time.sleep(1)
return np.array(all_embeddings, dtype=np.float32)
def _embed_batch_local(self, texts: list[str], show_progress: bool) -> np.ndarray:
return self._model.encode(
texts,
batch_size=BATCH_SIZE,
show_progress_bar=show_progress,
convert_to_numpy=True,
).astype(np.float32)