From 2ad5c29cbdd0737f6c37115013ee7b66c5556907 Mon Sep 17 00:00:00 2001 From: Chris Briddock Date: Wed, 27 May 2026 07:13:47 +0000 Subject: [PATCH] Add OpenAI-compatible API option in model configuration Backend-only cherry-pick from 20617db37a8417e4ee4f64efb6063fc5cd4aea98. --- api/services/configuration/check_validity.py | 52 +++++++++++++++++--- api/services/configuration/registry.py | 4 ++ api/services/pipecat/service_factory.py | 9 +++- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/api/services/configuration/check_validity.py b/api/services/configuration/check_validity.py index 721884b..682fdee 100644 --- a/api/services/configuration/check_validity.py +++ b/api/services/configuration/check_validity.py @@ -181,30 +181,58 @@ class UserConfigurationValidator: api_key = service_config.api_key try: - if not self._check_api_key(provider, api_key): + if not self._check_api_key(provider, api_key, service_config): return [ - {"model": service_name, "message": f"Invalid {provider} API key"} + { + "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." + ), + } ] except ValueError as e: return [{"model": service_name, "message": str(e)}] return [] - def _check_api_key(self, provider: str, api_key: str) -> bool: + def _check_api_key( + self, provider: str, api_key: str, service_config: Optional[ServiceConfig] = None + ) -> bool: """Check if an API key for a provider is valid.""" validator = self._validator_map.get(provider) if not validator: return False + if provider in ( + ServiceProviders.OPENAI.value, + ServiceProviders.OPENAI_REALTIME.value, + ): + return validator(provider, api_key, service_config) return validator(provider, api_key) - def _check_openai_api_key(self, model: str, api_key: str) -> bool: - client = openai.OpenAI(api_key=api_key) + 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) try: client.models.list() return True except openai.AuthenticationError: - return False + 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." + ) def _check_deepgram_api_key(self, model: str, api_key: str) -> bool: try: @@ -212,7 +240,11 @@ class UserConfigurationValidator: deepgram.manage.v1.projects.list() return True except Exception: - return False + 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/." + ) def _check_groq_api_key(self, model: str, api_key: str) -> bool: client = Groq(api_key=api_key) @@ -220,7 +252,11 @@ class UserConfigurationValidator: client.models.list() return True except Exception: - return False + 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." + ) def _validate_elevenlabs_api_key(self, model: str, api_key: str) -> bool: return True diff --git a/api/services/configuration/registry.py b/api/services/configuration/registry.py index e60db18..498a6fc 100644 --- a/api/services/configuration/registry.py +++ b/api/services/configuration/registry.py @@ -290,6 +290,10 @@ class OpenAILLMService(BaseLLMConfiguration): description="OpenAI chat model to use.", json_schema_extra={"examples": OPENAI_MODELS, "allow_custom_input": True}, ) + base_url: str = Field( + default="https://api.openai.com/v1", + description="Override only if using an OpenAI-compatible API (e.g. local LLM, proxy).", + ) @register_llm diff --git a/api/services/pipecat/service_factory.py b/api/services/pipecat/service_factory.py index ad5c357..082e0af 100644 --- a/api/services/pipecat/service_factory.py +++ b/api/services/pipecat/service_factory.py @@ -504,6 +504,9 @@ def create_llm_service_from_provider( """ logger.info(f"Creating LLM service: provider={provider}, model={model}") if provider == ServiceProviders.OPENAI.value: + kwargs = {} + if base_url: + kwargs["base_url"] = base_url if "gpt-5" in model: return OpenAILLMService( api_key=api_key, @@ -511,10 +514,12 @@ def create_llm_service_from_provider( model=model, extra={"reasoning_effort": "minimal", "verbosity": "low"}, ), + **kwargs, ) return OpenAILLMService( api_key=api_key, settings=OpenAILLMSettings(model=model, temperature=0.1), + **kwargs, ) elif provider == ServiceProviders.GROQ.value: return GroqLLMService( @@ -709,7 +714,9 @@ def create_llm_service(user_config): api_key = user_config.llm.api_key kwargs = {} - if provider == ServiceProviders.OPENROUTER.value: + if provider == ServiceProviders.OPENAI.value: + kwargs["base_url"] = user_config.llm.base_url + elif provider == ServiceProviders.OPENROUTER.value: kwargs["base_url"] = user_config.llm.base_url elif provider == ServiceProviders.AZURE.value: kwargs["endpoint"] = user_config.llm.endpoint