179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
import os
|
|
import numpy as np
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from typing import List, Tuple, Union, Optional, Dict, Literal
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import warnings
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings("ignore", category=UserWarning)
|
|
if "LITELLM_LOCAL_MODEL_COST_MAP" not in os.environ:
|
|
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
|
import litellm
|
|
|
|
litellm.drop_params = True
|
|
litellm.telemetry = False
|
|
|
|
from litellm.caching.caching import Cache
|
|
|
|
disk_cache_dir = os.path.join(Path.home(), ".storm_local_cache")
|
|
litellm.cache = Cache(disk_cache_dir=disk_cache_dir, type="disk")
|
|
|
|
except ImportError:
|
|
|
|
class LitellmPlaceholder:
|
|
def __getattr__(self, _):
|
|
raise ImportError(
|
|
"The LiteLLM package is not installed. Run `pip install litellm`."
|
|
)
|
|
|
|
litellm = LitellmPlaceholder()
|
|
|
|
|
|
class Encoder:
|
|
"""
|
|
A wrapper class for the LiteLLM embedding model, designed to handle embedding
|
|
generation tasks efficiently. It supports parallel processing and local caching of
|
|
embedding results for improved performance.
|
|
|
|
The Encoder utilizes the LiteLLM library to interact with various embedding models,
|
|
such as OpenAI and Azure embeddings. Users can specify the desired encoder type and
|
|
provide relevant API credentials during initialization.
|
|
|
|
Features:
|
|
- Support for multiple embedding models (e.g., OpenAI, Azure).
|
|
- Parallel processing for faster embedding generation.
|
|
- Local disk caching to store and reuse embedding results.
|
|
- Total token usage tracking for cost monitoring.
|
|
|
|
Note:
|
|
Refer to the LiteLLM documentation for details on supported embedding models:
|
|
https://docs.litellm.ai/docs/embedding/supported_embedding
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
encoder_type: Optional[str] = None,
|
|
api_key: Optional[str] = None,
|
|
api_base: Optional[str] = None,
|
|
api_version: Optional[str] = None,
|
|
):
|
|
"""
|
|
Initializes the Encoder with the appropriate embedding model.
|
|
|
|
Args:
|
|
encoder_type (Optional[str]): Type of encoder ('openai', 'azure', etc.).
|
|
api_key (Optional[str]): API key for the encoder service.
|
|
api_base (Optional[str]): API base URL for the encoder service.
|
|
api_version (Optional[str]): API version for the encoder service.
|
|
"""
|
|
self.embedding_model_name = None
|
|
self.kargs = {}
|
|
self.total_token_usage = 0
|
|
|
|
# Initialize the appropriate embedding model
|
|
encoder_type = encoder_type or os.getenv("ENCODER_API_TYPE")
|
|
if not encoder_type:
|
|
raise ValueError("ENCODER_API_TYPE environment variable is not set.")
|
|
|
|
if encoder_type.lower() == "openai":
|
|
self.embedding_model_name = "text-embedding-3-small"
|
|
self.kargs = {"api_key": api_key or os.getenv("OPENAI_API_KEY")}
|
|
elif encoder_type.lower() == "azure":
|
|
self.embedding_model_name = "azure/text-embedding-3-small"
|
|
self.kargs = {
|
|
"api_key": api_key or os.getenv("AZURE_API_KEY"),
|
|
"api_base": api_base or os.getenv("AZURE_API_BASE"),
|
|
"api_version": api_version or os.getenv("AZURE_API_VERSION"),
|
|
}
|
|
else:
|
|
raise ValueError(
|
|
f"Unsupported ENCODER_API_TYPE '{encoder_type}'. Supported types are 'openai', 'azure', 'together'."
|
|
)
|
|
|
|
def get_total_token_usage(self, reset: bool = False) -> int:
|
|
"""
|
|
Retrieves the total token usage.
|
|
|
|
Args:
|
|
reset (bool): If True, resets the total token usage counter after retrieval.
|
|
|
|
Returns:
|
|
int: The total number of tokens used.
|
|
"""
|
|
token_usage = self.total_token_usage
|
|
if reset:
|
|
self.total_token_usage = 0
|
|
return token_usage
|
|
|
|
def encode(self, texts: Union[str, List[str]], max_workers: int = 5) -> np.ndarray:
|
|
"""
|
|
Public method to get embeddings for the given texts.
|
|
|
|
Args:
|
|
texts (Union[str, List[str]]): A single text string or a list of text strings to embed.
|
|
|
|
Returns:
|
|
np.ndarray: The array of embeddings.
|
|
"""
|
|
return self._get_text_embeddings(texts, max_workers=max_workers)
|
|
|
|
def _get_single_text_embedding(self, text):
|
|
response = litellm.embedding(
|
|
model=self.embedding_model_name, input=text, caching=True, **self.kargs
|
|
)
|
|
embedding = response.data[0]["embedding"]
|
|
token_usage = response.get("usage", {}).get("total_tokens", 0)
|
|
return text, embedding, token_usage
|
|
|
|
def _get_text_embeddings(
|
|
self,
|
|
texts: Union[str, List[str]],
|
|
max_workers: int = 5,
|
|
) -> Tuple[np.ndarray, int]:
|
|
"""
|
|
Get text embeddings using OpenAI's text-embedding-3-small model.
|
|
|
|
Args:
|
|
texts (Union[str, List[str]]): A single text string or a list of text strings to embed.
|
|
max_workers (int): The maximum number of workers for parallel processing.
|
|
api_key (str): The API key for accessing OpenAI's services.
|
|
embedding_cache (Optional[Dict[str, np.ndarray]]): A cache to store previously computed embeddings.
|
|
|
|
Returns:
|
|
Tuple[np.ndarray, int]: The 2D array of embeddings and the total token usage.
|
|
"""
|
|
|
|
if isinstance(texts, str):
|
|
_, embedding, tokens = self._get_single_text_embedding(texts)
|
|
self.total_token_usage += tokens
|
|
return np.array(embedding)
|
|
|
|
embeddings = []
|
|
total_tokens = 0
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
futures = {
|
|
executor.submit(self._get_single_text_embedding, text): text
|
|
for text in texts
|
|
}
|
|
|
|
for future in as_completed(futures):
|
|
try:
|
|
text, embedding, tokens = future.result()
|
|
embeddings.append((text, embedding, tokens))
|
|
total_tokens += tokens
|
|
except Exception as e:
|
|
print(f"An error occurred for text: {futures[future]}")
|
|
print(e)
|
|
|
|
# Sort results to match the order of the input texts
|
|
embeddings.sort(key=lambda x: texts.index(x[0]))
|
|
embeddings = [result[1] for result in embeddings]
|
|
self.total_token_usage += total_tokens
|
|
|
|
return np.array(embeddings)
|