2026-03-16 15:04:08 +05:30
|
|
|
from typing import Optional, TypedDict
|
2025-09-09 14:37:32 +05:30
|
|
|
|
2026-06-19 09:53:27 +02:00
|
|
|
import httpx
|
2025-09-09 14:37:32 +05:30
|
|
|
import openai
|
2026-03-06 16:49:14 +05:30
|
|
|
from deepgram import DeepgramClient
|
2025-09-09 14:37:32 +05:30
|
|
|
from groq import Groq
|
|
|
|
|
|
|
|
|
|
# try:
|
|
|
|
|
# from pyneuphonic import Neuphonic
|
|
|
|
|
# except ImportError:
|
|
|
|
|
# Neuphonic = None
|
2026-06-12 14:55:30 +05:30
|
|
|
from api.schemas.ai_model_configuration import (
|
2026-06-09 16:30:03 +05:30
|
|
|
EffectiveAIModelConfiguration,
|
2025-09-09 14:37:32 +05:30
|
|
|
)
|
|
|
|
|
from api.services.configuration.registry import ServiceConfig, ServiceProviders
|
2026-03-16 15:04:08 +05:30
|
|
|
from api.services.mps_service_key_client import mps_service_key_client
|
2026-05-27 13:07:45 +05:30
|
|
|
from api.utils.url_security import validate_user_configured_service_url
|
2025-09-09 14:37:32 +05:30
|
|
|
|
2026-03-27 00:07:38 +05:30
|
|
|
AuthContext = TypedDict(
|
|
|
|
|
"AuthContext",
|
|
|
|
|
{"organization_id": Optional[int], "created_by": Optional[str]},
|
|
|
|
|
total=False,
|
|
|
|
|
)
|
|
|
|
|
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
class APIKeyStatus(TypedDict):
|
|
|
|
|
model: str
|
|
|
|
|
message: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class APIKeyStatusResponse(TypedDict):
|
|
|
|
|
status: list[APIKeyStatus]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserConfigurationValidator:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
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,
|
2026-02-09 13:31:32 +05:30
|
|
|
ServiceProviders.OPENROUTER.value: self._check_openrouter_api_key,
|
2026-06-19 09:53:27 +02:00
|
|
|
ServiceProviders.INWORLD.value: self._check_inworld_api_key,
|
2025-09-09 14:37:32 +05:30
|
|
|
ServiceProviders.ELEVENLABS.value: self._validate_elevenlabs_api_key,
|
|
|
|
|
ServiceProviders.GOOGLE.value: self._check_google_api_key,
|
|
|
|
|
ServiceProviders.AZURE.value: self._check_azure_api_key,
|
2026-06-02 12:50:00 +05:30
|
|
|
ServiceProviders.AZURE_SPEECH.value: self._check_azure_speech_api_key,
|
2025-09-09 14:37:32 +05:30
|
|
|
ServiceProviders.CARTESIA.value: self._check_cartesia_api_key,
|
|
|
|
|
ServiceProviders.DOGRAH.value: self._check_dograh_api_key,
|
2025-12-25 15:05:53 +05:30
|
|
|
ServiceProviders.SARVAM.value: self._check_sarvam_api_key,
|
2026-01-08 18:03:26 +05:30
|
|
|
ServiceProviders.SPEECHMATICS.value: self._check_speechmatics_api_key,
|
2026-03-24 15:24:07 +08:00
|
|
|
ServiceProviders.CAMB.value: self._check_camb_api_key,
|
2026-03-19 15:06:59 +05:30
|
|
|
ServiceProviders.AWS_BEDROCK.value: self._check_aws_bedrock_api_key,
|
2026-03-31 21:42:03 +05:30
|
|
|
ServiceProviders.SPEACHES.value: self._check_speaches_api_key,
|
2026-06-15 22:56:01 +05:30
|
|
|
ServiceProviders.HUGGINGFACE.value: self._check_huggingface_api_key,
|
2026-05-22 18:04:59 +05:30
|
|
|
ServiceProviders.GOOGLE_VERTEX.value: self._check_google_vertex_llm_api_key,
|
2026-03-31 21:42:03 +05:30
|
|
|
ServiceProviders.OPENAI_REALTIME.value: self._check_openai_api_key,
|
2026-05-22 18:04:59 +05:30
|
|
|
ServiceProviders.GROK_REALTIME.value: self._check_grok_realtime_api_key,
|
2026-05-23 12:51:55 +05:30
|
|
|
ServiceProviders.ULTRAVOX_REALTIME.value: self._check_ultravox_realtime_api_key,
|
2026-03-31 21:42:03 +05:30
|
|
|
ServiceProviders.GOOGLE_REALTIME.value: self._check_google_api_key,
|
2026-05-16 18:05:23 +05:30
|
|
|
ServiceProviders.GOOGLE_VERTEX_REALTIME.value: self._check_google_vertex_realtime_api_key,
|
2026-06-02 12:50:00 +05:30
|
|
|
ServiceProviders.AZURE_REALTIME.value: self._check_azure_realtime_api_key,
|
2026-04-03 07:10:37 +05:30
|
|
|
ServiceProviders.ASSEMBLYAI.value: self._check_assemblyai_api_key,
|
2026-04-04 14:47:48 +05:30
|
|
|
ServiceProviders.GLADIA.value: self._check_gladia_api_key,
|
2026-04-07 14:05:47 +05:30
|
|
|
ServiceProviders.RIME.value: self._check_rime_api_key,
|
2026-05-22 15:39:41 +08:00
|
|
|
ServiceProviders.MINIMAX.value: self._check_minimax_api_key,
|
feat: add Smallest AI TTS and STT provider integration (#444)
* feat: add Smallest AI TTS and STT provider integration
Integrates Smallest AI's Waves (TTS) and Pulse (STT) APIs as selectable
providers in the Dograh platform. Dograh's pipecat fork already contains
the pipecat-level service implementations; this wires them into the API
configuration registry and service factory.
- Added `SMALLEST = "smallest"` to `ServiceProviders` enum
- Registered `SmallestAITTSConfiguration` (lightning-v3.1/v2, voices,
language, speed) and `SmallestAISTTConfiguration` (pulse model, 30+
languages) Pydantic config classes with the TTS/STT registries
- Added factory branches in `create_tts_service` and `create_stt_service`
routing to `SmallestTTSService` and `SmallestSTTService` from pipecat
* fix: update Smallest AI models to v4 naming convention
- TTS: rename lightning-v3.1 → lightning_v3.1, add lightning_v3.1_pro, drop deprecated lightning-v2
- STT: keep pulse only (pulse-pro is not a streaming model)
* fix: change default TTS voice from emily to sophia for lightning_v3.1
emily is not a verified lightning_v3.1 voice; sophia is the pipecat
SmallestTTSService default and confirmed to work with the standard pool.
* fix: replace 9 invalid lightning_v3.1 voice IDs with verified ones
jasmine, james, michael, aria, lara, asel, sarah, rishi, deepika do not
exist in the lightning_v3.1 voice catalog. Replaced with avery, liam,
lucas, olivia, freya, devansh, maya, dhruv, maithili — all verified
against the API.
* fix: smallest ai config validation and tts model compatibility
* chore: ruff fix
* chore: updated smallest ai schema in openapi.json
---------
Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
Co-authored-by: Sabiha Khan <87858386+chewwbaka@users.noreply.github.com>
2026-06-17 12:55:53 +05:30
|
|
|
ServiceProviders.SMALLEST.value: self._check_smallest_api_key,
|
feat(tts): add xAI as a Voice (TTS) provider (#476)
* feat(tts): add xAI as a Voice (TTS) provider
pipecat already ships an xAI TTS service (XAITTSService, WebSocket
streaming) but dograh never wired it into the service configuration, so
xAI could not be selected as a Voice provider in the cascading pipeline.
Wire it through:
- registry: ServiceProviders.XAI + XAITTSConfiguration (voices
eve/ara/leo/rex/sal, language, computed model) registered in TTSConfig
- service_factory: build XAITTSService in create_tts_service
- check_validity: api-key validation hook
- tests for the factory + docs
The Voice provider dropdown is schema-driven, so xAI appears with no UI
changes.
* fix(tts): validate xAI API key and drop misleading auto-language hint
Addresses review feedback on the xAI Voice provider:
- check_validity: replace the no-op xAI key check with real validation
against xAI's OpenAI-compatible API (models.list on https://api.x.ai/v1),
so a bad BYOK key is caught at configuration time instead of at call time.
- registry: remove the "auto" language hint from the field description.
pipecat's Language enum has no "auto" member, so the factory fell back to
English silently; the description no longer advertises detection we don't do.
- tests: cover xAI key validation (registered, accepts valid, rejects bad).
* fix(tts): validate xAI key against the TTS voices endpoint
xAI supports endpoint-scoped API keys, so a key scoped to Text-to-Speech
may lack the /v1/models ACL and would be wrongly rejected by the previous
models.list() check. Validate against GET /v1/tts/voices instead — the
scope the key actually needs for TTS — treating 401/403 as an invalid key
and connection errors as a clean, actionable message.
* fix: harden xAI TTS integration
---------
Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
Co-authored-by: Sabiha Khan <87858386+chewwbaka@users.noreply.github.com>
2026-07-08 04:35:43 +01:00
|
|
|
ServiceProviders.XAI.value: self._check_xai_api_key,
|
2025-09-09 14:37:32 +05:30
|
|
|
}
|
|
|
|
|
|
2026-03-27 00:07:38 +05:30
|
|
|
async def validate(
|
|
|
|
|
self,
|
2026-06-09 16:30:03 +05:30
|
|
|
configuration: EffectiveAIModelConfiguration,
|
2026-03-27 00:07:38 +05:30
|
|
|
organization_id: Optional[int] = None,
|
|
|
|
|
created_by: Optional[str] = None,
|
|
|
|
|
) -> APIKeyStatusResponse:
|
|
|
|
|
self._auth_context: AuthContext = {
|
|
|
|
|
"organization_id": organization_id,
|
|
|
|
|
"created_by": created_by,
|
|
|
|
|
}
|
2025-09-09 14:37:32 +05:30
|
|
|
status_list = []
|
|
|
|
|
|
|
|
|
|
status_list.extend(self._validate_service(configuration.llm, "llm"))
|
2026-03-31 21:42:03 +05:30
|
|
|
if configuration.is_realtime:
|
|
|
|
|
status_list.extend(
|
|
|
|
|
self._validate_service(
|
|
|
|
|
configuration.realtime, "realtime", required=True
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-06-12 14:55:30 +05:30
|
|
|
else:
|
|
|
|
|
status_list.extend(self._validate_service(configuration.stt, "stt"))
|
|
|
|
|
status_list.extend(self._validate_service(configuration.tts, "tts"))
|
|
|
|
|
# Embeddings is optional - only validate if configured
|
|
|
|
|
status_list.extend(
|
|
|
|
|
self._validate_service(
|
|
|
|
|
configuration.embeddings, "embeddings", required=False
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
if status_list:
|
|
|
|
|
raise ValueError(status_list)
|
|
|
|
|
|
|
|
|
|
return {"status": [{"model": "all", "message": "ok"}]}
|
|
|
|
|
|
|
|
|
|
def _validate_service(
|
2026-01-17 14:37:03 +05:30
|
|
|
self,
|
|
|
|
|
service_config: Optional[ServiceConfig],
|
|
|
|
|
service_name: str,
|
|
|
|
|
required: bool = True,
|
2025-09-09 14:37:32 +05:30
|
|
|
) -> list[APIKeyStatus]:
|
|
|
|
|
"""Validate a service configuration and return any error statuses."""
|
|
|
|
|
if not service_config:
|
2026-01-17 14:37:03 +05:30
|
|
|
if required:
|
|
|
|
|
return [{"model": service_name, "message": "API key is missing"}]
|
|
|
|
|
return [] # Optional service not configured is OK
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
provider = service_config.provider
|
2026-03-19 15:06:59 +05:30
|
|
|
|
2026-05-27 13:07:45 +05:30
|
|
|
for url_field in ("base_url", "endpoint"):
|
|
|
|
|
url = getattr(service_config, url_field, None)
|
|
|
|
|
if url:
|
|
|
|
|
try:
|
|
|
|
|
validate_user_configured_service_url(
|
|
|
|
|
url,
|
|
|
|
|
field_name=url_field,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
|
|
|
|
|
2026-03-31 21:42:03 +05:30
|
|
|
# Speaches doesn't require an API key
|
|
|
|
|
if provider == ServiceProviders.SPEACHES.value:
|
2026-03-24 17:50:45 +05:30
|
|
|
try:
|
2026-03-31 21:42:03 +05:30
|
|
|
if not self._check_speaches_api_key(provider, service_config):
|
2026-03-24 17:50:45 +05:30
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": f"Invalid {provider} configuration",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
|
|
|
|
return []
|
|
|
|
|
|
2026-05-16 18:05:23 +05:30
|
|
|
# Vertex Realtime uses service-account credentials (or ADC) instead of api_key
|
|
|
|
|
if provider == ServiceProviders.GOOGLE_VERTEX_REALTIME.value:
|
|
|
|
|
try:
|
|
|
|
|
if not self._check_google_vertex_realtime_api_key(
|
|
|
|
|
provider, service_config
|
|
|
|
|
):
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": f"Invalid {provider} configuration",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
|
|
|
|
return []
|
|
|
|
|
|
2026-05-22 18:04:59 +05:30
|
|
|
# Vertex LLM uses service-account credentials (or ADC) instead of api_key
|
|
|
|
|
if provider == ServiceProviders.GOOGLE_VERTEX.value:
|
|
|
|
|
try:
|
|
|
|
|
if not self._check_google_vertex_llm_api_key(provider, service_config):
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": f"Invalid {provider} configuration",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
|
|
|
|
return []
|
|
|
|
|
|
2026-03-19 15:06:59 +05:30
|
|
|
# AWS Bedrock uses AWS credentials instead of api_key
|
|
|
|
|
if provider == ServiceProviders.AWS_BEDROCK.value:
|
|
|
|
|
try:
|
|
|
|
|
if not self._check_aws_bedrock_api_key(provider, service_config):
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": f"Invalid {provider} credentials",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
|
|
|
|
return []
|
|
|
|
|
|
2026-05-22 15:39:41 +08:00
|
|
|
# MiniMax TTS requires a group_id alongside the API key.
|
|
|
|
|
# LLM configs don't expose group_id, so only check when the field exists.
|
|
|
|
|
if provider == ServiceProviders.MINIMAX.value and hasattr(
|
|
|
|
|
service_config, "group_id"
|
|
|
|
|
):
|
|
|
|
|
if not getattr(service_config, "group_id", None):
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": "group_id is required for MiniMax TTS",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
2025-09-09 14:37:32 +05:30
|
|
|
api_key = service_config.api_key
|
|
|
|
|
|
2026-03-16 15:04:08 +05:30
|
|
|
try:
|
2026-05-27 13:07:45 +05:30
|
|
|
if not self._check_api_key(provider, api_key, service_config):
|
2026-03-16 15:04:08 +05:30
|
|
|
return [
|
2026-05-27 13:07:45 +05:30
|
|
|
{
|
|
|
|
|
"model": service_name,
|
|
|
|
|
"message": (
|
|
|
|
|
f"Invalid {provider} API key. Please verify your API key is "
|
|
|
|
|
f"correct, has not expired, and has the required permissions."
|
|
|
|
|
),
|
|
|
|
|
}
|
2026-03-16 15:04:08 +05:30
|
|
|
]
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return [{"model": service_name, "message": str(e)}]
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
return []
|
|
|
|
|
|
2026-05-27 13:07:45 +05:30
|
|
|
def _check_api_key(
|
|
|
|
|
self,
|
|
|
|
|
provider: str,
|
|
|
|
|
api_key: str,
|
|
|
|
|
service_config: Optional[ServiceConfig] = None,
|
|
|
|
|
) -> bool:
|
2025-09-09 14:37:32 +05:30
|
|
|
"""Check if an API key for a provider is valid."""
|
|
|
|
|
validator = self._validator_map.get(provider)
|
|
|
|
|
if not validator:
|
|
|
|
|
return False
|
|
|
|
|
|
2026-05-27 13:07:45 +05:30
|
|
|
if provider in (
|
|
|
|
|
ServiceProviders.OPENAI.value,
|
|
|
|
|
ServiceProviders.OPENAI_REALTIME.value,
|
|
|
|
|
):
|
|
|
|
|
return validator(provider, api_key, service_config)
|
2025-09-09 14:37:32 +05:30
|
|
|
return validator(provider, api_key)
|
|
|
|
|
|
2026-05-27 13:07:45 +05:30
|
|
|
def _check_openai_api_key(
|
|
|
|
|
self, model: str, api_key: str, service_config: Optional[ServiceConfig] = None
|
|
|
|
|
) -> bool:
|
|
|
|
|
client_kwargs: dict[str, str] = {"api_key": api_key}
|
|
|
|
|
base_url = getattr(service_config, "base_url", None) if service_config else None
|
|
|
|
|
if base_url:
|
|
|
|
|
client_kwargs["base_url"] = base_url
|
|
|
|
|
client = openai.OpenAI(**client_kwargs)
|
2025-09-09 14:37:32 +05:30
|
|
|
try:
|
|
|
|
|
client.models.list()
|
2026-03-16 15:04:08 +05:30
|
|
|
return True
|
2025-09-09 14:37:32 +05:30
|
|
|
except openai.AuthenticationError:
|
2026-05-27 13:07:45 +05:30
|
|
|
if base_url and "openai.com" not in base_url:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Invalid OpenAI API key. The key was rejected by the API at {base_url}. "
|
|
|
|
|
"Please check that your API key is correct and has not been revoked."
|
|
|
|
|
)
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid OpenAI API key. The key was rejected by the OpenAI API. "
|
|
|
|
|
"Please check that your API key is correct and has not been revoked. "
|
|
|
|
|
"You can verify your keys at https://platform.openai.com/api-keys."
|
|
|
|
|
)
|
|
|
|
|
except openai.APIConnectionError:
|
|
|
|
|
if base_url:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Could not connect to the OpenAI-compatible API at {base_url}. "
|
|
|
|
|
"Please verify that the base_url is correct and reachable, and try again."
|
|
|
|
|
)
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Could not connect to the OpenAI API. Please check your network connection "
|
|
|
|
|
"and try again."
|
|
|
|
|
)
|
|
|
|
|
except openai.APIError:
|
|
|
|
|
if base_url:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"The OpenAI-compatible API at {base_url} returned an error while "
|
|
|
|
|
"validating the API key. Please verify that the base_url is correct, "
|
|
|
|
|
"the service is available, and the API key is valid."
|
|
|
|
|
)
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"The OpenAI API returned an error while validating the API key. "
|
|
|
|
|
"Please try again later."
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
if base_url:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Failed to validate the OpenAI API key using the API at {base_url}. "
|
|
|
|
|
"Please verify that the base_url is correct and reachable, and that the "
|
|
|
|
|
"API key is valid."
|
|
|
|
|
)
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Failed to validate the OpenAI API key. Please try again later."
|
|
|
|
|
)
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
def _check_deepgram_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
try:
|
2026-03-06 16:49:14 +05:30
|
|
|
deepgram = DeepgramClient(api_key=api_key)
|
|
|
|
|
deepgram.manage.v1.projects.list()
|
2026-03-16 15:04:08 +05:30
|
|
|
return True
|
2026-03-06 16:49:14 +05:30
|
|
|
except Exception:
|
2026-05-27 13:07:45 +05:30
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid Deepgram API key. The key was rejected by the Deepgram API. "
|
|
|
|
|
"Please check that your API key is correct and active. "
|
|
|
|
|
"You can verify your keys at https://console.deepgram.com/."
|
|
|
|
|
)
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
def _check_groq_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
client = Groq(api_key=api_key)
|
|
|
|
|
try:
|
|
|
|
|
client.models.list()
|
2026-03-16 15:04:08 +05:30
|
|
|
return True
|
2025-09-09 14:37:32 +05:30
|
|
|
except Exception:
|
2026-05-27 13:07:45 +05:30
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid Groq API key. The key was rejected by the Groq API. "
|
|
|
|
|
"Please check that your API key is correct and active. "
|
|
|
|
|
"You can verify your keys at https://console.groq.com/keys."
|
|
|
|
|
)
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
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:
|
2026-06-02 12:50:00 +05:30
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _check_azure_speech_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _check_azure_realtime_api_key(self, model: str, api_key: str) -> bool:
|
2025-09-09 14:37:32 +05:30
|
|
|
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:
|
2026-03-16 15:04:08 +05:30
|
|
|
if api_key.startswith("dgr"):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"You provided a Dograh API key (dgr...) instead of a service key. "
|
|
|
|
|
"Please use a service key (mps...)."
|
|
|
|
|
)
|
2026-03-27 00:07:38 +05:30
|
|
|
auth = getattr(self, "_auth_context", {})
|
|
|
|
|
return mps_service_key_client.validate_service_key(
|
|
|
|
|
api_key,
|
|
|
|
|
organization_id=auth.get("organization_id"),
|
|
|
|
|
created_by=auth.get("created_by"),
|
|
|
|
|
)
|
2025-09-09 14:37:32 +05:30
|
|
|
|
2025-12-25 15:05:53 +05:30
|
|
|
def _check_sarvam_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
2026-01-12 12:47:32 +05:30
|
|
|
|
2026-02-09 13:31:32 +05:30
|
|
|
def _check_openrouter_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
2026-06-19 09:53:27 +02:00
|
|
|
def _check_inworld_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
try:
|
|
|
|
|
response = httpx.get(
|
|
|
|
|
"https://api.inworld.ai/voices/v1/voices",
|
|
|
|
|
headers={"Authorization": f"Basic {api_key}"},
|
|
|
|
|
params={"pageSize": 1},
|
|
|
|
|
timeout=10.0,
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
return True
|
|
|
|
|
except httpx.HTTPStatusError as exc:
|
|
|
|
|
if exc.response.status_code in (401, 403):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid Inworld API key. The key was rejected by the Inworld API. "
|
|
|
|
|
"Please verify that your API key is correct, active, and has voice read access."
|
|
|
|
|
) from exc
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"The Inworld API returned an error while validating the API key. "
|
|
|
|
|
"Please try again later."
|
|
|
|
|
) from exc
|
|
|
|
|
except httpx.RequestError as exc:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Could not connect to the Inworld API. Please check your network connection "
|
|
|
|
|
"and try again."
|
|
|
|
|
) from exc
|
|
|
|
|
|
2026-05-22 18:04:59 +05:30
|
|
|
def _check_grok_realtime_api_key(self, model: str, api_key: str) -> bool:
|
2026-05-23 12:51:55 +05:30
|
|
|
return True
|
|
|
|
|
|
feat(tts): add xAI as a Voice (TTS) provider (#476)
* feat(tts): add xAI as a Voice (TTS) provider
pipecat already ships an xAI TTS service (XAITTSService, WebSocket
streaming) but dograh never wired it into the service configuration, so
xAI could not be selected as a Voice provider in the cascading pipeline.
Wire it through:
- registry: ServiceProviders.XAI + XAITTSConfiguration (voices
eve/ara/leo/rex/sal, language, computed model) registered in TTSConfig
- service_factory: build XAITTSService in create_tts_service
- check_validity: api-key validation hook
- tests for the factory + docs
The Voice provider dropdown is schema-driven, so xAI appears with no UI
changes.
* fix(tts): validate xAI API key and drop misleading auto-language hint
Addresses review feedback on the xAI Voice provider:
- check_validity: replace the no-op xAI key check with real validation
against xAI's OpenAI-compatible API (models.list on https://api.x.ai/v1),
so a bad BYOK key is caught at configuration time instead of at call time.
- registry: remove the "auto" language hint from the field description.
pipecat's Language enum has no "auto" member, so the factory fell back to
English silently; the description no longer advertises detection we don't do.
- tests: cover xAI key validation (registered, accepts valid, rejects bad).
* fix(tts): validate xAI key against the TTS voices endpoint
xAI supports endpoint-scoped API keys, so a key scoped to Text-to-Speech
may lack the /v1/models ACL and would be wrongly rejected by the previous
models.list() check. Validate against GET /v1/tts/voices instead — the
scope the key actually needs for TTS — treating 401/403 as an invalid key
and connection errors as a clean, actionable message.
* fix: harden xAI TTS integration
---------
Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
Co-authored-by: Sabiha Khan <87858386+chewwbaka@users.noreply.github.com>
2026-07-08 04:35:43 +01:00
|
|
|
def _check_xai_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
# Use the TTS voices endpoint as a best-effort smoke test. Some xAI keys
|
|
|
|
|
# can be scoped in ways that block listing voices even though the key is
|
|
|
|
|
# still intended for TTS usage, so only a clear auth failure rejects save.
|
|
|
|
|
try:
|
|
|
|
|
response = httpx.get(
|
|
|
|
|
"https://api.x.ai/v1/tts/voices",
|
|
|
|
|
headers={"Authorization": f"Bearer {api_key}"},
|
|
|
|
|
timeout=10.0,
|
|
|
|
|
)
|
|
|
|
|
except httpx.RequestError:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Could not connect to the xAI API. Please check your network "
|
|
|
|
|
"connection and try again."
|
|
|
|
|
)
|
|
|
|
|
if response.status_code == 200:
|
|
|
|
|
return True
|
|
|
|
|
if response.status_code == 401:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid xAI API key. The key was rejected by the xAI API. "
|
|
|
|
|
"Please check that your API key is correct and active. "
|
|
|
|
|
"You can verify your keys at "
|
|
|
|
|
"https://console.x.ai."
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
|
2026-05-23 12:51:55 +05:30
|
|
|
def _check_ultravox_realtime_api_key(self, model: str, api_key: str) -> bool:
|
2026-05-22 18:04:59 +05:30
|
|
|
return True
|
|
|
|
|
|
2026-01-08 18:03:26 +05:30
|
|
|
def _check_speechmatics_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
2026-03-19 15:06:59 +05:30
|
|
|
|
2026-03-24 15:24:07 +08:00
|
|
|
def _check_camb_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
2026-03-24 17:50:45 +05:30
|
|
|
|
2026-03-31 21:42:03 +05:30
|
|
|
def _check_speaches_api_key(self, model: str, service_config) -> bool:
|
2026-03-24 17:50:45 +05:30
|
|
|
if not getattr(service_config, "base_url", None):
|
2026-03-31 21:42:03 +05:30
|
|
|
raise ValueError("base_url is required for Speaches services")
|
2026-03-24 17:50:45 +05:30
|
|
|
return True
|
|
|
|
|
|
2026-06-15 22:56:01 +05:30
|
|
|
def _check_huggingface_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
if not api_key.startswith("hf_"):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Invalid Hugging Face API token format. Use a token that starts with "
|
|
|
|
|
"'hf_' and has Inference Providers permission."
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
|
2026-05-16 18:05:23 +05:30
|
|
|
def _check_google_vertex_realtime_api_key(self, model: str, service_config) -> bool:
|
|
|
|
|
if not getattr(service_config, "project_id", None):
|
|
|
|
|
raise ValueError("project_id is required for Google Vertex Realtime")
|
|
|
|
|
if not getattr(service_config, "location", None):
|
|
|
|
|
raise ValueError("location is required for Google Vertex Realtime")
|
|
|
|
|
return True
|
|
|
|
|
|
2026-05-22 18:04:59 +05:30
|
|
|
def _check_google_vertex_llm_api_key(self, model: str, service_config) -> bool:
|
|
|
|
|
if not getattr(service_config, "project_id", None):
|
|
|
|
|
raise ValueError("project_id is required for Google Vertex")
|
|
|
|
|
if not getattr(service_config, "location", None):
|
|
|
|
|
raise ValueError("location is required for Google Vertex")
|
|
|
|
|
return True
|
|
|
|
|
|
2026-03-19 15:06:59 +05:30
|
|
|
def _check_aws_bedrock_api_key(self, model: str, service_config) -> bool:
|
|
|
|
|
if not service_config.aws_access_key or not service_config.aws_secret_key:
|
|
|
|
|
raise ValueError("AWS access key and secret key are required for Bedrock")
|
|
|
|
|
return True
|
2026-04-03 07:10:37 +05:30
|
|
|
|
|
|
|
|
def _check_assemblyai_api_key(self, model: str, service_config) -> bool:
|
|
|
|
|
return True
|
2026-04-04 14:47:48 +05:30
|
|
|
|
|
|
|
|
def _check_gladia_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
2026-04-07 14:05:47 +05:30
|
|
|
|
|
|
|
|
def _check_rime_api_key(self, model: str, api_key: str) -> bool:
|
|
|
|
|
return True
|
2026-05-22 15:39:41 +08:00
|
|
|
|
|
|
|
|
def _check_minimax_api_key(self, model: str, api_key: str) -> bool:
|
feat: add Smallest AI TTS and STT provider integration (#444)
* feat: add Smallest AI TTS and STT provider integration
Integrates Smallest AI's Waves (TTS) and Pulse (STT) APIs as selectable
providers in the Dograh platform. Dograh's pipecat fork already contains
the pipecat-level service implementations; this wires them into the API
configuration registry and service factory.
- Added `SMALLEST = "smallest"` to `ServiceProviders` enum
- Registered `SmallestAITTSConfiguration` (lightning-v3.1/v2, voices,
language, speed) and `SmallestAISTTConfiguration` (pulse model, 30+
languages) Pydantic config classes with the TTS/STT registries
- Added factory branches in `create_tts_service` and `create_stt_service`
routing to `SmallestTTSService` and `SmallestSTTService` from pipecat
* fix: update Smallest AI models to v4 naming convention
- TTS: rename lightning-v3.1 → lightning_v3.1, add lightning_v3.1_pro, drop deprecated lightning-v2
- STT: keep pulse only (pulse-pro is not a streaming model)
* fix: change default TTS voice from emily to sophia for lightning_v3.1
emily is not a verified lightning_v3.1 voice; sophia is the pipecat
SmallestTTSService default and confirmed to work with the standard pool.
* fix: replace 9 invalid lightning_v3.1 voice IDs with verified ones
jasmine, james, michael, aria, lara, asel, sarah, rishi, deepika do not
exist in the lightning_v3.1 voice catalog. Replaced with avery, liam,
lucas, olivia, freya, devansh, maya, dhruv, maithili — all verified
against the API.
* fix: smallest ai config validation and tts model compatibility
* chore: ruff fix
* chore: updated smallest ai schema in openapi.json
---------
Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
Co-authored-by: Sabiha Khan <87858386+chewwbaka@users.noreply.github.com>
2026-06-17 12:55:53 +05:30
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _check_smallest_api_key(self, model: str, api_key: str) -> bool:
|
2026-05-22 15:39:41 +08:00
|
|
|
return True
|