mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
Initial Commit 🚀 🚀
This commit is contained in:
commit
4f2a629340
444 changed files with 76863 additions and 0 deletions
0
api/services/configuration/__init__.py
Normal file
0
api/services/configuration/__init__.py
Normal file
153
api/services/configuration/check_validity.py
Normal file
153
api/services/configuration/check_validity.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
from typing import Dict, Optional, TypedDict
|
||||
|
||||
import openai
|
||||
from deepgram import (
|
||||
DeepgramClient,
|
||||
LiveOptions,
|
||||
)
|
||||
from groq import Groq
|
||||
|
||||
# try:
|
||||
# from pyneuphonic import Neuphonic
|
||||
# except ImportError:
|
||||
# Neuphonic = None
|
||||
from api.schemas.user_configuration import (
|
||||
UserConfiguration,
|
||||
)
|
||||
from api.services.configuration.registry import ServiceConfig, ServiceProviders
|
||||
|
||||
|
||||
class APIKeyStatus(TypedDict):
|
||||
model: str
|
||||
message: str
|
||||
|
||||
|
||||
class APIKeyStatusResponse(TypedDict):
|
||||
status: list[APIKeyStatus]
|
||||
|
||||
|
||||
class UserConfigurationValidator:
|
||||
def __init__(self):
|
||||
self._provider_api_key_validity_status: Dict[str, bool] = {}
|
||||
self._validator_map = {
|
||||
ServiceProviders.OPENAI.value: self._check_openai_api_key,
|
||||
ServiceProviders.DEEPGRAM.value: self._check_deepgram_api_key,
|
||||
ServiceProviders.GROQ.value: self._check_groq_api_key,
|
||||
ServiceProviders.ELEVENLABS.value: self._validate_elevenlabs_api_key,
|
||||
ServiceProviders.GOOGLE.value: self._check_google_api_key,
|
||||
ServiceProviders.AZURE.value: self._check_azure_api_key,
|
||||
ServiceProviders.CARTESIA.value: self._check_cartesia_api_key,
|
||||
ServiceProviders.DOGRAH.value: self._check_dograh_api_key,
|
||||
}
|
||||
|
||||
async def validate(self, configuration: UserConfiguration) -> APIKeyStatusResponse:
|
||||
status_list = []
|
||||
|
||||
status_list.extend(self._validate_service(configuration.llm, "llm"))
|
||||
status_list.extend(self._validate_service(configuration.stt, "stt"))
|
||||
status_list.extend(self._validate_service(configuration.tts, "tts"))
|
||||
|
||||
if status_list:
|
||||
raise ValueError(status_list)
|
||||
|
||||
return {"status": [{"model": "all", "message": "ok"}]}
|
||||
|
||||
def _validate_service(
|
||||
self, service_config: Optional[ServiceConfig], service_name: str
|
||||
) -> list[APIKeyStatus]:
|
||||
"""Validate a service configuration and return any error statuses."""
|
||||
if not service_config:
|
||||
return [{"model": service_name, "message": "API key is missing"}]
|
||||
|
||||
provider = service_config.provider
|
||||
api_key = service_config.api_key
|
||||
|
||||
if not self._check_api_key(provider, api_key):
|
||||
return [{"model": service_name, "message": f"Invalid {provider} API key"}]
|
||||
|
||||
return []
|
||||
|
||||
def _check_api_key(self, provider: str, api_key: str) -> bool:
|
||||
"""Check if an API key for a provider is valid."""
|
||||
validator = self._validator_map.get(provider)
|
||||
if not validator:
|
||||
return False
|
||||
|
||||
return validator(provider, api_key)
|
||||
|
||||
def _check_openai_api_key(self, model: str, api_key: str) -> bool:
|
||||
if model in self._provider_api_key_validity_status:
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
client = openai.OpenAI(api_key=api_key)
|
||||
try:
|
||||
client.models.list()
|
||||
self._provider_api_key_validity_status[model] = True
|
||||
except openai.AuthenticationError:
|
||||
self._provider_api_key_validity_status[model] = False
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
def _check_deepgram_api_key(self, model: str, api_key: str) -> bool:
|
||||
if model in self._provider_api_key_validity_status:
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
deepgram = DeepgramClient(api_key)
|
||||
dg_connection = deepgram.listen.websocket.v("1")
|
||||
|
||||
try:
|
||||
options = LiveOptions(
|
||||
model="nova-2",
|
||||
language="en-US",
|
||||
smart_format=True,
|
||||
)
|
||||
|
||||
connected = dg_connection.start(options)
|
||||
self._provider_api_key_validity_status[model] = connected
|
||||
finally:
|
||||
dg_connection.finish()
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
def _check_groq_api_key(self, model: str, api_key: str) -> bool:
|
||||
if model in self._provider_api_key_validity_status:
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
client = Groq(api_key=api_key)
|
||||
try:
|
||||
client.models.list()
|
||||
self._provider_api_key_validity_status[model] = True
|
||||
except Exception:
|
||||
self._provider_api_key_validity_status[model] = False
|
||||
return self._provider_api_key_validity_status[model]
|
||||
|
||||
def _validate_elevenlabs_api_key(self, model: str, api_key: str) -> bool:
|
||||
return True
|
||||
|
||||
def _check_google_api_key(self, model: str, api_key: str) -> bool:
|
||||
return True
|
||||
|
||||
def _check_azure_api_key(self, model: str, api_key: str) -> bool:
|
||||
return True
|
||||
|
||||
def _check_cartesia_api_key(self, model: str, api_key: str) -> bool:
|
||||
return True
|
||||
|
||||
def _check_dograh_api_key(self, model: str, api_key: str) -> bool:
|
||||
return True
|
||||
|
||||
# def _check_neuphonic_api_key(self, model: str, api_key: str) -> bool:
|
||||
# if not Neuphonic:
|
||||
# self._provider_api_key_validity_status[model] = False
|
||||
# return self._provider_api_key_validity_status[model]
|
||||
|
||||
# if model in self._provider_api_key_validity_status:
|
||||
# return self._provider_api_key_validity_status[model]
|
||||
|
||||
# client = Neuphonic(api_key=api_key)
|
||||
# try:
|
||||
# response = client.voices.list() # get's all available voices
|
||||
# voices = response.data["voices"]
|
||||
# self._provider_api_key_validity_status[model] = True
|
||||
# except Exception:
|
||||
# self._provider_api_key_validity_status[model] = False
|
||||
|
||||
# return self._provider_api_key_validity_status[model]
|
||||
34
api/services/configuration/defaults.py
Normal file
34
api/services/configuration/defaults.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from __future__ import annotations
|
||||
|
||||
"""Utilities for building default service configurations for a new user.
|
||||
|
||||
The defaults follow the same provider choices exposed by `/user/configurations/defaults`.
|
||||
Values for `api_key` are pulled from environment variables named *{PROVIDER}_API_KEY*.
|
||||
|
||||
If an environment variable is missing, that particular provider configuration is
|
||||
left as ``None``.
|
||||
"""
|
||||
|
||||
|
||||
from api.services.configuration.registry import (
|
||||
DeepgramSTTConfiguration,
|
||||
ElevenlabsTTSConfiguration,
|
||||
OpenAILLMService,
|
||||
ServiceProviders,
|
||||
)
|
||||
|
||||
# Mapping of service to (provider enum, configuration class)
|
||||
_DEFAULTS = {
|
||||
"llm": (ServiceProviders.OPENAI, OpenAILLMService),
|
||||
"tts": (ServiceProviders.ELEVENLABS, ElevenlabsTTSConfiguration),
|
||||
"stt": (ServiceProviders.DEEPGRAM, DeepgramSTTConfiguration),
|
||||
}
|
||||
|
||||
# Public mapping of service name -> default provider
|
||||
DEFAULT_SERVICE_PROVIDERS = {
|
||||
field: provider for field, (provider, _) in _DEFAULTS.items()
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_SERVICE_PROVIDERS",
|
||||
]
|
||||
69
api/services/configuration/masking.py
Normal file
69
api/services/configuration/masking.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
"""Utilities for masking API keys before they are sent to the client.
|
||||
|
||||
The rules are simple:
|
||||
1. Only expose the last *visible* characters (default 4) of a key.
|
||||
2. Incoming masked keys are considered a placeholder – if they equal the mask of
|
||||
the already-stored key, we treat them as *unchanged* and keep the real value
|
||||
in storage.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from api.schemas.user_configuration import UserConfiguration
|
||||
from api.services.configuration.registry import ServiceConfig
|
||||
|
||||
VISIBLE_CHARS = 4 # number of trailing characters to reveal
|
||||
MASK_CHAR = "*"
|
||||
|
||||
|
||||
def mask_key(real_key: str, visible: int = VISIBLE_CHARS) -> str:
|
||||
"""Return a masked representation of *real_key*.
|
||||
|
||||
Example:
|
||||
>>> mask_key("sk-1234567890abcdef")
|
||||
'****************cdef'
|
||||
"""
|
||||
if real_key is None:
|
||||
return ""
|
||||
|
||||
if visible <= 0 or visible >= len(real_key):
|
||||
# mask entire key or nothing to mask – edge-cases
|
||||
return MASK_CHAR * len(real_key)
|
||||
|
||||
masked_part = MASK_CHAR * (len(real_key) - visible)
|
||||
return f"{masked_part}{real_key[-visible:]}"
|
||||
|
||||
|
||||
def is_mask_of(masked: str, real_key: str) -> bool:
|
||||
"""Return *True* if *masked* equals the mask of *real_key* under the current rules."""
|
||||
return mask_key(real_key) == masked
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# High-level helpers for UserConfiguration objects
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _mask_service(service_cfg: Optional[ServiceConfig]) -> Optional[Dict[str, Any]]:
|
||||
if service_cfg is None:
|
||||
return None
|
||||
|
||||
# Work on a dict copy so we don't mutate original models
|
||||
data = service_cfg.model_dump()
|
||||
if "api_key" in data and data["api_key"]:
|
||||
data["api_key"] = mask_key(data["api_key"])
|
||||
return data
|
||||
|
||||
|
||||
def mask_user_config(config: UserConfiguration) -> Dict[str, Any]:
|
||||
"""Return a JSON-serialisable dict of *config* with every api_key masked."""
|
||||
|
||||
return {
|
||||
"llm": _mask_service(config.llm),
|
||||
"tts": _mask_service(config.tts),
|
||||
"stt": _mask_service(config.stt),
|
||||
"test_phone_number": config.test_phone_number,
|
||||
"timezone": config.timezone,
|
||||
}
|
||||
75
api/services/configuration/merge.py
Normal file
75
api/services/configuration/merge.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
from __future__ import annotations
|
||||
|
||||
"""Helpers for merging incoming user-configuration updates with what is already
|
||||
stored, while honouring masked API keys.
|
||||
"""
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from api.schemas.user_configuration import UserConfiguration
|
||||
from api.services.configuration.masking import is_mask_of
|
||||
|
||||
SERVICE_FIELDS = ("llm", "tts", "stt")
|
||||
|
||||
|
||||
def merge_user_configurations(
|
||||
existing: UserConfiguration, incoming_partial: Dict[str, dict]
|
||||
) -> UserConfiguration:
|
||||
"""Merge *incoming_partial* onto *existing* and return a new UserConfiguration.
|
||||
|
||||
*incoming_partial* is the body of the PUT request (already `model_dump()`ed or
|
||||
extracted via Pydantic `model_dump`).
|
||||
|
||||
Rules:
|
||||
1. If a service block is absent in the request, keep the existing one.
|
||||
2. If provider unchanged and the api_key field is either missing or equal to
|
||||
the masked placeholder, preserve the existing real key.
|
||||
3. If provider changes, the incoming api_key is used verbatim (validation
|
||||
will fail later if it is missing).
|
||||
4. Non-service top-level fields (e.g. `test_phone_number`) are overwritten
|
||||
when supplied.
|
||||
"""
|
||||
|
||||
merged = existing.model_dump(exclude_none=True)
|
||||
|
||||
def _merge_service_block(service_name: str):
|
||||
incoming_cfg = incoming_partial.get(service_name)
|
||||
if incoming_cfg is None:
|
||||
return # nothing to do
|
||||
|
||||
old_cfg = merged.get(service_name, {})
|
||||
|
||||
provider_changed = (
|
||||
old_cfg.get("provider") is not None
|
||||
and incoming_cfg.get("provider") is not None
|
||||
and incoming_cfg.get("provider") != old_cfg.get("provider")
|
||||
)
|
||||
|
||||
incoming_api_key = incoming_cfg.get("api_key")
|
||||
|
||||
if not provider_changed:
|
||||
# conditional preservation of api_key
|
||||
if incoming_api_key is not None:
|
||||
if (
|
||||
old_cfg
|
||||
and "api_key" in old_cfg
|
||||
and is_mask_of(incoming_api_key, old_cfg["api_key"])
|
||||
):
|
||||
incoming_cfg["api_key"] = old_cfg["api_key"]
|
||||
else:
|
||||
if "api_key" in old_cfg:
|
||||
incoming_cfg["api_key"] = old_cfg["api_key"]
|
||||
|
||||
merged[service_name] = incoming_cfg
|
||||
|
||||
for service in SERVICE_FIELDS:
|
||||
_merge_service_block(service)
|
||||
|
||||
# other simple fields
|
||||
if "test_phone_number" in incoming_partial:
|
||||
merged["test_phone_number"] = incoming_partial["test_phone_number"]
|
||||
|
||||
if "timezone" in incoming_partial:
|
||||
merged["timezone"] = incoming_partial["timezone"]
|
||||
|
||||
return UserConfiguration.model_validate(merged)
|
||||
356
api/services/configuration/registry.py
Normal file
356
api/services/configuration/registry.py
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
from enum import Enum, auto
|
||||
from typing import Annotated, Dict, Literal, Type, TypeVar, Union
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
|
||||
|
||||
class ServiceType(Enum):
|
||||
LLM = auto()
|
||||
TTS = auto()
|
||||
STT = auto()
|
||||
|
||||
|
||||
class ServiceProviders(str, Enum):
|
||||
OPENAI = "openai"
|
||||
DEEPGRAM = "deepgram"
|
||||
GROQ = "groq"
|
||||
CARTESIA = "cartesia"
|
||||
# NEUPHONIC = "neuphonic"
|
||||
ELEVENLABS = "elevenlabs"
|
||||
GOOGLE = "google"
|
||||
AZURE = "azure"
|
||||
DOGRAH = "dograh"
|
||||
|
||||
|
||||
class BaseServiceConfiguration(BaseModel):
|
||||
provider: Literal[
|
||||
ServiceProviders.OPENAI,
|
||||
ServiceProviders.DEEPGRAM,
|
||||
ServiceProviders.GROQ,
|
||||
ServiceProviders.ELEVENLABS,
|
||||
ServiceProviders.GOOGLE,
|
||||
ServiceProviders.AZURE,
|
||||
ServiceProviders.DOGRAH,
|
||||
]
|
||||
api_key: str
|
||||
|
||||
|
||||
class BaseLLMConfiguration(BaseServiceConfiguration):
|
||||
model: str
|
||||
|
||||
|
||||
class BaseTTSConfiguration(BaseServiceConfiguration):
|
||||
model: str
|
||||
|
||||
|
||||
class BaseSTTConfiguration(BaseServiceConfiguration):
|
||||
model: str
|
||||
|
||||
|
||||
# Unified registry for all service types
|
||||
REGISTRY: Dict[ServiceType, Dict[str, Type[BaseServiceConfiguration]]] = {
|
||||
ServiceType.LLM: {},
|
||||
ServiceType.TTS: {},
|
||||
ServiceType.STT: {},
|
||||
}
|
||||
|
||||
T = TypeVar("T", bound=BaseServiceConfiguration)
|
||||
|
||||
|
||||
def register_service(service_type: ServiceType):
|
||||
"""Generic decorator for registering service configurations"""
|
||||
|
||||
def decorator(cls: Type[T]) -> Type[T]:
|
||||
# Get provider from class attributes or field defaults
|
||||
provider = getattr(cls, "provider", None)
|
||||
if provider is None:
|
||||
# Try to get from model fields
|
||||
provider = cls.model_fields.get("provider", None)
|
||||
if provider is not None:
|
||||
provider = provider.default
|
||||
if provider is None:
|
||||
raise ValueError(f"Provider not specified for {cls.__name__}")
|
||||
|
||||
REGISTRY[service_type][provider] = cls
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Convenience decorators
|
||||
def register_llm(cls: Type[BaseLLMConfiguration]):
|
||||
return register_service(ServiceType.LLM)(cls)
|
||||
|
||||
|
||||
def register_tts(cls: Type[BaseTTSConfiguration]):
|
||||
return register_service(ServiceType.TTS)(cls)
|
||||
|
||||
|
||||
def register_stt(cls: Type[BaseSTTConfiguration]):
|
||||
return register_service(ServiceType.STT)(cls)
|
||||
|
||||
|
||||
###################################################### LLM ########################################################################
|
||||
|
||||
|
||||
class OpenAIModel(str, Enum):
|
||||
GPT3_5_TURBO = "gpt-3.5-turbo"
|
||||
GPT4_1 = "gpt-4.1"
|
||||
GPT4_1_MINI = "gpt-4.1-mini"
|
||||
GPT4_1_NANO = "gpt-4.1-nano"
|
||||
GPT5 = "gpt-5"
|
||||
GPT5_MINI = "gpt-5-mini"
|
||||
GPT5_NANO = "gpt-5-nano"
|
||||
|
||||
|
||||
@register_llm
|
||||
class OpenAILLMService(BaseLLMConfiguration):
|
||||
provider: Literal[ServiceProviders.OPENAI] = ServiceProviders.OPENAI
|
||||
model: OpenAIModel = OpenAIModel.GPT4_1
|
||||
api_key: str
|
||||
|
||||
|
||||
class GoogleModel(str, Enum):
|
||||
GEMINI_2_0_FLASH = "gemini-2.0-flash"
|
||||
GEMINI_2_0_FLASH_LITE = "gemini-2.0-flash-lite"
|
||||
GEMINI_2_5_FLASH = "gemini-2.5-flash"
|
||||
GEMINI_2_5_FLASH_LITE = "gemini-2.5-flash-lite"
|
||||
|
||||
|
||||
@register_llm
|
||||
class GoogleLLMService(BaseLLMConfiguration):
|
||||
provider: Literal[ServiceProviders.GOOGLE] = ServiceProviders.GOOGLE
|
||||
model: GoogleModel = GoogleModel.GEMINI_2_0_FLASH
|
||||
api_key: str
|
||||
|
||||
|
||||
class GroqModel(str, Enum):
|
||||
LLAMA_3_3_70B = "llama-3.3-70b-versatile"
|
||||
DEEPSEEK_R1_DISTILL_LLAMA_70B = "deepseek-r1-distill-llama-70b"
|
||||
QUEN_QWQ_32B = "qwen-qwq-32b"
|
||||
LLAMA_4_SCOUT_17B_16E_INSTRUCT = "meta-llama/llama-4-scout-17b-16e-instruct"
|
||||
LLAMA_4_MAVERICK_17B_128E_INSTRUCT = "meta-llama/llama-4-maverick-17b-128e-instruct"
|
||||
GEMMA2_9B_IT = "gemma2-9b-it"
|
||||
LLAMA_3_1_8B_INSTANT = "llama-3.1-8b-instant"
|
||||
OPENAI_GPT_OSS_120B = "openai/gpt-oss-120b"
|
||||
|
||||
|
||||
@register_llm
|
||||
class GroqLLMService(BaseLLMConfiguration):
|
||||
provider: Literal[ServiceProviders.GROQ] = ServiceProviders.GROQ
|
||||
model: GroqModel = GroqModel.LLAMA_3_3_70B
|
||||
api_key: str
|
||||
|
||||
|
||||
class AzureModel(str, Enum):
|
||||
GPT4_1_MINI = "gpt-4.1-mini"
|
||||
|
||||
|
||||
@register_llm
|
||||
class AzureLLMService(BaseLLMConfiguration):
|
||||
provider: Literal[ServiceProviders.AZURE] = ServiceProviders.AZURE
|
||||
model: AzureModel = AzureModel.GPT4_1_MINI
|
||||
api_key: str
|
||||
endpoint: str
|
||||
|
||||
|
||||
# Dograh LLM Service
|
||||
class DograhLLMModel(str, Enum):
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
@register_llm
|
||||
class DograhLLMService(BaseLLMConfiguration):
|
||||
provider: Literal[ServiceProviders.DOGRAH] = ServiceProviders.DOGRAH
|
||||
model: DograhLLMModel = DograhLLMModel.DEFAULT
|
||||
api_key: str
|
||||
|
||||
|
||||
LLMConfig = Annotated[
|
||||
Union[
|
||||
OpenAILLMService,
|
||||
GroqLLMService,
|
||||
GoogleLLMService,
|
||||
AzureLLMService,
|
||||
DograhLLMService,
|
||||
],
|
||||
Field(discriminator="provider"),
|
||||
]
|
||||
|
||||
###################################################### TTS ########################################################################
|
||||
|
||||
|
||||
class DeepgramVoice(str, Enum):
|
||||
HELENA = "aura-2-helena-en"
|
||||
THALIA = "aura-2-thalia-en"
|
||||
|
||||
|
||||
@register_tts
|
||||
class DeepgramTTSConfiguration(BaseServiceConfiguration):
|
||||
provider: Literal[ServiceProviders.DEEPGRAM] = ServiceProviders.DEEPGRAM
|
||||
voice: DeepgramVoice = DeepgramVoice.HELENA
|
||||
api_key: str
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def model(self) -> str:
|
||||
# Deepgram model's name is inferred using the voice name.
|
||||
# It can either contain aura-2 or aura-1
|
||||
if "aura-2" in self.voice:
|
||||
return "aura-2"
|
||||
elif "aura-1" in self.voice:
|
||||
return "aura-1"
|
||||
else:
|
||||
# Default fallback
|
||||
return "aura-2"
|
||||
|
||||
|
||||
class ElevenlabsVoice(str, Enum):
|
||||
ALEXANDRA = "Alexandra - 3dzJXoCYueSQiptQ6euE"
|
||||
AMY = "Amy - oGn4Ha2pe2vSJkmIJgLQ"
|
||||
ANGELA = "Angela - FUfBrNit0NNZAwb58KWH"
|
||||
ARIA = "Aria - 9BWtsMINqrJLrRacOk9x"
|
||||
CHELSEA = "Chelsea - NHRgOEwqx5WZNClv5sat"
|
||||
CHRISTINA = "Christina - X03mvPuTfprif8QBAVeJ"
|
||||
CLARA = "Clara - ZIlrSGI4jZqobxRKprJz"
|
||||
CLYDE = "Clyde - 2EiwWnXFnvU5JabPnv8n"
|
||||
DAVE = "Dave - CYw3kZ02Hs0563khs1Fj"
|
||||
DOMI = "Domi - AZnzlk1XvdvUeBnXmlld"
|
||||
DREW = "Drew - 29vD33N1CtxCmqQRPOHJ"
|
||||
EVE = "Eve - BZgkqPqms7Kj9ulSkVzn"
|
||||
FIN = "Fin - D38z5RcWu1voky8WS1ja"
|
||||
HOPE_BESTIE = "Hope_Bestie - uYXf8XasLslADfZ2MB4u"
|
||||
HOPE_NATURAL = "Hope_Natural - OYTbf65OHHFELVut7v2H"
|
||||
JARNATHAN = "Jarnathan - c6SfcYrb2t09NHXiT80T"
|
||||
JENNA = "Jenna - C2BkQxlGNzBn7WD2bqfR"
|
||||
JESSICA = "Jessica - cgSgspJ2msm6clMCkdW9"
|
||||
JUNIPER = "Juniper - aMSt68OGf4xUZAnLpTU8"
|
||||
LAUREN = "Lauren - 3liN8q8YoeB9Hk6AboKe"
|
||||
LINA = "Lina - oWjuL7HSoaEJRMDMP3HD"
|
||||
OLIVIA = "Olivia - 1rviaVF7GGGkTU36HNpz"
|
||||
PAUL = "Paul - 5Q0t7uMcjvnagumLfvZi"
|
||||
RACHEL = "Rachel - 21m00Tcm4TlvDq8ikWAM"
|
||||
ROGER = "Roger - CwhRBWXzGAHq8TQ4Fs17"
|
||||
SAMI_REAL = "Sami_Real - O4cGUVdAocn0z4EpQ9yF"
|
||||
SARAH = "Sarah - EXAVITQu4vr4xnSDxMaL"
|
||||
|
||||
|
||||
class ElevenlabsModel(str, Enum):
|
||||
FLASH_2 = "eleven_flash_v2_5"
|
||||
|
||||
|
||||
@register_tts
|
||||
class ElevenlabsTTSConfiguration(BaseServiceConfiguration):
|
||||
provider: Literal[ServiceProviders.ELEVENLABS] = ServiceProviders.ELEVENLABS
|
||||
voice: ElevenlabsVoice = ElevenlabsVoice.RACHEL
|
||||
speed: float = Field(default=1.0, ge=0.1, le=2.0, description="Speed of the voice")
|
||||
model: ElevenlabsModel = ElevenlabsModel.FLASH_2
|
||||
api_key: str
|
||||
|
||||
|
||||
class OpenAIVoice(str, Enum):
|
||||
ALLY = "alloy"
|
||||
|
||||
|
||||
class OpenAITTSModel(str, Enum):
|
||||
GPT_4o_MINI = "gpt-4o-mini-tts"
|
||||
|
||||
|
||||
@register_tts
|
||||
class OpenAITTSService(BaseTTSConfiguration):
|
||||
provider: Literal[ServiceProviders.OPENAI] = ServiceProviders.OPENAI
|
||||
model: OpenAITTSModel = OpenAITTSModel.GPT_4o_MINI
|
||||
voice: OpenAIVoice = OpenAIVoice.ALLY
|
||||
api_key: str
|
||||
|
||||
|
||||
# class NeuphonicVoice(str, Enum):
|
||||
# EMILY = "Emily - fc854436-2dac-4d21-aa69-ae17b54e98eb"
|
||||
|
||||
|
||||
# @register_tts
|
||||
# class NeuphonicTTSService(BaseTTSConfiguration):
|
||||
# provider: Literal[ServiceProviders.NEUPHONIC] = ServiceProviders.NEUPHONIC
|
||||
# voice: NeuphonicVoice = NeuphonicVoice.EMILY
|
||||
# model: str = "NA"
|
||||
# api_key: str
|
||||
|
||||
|
||||
# Dograh TTS Service
|
||||
class DograhVoice(str, Enum):
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
class DograhTTSModel(str, Enum):
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
@register_tts
|
||||
class DograhTTSService(BaseTTSConfiguration):
|
||||
provider: Literal[ServiceProviders.DOGRAH] = ServiceProviders.DOGRAH
|
||||
model: DograhTTSModel = DograhTTSModel.DEFAULT
|
||||
voice: DograhVoice = DograhVoice.DEFAULT
|
||||
api_key: str
|
||||
|
||||
|
||||
TTSConfig = Annotated[
|
||||
Union[
|
||||
DeepgramTTSConfiguration,
|
||||
OpenAITTSService,
|
||||
ElevenlabsTTSConfiguration,
|
||||
DograhTTSService,
|
||||
],
|
||||
Field(discriminator="provider"),
|
||||
]
|
||||
|
||||
###################################################### STT ########################################################################
|
||||
|
||||
|
||||
class DeepgramSTTModel(str, Enum):
|
||||
NOVA_3_GENERAL = "nova-3-general"
|
||||
|
||||
|
||||
@register_stt
|
||||
class DeepgramSTTConfiguration(BaseSTTConfiguration):
|
||||
provider: Literal[ServiceProviders.DEEPGRAM] = ServiceProviders.DEEPGRAM
|
||||
model: DeepgramSTTModel = DeepgramSTTModel.NOVA_3_GENERAL
|
||||
api_key: str
|
||||
|
||||
|
||||
@register_stt
|
||||
class CartesiaSTTConfiguration(BaseSTTConfiguration):
|
||||
provider: Literal[ServiceProviders.CARTESIA] = ServiceProviders.CARTESIA
|
||||
api_key: str
|
||||
|
||||
|
||||
class OpenAISTTModel(str, Enum):
|
||||
GPT_4o_TRANSCRIBE = "gpt-4o-transcribe"
|
||||
|
||||
|
||||
@register_stt
|
||||
class OpenAISTTConfiguration(BaseSTTConfiguration):
|
||||
provider: Literal[ServiceProviders.OPENAI] = ServiceProviders.OPENAI
|
||||
model: OpenAISTTModel = OpenAISTTModel.GPT_4o_TRANSCRIBE
|
||||
api_key: str
|
||||
|
||||
|
||||
# Dograh STT Service
|
||||
class DograhSTTModel(str, Enum):
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
@register_stt
|
||||
class DograhSTTService(BaseSTTConfiguration):
|
||||
provider: Literal[ServiceProviders.DOGRAH] = ServiceProviders.DOGRAH
|
||||
model: DograhSTTModel = DograhSTTModel.DEFAULT
|
||||
api_key: str
|
||||
|
||||
|
||||
STTConfig = Annotated[
|
||||
Union[DeepgramSTTConfiguration, OpenAISTTConfiguration, DograhSTTService],
|
||||
Field(discriminator="provider"),
|
||||
]
|
||||
|
||||
ServiceConfig = Annotated[
|
||||
Union[LLMConfig, TTSConfig, STTConfig], Field(discriminator="provider")
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue