From 3ea137bfef710bb8e1ca67d2a836c95917f15ee5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 16 Jan 2026 13:02:02 +0300 Subject: [PATCH] Manage apikeys in settings 1. More pydantic management of api keys. 2. Clean up of variable declarations from docker compose files, used to block .env imports. Now should be managed ether by settings.py defaults or .env --- application/core/model_settings.py | 39 ++++++++++------------------ application/core/model_utils.py | 8 +++--- application/core/settings.py | 31 ++++++++++++++++++++++ deployment/docker-compose-azure.yaml | 20 +++++--------- deployment/docker-compose.yaml | 15 ++--------- 5 files changed, 55 insertions(+), 58 deletions(-) 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