feat: update all OpenAI/ChatGPT refs + settings dropdown for fixed-value fields

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-23 20:06:57 +00:00
parent dcdef44303
commit 044da0ff47
17 changed files with 383 additions and 113 deletions
+57 -1
View File
@@ -1,5 +1,9 @@
"""
OpenAI API endpoints
AI provider and OpenAI API endpoints.
Exposes two endpoints:
- GET /api/ai/test tests the currently configured AI provider (generic, provider-agnostic)
- GET /api/openai/test backward-compatible alias that tests the OpenAI API specifically
"""
import logging
@@ -146,3 +150,55 @@ async def test_openai_connection(request: Request):
except Exception as e:
logger.exception("Unexpected error testing OpenAI connection")
return {"status": "error", "message": f"Unexpected error: {str(e)}"}
@router.get("/ai/test")
@require_login
async def test_ai_provider_connection(request: Request):
"""
Test the currently configured AI provider connection.
Uses ``get_ai_provider()`` to instantiate the active provider and sends a
minimal chat completion to verify that the credentials and endpoint are
reachable. Works for all supported providers (OpenAI, Azure, Anthropic,
Gemini, Ollama, OpenRouter, Portkey, LiteLLM).
"""
from app.utils.ai_provider import get_ai_provider
provider_name = settings.ai_provider
model = settings.ai_model or settings.openai_model
logger.info(f"Testing AI provider connection: provider={provider_name}, model={model}")
try:
provider = get_ai_provider()
response = provider.chat_completion(
messages=[{"role": "user", "content": "Reply with the single word: ok"}],
model=model,
temperature=0,
max_tokens=5,
)
logger.info(f"AI provider test successful: provider={provider_name}")
return {
"status": "success",
"message": f"AI provider '{provider_name}' is reachable and responding",
"provider": provider_name,
"model": model,
"response_preview": (response or "")[:50],
}
except ValueError as e:
# Configuration errors (missing keys, unknown provider)
logger.warning(f"AI provider configuration error: {e}")
return {
"status": "error",
"message": str(e),
"provider": provider_name,
}
except Exception as e:
detail = _get_exception_chain_detail(e)
logger.error(f"AI provider test failed for '{provider_name}': {detail}", exc_info=True)
return {
"status": "error",
"message": f"Connection failed: {detail}",
"provider": provider_name,
}
+15 -6
View File
@@ -11,7 +11,7 @@ from app.api.google_drive import test_google_drive_token
from app.api.onedrive import test_onedrive_token
# Import the test functions from API routes
from app.api.openai import test_openai_connection
from app.api.openai import test_ai_provider_connection, test_openai_connection
from app.celery_app import celery
from app.config import settings
@@ -75,8 +75,17 @@ def unwrap_decorated_function(func):
# Create synchronous versions of the test functions that bypass authentication
def sync_test_ai_provider_connection():
"""Synchronous wrapper for the AI provider test function that bypasses auth."""
inner_func = unwrap_decorated_function(test_ai_provider_connection)
request = MockRequest()
if inspect.iscoroutinefunction(inner_func):
return asyncio.run(inner_func(request))
return inner_func(request)
def sync_test_openai_connection():
"""Synchronous wrapper for the OpenAI test function that bypasses auth"""
"""Synchronous wrapper for the OpenAI test function that bypasses auth."""
# Get the original function without the @require_login decorator
inner_func = unwrap_decorated_function(test_openai_connection)
request = MockRequest()
@@ -139,10 +148,10 @@ def check_credentials():
# Define services with their test functions and configuration status
services = [
{
"name": "OpenAI",
"check_func": sync_test_openai_connection,
"configured": provider_status.get("OpenAI", {}).get("configured", False),
"config_issues": [], # OpenAI isn't in storage_configs
"name": "AI Provider",
"check_func": sync_test_ai_provider_connection,
"configured": provider_status.get("AI Provider", {}).get("configured", False),
"config_issues": [],
},
{
"name": "Azure Document Intelligence",
+62 -13
View File
@@ -57,20 +57,69 @@ def get_provider_status() -> dict[str, dict[str, object]]:
"test_endpoint": "/api/diagnostic/test-notification",
}
# Add AI services first
providers["OpenAI"] = {
"name": "OpenAI",
"icon": "fa-brands fa-openai",
"configured": bool(
getattr(settings, "openai_api_key", None) and str(getattr(settings, "openai_api_key", "")).startswith("sk-")
),
# Add AI provider (dynamic reflects whichever provider is configured)
ai_provider_name = getattr(settings, "ai_provider", "openai").lower()
model = getattr(settings, "ai_model", None) or getattr(settings, "openai_model", "gpt-4o-mini")
# Determine whether the active provider has its required credentials set
def _ai_configured() -> bool:
if ai_provider_name in ("openai", "azure", "litellm"):
return bool(getattr(settings, "openai_api_key", None))
elif ai_provider_name == "anthropic":
return bool(getattr(settings, "anthropic_api_key", None))
elif ai_provider_name == "gemini":
return bool(getattr(settings, "gemini_api_key", None))
elif ai_provider_name == "ollama":
return bool(getattr(settings, "ollama_base_url", None))
elif ai_provider_name == "openrouter":
return bool(getattr(settings, "openrouter_api_key", None))
elif ai_provider_name == "portkey":
return bool(getattr(settings, "portkey_api_key", None))
return False
# Build provider-specific detail rows
def _ai_details() -> dict:
base = {"provider": ai_provider_name, "model": model}
if ai_provider_name in ("openai", "azure", "litellm"):
base["api_key"] = mask_sensitive_value(getattr(settings, "openai_api_key", None))
base["base_url"] = getattr(settings, "openai_base_url", "https://api.openai.com/v1")
elif ai_provider_name == "anthropic":
base["api_key"] = mask_sensitive_value(getattr(settings, "anthropic_api_key", None))
elif ai_provider_name == "gemini":
base["api_key"] = mask_sensitive_value(getattr(settings, "gemini_api_key", None))
elif ai_provider_name == "ollama":
base["base_url"] = getattr(settings, "ollama_base_url", "http://localhost:11434")
elif ai_provider_name == "openrouter":
base["api_key"] = mask_sensitive_value(getattr(settings, "openrouter_api_key", None))
base["base_url"] = getattr(settings, "openrouter_base_url", "https://openrouter.ai/api/v1")
elif ai_provider_name == "portkey":
base["api_key"] = mask_sensitive_value(getattr(settings, "portkey_api_key", None))
if getattr(settings, "portkey_virtual_key", None):
base["virtual_key"] = mask_sensitive_value(settings.portkey_virtual_key)
if getattr(settings, "portkey_config", None):
base["config"] = settings.portkey_config
return base
_PROVIDER_ICONS = {
"openai": "fa-brands fa-openai",
"azure": "fa-brands fa-microsoft",
"anthropic": "fa-solid fa-robot",
"gemini": "fa-brands fa-google",
"ollama": "fa-solid fa-server",
"openrouter": "fa-solid fa-route",
"portkey": "fa-solid fa-key",
"litellm": "fa-solid fa-layer-group",
}
providers["AI Provider"] = {
"name": "AI Provider",
"icon": _PROVIDER_ICONS.get(ai_provider_name, "fa-solid fa-microchip"),
"configured": _ai_configured(),
"enabled": True,
"description": "AI-powered document analysis and metadata extraction",
"details": {
"api_key": mask_sensitive_value(getattr(settings, "openai_api_key", None)),
"base_url": getattr(settings, "openai_base_url", "https://api.openai.com/v1"),
"model": getattr(settings, "openai_model", "gpt-4"),
},
"description": f"AI-powered metadata extraction and OCR refinement ({ai_provider_name})",
"details": _ai_details(),
"testable": True,
"test_endpoint": "/api/ai/test",
}
providers["Azure AI"] = {
@@ -170,9 +170,21 @@ def get_settings_for_display(show_values: bool = False) -> dict[str, list[dict[s
"s3_acl",
],
"AI Services": [
"ai_provider",
"ai_model",
"openai_api_key",
"openai_base_url",
"openai_model",
"anthropic_api_key",
"gemini_api_key",
"ollama_base_url",
"openrouter_api_key",
"openrouter_base_url",
"portkey_api_key",
"portkey_virtual_key",
"portkey_config",
"portkey_base_url",
"azure_openai_api_version",
"azure_ai_key",
"azure_endpoint",
"azure_region",
+109 -11
View File
@@ -136,15 +136,15 @@ SETTING_METADATA = {
# AI Services
"openai_api_key": {
"category": "AI Services",
"description": "OpenAI API key for metadata extraction",
"description": "API key for the AI provider (required for OpenAI, Azure, LiteLLM; unused for Ollama)",
"type": "string",
"sensitive": True,
"required": True,
"required": False,
"restart_required": False,
},
"openai_base_url": {
"category": "AI Services",
"description": "OpenAI API base URL (default: https://api.openai.com/v1)",
"description": "API base URL override for Azure endpoints, local proxies, or LiteLLM (default: https://api.openai.com/v1)",
"type": "string",
"sensitive": False,
"required": False,
@@ -152,34 +152,132 @@ SETTING_METADATA = {
},
"openai_model": {
"category": "AI Services",
"description": "OpenAI model to use (e.g., gpt-4o-mini)",
"description": "Fallback model name used when AI_MODEL is not set (e.g. gpt-4o-mini)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"azure_ai_key": {
"ai_provider": {
"category": "AI Services",
"description": "Azure AI key for document intelligence",
"description": "Active AI provider for metadata extraction and OCR refinement",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
"options": ["openai", "azure", "anthropic", "gemini", "ollama", "openrouter", "portkey", "litellm"],
},
"ai_model": {
"category": "AI Services",
"description": "Model name for the selected provider (overrides OPENAI_MODEL). E.g. gpt-4o, claude-3-5-sonnet-20241022, gemini-1.5-pro, llama3.2",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"anthropic_api_key": {
"category": "AI Services",
"description": "Anthropic API key (required when AI_PROVIDER=anthropic)",
"type": "string",
"sensitive": True,
"required": True,
"required": False,
"restart_required": False,
},
"gemini_api_key": {
"category": "AI Services",
"description": "Google AI Studio API key (required when AI_PROVIDER=gemini)",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"ollama_base_url": {
"category": "AI Services",
"description": "Ollama server URL for local LLM inference (used when AI_PROVIDER=ollama)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"openrouter_api_key": {
"category": "AI Services",
"description": "OpenRouter API key (required when AI_PROVIDER=openrouter)",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"openrouter_base_url": {
"category": "AI Services",
"description": "OpenRouter gateway URL (default: https://openrouter.ai/api/v1)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"portkey_api_key": {
"category": "AI Services",
"description": "Portkey account API key (required when AI_PROVIDER=portkey)",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"portkey_virtual_key": {
"category": "AI Services",
"description": "Portkey Virtual Key routes to provider credentials stored in the Portkey vault (optional)",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"portkey_config": {
"category": "AI Services",
"description": "Portkey Config ID for advanced routing, fallbacks, and load balancing (optional, e.g. pc-my-config-abc123)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"portkey_base_url": {
"category": "AI Services",
"description": "Portkey gateway URL (default: https://api.portkey.ai/v1; override for self-hosted deployments)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"azure_openai_api_version": {
"category": "AI Services",
"description": "Azure OpenAI API version string (used when AI_PROVIDER=azure, default: 2024-02-01)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Azure Document Intelligence (OCR) separate from the AI provider above
"azure_ai_key": {
"category": "AI Services",
"description": "Azure Document Intelligence API key for OCR processing",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"azure_region": {
"category": "AI Services",
"description": "Azure region for AI services",
"description": "Azure region for Document Intelligence services (e.g., eastus)",
"type": "string",
"sensitive": False,
"required": True,
"required": False,
"restart_required": False,
},
"azure_endpoint": {
"category": "AI Services",
"description": "Azure AI endpoint URL",
"description": "Azure Document Intelligence endpoint URL",
"type": "string",
"sensitive": False,
"required": True,
"required": False,
"restart_required": False,
},
# Storage Providers - Dropbox
+17 -28
View File
@@ -90,10 +90,21 @@ def get_required_settings() -> List[Dict[str, Any]]:
"wizard_step": 2,
"wizard_category": "Security",
},
{
"key": "ai_provider",
"label": "AI Provider",
"description": "AI provider for metadata extraction and OCR refinement (openai, azure, anthropic, gemini, ollama, openrouter, portkey, litellm)",
"type": "string",
"sensitive": False,
"default": "openai",
"options": ["openai", "azure", "anthropic", "gemini", "ollama", "openrouter", "portkey", "litellm"],
"wizard_step": 3,
"wizard_category": "AI Services",
},
{
"key": "openai_api_key",
"label": "OpenAI API Key",
"description": "API key for OpenAI services (metadata extraction)",
"label": "API Key (OpenAI / Azure / LiteLLM)",
"description": "API key for OpenAI, Azure OpenAI, or LiteLLM (not required for Ollama)",
"type": "string",
"sensitive": True,
"default": None,
@@ -101,32 +112,12 @@ def get_required_settings() -> List[Dict[str, Any]]:
"wizard_category": "AI Services",
},
{
"key": "azure_ai_key",
"label": "Azure AI Key",
"description": "Azure AI key for document intelligence (OCR)",
"type": "string",
"sensitive": True,
"default": None,
"wizard_step": 3,
"wizard_category": "AI Services",
},
{
"key": "azure_region",
"label": "Azure Region",
"description": "Azure region for AI services (e.g., eastus)",
"key": "openai_model",
"label": "Default Model",
"description": "Model name used when AI_MODEL is not set (e.g. gpt-4o-mini, claude-3-5-sonnet-20241022, llama3.2)",
"type": "string",
"sensitive": False,
"default": "eastus",
"wizard_step": 3,
"wizard_category": "AI Services",
},
{
"key": "azure_endpoint",
"label": "Azure Endpoint",
"description": "Azure AI endpoint URL",
"type": "string",
"sensitive": False,
"default": None,
"default": "gpt-4o-mini",
"wizard_step": 3,
"wizard_category": "AI Services",
},
@@ -147,8 +138,6 @@ def is_setup_required() -> bool:
critical_settings = [
("session_secret", ["INSECURE_DEFAULT_FOR_DEVELOPMENT_ONLY_DO_NOT_USE_IN_PRODUCTION_MINIMUM_32_CHARS"]),
("admin_password", [None, "", "your_secure_password", "changeme", "admin"]),
("openai_api_key", [None, "", "<OPENAI_API_KEY>", "test-key"]),
("azure_ai_key", [None, "", "<AZURE_AI_KEY>", "test-key"]),
]
for setting_key, invalid_values in critical_settings: