diff --git a/application/core/model_settings.py b/application/core/model_settings.py index 3f4dc030..5361f067 100644 --- a/application/core/model_settings.py +++ b/application/core/model_settings.py @@ -6,19 +6,6 @@ from typing import Dict, List, Optional logger = logging.getLogger(__name__) -def _is_valid_api_key(key: Optional[str]) -> bool: - """ - Check if an API key is valid (not None, not empty, not string 'None'). - Handles Pydantic loading 'None' from .env as string "None". - """ - if key is None: - return False - if not isinstance(key, str): - return False - key_stripped = key.strip().lower() - return key_stripped != "" and key_stripped != "none" - - class ModelProvider(str, Enum): OPENAI = "openai" AZURE_OPENAI = "azure_openai" @@ -101,29 +88,29 @@ class ModelRegistry: if not settings.OPENAI_BASE_URL: self._add_docsgpt_models(settings) if ( - _is_valid_api_key(settings.OPENAI_API_KEY) - or (settings.LLM_PROVIDER == "openai" and _is_valid_api_key(settings.API_KEY)) + settings.OPENAI_API_KEY + or (settings.LLM_PROVIDER == "openai" and settings.API_KEY) or settings.OPENAI_BASE_URL ): self._add_openai_models(settings) if settings.OPENAI_API_BASE or ( - settings.LLM_PROVIDER == "azure_openai" and _is_valid_api_key(settings.API_KEY) + settings.LLM_PROVIDER == "azure_openai" and settings.API_KEY ): self._add_azure_openai_models(settings) - if _is_valid_api_key(settings.ANTHROPIC_API_KEY) or ( - settings.LLM_PROVIDER == "anthropic" and _is_valid_api_key(settings.API_KEY) + if settings.ANTHROPIC_API_KEY or ( + settings.LLM_PROVIDER == "anthropic" and settings.API_KEY ): self._add_anthropic_models(settings) - if _is_valid_api_key(settings.GOOGLE_API_KEY) or ( - settings.LLM_PROVIDER == "google" and _is_valid_api_key(settings.API_KEY) + if settings.GOOGLE_API_KEY or ( + settings.LLM_PROVIDER == "google" and settings.API_KEY ): self._add_google_models(settings) - if _is_valid_api_key(settings.GROQ_API_KEY) or ( - settings.LLM_PROVIDER == "groq" and _is_valid_api_key(settings.API_KEY) + if settings.GROQ_API_KEY or ( + settings.LLM_PROVIDER == "groq" and settings.API_KEY ): self._add_groq_models(settings) - if _is_valid_api_key(settings.HUGGINGFACE_API_KEY) or ( - settings.LLM_PROVIDER == "huggingface" and _is_valid_api_key(settings.API_KEY) + if settings.HUGGINGFACE_API_KEY or ( + settings.LLM_PROVIDER == "huggingface" and settings.API_KEY ): self._add_huggingface_models(settings) # Default model selection @@ -140,7 +127,7 @@ class ModelRegistry: self.default_model_id = settings.LLM_NAME if not self.default_model_id: - if settings.LLM_PROVIDER and _is_valid_api_key(settings.API_KEY): + if settings.LLM_PROVIDER and settings.API_KEY: for model_id, model in self.models.items(): if model.provider.value == settings.LLM_PROVIDER: self.default_model_id = model_id @@ -178,7 +165,7 @@ class ModelRegistry: ) else: # Standard OpenAI API usage - add standard models if API key is valid - if _is_valid_api_key(settings.OPENAI_API_KEY): + if settings.OPENAI_API_KEY: for model in OPENAI_MODELS: self.models[model.id] = model diff --git a/application/core/model_utils.py b/application/core/model_utils.py index 32f207f5..f24dbf47 100644 --- a/application/core/model_utils.py +++ b/application/core/model_utils.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional -from application.core.model_settings import ModelRegistry, _is_valid_api_key +from application.core.model_settings import ModelRegistry def get_api_key_for_provider(provider: str) -> Optional[str]: @@ -19,11 +19,9 @@ def get_api_key_for_provider(provider: str) -> Optional[str]: } provider_key = provider_key_map.get(provider) - if provider_key and _is_valid_api_key(provider_key): + if provider_key: return provider_key - if _is_valid_api_key(settings.API_KEY): - return settings.API_KEY - return None + return settings.API_KEY def get_all_available_models() -> Dict[str, Dict[str, Any]]: diff --git a/application/core/settings.py b/application/core/settings.py index 1cc36b18..c02812e7 100644 --- a/application/core/settings.py +++ b/application/core/settings.py @@ -2,6 +2,7 @@ import os from pathlib import Path from typing import Optional +from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict current_dir = os.path.dirname( @@ -158,6 +159,36 @@ class Settings(BaseSettings): COMPRESSION_PROMPT_VERSION: str = "v1.0" # Track prompt iterations COMPRESSION_MAX_HISTORY_POINTS: int = 3 # Keep only last N compression points to prevent DB bloat + @field_validator( + "API_KEY", + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "GOOGLE_API_KEY", + "GROQ_API_KEY", + "HUGGINGFACE_API_KEY", + "EMBEDDINGS_KEY", + "FALLBACK_LLM_API_KEY", + "QDRANT_API_KEY", + "ELEVENLABS_API_KEY", + "INTERNAL_KEY", + mode="before", + ) + @classmethod + def normalize_api_key(cls, v: Optional[str]) -> Optional[str]: + """ + Normalize API keys: convert 'None', 'none', empty strings, + and whitespace-only strings to actual None. + Handles Pydantic loading 'None' from .env as string "None". + """ + if v is None: + return None + if not isinstance(v, str): + return v + stripped = v.strip() + if stripped == "" or stripped.lower() == "none": + return None + return stripped + # Project root is one level above application/ path = Path(__file__).parent.parent.parent.absolute() diff --git a/deployment/docker-compose-azure.yaml b/deployment/docker-compose-azure.yaml index 9e8b6fce..de62d3a0 100644 --- a/deployment/docker-compose-azure.yaml +++ b/deployment/docker-compose-azure.yaml @@ -11,17 +11,13 @@ services: backend: build: ../application + env_file: + - ../.env environment: - - API_KEY=$OPENAI_API_KEY - - EMBEDDINGS_KEY=$OPENAI_API_KEY + # Override URLs to use docker service names - CELERY_BROKER_URL=redis://redis:6379/0 - CELERY_RESULT_BACKEND=redis://redis:6379/1 - MONGO_URI=mongodb://mongo:27017/docsgpt - - OPENAI_API_KEY=$OPENAI_API_KEY - - OPENAI_API_BASE=$OPENAI_API_BASE - - OPENAI_API_VERSION=$OPENAI_API_VERSION - - AZURE_DEPLOYMENT_NAME=$AZURE_DEPLOYMENT_NAME - - AZURE_EMBEDDINGS_DEPLOYMENT_NAME=$AZURE_EMBEDDINGS_DEPLOYMENT_NAME ports: - "7091:7091" volumes: @@ -35,18 +31,14 @@ services: worker: build: ../application command: celery -A application.app.celery worker -l INFO + env_file: + - ../.env environment: - - API_KEY=$OPENAI_API_KEY - - EMBEDDINGS_KEY=$OPENAI_API_KEY + # Override URLs to use docker service names - CELERY_BROKER_URL=redis://redis:6379/0 - CELERY_RESULT_BACKEND=redis://redis:6379/1 - MONGO_URI=mongodb://mongo:27017/docsgpt - API_URL=http://backend:7091 - - OPENAI_API_KEY=$OPENAI_API_KEY - - OPENAI_API_BASE=$OPENAI_API_BASE - - OPENAI_API_VERSION=$OPENAI_API_VERSION - - AZURE_DEPLOYMENT_NAME=$AZURE_DEPLOYMENT_NAME - - AZURE_EMBEDDINGS_DEPLOYMENT_NAME=$AZURE_EMBEDDINGS_DEPLOYMENT_NAME depends_on: - redis - mongo diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index 91c22409..6c99166d 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -19,17 +19,11 @@ services: env_file: - ../.env environment: - - API_KEY=$API_KEY - - EMBEDDINGS_KEY=$EMBEDDINGS_KEY - - EMBEDDINGS_BASE_URL=$EMBEDDINGS_BASE_URL - - LLM_PROVIDER=$LLM_PROVIDER - - LLM_NAME=$LLM_NAME + # Override URLs to use docker service names - CELERY_BROKER_URL=redis://redis:6379/0 - CELERY_RESULT_BACKEND=redis://redis:6379/1 - MONGO_URI=mongodb://mongo:27017/docsgpt - CACHE_REDIS_URL=redis://redis:6379/2 - - OPENAI_BASE_URL=$OPENAI_BASE_URL - - INTERNAL_KEY=$INTERNAL_KEY ports: - "7091:7091" volumes: @@ -47,17 +41,12 @@ services: env_file: - ../.env environment: - - API_KEY=$API_KEY - - EMBEDDINGS_KEY=$EMBEDDINGS_KEY - - EMBEDDINGS_BASE_URL=$EMBEDDINGS_BASE_URL - - LLM_PROVIDER=$LLM_PROVIDER - - LLM_NAME=$LLM_NAME + # Override URLs to use docker service names - CELERY_BROKER_URL=redis://redis:6379/0 - CELERY_RESULT_BACKEND=redis://redis:6379/1 - MONGO_URI=mongodb://mongo:27017/docsgpt - API_URL=http://backend:7091 - CACHE_REDIS_URL=redis://redis:6379/2 - - INTERNAL_KEY=$INTERNAL_KEY volumes: - ../application/indexes:/app/indexes - ../application/inputs:/app/inputs