techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 4)
4b9d59b verified
Raw
History Blame Contribute Delete
17.4 kB
"""
Multi-provider embedding implementations.
This module provides concrete implementations of BaseEmbeddingProvider for:
- OpenAI (text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002)
- Cohere (embed-english-v3.0, embed-multilingual-v3.0, embed-english-light-v3.0)
- Voyage (voyage-2, voyage-large-2, voyage-code-2)
- Nomic (nomic-embed-text-v1.5, nomic-embed-text-v1)
- Jina (jina-embeddings-v2, jina-embeddings-v3)
"""
import asyncio
from typing import List, Dict, Any
from .base import (
BaseEmbeddingProvider,
EmbeddingProviderError,
EmbeddingRateLimitError,
EmbeddingContextLimitError)
class OpenAIEmbeddingProvider(BaseEmbeddingProvider):
"""
OpenAI embedding provider using text-embedding-3 models.
Models:
- text-embedding-3-small: 1536 dimensions, $0.02/1M tokens
- text-embedding-3-large: 3072 dimensions, $0.13/1M tokens
- text-embedding-ada-002: 1536 dimensions, $0.10/1M tokens
"""
# Model specifications
MODELS = {
"text-embedding-3-small": {
"dimensions": 1536,
"context_limit": 8191,
"cost_per_1m_tokens": 0.02,
},
"text-embedding-3-large": {
"dimensions": 3072,
"context_limit": 8191,
"cost_per_1m_tokens": 0.13,
},
"text-embedding-ada-002": {
"dimensions": 1536,
"context_limit": 8191,
"cost_per_1m_tokens": 0.10,
},
}
def __init__(self, api_key: str = None):
super().__init__(api_key)
try:
from openai import AsyncOpenAI
self._client = AsyncOpenAI(api_key=api_key)
except ImportError:
raise EmbeddingProviderError(
"OpenAI package not installed. Install with: pip install openai"
)
async def generate_embedding(self, text: str, model: str) -> List[float]:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embeddings.create(
input=text, model=model, encoding_format="float"
)
return response.data[0].embedding
except Exception as e:
if "rate" in str(e).lower():
raise EmbeddingRateLimitError(f"OpenAI rate limit: {e}")
raise EmbeddingProviderError(f"OpenAI API error: {e}")
async def generate_embeddings_batch(
self, texts: List[str], model: str
) -> List[List[float]]:
if not texts:
return []
for text in texts:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embeddings.create(
input=texts, model=model, encoding_format="float"
)
return [item.embedding for item in response.data]
except Exception as e:
if "rate" in str(e).lower():
raise EmbeddingRateLimitError(f"OpenAI rate limit: {e}")
raise EmbeddingProviderError(f"OpenAI API error: {e}")
def get_model_name(self, model_id: str) -> str:
names = {
"text-embedding-3-small": "OpenAI text-embedding-3-small",
"text-embedding-3-large": "OpenAI text-embedding-3-large",
"text-embedding-ada-002": "OpenAI text-embedding-ada-002",
}
return names.get(model_id, model_id)
def estimate_cost(self, text: str, model: str) -> float:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
tokens = self._estimate_tokens(text)
cost_per_1m = self.MODELS[model]["cost_per_1m_tokens"]
return (tokens / 1_000_000) * cost_per_1m
def get_context_limit(self, model: str) -> int:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
return self.MODELS[model]["context_limit"]
def get_provider_name(self) -> str:
return "OpenAI"
class CohereEmbeddingProvider(BaseEmbeddingProvider):
"""
Cohere embedding provider using embed-v3 models.
Models:
- embed-english-v3.0: 1024 dimensions, $0.10/1M tokens
- embed-multilingual-v3.0: 1024 dimensions, $0.15/1M tokens
- embed-english-light-v3.0: 384 dimensions, $0.05/1M tokens
"""
MODELS = {
"embed-english-v3.0": {
"dimensions": 1024,
"context_limit": 512,
"cost_per_1m_tokens": 0.10,
},
"embed-multilingual-v3.0": {
"dimensions": 1024,
"context_limit": 512,
"cost_per_1m_tokens": 0.15,
},
"embed-english-light-v3.0": {
"dimensions": 384,
"context_limit": 512,
"cost_per_1m_tokens": 0.05,
},
}
def __init__(self, api_key: str = None):
super().__init__(api_key)
try:
import cohere
self._client = cohere.AsyncClient(api_key=api_key)
except ImportError:
raise EmbeddingProviderError(
"Cohere package not installed. Install with: pip install cohere"
)
async def generate_embedding(self, text: str, model: str) -> List[float]:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embed(
texts=[text], model=model, input_type="search_document"
)
return response.embeddings[0]
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Cohere rate limit: {e}")
raise EmbeddingProviderError(f"Cohere API error: {e}")
async def generate_embeddings_batch(
self, texts: List[str], model: str
) -> List[List[float]]:
if not texts:
return []
for text in texts:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embed(
texts=texts, model=model, input_type="search_document"
)
return response.embeddings
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Cohere rate limit: {e}")
raise EmbeddingProviderError(f"Cohere API error: {e}")
def get_model_name(self, model_id: str) -> str:
names = {
"embed-english-v3.0": "Cohere embed-english-v3.0",
"embed-multilingual-v3.0": "Cohere embed-multilingual-v3.0",
"embed-english-light-v3.0": "Cohere embed-english-light-v3.0",
}
return names.get(model_id, model_id)
def estimate_cost(self, text: str, model: str) -> float:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
tokens = self._estimate_tokens(text)
cost_per_1m = self.MODELS[model]["cost_per_1m_tokens"]
return (tokens / 1_000_000) * cost_per_1m
def get_context_limit(self, model: str) -> int:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
return self.MODELS[model]["context_limit"]
def get_provider_name(self) -> str:
return "Cohere"
class VoyageEmbeddingProvider(BaseEmbeddingProvider):
"""
Voyage AI embedding provider.
Models:
- voyage-2: 1024 dimensions, $0.10/1M tokens
- voyage-large-2: 1536 dimensions, $0.25/1M tokens
- voyage-code-2: 1536 dimensions, $0.15/1M tokens
"""
MODELS = {
"voyage-2": {
"dimensions": 1024,
"context_limit": 128,
"cost_per_1m_tokens": 0.10,
},
"voyage-large-2": {
"dimensions": 1536,
"context_limit": 128,
"cost_per_1m_tokens": 0.25,
},
"voyage-code-2": {
"dimensions": 1536,
"context_limit": 128,
"cost_per_1m_tokens": 0.15,
},
}
def __init__(self, api_key: str = None):
super().__init__(api_key)
try:
import voyageai
self._client = voyageai.Client(api_key=api_key)
except ImportError:
raise EmbeddingProviderError(
"VoyageAI package not installed. Install with: pip install voyageai"
)
async def generate_embedding(self, text: str, model: str) -> List[float]:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
# Voyage AI doesn't have native async, use thread pool
result = await asyncio.to_thread(
self._client.embed, text, model=model, input_type="document"
)
return result[0]
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Voyage rate limit: {e}")
raise EmbeddingProviderError(f"Voyage API error: {e}")
async def generate_embeddings_batch(
self, texts: List[str], model: str
) -> List[List[float]]:
if not texts:
return []
for text in texts:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
result = await asyncio.to_thread(
self._client.embed, texts, model=model, input_type="document"
)
return result
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Voyage rate limit: {e}")
raise EmbeddingProviderError(f"Voyage API error: {e}")
def get_model_name(self, model_id: str) -> str:
names = {
"voyage-2": "Voyage voyage-2",
"voyage-large-2": "Voyage voyage-large-2",
"voyage-code-2": "Voyage voyage-code-2",
}
return names.get(model_id, model_id)
def estimate_cost(self, text: str, model: str) -> float:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
tokens = self._estimate_tokens(text)
cost_per_1m = self.MODELS[model]["cost_per_1m_tokens"]
return (tokens / 1_000_000) * cost_per_1m
def get_context_limit(self, model: str) -> int:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
return self.MODELS[model]["context_limit"]
def get_provider_name(self) -> str:
return "Voyage"
class NomicEmbeddingProvider(BaseEmbeddingProvider):
"""
Nomic AI embedding provider.
Models:
- nomic-embed-text-v1.5: 768 dimensions, $0.08/1M tokens
- nomic-embed-text-v1: 768 dimensions, $0.10/1M tokens
"""
MODELS = {
"nomic-embed-text-v1.5": {
"dimensions": 768,
"context_limit": 8192,
"cost_per_1m_tokens": 0.08,
},
"nomic-embed-text-v1": {
"dimensions": 768,
"context_limit": 8192,
"cost_per_1m_tokens": 0.10,
},
}
def __init__(self, api_key: str = None):
super().__init__(api_key)
try:
import nomic
self._client = nomic.Embedding(api_key=api_key)
except ImportError:
raise EmbeddingProviderError(
"Nomic package not installed. Install with: pip install nomic"
)
async def generate_embedding(self, text: str, model: str) -> List[float]:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
result = await asyncio.to_thread(
self._client.embed, texts=[text], model=model, task_type="search_document"
)
return result["embeddings"][0]
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Nomic rate limit: {e}")
raise EmbeddingProviderError(f"Nomic API error: {e}")
async def generate_embeddings_batch(
self, texts: List[str], model: str
) -> List[List[float]]:
if not texts:
return []
for text in texts:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
result = await asyncio.to_thread(
self._client.embed, texts=texts, model=model, task_type="search_document"
)
return result["embeddings"]
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Nomic rate limit: {e}")
raise EmbeddingProviderError(f"Nomic API error: {e}")
def get_model_name(self, model_id: str) -> str:
names = {
"nomic-embed-text-v1.5": "Nomic nomic-embed-text-v1.5",
"nomic-embed-text-v1": "Nomic nomic-embed-text-v1",
}
return names.get(model_id, model_id)
def estimate_cost(self, text: str, model: str) -> float:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
tokens = self._estimate_tokens(text)
cost_per_1m = self.MODELS[model]["cost_per_1m_tokens"]
return (tokens / 1_000_000) * cost_per_1m
def get_context_limit(self, model: str) -> int:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
return self.MODELS[model]["context_limit"]
def get_provider_name(self) -> str:
return "Nomic"
class JinaEmbeddingProvider(BaseEmbeddingProvider):
"""
Jina AI embedding provider using OpenAI-compatible API.
Models:
- jina-embeddings-v2: 768 dimensions, $0.02/1M tokens
- jina-embeddings-v3: 1024 dimensions, $0.03/1M tokens
"""
MODELS = {
"jina-embeddings-v2": {
"dimensions": 768,
"context_limit": 8192,
"cost_per_1m_tokens": 0.02,
},
"jina-embeddings-v3": {
"dimensions": 1024,
"context_limit": 8191,
"cost_per_1m_tokens": 0.03,
},
}
BASE_URL = "https://api.jina.ai/v1"
def __init__(self, api_key: str = None):
super().__init__(api_key)
try:
from openai import AsyncOpenAI
self._client = AsyncOpenAI(
api_key=api_key, base_url=self.BASE_URL
)
except ImportError:
raise EmbeddingProviderError(
"OpenAI package not installed. Install with: pip install openai"
)
async def generate_embedding(self, text: str, model: str) -> List[float]:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embeddings.create(
input=text, model=model
)
return response.data[0].embedding
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Jina rate limit: {e}")
raise EmbeddingProviderError(f"Jina API error: {e}")
async def generate_embeddings_batch(
self, texts: List[str], model: str
) -> List[List[float]]:
if not texts:
return []
for text in texts:
self._validate_text_input(text)
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
try:
response = await self._client.embeddings.create(
input=texts, model=model
)
return [item.embedding for item in response.data]
except Exception as e:
if "rate" in str(e).lower() or "429" in str(e):
raise EmbeddingRateLimitError(f"Jina rate limit: {e}")
raise EmbeddingProviderError(f"Jina API error: {e}")
def get_model_name(self, model_id: str) -> str:
names = {
"jina-embeddings-v2": "Jina jina-embeddings-v2",
"jina-embeddings-v3": "Jina jina-embeddings-v3",
}
return names.get(model_id, model_id)
def estimate_cost(self, text: str, model: str) -> float:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
tokens = self._estimate_tokens(text)
cost_per_1m = self.MODELS[model]["cost_per_1m_tokens"]
return (tokens / 1_000_000) * cost_per_1m
def get_context_limit(self, model: str) -> int:
if model not in self.MODELS:
raise ValueError(f"Unknown model: {model}")
return self.MODELS[model]["context_limit"]
def get_provider_name(self) -> str:
return "Jina"
__all__ = [
"OpenAIEmbeddingProvider",
"CohereEmbeddingProvider",
"VoyageEmbeddingProvider",
"NomicEmbeddingProvider",
"JinaEmbeddingProvider",
]