From 974d98b94b32a03996efe3d2cebe402011fb5afb Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 12 Jan 2026 23:48:52 +0300 Subject: [PATCH] fixes setup scripts fixes to env handling in setup script plus other minor fixes --- application/core/model_settings.py | 117 +++++++++++++++++++---------- application/core/model_utils.py | 8 +- deployment/docker-compose-hub.yaml | 5 ++ setup.ps1 | 24 +++--- setup.sh | 54 ++++++------- 5 files changed, 125 insertions(+), 83 deletions(-) diff --git a/application/core/model_settings.py b/application/core/model_settings.py index bc38239a..3f4dc030 100644 --- a/application/core/model_settings.py +++ b/application/core/model_settings.py @@ -6,6 +6,19 @@ 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" @@ -87,40 +100,53 @@ class ModelRegistry: # Skip DocsGPT model if using custom OpenAI-compatible endpoint if not settings.OPENAI_BASE_URL: self._add_docsgpt_models(settings) - if settings.OPENAI_API_KEY or ( - settings.LLM_PROVIDER == "openai" and settings.API_KEY + if ( + _is_valid_api_key(settings.OPENAI_API_KEY) + or (settings.LLM_PROVIDER == "openai" and _is_valid_api_key(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 settings.API_KEY + settings.LLM_PROVIDER == "azure_openai" and _is_valid_api_key(settings.API_KEY) ): self._add_azure_openai_models(settings) - if settings.ANTHROPIC_API_KEY or ( - settings.LLM_PROVIDER == "anthropic" and settings.API_KEY + if _is_valid_api_key(settings.ANTHROPIC_API_KEY) or ( + settings.LLM_PROVIDER == "anthropic" and _is_valid_api_key(settings.API_KEY) ): self._add_anthropic_models(settings) - if settings.GOOGLE_API_KEY or ( - settings.LLM_PROVIDER == "google" and settings.API_KEY + if _is_valid_api_key(settings.GOOGLE_API_KEY) or ( + settings.LLM_PROVIDER == "google" and _is_valid_api_key(settings.API_KEY) ): self._add_google_models(settings) - if settings.GROQ_API_KEY or ( - settings.LLM_PROVIDER == "groq" and settings.API_KEY + if _is_valid_api_key(settings.GROQ_API_KEY) or ( + settings.LLM_PROVIDER == "groq" and _is_valid_api_key(settings.API_KEY) ): self._add_groq_models(settings) - if settings.HUGGINGFACE_API_KEY or ( - settings.LLM_PROVIDER == "huggingface" and settings.API_KEY + if _is_valid_api_key(settings.HUGGINGFACE_API_KEY) or ( + settings.LLM_PROVIDER == "huggingface" and _is_valid_api_key(settings.API_KEY) ): self._add_huggingface_models(settings) # Default model selection - - if settings.LLM_NAME and settings.LLM_NAME in self.models: - self.default_model_id = settings.LLM_NAME - elif 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 + if settings.LLM_NAME: + # Parse LLM_NAME (may be comma-separated) + model_names = self._parse_model_names(settings.LLM_NAME) + # First model in the list becomes default + for model_name in model_names: + if model_name in self.models: + self.default_model_id = model_name break - else: + # Backward compat: try exact match if no parsed model found + if not self.default_model_id and settings.LLM_NAME in self.models: + 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): + for model_id, model in self.models.items(): + if model.provider.value == settings.LLM_PROVIDER: + self.default_model_id = model_id + break + + if not self.default_model_id and self.models: self.default_model_id = next(iter(self.models.keys())) logger.info( f"ModelRegistry loaded {len(self.models)} models, default: {self.default_model_id}" @@ -132,29 +158,29 @@ class ModelRegistry: create_custom_openai_model, ) - # Add standard OpenAI models if API key is present - if settings.OPENAI_API_KEY: - for model in OPENAI_MODELS: - self.models[model.id] = model + # Check if using local OpenAI-compatible endpoint (Ollama, LM Studio, etc.) + using_local_endpoint = bool( + settings.OPENAI_BASE_URL and settings.OPENAI_BASE_URL.strip() + ) - # Add custom model if OPENAI_BASE_URL is configured with a custom LLM_NAME - if ( - settings.LLM_PROVIDER == "openai" - and settings.OPENAI_BASE_URL - and settings.LLM_NAME - ): - custom_model = create_custom_openai_model( - settings.LLM_NAME, settings.OPENAI_BASE_URL - ) - self.models[settings.LLM_NAME] = custom_model - logger.info( - f"Registered custom OpenAI model: {settings.LLM_NAME} at {settings.OPENAI_BASE_URL}" - ) - - # Fallback: add all OpenAI models if none were added - if not any(m.provider.value == "openai" for m in self.models.values()): - for model in OPENAI_MODELS: - self.models[model.id] = model + if using_local_endpoint: + # When OPENAI_BASE_URL is set, ONLY register custom models from LLM_NAME + # Do NOT add standard OpenAI models (gpt-5.1, etc.) + if settings.LLM_NAME: + model_names = self._parse_model_names(settings.LLM_NAME) + for model_name in model_names: + custom_model = create_custom_openai_model( + model_name, settings.OPENAI_BASE_URL + ) + self.models[model_name] = custom_model + logger.info( + f"Registered custom OpenAI model: {model_name} at {settings.OPENAI_BASE_URL}" + ) + else: + # Standard OpenAI API usage - add standard models if API key is valid + if _is_valid_api_key(settings.OPENAI_API_KEY): + for model in OPENAI_MODELS: + self.models[model.id] = model def _add_azure_openai_models(self, settings): from application.core.model_configs import AZURE_OPENAI_MODELS @@ -240,6 +266,15 @@ class ModelRegistry: ) self.models[model_id] = model + def _parse_model_names(self, llm_name: str) -> List[str]: + """ + Parse LLM_NAME which may contain comma-separated model names. + E.g., 'deepseek-r1:1.5b,gemma:2b' -> ['deepseek-r1:1.5b', 'gemma:2b'] + """ + if not llm_name: + return [] + return [name.strip() for name in llm_name.split(",") if name.strip()] + def get_model(self, model_id: str) -> Optional[AvailableModel]: return self.models.get(model_id) diff --git a/application/core/model_utils.py b/application/core/model_utils.py index f24dbf47..32f207f5 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 +from application.core.model_settings import ModelRegistry, _is_valid_api_key def get_api_key_for_provider(provider: str) -> Optional[str]: @@ -19,9 +19,11 @@ def get_api_key_for_provider(provider: str) -> Optional[str]: } provider_key = provider_key_map.get(provider) - if provider_key: + if provider_key and _is_valid_api_key(provider_key): return provider_key - return settings.API_KEY + if _is_valid_api_key(settings.API_KEY): + return settings.API_KEY + return None def get_all_available_models() -> Dict[str, Dict[str, Any]]: diff --git a/deployment/docker-compose-hub.yaml b/deployment/docker-compose-hub.yaml index b54aa7b7..f7ba06de 100644 --- a/deployment/docker-compose-hub.yaml +++ b/deployment/docker-compose-hub.yaml @@ -16,6 +16,8 @@ services: backend: user: root image: arc53/docsgpt:develop + env_file: + - ../.env environment: - API_KEY=$API_KEY - EMBEDDINGS_KEY=$API_KEY @@ -41,6 +43,8 @@ services: user: root image: arc53/docsgpt:develop command: celery -A application.app.celery worker -l INFO -B + env_file: + - ../.env environment: - API_KEY=$API_KEY - EMBEDDINGS_KEY=$API_KEY @@ -51,6 +55,7 @@ services: - MONGO_URI=mongodb://mongo:27017/docsgpt - API_URL=http://backend:7091 - CACHE_REDIS_URL=redis://redis:6379/2 + - OPENAI_BASE_URL=$OPENAI_BASE_URL volumes: - ../application/indexes:/app/indexes - ../application/inputs:/app/inputs diff --git a/setup.ps1 b/setup.ps1 index 1ed3a821..45b354e1 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -398,9 +398,9 @@ function Serve-LocalOllama { # Create .env file "API_KEY=xxxx" | Out-File -FilePath $ENV_FILE -Encoding utf8 -Force "LLM_PROVIDER=openai" | Add-Content -Path $ENV_FILE -Encoding utf8 - "MODEL_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 + "LLM_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 "VITE_API_STREAMING=true" | Add-Content -Path $ENV_FILE -Encoding utf8 - "OPENAI_BASE_URL=http://host.docker.internal:11434/v1" | Add-Content -Path $ENV_FILE -Encoding utf8 + "OPENAI_BASE_URL=http://ollama:11434/v1" | Add-Content -Path $ENV_FILE -Encoding utf8 "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" | Add-Content -Path $ENV_FILE -Encoding utf8 Write-ColorText ".env file configured for Ollama ($($docker_compose_file_suffix.ToUpper()))." -ForegroundColor "Green" @@ -495,49 +495,49 @@ function Connect-LocalInferenceEngine { switch ($engine_choice) { "1" { # LLaMa.cpp $script:engine_name = "LLaMa.cpp" - $script:openai_base_url = "http://localhost:8000/v1" + $script:openai_base_url = "http://host.docker.internal:8000/v1" Get-ModelName break } "2" { # Ollama $script:engine_name = "Ollama" - $script:openai_base_url = "http://localhost:11434/v1" + $script:openai_base_url = "http://host.docker.internal:11434/v1" Get-ModelName break } "3" { # TGI $script:engine_name = "TGI" - $script:openai_base_url = "http://localhost:8080/v1" + $script:openai_base_url = "http://host.docker.internal:8080/v1" Get-ModelName break } "4" { # SGLang $script:engine_name = "SGLang" - $script:openai_base_url = "http://localhost:30000/v1" + $script:openai_base_url = "http://host.docker.internal:30000/v1" Get-ModelName break } "5" { # vLLM $script:engine_name = "vLLM" - $script:openai_base_url = "http://localhost:8000/v1" + $script:openai_base_url = "http://host.docker.internal:8000/v1" Get-ModelName break } "6" { # Aphrodite $script:engine_name = "Aphrodite" - $script:openai_base_url = "http://localhost:2242/v1" + $script:openai_base_url = "http://host.docker.internal:2242/v1" Get-ModelName break } "7" { # FriendliAI $script:engine_name = "FriendliAI" - $script:openai_base_url = "http://localhost:8997/v1" + $script:openai_base_url = "http://host.docker.internal:8997/v1" Get-ModelName break } "8" { # LMDeploy $script:engine_name = "LMDeploy" - $script:openai_base_url = "http://localhost:23333/v1" + $script:openai_base_url = "http://host.docker.internal:23333/v1" Get-ModelName break } @@ -561,7 +561,7 @@ function Connect-LocalInferenceEngine { # Create .env file "API_KEY=None" | Out-File -FilePath $ENV_FILE -Encoding utf8 -Force "LLM_PROVIDER=openai" | Add-Content -Path $ENV_FILE -Encoding utf8 - "MODEL_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 + "LLM_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 "VITE_API_STREAMING=true" | Add-Content -Path $ENV_FILE -Encoding utf8 "OPENAI_BASE_URL=$openai_base_url" | Add-Content -Path $ENV_FILE -Encoding utf8 "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" | Add-Content -Path $ENV_FILE -Encoding utf8 @@ -694,7 +694,7 @@ function Connect-CloudAPIProvider { # Create .env file "API_KEY=$api_key" | Out-File -FilePath $ENV_FILE -Encoding utf8 -Force "LLM_PROVIDER=$llm_name" | Add-Content -Path $ENV_FILE -Encoding utf8 - "MODEL_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 + "LLM_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8 "VITE_API_STREAMING=true" | Add-Content -Path $ENV_FILE -Encoding utf8 Write-ColorText ".env file configured for $provider_name." -ForegroundColor "Green" diff --git a/setup.sh b/setup.sh index 23aeb717..690400a7 100755 --- a/setup.sh +++ b/setup.sh @@ -173,8 +173,8 @@ prompt_ollama_options() { # 1) Use DocsGPT Public API Endpoint (simple and free) use_docs_public_api_endpoint() { echo -e "\n${NC}Setting up DocsGPT Public API Endpoint...${NC}" - echo "LLM_PROVIDER=docsgpt" > .env - echo "VITE_API_STREAMING=true" >> .env + echo "LLM_PROVIDER=docsgpt" > "$ENV_FILE" + echo "VITE_API_STREAMING=true" >> "$ENV_FILE" echo -e "${GREEN}.env file configured for DocsGPT Public API.${NC}" check_and_start_docker @@ -240,12 +240,12 @@ serve_local_ollama() { echo -e "\n${NC}Configuring for Ollama ($(echo "$docker_compose_file_suffix" | tr '[:lower:]' '[:upper:]'))...${NC}" # Using tr for uppercase - more compatible - echo "API_KEY=xxxx" > .env # Placeholder API Key - echo "LLM_PROVIDER=openai" >> .env - echo "LLM_NAME=$model_name" >> .env - echo "VITE_API_STREAMING=true" >> .env - echo "OPENAI_BASE_URL=http://ollama:11434/v1" >> .env - echo "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" >> .env + echo "API_KEY=xxxx" > "$ENV_FILE" # Placeholder API Key + echo "LLM_PROVIDER=openai" >> "$ENV_FILE" + echo "LLM_NAME=$model_name" >> "$ENV_FILE" + echo "VITE_API_STREAMING=true" >> "$ENV_FILE" + echo "OPENAI_BASE_URL=http://ollama:11434/v1" >> "$ENV_FILE" + echo "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" >> "$ENV_FILE" echo -e "${GREEN}.env file configured for Ollama ($(echo "$docker_compose_file_suffix" | tr '[:lower:]' '[:upper:]')${NC}${GREEN}).${NC}" @@ -308,42 +308,42 @@ connect_local_inference_engine() { case "$engine_choice" in 1) # LLaMa.cpp engine_name="LLaMa.cpp" - openai_base_url="http://localhost:8000/v1" + openai_base_url="http://host.docker.internal:8000/v1" get_model_name break ;; 2) # Ollama engine_name="Ollama" - openai_base_url="http://localhost:11434/v1" + openai_base_url="http://host.docker.internal:11434/v1" get_model_name break ;; 3) # TGI engine_name="TGI" - openai_base_url="http://localhost:8080/v1" + openai_base_url="http://host.docker.internal:8080/v1" get_model_name break ;; 4) # SGLang engine_name="SGLang" - openai_base_url="http://localhost:30000/v1" + openai_base_url="http://host.docker.internal:30000/v1" get_model_name break ;; 5) # vLLM engine_name="vLLM" - openai_base_url="http://localhost:8000/v1" + openai_base_url="http://host.docker.internal:8000/v1" get_model_name break ;; 6) # Aphrodite engine_name="Aphrodite" - openai_base_url="http://localhost:2242/v1" + openai_base_url="http://host.docker.internal:2242/v1" get_model_name break ;; 7) # FriendliAI engine_name="FriendliAI" - openai_base_url="http://localhost:8997/v1" + openai_base_url="http://host.docker.internal:8997/v1" get_model_name break ;; 8) # LMDeploy engine_name="LMDeploy" - openai_base_url="http://localhost:23333/v1" + openai_base_url="http://host.docker.internal:23333/v1" get_model_name break ;; b|B) clear; return ;; # Back to Main Menu @@ -352,19 +352,19 @@ connect_local_inference_engine() { done echo -e "\n${NC}Configuring for Local Inference Engine: ${BOLD}${engine_name}...${NC}" - echo "API_KEY=None" > .env - echo "LLM_PROVIDER=openai" >> .env - echo "LLM_NAME=$model_name" >> .env - echo "VITE_API_STREAMING=true" >> .env - echo "OPENAI_BASE_URL=$openai_base_url" >> .env - echo "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" >> .env + echo "API_KEY=None" > "$ENV_FILE" + echo "LLM_PROVIDER=openai" >> "$ENV_FILE" + echo "LLM_NAME=$model_name" >> "$ENV_FILE" + echo "VITE_API_STREAMING=true" >> "$ENV_FILE" + echo "OPENAI_BASE_URL=$openai_base_url" >> "$ENV_FILE" + echo "EMBEDDINGS_NAME=huggingface_sentence-transformers/all-mpnet-base-v2" >> "$ENV_FILE" echo -e "${GREEN}.env file configured for ${BOLD}${engine_name}${NC}${GREEN} with OpenAI API format.${NC}" echo -e "${YELLOW}Note: MODEL_NAME is set to '${BOLD}$model_name${NC}${YELLOW}'. You can change it later in the .env file.${NC}" check_and_start_docker echo -e "\n${NC}Starting Docker Compose...${NC}" - docker compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" pull && docker compose -f "${COMPOSE_FILE}" up -d + docker compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" pull && docker compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" up -d docker_compose_status=$? echo "Docker Compose Exit Status: $docker_compose_status" # Debug output @@ -444,10 +444,10 @@ connect_cloud_api_provider() { done echo -e "\n${NC}Configuring for Cloud API Provider: ${BOLD}${provider_name}...${NC}" - echo "API_KEY=$api_key" > .env - echo "LLM_PROVIDER=$llm_provider" >> .env - echo "LLM_NAME=$model_name" >> .env - echo "VITE_API_STREAMING=true" >> .env + echo "API_KEY=$api_key" > "$ENV_FILE" + echo "LLM_PROVIDER=$llm_provider" >> "$ENV_FILE" + echo "LLM_NAME=$model_name" >> "$ENV_FILE" + echo "VITE_API_STREAMING=true" >> "$ENV_FILE" echo -e "${GREEN}.env file configured for ${BOLD}${provider_name}${NC}${GREEN}.${NC}" check_and_start_docker