Spaces:
Build error
Build error
| import asyncio | |
| import inspect | |
| import json | |
| import logging | |
| import mimetypes | |
| import os | |
| import shutil | |
| import sys | |
| import time | |
| import random | |
| import re | |
| from uuid import uuid4 | |
| from contextlib import asynccontextmanager | |
| from urllib.parse import urlencode, parse_qs, urlparse | |
| from pydantic import BaseModel | |
| from sqlalchemy import text | |
| from typing import Optional | |
| from aiocache import cached | |
| import aiohttp | |
| import anyio.to_thread | |
| from redis import Redis | |
| from fastapi import ( | |
| Depends, | |
| FastAPI, | |
| File, | |
| Form, | |
| HTTPException, | |
| Request, | |
| UploadFile, | |
| status, | |
| applications, | |
| BackgroundTasks, | |
| ) | |
| from fastapi.openapi.docs import get_swagger_ui_html | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, JSONResponse, RedirectResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from starlette_compress import CompressMiddleware | |
| from starlette.exceptions import HTTPException as StarletteHTTPException | |
| from starlette.middleware.sessions import SessionMiddleware | |
| from starlette.responses import Response, StreamingResponse | |
| from starlette.datastructures import Headers | |
| from starsessions import ( | |
| SessionMiddleware as StarSessionsMiddleware, | |
| SessionAutoloadMiddleware, | |
| ) | |
| from starsessions.stores.redis import RedisStore | |
| from open_webui.utils import logger | |
| from open_webui.utils.asgi_middleware import ( | |
| AuthTokenMiddleware, | |
| CommitSessionMiddleware, | |
| RedirectMiddleware, | |
| WebsocketUpgradeGuardMiddleware, | |
| ) | |
| from open_webui.utils.audit import AuditLevel, AuditLoggingMiddleware | |
| from open_webui.utils.logger import start_logger | |
| from open_webui.utils.session_pool import get_session | |
| from open_webui.socket.main import ( | |
| MODELS, | |
| app as socket_app, | |
| periodic_usage_pool_cleanup, | |
| periodic_session_pool_cleanup, | |
| get_event_emitter, | |
| get_models_in_use, | |
| get_user_id_from_session_pool, | |
| ) | |
| from open_webui.routers import ( | |
| analytics, | |
| audio, | |
| images, | |
| ollama, | |
| openai, | |
| retrieval, | |
| pipelines, | |
| tasks, | |
| auths, | |
| channels, | |
| chats, | |
| notes, | |
| folders, | |
| configs, | |
| groups, | |
| files, | |
| functions, | |
| memories, | |
| models, | |
| knowledge, | |
| prompts, | |
| evaluations, | |
| skills, | |
| tools, | |
| users, | |
| utils, | |
| scim, | |
| terminals, | |
| automations, | |
| calendar, | |
| ) | |
| from open_webui.routers.retrieval import ( | |
| get_embedding_function, | |
| get_reranking_function, | |
| get_ef, | |
| get_rf, | |
| ) | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from open_webui.internal.db import ScopedSession, engine, get_async_session | |
| from open_webui.models.functions import Functions | |
| from open_webui.models.models import Models | |
| from open_webui.models.users import UserModel, Users | |
| from open_webui.models.chats import Chats, ChatForm | |
| from open_webui.config import ( | |
| # Ollama | |
| ENABLE_OLLAMA_API, | |
| OLLAMA_BASE_URLS, | |
| OLLAMA_API_CONFIGS, | |
| # OpenAI | |
| ENABLE_OPENAI_API, | |
| OPENAI_API_BASE_URLS, | |
| OPENAI_API_KEYS, | |
| OPENAI_API_CONFIGS, | |
| # Direct Connections | |
| ENABLE_DIRECT_CONNECTIONS, | |
| # Model list | |
| ENABLE_BASE_MODELS_CACHE, | |
| # Thread pool size for FastAPI/AnyIO | |
| THREAD_POOL_SIZE, | |
| # Tool Server Configs | |
| TOOL_SERVER_CONNECTIONS, | |
| # Terminal Server | |
| TERMINAL_SERVER_CONNECTIONS, | |
| # Code Execution | |
| ENABLE_CODE_EXECUTION, | |
| CODE_EXECUTION_ENGINE, | |
| CODE_EXECUTION_JUPYTER_URL, | |
| CODE_EXECUTION_JUPYTER_AUTH, | |
| CODE_EXECUTION_JUPYTER_AUTH_TOKEN, | |
| CODE_EXECUTION_JUPYTER_AUTH_PASSWORD, | |
| CODE_EXECUTION_JUPYTER_TIMEOUT, | |
| ENABLE_CODE_INTERPRETER, | |
| CODE_INTERPRETER_ENGINE, | |
| CODE_INTERPRETER_PROMPT_TEMPLATE, | |
| CODE_INTERPRETER_JUPYTER_URL, | |
| CODE_INTERPRETER_JUPYTER_AUTH, | |
| CODE_INTERPRETER_JUPYTER_AUTH_TOKEN, | |
| CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD, | |
| CODE_INTERPRETER_JUPYTER_TIMEOUT, | |
| ENABLE_MEMORIES, | |
| # Image | |
| AUTOMATIC1111_API_AUTH, | |
| AUTOMATIC1111_BASE_URL, | |
| AUTOMATIC1111_PARAMS, | |
| COMFYUI_BASE_URL, | |
| COMFYUI_API_KEY, | |
| COMFYUI_WORKFLOW, | |
| COMFYUI_WORKFLOW_NODES, | |
| ENABLE_IMAGE_GENERATION, | |
| ENABLE_IMAGE_PROMPT_GENERATION, | |
| IMAGE_GENERATION_ENGINE, | |
| IMAGE_GENERATION_MODEL, | |
| IMAGE_SIZE, | |
| IMAGE_STEPS, | |
| IMAGES_OPENAI_API_BASE_URL, | |
| IMAGES_OPENAI_API_VERSION, | |
| IMAGES_OPENAI_API_KEY, | |
| IMAGES_OPENAI_API_PARAMS, | |
| IMAGES_GEMINI_API_BASE_URL, | |
| IMAGES_GEMINI_API_KEY, | |
| IMAGES_GEMINI_ENDPOINT_METHOD, | |
| ENABLE_IMAGE_EDIT, | |
| IMAGE_EDIT_ENGINE, | |
| IMAGE_EDIT_MODEL, | |
| IMAGE_EDIT_SIZE, | |
| IMAGES_EDIT_OPENAI_API_BASE_URL, | |
| IMAGES_EDIT_OPENAI_API_KEY, | |
| IMAGES_EDIT_OPENAI_API_VERSION, | |
| IMAGES_EDIT_GEMINI_API_BASE_URL, | |
| IMAGES_EDIT_GEMINI_API_KEY, | |
| IMAGES_EDIT_COMFYUI_BASE_URL, | |
| IMAGES_EDIT_COMFYUI_API_KEY, | |
| IMAGES_EDIT_COMFYUI_WORKFLOW, | |
| IMAGES_EDIT_COMFYUI_WORKFLOW_NODES, | |
| # Audio | |
| AUDIO_STT_ENGINE, | |
| AUDIO_STT_MODEL, | |
| AUDIO_STT_SUPPORTED_CONTENT_TYPES, | |
| AUDIO_STT_OPENAI_API_BASE_URL, | |
| AUDIO_STT_OPENAI_API_KEY, | |
| AUDIO_STT_AZURE_API_KEY, | |
| AUDIO_STT_AZURE_REGION, | |
| AUDIO_STT_AZURE_LOCALES, | |
| AUDIO_STT_AZURE_BASE_URL, | |
| AUDIO_STT_AZURE_MAX_SPEAKERS, | |
| AUDIO_STT_MISTRAL_API_KEY, | |
| AUDIO_STT_MISTRAL_API_BASE_URL, | |
| AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS, | |
| AUDIO_TTS_ENGINE, | |
| AUDIO_TTS_MODEL, | |
| AUDIO_TTS_VOICE, | |
| AUDIO_TTS_OPENAI_API_BASE_URL, | |
| AUDIO_TTS_OPENAI_API_KEY, | |
| AUDIO_TTS_OPENAI_PARAMS, | |
| AUDIO_TTS_API_KEY, | |
| AUDIO_TTS_SPLIT_ON, | |
| AUDIO_TTS_AZURE_SPEECH_REGION, | |
| AUDIO_TTS_AZURE_SPEECH_BASE_URL, | |
| AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT, | |
| AUDIO_TTS_MISTRAL_API_KEY, | |
| AUDIO_TTS_MISTRAL_API_BASE_URL, | |
| PLAYWRIGHT_WS_URL, | |
| PLAYWRIGHT_TIMEOUT, | |
| FIRECRAWL_API_BASE_URL, | |
| FIRECRAWL_API_KEY, | |
| FIRECRAWL_TIMEOUT, | |
| WEB_LOADER_ENGINE, | |
| WEB_LOADER_CONCURRENT_REQUESTS, | |
| WEB_LOADER_TIMEOUT, | |
| WHISPER_MODEL, | |
| WHISPER_VAD_FILTER, | |
| WHISPER_LANGUAGE, | |
| DEEPGRAM_API_KEY, | |
| WHISPER_MODEL_AUTO_UPDATE, | |
| WHISPER_MODEL_DIR, | |
| # Retrieval | |
| RAG_TEMPLATE, | |
| DEFAULT_RAG_TEMPLATE, | |
| RAG_FULL_CONTEXT, | |
| BYPASS_EMBEDDING_AND_RETRIEVAL, | |
| RAG_EMBEDDING_MODEL, | |
| RAG_EMBEDDING_MODEL_AUTO_UPDATE, | |
| RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE, | |
| RAG_RERANKING_ENGINE, | |
| RAG_RERANKING_MODEL, | |
| RAG_EXTERNAL_RERANKER_URL, | |
| RAG_EXTERNAL_RERANKER_API_KEY, | |
| RAG_EXTERNAL_RERANKER_TIMEOUT, | |
| RAG_RERANKING_BATCH_SIZE, | |
| RAG_RERANKING_MODEL_AUTO_UPDATE, | |
| RAG_RERANKING_MODEL_TRUST_REMOTE_CODE, | |
| RAG_EMBEDDING_ENGINE, | |
| RAG_EMBEDDING_BATCH_SIZE, | |
| ENABLE_ASYNC_EMBEDDING, | |
| RAG_EMBEDDING_CONCURRENT_REQUESTS, | |
| RAG_TOP_K, | |
| RAG_TOP_K_RERANKER, | |
| RAG_RELEVANCE_THRESHOLD, | |
| RAG_HYBRID_BM25_WEIGHT, | |
| RAG_ALLOWED_FILE_EXTENSIONS, | |
| RAG_FILE_MAX_COUNT, | |
| RAG_FILE_MAX_SIZE, | |
| FILE_IMAGE_COMPRESSION_WIDTH, | |
| FILE_IMAGE_COMPRESSION_HEIGHT, | |
| RAG_OPENAI_API_BASE_URL, | |
| RAG_OPENAI_API_KEY, | |
| RAG_AZURE_OPENAI_BASE_URL, | |
| RAG_AZURE_OPENAI_API_KEY, | |
| RAG_AZURE_OPENAI_API_VERSION, | |
| RAG_OLLAMA_BASE_URL, | |
| RAG_OLLAMA_API_KEY, | |
| CHUNK_OVERLAP, | |
| CHUNK_MIN_SIZE_TARGET, | |
| CHUNK_SIZE, | |
| CONTENT_EXTRACTION_ENGINE, | |
| DATALAB_MARKER_API_KEY, | |
| DATALAB_MARKER_API_BASE_URL, | |
| DATALAB_MARKER_ADDITIONAL_CONFIG, | |
| DATALAB_MARKER_SKIP_CACHE, | |
| DATALAB_MARKER_FORCE_OCR, | |
| DATALAB_MARKER_PAGINATE, | |
| DATALAB_MARKER_STRIP_EXISTING_OCR, | |
| DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION, | |
| DATALAB_MARKER_FORMAT_LINES, | |
| DATALAB_MARKER_OUTPUT_FORMAT, | |
| MINERU_API_MODE, | |
| MINERU_API_URL, | |
| MINERU_API_KEY, | |
| MINERU_API_TIMEOUT, | |
| MINERU_PARAMS, | |
| DATALAB_MARKER_USE_LLM, | |
| EXTERNAL_DOCUMENT_LOADER_URL, | |
| EXTERNAL_DOCUMENT_LOADER_API_KEY, | |
| TIKA_SERVER_URL, | |
| DOCLING_SERVER_URL, | |
| DOCLING_API_KEY, | |
| DOCLING_PARAMS, | |
| DOCUMENT_INTELLIGENCE_ENDPOINT, | |
| DOCUMENT_INTELLIGENCE_KEY, | |
| DOCUMENT_INTELLIGENCE_MODEL, | |
| MISTRAL_OCR_API_BASE_URL, | |
| MISTRAL_OCR_API_KEY, | |
| PADDLEOCR_VL_BASE_URL, | |
| PADDLEOCR_VL_TOKEN, | |
| RAG_TEXT_SPLITTER, | |
| ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER, | |
| TIKTOKEN_ENCODING_NAME, | |
| PDF_EXTRACT_IMAGES, | |
| PDF_LOADER_MODE, | |
| YOUTUBE_LOADER_LANGUAGE, | |
| YOUTUBE_LOADER_PROXY_URL, | |
| # Retrieval (Web Search) | |
| ENABLE_WEB_SEARCH, | |
| WEB_SEARCH_ENGINE, | |
| BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL, | |
| BYPASS_WEB_SEARCH_WEB_LOADER, | |
| WEB_SEARCH_RESULT_COUNT, | |
| WEB_SEARCH_CONCURRENT_REQUESTS, | |
| WEB_FETCH_MAX_CONTENT_LENGTH, | |
| WEB_SEARCH_TRUST_ENV, | |
| WEB_SEARCH_DOMAIN_FILTER_LIST, | |
| OLLAMA_CLOUD_WEB_SEARCH_API_KEY, | |
| JINA_API_KEY, | |
| JINA_API_BASE_URL, | |
| SEARCHAPI_API_KEY, | |
| SEARCHAPI_ENGINE, | |
| SERPAPI_API_KEY, | |
| SERPAPI_ENGINE, | |
| SEARXNG_QUERY_URL, | |
| SEARXNG_LANGUAGE, | |
| YACY_QUERY_URL, | |
| YACY_USERNAME, | |
| YACY_PASSWORD, | |
| SERPER_API_KEY, | |
| SERPLY_API_KEY, | |
| DDGS_BACKEND, | |
| SERPSTACK_API_KEY, | |
| SERPSTACK_HTTPS, | |
| TAVILY_API_KEY, | |
| TAVILY_EXTRACT_DEPTH, | |
| BING_SEARCH_V7_ENDPOINT, | |
| BING_SEARCH_V7_SUBSCRIPTION_KEY, | |
| BRAVE_SEARCH_API_KEY, | |
| EXA_API_KEY, | |
| PERPLEXITY_API_KEY, | |
| PERPLEXITY_MODEL, | |
| PERPLEXITY_SEARCH_CONTEXT_USAGE, | |
| PERPLEXITY_SEARCH_API_URL, | |
| SOUGOU_API_SID, | |
| SOUGOU_API_SK, | |
| KAGI_SEARCH_API_KEY, | |
| MOJEEK_SEARCH_API_KEY, | |
| BOCHA_SEARCH_API_KEY, | |
| GOOGLE_PSE_API_KEY, | |
| GOOGLE_PSE_ENGINE_ID, | |
| GOOGLE_DRIVE_CLIENT_ID, | |
| GOOGLE_DRIVE_API_KEY, | |
| ENABLE_ONEDRIVE_INTEGRATION, | |
| ONEDRIVE_CLIENT_ID_PERSONAL, | |
| ONEDRIVE_CLIENT_ID_BUSINESS, | |
| ONEDRIVE_SHAREPOINT_URL, | |
| ONEDRIVE_SHAREPOINT_TENANT_ID, | |
| ENABLE_ONEDRIVE_PERSONAL, | |
| ENABLE_ONEDRIVE_BUSINESS, | |
| ENABLE_RAG_HYBRID_SEARCH, | |
| ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS, | |
| ENABLE_RAG_LOCAL_WEB_FETCH, | |
| ENABLE_WEB_LOADER_SSL_VERIFICATION, | |
| ENABLE_GOOGLE_DRIVE_INTEGRATION, | |
| UPLOAD_DIR, | |
| EXTERNAL_WEB_SEARCH_URL, | |
| EXTERNAL_WEB_SEARCH_API_KEY, | |
| EXTERNAL_WEB_LOADER_URL, | |
| EXTERNAL_WEB_LOADER_API_KEY, | |
| YANDEX_WEB_SEARCH_URL, | |
| YANDEX_WEB_SEARCH_API_KEY, | |
| YANDEX_WEB_SEARCH_CONFIG, | |
| YOUCOM_API_KEY, | |
| # WebUI | |
| WEBUI_AUTH, | |
| WEBUI_NAME, | |
| WEBUI_BANNERS, | |
| WEBHOOK_URL, | |
| ADMIN_EMAIL, | |
| SHOW_ADMIN_DETAILS, | |
| JWT_EXPIRES_IN, | |
| ENABLE_SIGNUP, | |
| ENABLE_LOGIN_FORM, | |
| ENABLE_PASSWORD_CHANGE_FORM, | |
| ENABLE_API_KEYS, | |
| ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, | |
| API_KEYS_ALLOWED_ENDPOINTS, | |
| ENABLE_FOLDERS, | |
| FOLDER_MAX_FILE_COUNT, | |
| ENABLE_AUTOMATIONS, | |
| AUTOMATION_MAX_COUNT, | |
| AUTOMATION_MIN_INTERVAL, | |
| ENABLE_CHANNELS, | |
| ENABLE_CALENDAR, | |
| ENABLE_NOTES, | |
| ENABLE_USER_STATUS, | |
| ENABLE_COMMUNITY_SHARING, | |
| ENABLE_MESSAGE_RATING, | |
| ENABLE_USER_WEBHOOKS, | |
| ENABLE_EVALUATION_ARENA_MODELS, | |
| BYPASS_ADMIN_ACCESS_CONTROL, | |
| USER_PERMISSIONS, | |
| DEFAULT_USER_ROLE, | |
| DEFAULT_GROUP_ID, | |
| PENDING_USER_OVERLAY_CONTENT, | |
| PENDING_USER_OVERLAY_TITLE, | |
| DEFAULT_PROMPT_SUGGESTIONS, | |
| DEFAULT_MODELS, | |
| DEFAULT_PINNED_MODELS, | |
| DEFAULT_ARENA_MODEL, | |
| MODEL_ORDER_LIST, | |
| DEFAULT_MODEL_METADATA, | |
| DEFAULT_MODEL_PARAMS, | |
| EVALUATION_ARENA_MODELS, | |
| # WebUI (OAuth) | |
| ENABLE_OAUTH_ROLE_MANAGEMENT, | |
| OAUTH_SUB_CLAIM, | |
| OAUTH_ROLES_CLAIM, | |
| OAUTH_EMAIL_CLAIM, | |
| OAUTH_PICTURE_CLAIM, | |
| OAUTH_USERNAME_CLAIM, | |
| OAUTH_ALLOWED_ROLES, | |
| OAUTH_ADMIN_ROLES, | |
| # WebUI (LDAP) | |
| ENABLE_LDAP, | |
| LDAP_SERVER_LABEL, | |
| LDAP_SERVER_HOST, | |
| LDAP_SERVER_PORT, | |
| LDAP_ATTRIBUTE_FOR_MAIL, | |
| LDAP_ATTRIBUTE_FOR_USERNAME, | |
| LDAP_SEARCH_FILTERS, | |
| LDAP_SEARCH_BASE, | |
| LDAP_APP_DN, | |
| LDAP_APP_PASSWORD, | |
| LDAP_USE_TLS, | |
| LDAP_CA_CERT_FILE, | |
| LDAP_VALIDATE_CERT, | |
| LDAP_CIPHERS, | |
| # LDAP Group Management | |
| ENABLE_LDAP_GROUP_MANAGEMENT, | |
| ENABLE_LDAP_GROUP_CREATION, | |
| LDAP_ATTRIBUTE_FOR_GROUPS, | |
| # Misc | |
| ENV, | |
| CACHE_DIR, | |
| STATIC_DIR, | |
| FRONTEND_BUILD_DIR, | |
| CORS_ALLOW_ORIGIN, | |
| DEFAULT_LOCALE, | |
| OAUTH_PROVIDERS, | |
| WEBUI_URL, | |
| RESPONSE_WATERMARK, | |
| # Admin | |
| ENABLE_ADMIN_CHAT_ACCESS, | |
| ENABLE_ADMIN_ANALYTICS, | |
| BYPASS_ADMIN_ACCESS_CONTROL, | |
| ENABLE_ADMIN_EXPORT, | |
| # Tasks | |
| TASK_MODEL, | |
| TASK_MODEL_EXTERNAL, | |
| ENABLE_TAGS_GENERATION, | |
| ENABLE_TITLE_GENERATION, | |
| ENABLE_FOLLOW_UP_GENERATION, | |
| ENABLE_SEARCH_QUERY_GENERATION, | |
| ENABLE_RETRIEVAL_QUERY_GENERATION, | |
| ENABLE_AUTOCOMPLETE_GENERATION, | |
| TITLE_GENERATION_PROMPT_TEMPLATE, | |
| FOLLOW_UP_GENERATION_PROMPT_TEMPLATE, | |
| TAGS_GENERATION_PROMPT_TEMPLATE, | |
| IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE, | |
| TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, | |
| VOICE_MODE_PROMPT_TEMPLATE, | |
| QUERY_GENERATION_PROMPT_TEMPLATE, | |
| AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE, | |
| AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH, | |
| AppConfig, | |
| reset_config, | |
| async_reset_config, | |
| ) | |
| from open_webui.env import ( | |
| ENABLE_CUSTOM_MODEL_FALLBACK, | |
| LICENSE_KEY, | |
| AUDIT_EXCLUDED_PATHS, | |
| AUDIT_INCLUDED_PATHS, | |
| ENABLE_AUDIT_GET_REQUESTS, | |
| AUDIT_LOG_LEVEL, | |
| CHANGELOG, | |
| REDIS_URL, | |
| REDIS_CLUSTER, | |
| REDIS_KEY_PREFIX, | |
| REDIS_SENTINEL_HOSTS, | |
| REDIS_SENTINEL_PORT, | |
| GLOBAL_LOG_LEVEL, | |
| MAX_BODY_LOG_SIZE, | |
| SAFE_MODE, | |
| VERSION, | |
| DEPLOYMENT_ID, | |
| INSTANCE_ID, | |
| WEBUI_BUILD_HASH, | |
| WEBUI_SECRET_KEY, | |
| WEBUI_SESSION_COOKIE_SAME_SITE, | |
| WEBUI_SESSION_COOKIE_SECURE, | |
| ENABLE_SIGNUP_PASSWORD_CONFIRMATION, | |
| WEBUI_AUTH_TRUSTED_EMAIL_HEADER, | |
| WEBUI_AUTH_TRUSTED_NAME_HEADER, | |
| WEBUI_AUTH_SIGNOUT_REDIRECT_URL, | |
| # SCIM | |
| ENABLE_SCIM, | |
| SCIM_TOKEN, | |
| ENABLE_COMPRESSION_MIDDLEWARE, | |
| ENABLE_WEBSOCKET_SUPPORT, | |
| BYPASS_MODEL_ACCESS_CONTROL, | |
| RESET_CONFIG_ON_START, | |
| ENABLE_VERSION_UPDATE_CHECK, | |
| ENABLE_OTEL, | |
| EXTERNAL_PWA_MANIFEST_URL, | |
| AIOHTTP_CLIENT_SESSION_SSL, | |
| ENABLE_STAR_SESSIONS_MIDDLEWARE, | |
| ENABLE_PUBLIC_ACTIVE_USERS_COUNT, | |
| # Admin Account Runtime Creation | |
| WEBUI_ADMIN_EMAIL, | |
| WEBUI_ADMIN_PASSWORD, | |
| WEBUI_ADMIN_NAME, | |
| ENABLE_EASTER_EGGS, | |
| LOG_FORMAT, | |
| # OAuth Back-Channel Logout | |
| ENABLE_OAUTH_BACKCHANNEL_LOGOUT, | |
| ) | |
| from open_webui.utils.models import ( | |
| get_all_models, | |
| get_all_base_models, | |
| check_model_access, | |
| get_filtered_models, | |
| ) | |
| from open_webui.utils.chat import ( | |
| generate_chat_completion as chat_completion_handler, | |
| chat_completed as chat_completed_handler, | |
| ) | |
| from open_webui.utils.actions import chat_action as chat_action_handler | |
| from open_webui.utils.embeddings import generate_embeddings | |
| from open_webui.utils.middleware import ( | |
| build_chat_response_context, | |
| process_chat_payload, | |
| process_chat_response, | |
| ) | |
| from open_webui.utils.tools import set_tool_servers, set_terminal_servers | |
| from open_webui.utils.auth import ( | |
| get_license_data, | |
| get_http_authorization_cred, | |
| decode_token, | |
| get_admin_user, | |
| get_verified_user, | |
| create_admin_user, | |
| ) | |
| from open_webui.utils.plugin import install_tool_and_function_dependencies | |
| from open_webui.utils.oauth import ( | |
| get_oauth_client_info_with_dynamic_client_registration, | |
| get_oauth_client_info_with_static_credentials, | |
| encrypt_data, | |
| decrypt_data, | |
| resolve_oauth_client_info, | |
| OAuthManager, | |
| OAuthClientManager, | |
| OAuthClientInformationFull, | |
| ) | |
| from open_webui.utils.security_headers import SecurityHeadersMiddleware | |
| from open_webui.utils.redis import get_redis_connection | |
| from open_webui.tasks import ( | |
| redis_task_command_listener, | |
| list_task_ids_by_item_id, | |
| create_task, | |
| stop_task, | |
| stop_item_tasks, | |
| list_tasks, | |
| ) # Import from tasks.py | |
| from open_webui.utils.redis import get_sentinels_from_env | |
| from open_webui.constants import ERROR_MESSAGES, TASKS | |
| if SAFE_MODE: | |
| print('SAFE MODE ENABLED') | |
| # Functions.deactivate_all_functions() is awaited in lifespan below | |
| logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) | |
| log = logging.getLogger(__name__) | |
| class SPAStaticFiles(StaticFiles): | |
| async def get_response(self, path: str, scope): | |
| try: | |
| return await super().get_response(path, scope) | |
| except (HTTPException, StarletteHTTPException) as ex: | |
| if ex.status_code == 404: | |
| if path.endswith('.js'): | |
| # Return 404 for javascript files | |
| raise ex | |
| else: | |
| return await super().get_response('index.html', scope) | |
| else: | |
| raise ex | |
| if LOG_FORMAT != 'json': | |
| print(rf""" | |
| ██████╗ ██████╗ ███████╗███╗ ██╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██╗ | |
| ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██║ ██║██╔════╝██╔══██╗██║ ██║██║ | |
| ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ █╗ ██║█████╗ ██████╔╝██║ ██║██║ | |
| ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║███╗██║██╔══╝ ██╔══██╗██║ ██║██║ | |
| ╚██████╔╝██║ ███████╗██║ ╚████║ ╚███╔███╔╝███████╗██████╔╝╚██████╔╝██║ | |
| ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═╝ | |
| v{VERSION} - building the best AI user interface. | |
| {f'Commit: {WEBUI_BUILD_HASH}' if WEBUI_BUILD_HASH != 'dev-build' else ''} | |
| https://github.com/open-webui/open-webui | |
| """) | |
| async def lifespan(app: FastAPI): | |
| # Store reference to main event loop for sync->async calls (e.g., embedding generation) | |
| # This allows sync functions to schedule work on the main loop without blocking health checks | |
| app.state.main_loop = asyncio.get_running_loop() | |
| app.state.instance_id = INSTANCE_ID | |
| start_logger() | |
| if RESET_CONFIG_ON_START: | |
| await async_reset_config() | |
| if LICENSE_KEY: | |
| get_license_data(app, LICENSE_KEY) | |
| # Create admin account from env vars if specified and no users exist | |
| if WEBUI_ADMIN_EMAIL and WEBUI_ADMIN_PASSWORD: | |
| if await create_admin_user(WEBUI_ADMIN_EMAIL, WEBUI_ADMIN_PASSWORD, WEBUI_ADMIN_NAME): | |
| # Disable signup since we now have an admin | |
| app.state.config.ENABLE_SIGNUP = False | |
| if SAFE_MODE: | |
| await Functions.deactivate_all_functions() | |
| # This should be blocking (sync) so functions are not deactivated on first /get_models calls | |
| # when the first user lands on the / route. | |
| log.info('Installing external dependencies of functions and tools...') | |
| await install_tool_and_function_dependencies() | |
| app.state.redis = get_redis_connection( | |
| redis_url=REDIS_URL, | |
| redis_sentinels=get_sentinels_from_env(REDIS_SENTINEL_HOSTS, REDIS_SENTINEL_PORT), | |
| redis_cluster=REDIS_CLUSTER, | |
| async_mode=True, | |
| ) | |
| if app.state.redis is not None: | |
| app.state.redis_task_command_listener = asyncio.create_task(redis_task_command_listener(app)) | |
| if THREAD_POOL_SIZE and THREAD_POOL_SIZE > 0: | |
| limiter = anyio.to_thread.current_default_thread_limiter() | |
| limiter.total_tokens = THREAD_POOL_SIZE | |
| asyncio.create_task(periodic_usage_pool_cleanup()) | |
| asyncio.create_task(periodic_session_pool_cleanup()) | |
| from open_webui.utils.automations import scheduler_worker_loop | |
| asyncio.create_task(scheduler_worker_loop(app)) | |
| if app.state.config.ENABLE_BASE_MODELS_CACHE: | |
| try: | |
| await get_all_models( | |
| Request( | |
| # Creating a mock request object to pass to get_all_models | |
| { | |
| 'type': 'http', | |
| 'asgi.version': '3.0', | |
| 'asgi.spec_version': '2.0', | |
| 'method': 'GET', | |
| 'path': '/internal', | |
| 'query_string': b'', | |
| 'headers': Headers({}).raw, | |
| 'client': ('127.0.0.1', 12345), | |
| 'server': ('127.0.0.1', 80), | |
| 'scheme': 'http', | |
| 'app': app, | |
| } | |
| ), | |
| None, | |
| ) | |
| except Exception as e: | |
| log.warning(f'Failed to pre-fetch models at startup: {e}') | |
| # Pre-fetch tool server specs so the first request doesn't pay the latency cost | |
| if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0: | |
| log.info('Initializing tool servers...') | |
| try: | |
| mock_request = Request( | |
| { | |
| 'type': 'http', | |
| 'asgi.version': '3.0', | |
| 'asgi.spec_version': '2.0', | |
| 'method': 'GET', | |
| 'path': '/internal', | |
| 'query_string': b'', | |
| 'headers': Headers({}).raw, | |
| 'client': ('127.0.0.1', 12345), | |
| 'server': ('127.0.0.1', 80), | |
| 'scheme': 'http', | |
| 'app': app, | |
| } | |
| ) | |
| await set_tool_servers(mock_request) | |
| log.info(f'Initialized {len(app.state.TOOL_SERVERS)} tool server(s)') | |
| await set_terminal_servers(mock_request) | |
| log.info(f'Initialized {len(app.state.TERMINAL_SERVERS)} terminal server(s)') | |
| except Exception as e: | |
| log.warning(f'Failed to initialize tool/terminal servers at startup: {e}') | |
| # Mark application as ready to accept traffic from a startup perspective. | |
| app.state.startup_complete = True | |
| yield | |
| # Shutdown: clean up shared resources | |
| from open_webui.utils.session_pool import close_session | |
| await close_session() | |
| if hasattr(app.state, 'redis_task_command_listener'): | |
| app.state.redis_task_command_listener.cancel() | |
| app = FastAPI( | |
| title='Open WebUI', | |
| docs_url='/docs' if ENV == 'dev' else None, | |
| openapi_url='/openapi.json' if ENV == 'dev' else None, | |
| redoc_url=None, | |
| lifespan=lifespan, | |
| ) | |
| # Used by readiness checks to gate traffic until startup work is done. | |
| app.state.startup_complete = False | |
| # For Open WebUI OIDC/OAuth2 | |
| oauth_manager = OAuthManager(app) | |
| app.state.oauth_manager = oauth_manager | |
| # For Integrations | |
| oauth_client_manager = OAuthClientManager(app) | |
| app.state.oauth_client_manager = oauth_client_manager | |
| app.state.instance_id = None | |
| app.state.config = AppConfig( | |
| redis_url=REDIS_URL, | |
| redis_sentinels=get_sentinels_from_env(REDIS_SENTINEL_HOSTS, REDIS_SENTINEL_PORT), | |
| redis_cluster=REDIS_CLUSTER, | |
| redis_key_prefix=REDIS_KEY_PREFIX, | |
| ) | |
| app.state.redis = None | |
| app.state.WEBUI_NAME = WEBUI_NAME | |
| app.state.LICENSE_METADATA = None | |
| ######################################## | |
| # | |
| # OPENTELEMETRY | |
| # | |
| ######################################## | |
| if ENABLE_OTEL: | |
| from open_webui.utils.telemetry.setup import setup as setup_opentelemetry | |
| setup_opentelemetry(app=app, db_engine=engine) | |
| ######################################## | |
| # | |
| # OLLAMA | |
| # | |
| ######################################## | |
| app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API | |
| app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS | |
| app.state.config.OLLAMA_API_CONFIGS = OLLAMA_API_CONFIGS | |
| app.state.OLLAMA_MODELS = {} | |
| ######################################## | |
| # | |
| # OPENAI | |
| # | |
| ######################################## | |
| app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API | |
| app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS | |
| app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS | |
| app.state.config.OPENAI_API_CONFIGS = OPENAI_API_CONFIGS | |
| app.state.OPENAI_MODELS = {} | |
| ######################################## | |
| # | |
| # TOOL SERVERS | |
| # | |
| ######################################## | |
| app.state.config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS | |
| app.state.TOOL_SERVERS = [] | |
| ######################################## | |
| # | |
| # TERMINAL SERVER | |
| # | |
| ######################################## | |
| app.state.config.TERMINAL_SERVER_CONNECTIONS = TERMINAL_SERVER_CONNECTIONS | |
| app.state.TERMINAL_SERVERS = [] | |
| ######################################## | |
| # | |
| # DIRECT CONNECTIONS | |
| # | |
| ######################################## | |
| app.state.config.ENABLE_DIRECT_CONNECTIONS = ENABLE_DIRECT_CONNECTIONS | |
| ######################################## | |
| # | |
| # SCIM | |
| # | |
| ######################################## | |
| app.state.ENABLE_SCIM = ENABLE_SCIM | |
| app.state.SCIM_TOKEN = SCIM_TOKEN | |
| ######################################## | |
| # | |
| # MODELS | |
| # | |
| ######################################## | |
| app.state.config.ENABLE_BASE_MODELS_CACHE = ENABLE_BASE_MODELS_CACHE | |
| app.state.BASE_MODELS = [] | |
| ######################################## | |
| # | |
| # WEBUI | |
| # | |
| ######################################## | |
| app.state.config.WEBUI_URL = WEBUI_URL | |
| app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP | |
| app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM | |
| app.state.config.ENABLE_PASSWORD_CHANGE_FORM = ENABLE_PASSWORD_CHANGE_FORM | |
| app.state.config.ENABLE_API_KEYS = ENABLE_API_KEYS | |
| app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS | |
| app.state.config.API_KEYS_ALLOWED_ENDPOINTS = API_KEYS_ALLOWED_ENDPOINTS | |
| app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN | |
| app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS | |
| app.state.config.ADMIN_EMAIL = ADMIN_EMAIL | |
| app.state.config.DEFAULT_MODELS = DEFAULT_MODELS | |
| app.state.config.DEFAULT_PINNED_MODELS = DEFAULT_PINNED_MODELS | |
| app.state.config.MODEL_ORDER_LIST = MODEL_ORDER_LIST | |
| app.state.config.DEFAULT_MODEL_METADATA = DEFAULT_MODEL_METADATA | |
| app.state.config.DEFAULT_MODEL_PARAMS = DEFAULT_MODEL_PARAMS | |
| app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS | |
| app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE | |
| app.state.config.DEFAULT_GROUP_ID = DEFAULT_GROUP_ID | |
| app.state.config.PENDING_USER_OVERLAY_CONTENT = PENDING_USER_OVERLAY_CONTENT | |
| app.state.config.PENDING_USER_OVERLAY_TITLE = PENDING_USER_OVERLAY_TITLE | |
| app.state.config.RESPONSE_WATERMARK = RESPONSE_WATERMARK | |
| app.state.config.USER_PERMISSIONS = USER_PERMISSIONS | |
| app.state.config.WEBHOOK_URL = WEBHOOK_URL | |
| app.state.config.BANNERS = WEBUI_BANNERS | |
| app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS | |
| app.state.config.FOLDER_MAX_FILE_COUNT = FOLDER_MAX_FILE_COUNT | |
| app.state.config.ENABLE_AUTOMATIONS = ENABLE_AUTOMATIONS | |
| app.state.config.AUTOMATION_MAX_COUNT = AUTOMATION_MAX_COUNT | |
| app.state.config.AUTOMATION_MIN_INTERVAL = AUTOMATION_MIN_INTERVAL | |
| app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS | |
| app.state.config.ENABLE_CALENDAR = ENABLE_CALENDAR | |
| app.state.config.ENABLE_NOTES = ENABLE_NOTES | |
| app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING | |
| app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING | |
| app.state.config.ENABLE_USER_WEBHOOKS = ENABLE_USER_WEBHOOKS | |
| app.state.config.ENABLE_USER_STATUS = ENABLE_USER_STATUS | |
| app.state.config.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS | |
| app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS | |
| # Migrate legacy access_control → access_grants on boot | |
| from open_webui.utils.access_control import migrate_access_control | |
| connections = app.state.config.TOOL_SERVER_CONNECTIONS | |
| if any('access_control' in c.get('config', {}) for c in connections): | |
| for connection in connections: | |
| migrate_access_control(connection.get('config', {})) | |
| app.state.config.TOOL_SERVER_CONNECTIONS = connections | |
| arena_models = app.state.config.EVALUATION_ARENA_MODELS | |
| if any('access_control' in m.get('meta', {}) for m in arena_models): | |
| for model in arena_models: | |
| migrate_access_control(model.get('meta', {})) | |
| app.state.config.EVALUATION_ARENA_MODELS = arena_models | |
| app.state.config.OAUTH_SUB_CLAIM = OAUTH_SUB_CLAIM | |
| app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM | |
| app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM | |
| app.state.config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM | |
| app.state.config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT | |
| app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM | |
| app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES | |
| app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES | |
| app.state.config.ENABLE_LDAP = ENABLE_LDAP | |
| app.state.config.LDAP_SERVER_LABEL = LDAP_SERVER_LABEL | |
| app.state.config.LDAP_SERVER_HOST = LDAP_SERVER_HOST | |
| app.state.config.LDAP_SERVER_PORT = LDAP_SERVER_PORT | |
| app.state.config.LDAP_ATTRIBUTE_FOR_MAIL = LDAP_ATTRIBUTE_FOR_MAIL | |
| app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME = LDAP_ATTRIBUTE_FOR_USERNAME | |
| app.state.config.LDAP_APP_DN = LDAP_APP_DN | |
| app.state.config.LDAP_APP_PASSWORD = LDAP_APP_PASSWORD | |
| app.state.config.LDAP_SEARCH_BASE = LDAP_SEARCH_BASE | |
| app.state.config.LDAP_SEARCH_FILTERS = LDAP_SEARCH_FILTERS | |
| app.state.config.LDAP_USE_TLS = LDAP_USE_TLS | |
| app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE | |
| app.state.config.LDAP_VALIDATE_CERT = LDAP_VALIDATE_CERT | |
| app.state.config.LDAP_CIPHERS = LDAP_CIPHERS | |
| # For LDAP Group Management | |
| app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT = ENABLE_LDAP_GROUP_MANAGEMENT | |
| app.state.config.ENABLE_LDAP_GROUP_CREATION = ENABLE_LDAP_GROUP_CREATION | |
| app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS = LDAP_ATTRIBUTE_FOR_GROUPS | |
| app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER | |
| app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER | |
| app.state.WEBUI_AUTH_SIGNOUT_REDIRECT_URL = WEBUI_AUTH_SIGNOUT_REDIRECT_URL | |
| app.state.EXTERNAL_PWA_MANIFEST_URL = EXTERNAL_PWA_MANIFEST_URL | |
| app.state.USER_COUNT = None | |
| app.state.TOOLS = {} | |
| app.state.TOOL_CONTENTS = {} | |
| app.state.FUNCTIONS = {} | |
| app.state.FUNCTION_CONTENTS = {} | |
| ######################################## | |
| # | |
| # RETRIEVAL | |
| # | |
| ######################################## | |
| app.state.config.TOP_K = RAG_TOP_K | |
| app.state.config.TOP_K_RERANKER = RAG_TOP_K_RERANKER | |
| app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD | |
| app.state.config.HYBRID_BM25_WEIGHT = RAG_HYBRID_BM25_WEIGHT | |
| app.state.config.ALLOWED_FILE_EXTENSIONS = RAG_ALLOWED_FILE_EXTENSIONS | |
| app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE | |
| app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT | |
| app.state.config.FILE_IMAGE_COMPRESSION_WIDTH = FILE_IMAGE_COMPRESSION_WIDTH | |
| app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT = FILE_IMAGE_COMPRESSION_HEIGHT | |
| app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT | |
| app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = BYPASS_EMBEDDING_AND_RETRIEVAL | |
| app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH | |
| app.state.config.ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS | |
| app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION = ENABLE_WEB_LOADER_SSL_VERIFICATION | |
| app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE | |
| app.state.config.DATALAB_MARKER_API_KEY = DATALAB_MARKER_API_KEY | |
| app.state.config.DATALAB_MARKER_API_BASE_URL = DATALAB_MARKER_API_BASE_URL | |
| app.state.config.DATALAB_MARKER_ADDITIONAL_CONFIG = DATALAB_MARKER_ADDITIONAL_CONFIG | |
| app.state.config.DATALAB_MARKER_SKIP_CACHE = DATALAB_MARKER_SKIP_CACHE | |
| app.state.config.DATALAB_MARKER_FORCE_OCR = DATALAB_MARKER_FORCE_OCR | |
| app.state.config.DATALAB_MARKER_PAGINATE = DATALAB_MARKER_PAGINATE | |
| app.state.config.DATALAB_MARKER_STRIP_EXISTING_OCR = DATALAB_MARKER_STRIP_EXISTING_OCR | |
| app.state.config.DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION | |
| app.state.config.DATALAB_MARKER_FORMAT_LINES = DATALAB_MARKER_FORMAT_LINES | |
| app.state.config.DATALAB_MARKER_USE_LLM = DATALAB_MARKER_USE_LLM | |
| app.state.config.DATALAB_MARKER_OUTPUT_FORMAT = DATALAB_MARKER_OUTPUT_FORMAT | |
| app.state.config.EXTERNAL_DOCUMENT_LOADER_URL = EXTERNAL_DOCUMENT_LOADER_URL | |
| app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY = EXTERNAL_DOCUMENT_LOADER_API_KEY | |
| app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL | |
| app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL | |
| app.state.config.DOCLING_API_KEY = DOCLING_API_KEY | |
| app.state.config.DOCLING_PARAMS = DOCLING_PARAMS | |
| app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT = DOCUMENT_INTELLIGENCE_ENDPOINT | |
| app.state.config.DOCUMENT_INTELLIGENCE_KEY = DOCUMENT_INTELLIGENCE_KEY | |
| app.state.config.DOCUMENT_INTELLIGENCE_MODEL = DOCUMENT_INTELLIGENCE_MODEL | |
| app.state.config.MISTRAL_OCR_API_BASE_URL = MISTRAL_OCR_API_BASE_URL | |
| app.state.config.MISTRAL_OCR_API_KEY = MISTRAL_OCR_API_KEY | |
| app.state.config.PADDLEOCR_VL_BASE_URL = PADDLEOCR_VL_BASE_URL | |
| app.state.config.PADDLEOCR_VL_TOKEN = PADDLEOCR_VL_TOKEN | |
| app.state.config.MINERU_API_MODE = MINERU_API_MODE | |
| app.state.config.MINERU_API_URL = MINERU_API_URL | |
| app.state.config.MINERU_API_KEY = MINERU_API_KEY | |
| app.state.config.MINERU_API_TIMEOUT = MINERU_API_TIMEOUT | |
| app.state.config.MINERU_PARAMS = MINERU_PARAMS | |
| app.state.config.TEXT_SPLITTER = RAG_TEXT_SPLITTER | |
| app.state.config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER = ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER | |
| app.state.config.TIKTOKEN_ENCODING_NAME = TIKTOKEN_ENCODING_NAME | |
| app.state.config.CHUNK_SIZE = CHUNK_SIZE | |
| app.state.config.CHUNK_MIN_SIZE_TARGET = CHUNK_MIN_SIZE_TARGET | |
| app.state.config.CHUNK_OVERLAP = CHUNK_OVERLAP | |
| app.state.config.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE | |
| app.state.config.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL | |
| app.state.config.RAG_EMBEDDING_BATCH_SIZE = RAG_EMBEDDING_BATCH_SIZE | |
| app.state.config.ENABLE_ASYNC_EMBEDDING = ENABLE_ASYNC_EMBEDDING | |
| app.state.config.RAG_EMBEDDING_CONCURRENT_REQUESTS = RAG_EMBEDDING_CONCURRENT_REQUESTS | |
| app.state.config.RAG_RERANKING_ENGINE = RAG_RERANKING_ENGINE | |
| app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL | |
| app.state.config.RAG_EXTERNAL_RERANKER_URL = RAG_EXTERNAL_RERANKER_URL | |
| app.state.config.RAG_EXTERNAL_RERANKER_API_KEY = RAG_EXTERNAL_RERANKER_API_KEY | |
| app.state.config.RAG_EXTERNAL_RERANKER_TIMEOUT = RAG_EXTERNAL_RERANKER_TIMEOUT | |
| app.state.config.RAG_RERANKING_BATCH_SIZE = RAG_RERANKING_BATCH_SIZE | |
| app.state.config.RAG_TEMPLATE = RAG_TEMPLATE | |
| app.state.config.RAG_OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL | |
| app.state.config.RAG_OPENAI_API_KEY = RAG_OPENAI_API_KEY | |
| app.state.config.RAG_AZURE_OPENAI_BASE_URL = RAG_AZURE_OPENAI_BASE_URL | |
| app.state.config.RAG_AZURE_OPENAI_API_KEY = RAG_AZURE_OPENAI_API_KEY | |
| app.state.config.RAG_AZURE_OPENAI_API_VERSION = RAG_AZURE_OPENAI_API_VERSION | |
| app.state.config.RAG_OLLAMA_BASE_URL = RAG_OLLAMA_BASE_URL | |
| app.state.config.RAG_OLLAMA_API_KEY = RAG_OLLAMA_API_KEY | |
| app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES | |
| app.state.config.PDF_LOADER_MODE = PDF_LOADER_MODE | |
| app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE | |
| app.state.config.YOUTUBE_LOADER_PROXY_URL = YOUTUBE_LOADER_PROXY_URL | |
| app.state.config.ENABLE_WEB_SEARCH = ENABLE_WEB_SEARCH | |
| app.state.config.WEB_SEARCH_ENGINE = WEB_SEARCH_ENGINE | |
| app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST = WEB_SEARCH_DOMAIN_FILTER_LIST | |
| app.state.config.WEB_SEARCH_RESULT_COUNT = WEB_SEARCH_RESULT_COUNT | |
| app.state.config.WEB_SEARCH_CONCURRENT_REQUESTS = WEB_SEARCH_CONCURRENT_REQUESTS | |
| app.state.config.WEB_FETCH_MAX_CONTENT_LENGTH = WEB_FETCH_MAX_CONTENT_LENGTH | |
| app.state.config.WEB_LOADER_ENGINE = WEB_LOADER_ENGINE | |
| app.state.config.WEB_LOADER_CONCURRENT_REQUESTS = WEB_LOADER_CONCURRENT_REQUESTS | |
| app.state.config.WEB_LOADER_TIMEOUT = WEB_LOADER_TIMEOUT | |
| app.state.config.WEB_SEARCH_TRUST_ENV = WEB_SEARCH_TRUST_ENV | |
| app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL | |
| app.state.config.BYPASS_WEB_SEARCH_WEB_LOADER = BYPASS_WEB_SEARCH_WEB_LOADER | |
| app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION | |
| app.state.config.ENABLE_ONEDRIVE_INTEGRATION = ENABLE_ONEDRIVE_INTEGRATION | |
| app.state.config.OLLAMA_CLOUD_WEB_SEARCH_API_KEY = OLLAMA_CLOUD_WEB_SEARCH_API_KEY | |
| app.state.config.SEARXNG_QUERY_URL = SEARXNG_QUERY_URL | |
| app.state.config.SEARXNG_LANGUAGE = SEARXNG_LANGUAGE | |
| app.state.config.YACY_QUERY_URL = YACY_QUERY_URL | |
| app.state.config.YACY_USERNAME = YACY_USERNAME | |
| app.state.config.YACY_PASSWORD = YACY_PASSWORD | |
| app.state.config.GOOGLE_PSE_API_KEY = GOOGLE_PSE_API_KEY | |
| app.state.config.GOOGLE_PSE_ENGINE_ID = GOOGLE_PSE_ENGINE_ID | |
| app.state.config.BRAVE_SEARCH_API_KEY = BRAVE_SEARCH_API_KEY | |
| app.state.config.KAGI_SEARCH_API_KEY = KAGI_SEARCH_API_KEY | |
| app.state.config.MOJEEK_SEARCH_API_KEY = MOJEEK_SEARCH_API_KEY | |
| app.state.config.BOCHA_SEARCH_API_KEY = BOCHA_SEARCH_API_KEY | |
| app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY | |
| app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS | |
| app.state.config.SERPER_API_KEY = SERPER_API_KEY | |
| app.state.config.SERPLY_API_KEY = SERPLY_API_KEY | |
| app.state.config.DDGS_BACKEND = DDGS_BACKEND | |
| app.state.config.TAVILY_API_KEY = TAVILY_API_KEY | |
| app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY | |
| app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE | |
| app.state.config.SERPAPI_API_KEY = SERPAPI_API_KEY | |
| app.state.config.SERPAPI_ENGINE = SERPAPI_ENGINE | |
| app.state.config.JINA_API_KEY = JINA_API_KEY | |
| app.state.config.JINA_API_BASE_URL = JINA_API_BASE_URL | |
| app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT | |
| app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY | |
| app.state.config.EXA_API_KEY = EXA_API_KEY | |
| app.state.config.PERPLEXITY_API_KEY = PERPLEXITY_API_KEY | |
| app.state.config.PERPLEXITY_MODEL = PERPLEXITY_MODEL | |
| app.state.config.PERPLEXITY_SEARCH_CONTEXT_USAGE = PERPLEXITY_SEARCH_CONTEXT_USAGE | |
| app.state.config.PERPLEXITY_SEARCH_API_URL = PERPLEXITY_SEARCH_API_URL | |
| app.state.config.SOUGOU_API_SID = SOUGOU_API_SID | |
| app.state.config.SOUGOU_API_SK = SOUGOU_API_SK | |
| app.state.config.EXTERNAL_WEB_SEARCH_URL = EXTERNAL_WEB_SEARCH_URL | |
| app.state.config.EXTERNAL_WEB_SEARCH_API_KEY = EXTERNAL_WEB_SEARCH_API_KEY | |
| app.state.config.EXTERNAL_WEB_LOADER_URL = EXTERNAL_WEB_LOADER_URL | |
| app.state.config.EXTERNAL_WEB_LOADER_API_KEY = EXTERNAL_WEB_LOADER_API_KEY | |
| app.state.config.YANDEX_WEB_SEARCH_URL = YANDEX_WEB_SEARCH_URL | |
| app.state.config.YANDEX_WEB_SEARCH_API_KEY = YANDEX_WEB_SEARCH_API_KEY | |
| app.state.config.YANDEX_WEB_SEARCH_CONFIG = YANDEX_WEB_SEARCH_CONFIG | |
| app.state.config.YOUCOM_API_KEY = YOUCOM_API_KEY | |
| app.state.config.PLAYWRIGHT_WS_URL = PLAYWRIGHT_WS_URL | |
| app.state.config.PLAYWRIGHT_TIMEOUT = PLAYWRIGHT_TIMEOUT | |
| app.state.config.FIRECRAWL_API_BASE_URL = FIRECRAWL_API_BASE_URL | |
| app.state.config.FIRECRAWL_API_KEY = FIRECRAWL_API_KEY | |
| app.state.config.FIRECRAWL_TIMEOUT = FIRECRAWL_TIMEOUT | |
| app.state.config.TAVILY_EXTRACT_DEPTH = TAVILY_EXTRACT_DEPTH | |
| app.state.EMBEDDING_FUNCTION = None | |
| app.state.RERANKING_FUNCTION = None | |
| app.state.ef = None | |
| app.state.rf = None | |
| app.state.YOUTUBE_LOADER_TRANSLATION = None | |
| try: | |
| app.state.ef = get_ef(app.state.config.RAG_EMBEDDING_ENGINE, app.state.config.RAG_EMBEDDING_MODEL) | |
| if app.state.config.ENABLE_RAG_HYBRID_SEARCH and not app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL: | |
| app.state.rf = get_rf( | |
| app.state.config.RAG_RERANKING_ENGINE, | |
| app.state.config.RAG_RERANKING_MODEL, | |
| app.state.config.RAG_EXTERNAL_RERANKER_URL, | |
| app.state.config.RAG_EXTERNAL_RERANKER_API_KEY, | |
| app.state.config.RAG_EXTERNAL_RERANKER_TIMEOUT, | |
| ) | |
| else: | |
| app.state.rf = None | |
| except Exception as e: | |
| log.error(f'Error updating models: {e}') | |
| pass | |
| app.state.EMBEDDING_FUNCTION = get_embedding_function( | |
| app.state.config.RAG_EMBEDDING_ENGINE, | |
| app.state.config.RAG_EMBEDDING_MODEL, | |
| embedding_function=app.state.ef, | |
| url=( | |
| app.state.config.RAG_OPENAI_API_BASE_URL | |
| if app.state.config.RAG_EMBEDDING_ENGINE == 'openai' | |
| else ( | |
| app.state.config.RAG_OLLAMA_BASE_URL | |
| if app.state.config.RAG_EMBEDDING_ENGINE == 'ollama' | |
| else app.state.config.RAG_AZURE_OPENAI_BASE_URL | |
| ) | |
| ), | |
| key=( | |
| app.state.config.RAG_OPENAI_API_KEY | |
| if app.state.config.RAG_EMBEDDING_ENGINE == 'openai' | |
| else ( | |
| app.state.config.RAG_OLLAMA_API_KEY | |
| if app.state.config.RAG_EMBEDDING_ENGINE == 'ollama' | |
| else app.state.config.RAG_AZURE_OPENAI_API_KEY | |
| ) | |
| ), | |
| embedding_batch_size=app.state.config.RAG_EMBEDDING_BATCH_SIZE, | |
| azure_api_version=( | |
| app.state.config.RAG_AZURE_OPENAI_API_VERSION | |
| if app.state.config.RAG_EMBEDDING_ENGINE == 'azure_openai' | |
| else None | |
| ), | |
| enable_async=app.state.config.ENABLE_ASYNC_EMBEDDING, | |
| concurrent_requests=app.state.config.RAG_EMBEDDING_CONCURRENT_REQUESTS, | |
| ) | |
| app.state.RERANKING_FUNCTION = get_reranking_function( | |
| app.state.config.RAG_RERANKING_ENGINE, | |
| app.state.config.RAG_RERANKING_MODEL, | |
| reranking_function=app.state.rf, | |
| reranking_batch_size=app.state.config.RAG_RERANKING_BATCH_SIZE, | |
| ) | |
| ######################################## | |
| # | |
| # CODE EXECUTION | |
| # | |
| ######################################## | |
| app.state.config.ENABLE_CODE_EXECUTION = ENABLE_CODE_EXECUTION | |
| app.state.config.CODE_EXECUTION_ENGINE = CODE_EXECUTION_ENGINE | |
| app.state.config.CODE_EXECUTION_JUPYTER_URL = CODE_EXECUTION_JUPYTER_URL | |
| app.state.config.CODE_EXECUTION_JUPYTER_AUTH = CODE_EXECUTION_JUPYTER_AUTH | |
| app.state.config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN = CODE_EXECUTION_JUPYTER_AUTH_TOKEN | |
| app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = CODE_EXECUTION_JUPYTER_AUTH_PASSWORD | |
| app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT = CODE_EXECUTION_JUPYTER_TIMEOUT | |
| app.state.config.ENABLE_CODE_INTERPRETER = ENABLE_CODE_INTERPRETER | |
| app.state.config.CODE_INTERPRETER_ENGINE = CODE_INTERPRETER_ENGINE | |
| app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE = CODE_INTERPRETER_PROMPT_TEMPLATE | |
| app.state.config.CODE_INTERPRETER_JUPYTER_URL = CODE_INTERPRETER_JUPYTER_URL | |
| app.state.config.CODE_INTERPRETER_JUPYTER_AUTH = CODE_INTERPRETER_JUPYTER_AUTH | |
| app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = CODE_INTERPRETER_JUPYTER_AUTH_TOKEN | |
| app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD | |
| app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = CODE_INTERPRETER_JUPYTER_TIMEOUT | |
| ######################################## | |
| # | |
| # IMAGES | |
| # | |
| ######################################## | |
| app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE | |
| app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION | |
| app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION | |
| app.state.config.ENABLE_MEMORIES = ENABLE_MEMORIES | |
| app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL | |
| app.state.config.IMAGE_SIZE = IMAGE_SIZE | |
| app.state.config.IMAGE_STEPS = IMAGE_STEPS | |
| app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL | |
| app.state.config.IMAGES_OPENAI_API_VERSION = IMAGES_OPENAI_API_VERSION | |
| app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY | |
| app.state.config.IMAGES_OPENAI_API_PARAMS = IMAGES_OPENAI_API_PARAMS | |
| app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL | |
| app.state.config.IMAGES_GEMINI_API_KEY = IMAGES_GEMINI_API_KEY | |
| app.state.config.IMAGES_GEMINI_ENDPOINT_METHOD = IMAGES_GEMINI_ENDPOINT_METHOD | |
| app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL | |
| app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH | |
| app.state.config.AUTOMATIC1111_PARAMS = AUTOMATIC1111_PARAMS | |
| app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL | |
| app.state.config.COMFYUI_API_KEY = COMFYUI_API_KEY | |
| app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW | |
| app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES | |
| app.state.config.ENABLE_IMAGE_EDIT = ENABLE_IMAGE_EDIT | |
| app.state.config.IMAGE_EDIT_ENGINE = IMAGE_EDIT_ENGINE | |
| app.state.config.IMAGE_EDIT_MODEL = IMAGE_EDIT_MODEL | |
| app.state.config.IMAGE_EDIT_SIZE = IMAGE_EDIT_SIZE | |
| app.state.config.IMAGES_EDIT_OPENAI_API_BASE_URL = IMAGES_EDIT_OPENAI_API_BASE_URL | |
| app.state.config.IMAGES_EDIT_OPENAI_API_KEY = IMAGES_EDIT_OPENAI_API_KEY | |
| app.state.config.IMAGES_EDIT_OPENAI_API_VERSION = IMAGES_EDIT_OPENAI_API_VERSION | |
| app.state.config.IMAGES_EDIT_GEMINI_API_BASE_URL = IMAGES_EDIT_GEMINI_API_BASE_URL | |
| app.state.config.IMAGES_EDIT_GEMINI_API_KEY = IMAGES_EDIT_GEMINI_API_KEY | |
| app.state.config.IMAGES_EDIT_COMFYUI_BASE_URL = IMAGES_EDIT_COMFYUI_BASE_URL | |
| app.state.config.IMAGES_EDIT_COMFYUI_API_KEY = IMAGES_EDIT_COMFYUI_API_KEY | |
| app.state.config.IMAGES_EDIT_COMFYUI_WORKFLOW = IMAGES_EDIT_COMFYUI_WORKFLOW | |
| app.state.config.IMAGES_EDIT_COMFYUI_WORKFLOW_NODES = IMAGES_EDIT_COMFYUI_WORKFLOW_NODES | |
| ######################################## | |
| # | |
| # AUDIO | |
| # | |
| ######################################## | |
| app.state.config.STT_ENGINE = AUDIO_STT_ENGINE | |
| app.state.config.STT_MODEL = AUDIO_STT_MODEL | |
| app.state.config.STT_SUPPORTED_CONTENT_TYPES = AUDIO_STT_SUPPORTED_CONTENT_TYPES | |
| app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL | |
| app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY | |
| app.state.config.WHISPER_MODEL = WHISPER_MODEL | |
| app.state.config.DEEPGRAM_API_KEY = DEEPGRAM_API_KEY | |
| app.state.config.AUDIO_STT_AZURE_API_KEY = AUDIO_STT_AZURE_API_KEY | |
| app.state.config.AUDIO_STT_AZURE_REGION = AUDIO_STT_AZURE_REGION | |
| app.state.config.AUDIO_STT_AZURE_LOCALES = AUDIO_STT_AZURE_LOCALES | |
| app.state.config.AUDIO_STT_AZURE_BASE_URL = AUDIO_STT_AZURE_BASE_URL | |
| app.state.config.AUDIO_STT_AZURE_MAX_SPEAKERS = AUDIO_STT_AZURE_MAX_SPEAKERS | |
| app.state.config.AUDIO_STT_MISTRAL_API_KEY = AUDIO_STT_MISTRAL_API_KEY | |
| app.state.config.AUDIO_STT_MISTRAL_API_BASE_URL = AUDIO_STT_MISTRAL_API_BASE_URL | |
| app.state.config.AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS = AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS | |
| app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE | |
| app.state.config.TTS_MODEL = AUDIO_TTS_MODEL | |
| app.state.config.TTS_VOICE = AUDIO_TTS_VOICE | |
| app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL | |
| app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY | |
| app.state.config.TTS_OPENAI_PARAMS = AUDIO_TTS_OPENAI_PARAMS | |
| app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY | |
| app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON | |
| app.state.config.TTS_AZURE_SPEECH_REGION = AUDIO_TTS_AZURE_SPEECH_REGION | |
| app.state.config.TTS_AZURE_SPEECH_BASE_URL = AUDIO_TTS_AZURE_SPEECH_BASE_URL | |
| app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT | |
| app.state.config.TTS_MISTRAL_API_KEY = AUDIO_TTS_MISTRAL_API_KEY | |
| app.state.config.TTS_MISTRAL_API_BASE_URL = AUDIO_TTS_MISTRAL_API_BASE_URL | |
| app.state.faster_whisper_model = None | |
| app.state.speech_synthesiser = None | |
| app.state.speech_speaker_embeddings_dataset = None | |
| ######################################## | |
| # | |
| # TASKS | |
| # | |
| ######################################## | |
| app.state.config.TASK_MODEL = TASK_MODEL | |
| app.state.config.TASK_MODEL_EXTERNAL = TASK_MODEL_EXTERNAL | |
| app.state.config.ENABLE_SEARCH_QUERY_GENERATION = ENABLE_SEARCH_QUERY_GENERATION | |
| app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION = ENABLE_RETRIEVAL_QUERY_GENERATION | |
| app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = ENABLE_AUTOCOMPLETE_GENERATION | |
| app.state.config.ENABLE_TAGS_GENERATION = ENABLE_TAGS_GENERATION | |
| app.state.config.ENABLE_TITLE_GENERATION = ENABLE_TITLE_GENERATION | |
| app.state.config.ENABLE_FOLLOW_UP_GENERATION = ENABLE_FOLLOW_UP_GENERATION | |
| app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE = TITLE_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE = TAGS_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = FOLLOW_UP_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE | |
| app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE = QUERY_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE | |
| app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH | |
| app.state.config.VOICE_MODE_PROMPT_TEMPLATE = VOICE_MODE_PROMPT_TEMPLATE | |
| ######################################## | |
| # | |
| # WEBUI | |
| # | |
| ######################################## | |
| app.state.MODELS = MODELS | |
| # Add the middleware to the app | |
| if ENABLE_COMPRESSION_MIDDLEWARE: | |
| app.add_middleware(CompressMiddleware) | |
| # All HTTP middlewares below are pure-ASGI implementations. The previous | |
| # `BaseHTTPMiddleware` / `@app.middleware('http')` versions wrapped the | |
| # downstream app in an anyio task group whose cancel scope cancelled | |
| # in-flight DB calls (and any other awaits) on client disconnect / | |
| # response completion — which surfaced as noisy SQLAlchemy | |
| # `terminate_force_close` tracebacks under aiosqlite and as random | |
| # CancelledError storms across the request path. See | |
| # `open_webui.utils.asgi_middleware` for the rationale. | |
| app.add_middleware(RedirectMiddleware) | |
| app.add_middleware(SecurityHeadersMiddleware) | |
| app.add_middleware(CommitSessionMiddleware) | |
| app.add_middleware(AuthTokenMiddleware, fastapi_app=app) | |
| app.add_middleware(WebsocketUpgradeGuardMiddleware) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=CORS_ALLOW_ORIGIN, | |
| allow_credentials=True, | |
| allow_methods=['*'], | |
| allow_headers=['*'], | |
| ) | |
| app.mount('/ws', socket_app) | |
| app.include_router(ollama.router, prefix='/ollama', tags=['ollama']) | |
| app.include_router(openai.router, prefix='/openai', tags=['openai']) | |
| app.include_router(pipelines.router, prefix='/api/v1/pipelines', tags=['pipelines']) | |
| app.include_router(tasks.router, prefix='/api/v1/tasks', tags=['tasks']) | |
| app.include_router(images.router, prefix='/api/v1/images', tags=['images']) | |
| app.include_router(audio.router, prefix='/api/v1/audio', tags=['audio']) | |
| app.include_router(retrieval.router, prefix='/api/v1/retrieval', tags=['retrieval']) | |
| app.include_router(configs.router, prefix='/api/v1/configs', tags=['configs']) | |
| app.include_router(auths.router, prefix='/api/v1/auths', tags=['auths']) | |
| app.include_router(users.router, prefix='/api/v1/users', tags=['users']) | |
| app.include_router(channels.router, prefix='/api/v1/channels', tags=['channels']) | |
| app.include_router(chats.router, prefix='/api/v1/chats', tags=['chats']) | |
| app.include_router(notes.router, prefix='/api/v1/notes', tags=['notes']) | |
| app.include_router(models.router, prefix='/api/v1/models', tags=['models']) | |
| app.include_router(knowledge.router, prefix='/api/v1/knowledge', tags=['knowledge']) | |
| app.include_router(prompts.router, prefix='/api/v1/prompts', tags=['prompts']) | |
| app.include_router(tools.router, prefix='/api/v1/tools', tags=['tools']) | |
| app.include_router(skills.router, prefix='/api/v1/skills', tags=['skills']) | |
| app.include_router(memories.router, prefix='/api/v1/memories', tags=['memories']) | |
| app.include_router(folders.router, prefix='/api/v1/folders', tags=['folders']) | |
| app.include_router(groups.router, prefix='/api/v1/groups', tags=['groups']) | |
| app.include_router(files.router, prefix='/api/v1/files', tags=['files']) | |
| app.include_router(functions.router, prefix='/api/v1/functions', tags=['functions']) | |
| app.include_router(evaluations.router, prefix='/api/v1/evaluations', tags=['evaluations']) | |
| if ENABLE_ADMIN_ANALYTICS: | |
| app.include_router(analytics.router, prefix='/api/v1/analytics', tags=['analytics']) | |
| app.include_router(utils.router, prefix='/api/v1/utils', tags=['utils']) | |
| app.include_router(terminals.router, prefix='/api/v1/terminals', tags=['terminals']) | |
| app.include_router(automations.router, prefix='/api/v1/automations', tags=['automations']) | |
| app.include_router(calendar.router, prefix='/api/v1/calendars', tags=['calendars']) | |
| # SCIM 2.0 API for identity management | |
| if ENABLE_SCIM: | |
| app.include_router(scim.router, prefix='/api/v1/scim/v2', tags=['scim']) | |
| try: | |
| audit_level = AuditLevel(AUDIT_LOG_LEVEL) | |
| except ValueError as e: | |
| logger.error(f'Invalid audit level: {AUDIT_LOG_LEVEL}. Error: {e}') | |
| audit_level = AuditLevel.NONE | |
| if audit_level != AuditLevel.NONE: | |
| app.add_middleware( | |
| AuditLoggingMiddleware, | |
| audit_level=audit_level, | |
| excluded_paths=AUDIT_EXCLUDED_PATHS, | |
| included_paths=AUDIT_INCLUDED_PATHS, | |
| audit_get_requests=ENABLE_AUDIT_GET_REQUESTS, | |
| max_body_size=MAX_BODY_LOG_SIZE, | |
| ) | |
| ################################## | |
| # | |
| # Chat Endpoints | |
| # | |
| ################################## | |
| # Experimental: Compatibility with OpenAI API | |
| async def get_models(request: Request, refresh: bool = False, user=Depends(get_verified_user)): | |
| all_models = await get_all_models(request, refresh=refresh, user=user) | |
| models = [] | |
| for model in all_models: | |
| # Filter out filter pipelines | |
| if 'pipeline' in model and model['pipeline'].get('type', None) == 'filter': | |
| continue | |
| # Remove profile image URL to reduce payload size | |
| if model.get('info', {}).get('meta', {}).get('profile_image_url'): | |
| model['info']['meta'].pop('profile_image_url', None) | |
| try: | |
| model_tags = [tag.get('name') for tag in model.get('info', {}).get('meta', {}).get('tags', [])] | |
| tags = [tag.get('name') for tag in model.get('tags', [])] | |
| tags = list(set(model_tags + tags)) | |
| model['tags'] = [{'name': tag} for tag in tags] | |
| except Exception as e: | |
| log.debug(f'Error processing model tags: {e}') | |
| model['tags'] = [] | |
| pass | |
| models.append(model) | |
| model_order_list = request.app.state.config.MODEL_ORDER_LIST | |
| if model_order_list: | |
| model_order_dict = {model_id: i for i, model_id in enumerate(model_order_list)} | |
| # Sort models by order list priority, with fallback for those not in the list | |
| models.sort( | |
| key=lambda model: ( | |
| model_order_dict.get(model.get('id', ''), float('inf')), | |
| (model.get('name', '') or ''), | |
| ) | |
| ) | |
| models = await get_filtered_models(models, user) | |
| log.debug( | |
| f'/api/models returned filtered models accessible to the user: {json.dumps([model.get("id") for model in models])}' | |
| ) | |
| return {'data': models} | |
| async def get_base_models(request: Request, user=Depends(get_admin_user)): | |
| models = await get_all_base_models(request, user=user) | |
| return {'data': models} | |
| ################################## | |
| # Embeddings | |
| ################################## | |
| # Experimental: Compatibility with OpenAI API | |
| async def embeddings(request: Request, form_data: dict, user=Depends(get_verified_user)): | |
| """ | |
| OpenAI-compatible embeddings endpoint. | |
| This handler: | |
| - Performs user/model checks and dispatches to the correct backend. | |
| - Supports OpenAI, Ollama, arena models, pipelines, and any compatible provider. | |
| Args: | |
| request (Request): Request context. | |
| form_data (dict): OpenAI-like payload (e.g., {"model": "...", "input": [...]}) | |
| user (UserModel): Authenticated user. | |
| Returns: | |
| dict: OpenAI-compatible embeddings response. | |
| """ | |
| # Make sure models are loaded in app state | |
| if not request.app.state.MODELS: | |
| await get_all_models(request, user=user) | |
| # Use generic dispatcher in utils.embeddings | |
| return await generate_embeddings(request, form_data, user) | |
| # Experimental: Compatibility with OpenAI API | |
| async def chat_completion( | |
| request: Request, | |
| form_data: dict, | |
| user=Depends(get_verified_user), | |
| ): | |
| if not request.app.state.MODELS: | |
| await get_all_models(request, user=user) | |
| model_id = form_data.get('model', None) | |
| model_item = form_data.pop('model_item', {}) | |
| tasks = form_data.pop('background_tasks', None) | |
| metadata = {} | |
| try: | |
| model_info = None | |
| if not model_item.get('direct', False): | |
| if model_id not in request.app.state.MODELS: | |
| raise Exception('Model not found') | |
| model = request.app.state.MODELS[model_id] | |
| model_info = await Models.get_model_by_id(model_id) | |
| # Check if user has access to the model | |
| if not BYPASS_MODEL_ACCESS_CONTROL and (user.role != 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL): | |
| try: | |
| await check_model_access(user, model) | |
| except Exception as e: | |
| raise e | |
| else: | |
| model = model_item | |
| request.state.direct = True | |
| request.state.model = model | |
| # Model params: global defaults as base, per-model overrides win | |
| default_model_params = getattr(request.app.state.config, 'DEFAULT_MODEL_PARAMS', None) or {} | |
| model_info_params = { | |
| **default_model_params, | |
| **(model_info.params.model_dump() if model_info and model_info.params else {}), | |
| } | |
| # Check base model existence for custom models | |
| if model_info and model_info.base_model_id: | |
| base_model_id = model_info.base_model_id | |
| if base_model_id not in request.app.state.MODELS: | |
| if ENABLE_CUSTOM_MODEL_FALLBACK: | |
| default_models = (request.app.state.config.DEFAULT_MODELS or '').split(',') | |
| fallback_model_id = default_models[0].strip() if default_models[0] else None | |
| if fallback_model_id and fallback_model_id in request.app.state.MODELS: | |
| # Update model and form_data so routing uses the fallback model's type | |
| model = request.app.state.MODELS[fallback_model_id] | |
| form_data['model'] = fallback_model_id | |
| else: | |
| raise Exception('Model not found') | |
| else: | |
| raise Exception('Model not found') | |
| # Chat Params | |
| stream_delta_chunk_size = form_data.get('params', {}).get('stream_delta_chunk_size') | |
| reasoning_tags = form_data.get('params', {}).get('reasoning_tags') | |
| # Model Params | |
| if model_info_params.get('stream_response') is not None: | |
| form_data['stream'] = model_info_params.get('stream_response') | |
| if model_info_params.get('stream_delta_chunk_size'): | |
| stream_delta_chunk_size = model_info_params.get('stream_delta_chunk_size') | |
| if model_info_params.get('reasoning_tags') is not None: | |
| reasoning_tags = model_info_params.get('reasoning_tags') | |
| # parent_id signals intent: | |
| # null → new chat (root message, no parent) | |
| # value → follow-up (user message's parentId = prev assistant) | |
| # absent → legacy caller, no chat management | |
| is_new_chat = 'parent_id' in form_data and form_data['parent_id'] is None and not form_data.get('chat_id') | |
| parent_id = form_data.pop('parent_id', None) | |
| form_data.pop('new_chat', None) # Legacy field | |
| # Multi-model: {model_id: assistant_message_id} | |
| # Single-model fallback: built from 'model' + 'id' | |
| message_ids = form_data.pop('message_ids', None) | |
| if not message_ids: | |
| message_ids = {model_id: form_data.pop('id', None)} | |
| else: | |
| form_data.pop('id', None) | |
| user_message = form_data.pop('user_message', None) or form_data.pop('parent_message', None) | |
| metadata = { | |
| 'user_id': user.id, | |
| 'chat_id': form_data.pop('chat_id', None), | |
| 'user_message': user_message, | |
| 'user_message_id': user_message.get('id') if user_message else None, | |
| 'session_id': form_data.pop('session_id', None), | |
| 'folder_id': form_data.pop('folder_id', None), | |
| 'filter_ids': form_data.pop('filter_ids', []), | |
| 'tool_ids': form_data.get('tool_ids', None), | |
| 'tool_servers': form_data.pop('tool_servers', None), | |
| 'files': form_data.get('files', None), | |
| 'features': form_data.get('features', {}), | |
| 'variables': form_data.get('variables', {}), | |
| 'model': model, | |
| 'direct': model_item.get('direct', False), | |
| 'params': { | |
| 'stream_delta_chunk_size': stream_delta_chunk_size, | |
| 'reasoning_tags': reasoning_tags, | |
| 'function_calling': ( | |
| 'native' | |
| if ( | |
| form_data.get('params', {}).get('function_calling') == 'native' | |
| or model_info_params.get('function_calling') == 'native' | |
| ) | |
| else 'default' | |
| ), | |
| }, | |
| } | |
| if is_new_chat: | |
| metadata['chat_id'] = str(uuid4()) | |
| if metadata.get('chat_id') and user: | |
| chat_id = metadata['chat_id'] | |
| if not chat_id.startswith('local:'): # temporary chats are not stored | |
| if is_new_chat: | |
| # Build the full history upfront with ALL assistant placeholders | |
| user_message = metadata.get('user_message') or {} | |
| user_message_id = user_message.get('id') if user_message else None | |
| history_messages = {} | |
| all_assistant_ids = [assistant_id for assistant_id in message_ids.values() if assistant_id] | |
| if user_message_id and user_message: | |
| user_message['childrenIds'] = all_assistant_ids | |
| history_messages[user_message_id] = user_message | |
| for target_model_id, assistant_message_id in message_ids.items(): | |
| if assistant_message_id: | |
| history_messages[assistant_message_id] = { | |
| 'id': assistant_message_id, | |
| 'parentId': user_message_id, | |
| 'childrenIds': [], | |
| 'role': 'assistant', | |
| 'content': '', | |
| 'done': False, | |
| 'model': target_model_id, | |
| 'timestamp': int(time.time()), | |
| } | |
| await Chats.insert_new_chat( | |
| chat_id, | |
| user.id, | |
| ChatForm( | |
| chat={ | |
| 'id': chat_id, | |
| 'title': 'New Chat', | |
| 'models': list(message_ids.keys()), | |
| 'history': { | |
| 'currentId': all_assistant_ids[0] if all_assistant_ids else user_message_id, | |
| 'messages': history_messages, | |
| }, | |
| 'messages': [ | |
| {'role': 'user', 'content': user_message.get('content', '')}, | |
| ] | |
| if user_message_id | |
| else [], | |
| 'tags': [], | |
| 'timestamp': int(time.time() * 1000), | |
| }, | |
| folder_id=metadata.get('folder_id'), | |
| ), | |
| ) | |
| # Insert chat files from user message if any | |
| user_message_files = user_message.get('files', []) | |
| if user_message_files: | |
| try: | |
| await Chats.insert_chat_files( | |
| chat_id, | |
| user_message_id, | |
| [ | |
| file_item.get('id') | |
| for file_item in user_message_files | |
| if file_item.get('type') == 'file' | |
| ], | |
| user.id, | |
| ) | |
| except Exception as e: | |
| log.debug(f'Error inserting chat files: {e}') | |
| pass | |
| else: | |
| # Existing chat — verify ownership | |
| if not await Chats.is_chat_owner(chat_id, user.id) and user.role != 'admin': | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail=ERROR_MESSAGES.DEFAULT(), | |
| ) | |
| # Save user message to DB | |
| user_message = metadata.get('user_message') or {} | |
| if user_message and user_message.get('id'): | |
| await Chats.upsert_message_to_chat_by_id_and_message_id( | |
| chat_id, | |
| user_message['id'], | |
| user_message, | |
| ) | |
| # Link grandparent → user message (childrenIds) | |
| grandparent_id = user_message.get('parentId') | |
| if grandparent_id: | |
| grandparent = await Chats.get_message_by_id_and_message_id(chat_id, grandparent_id) | |
| if grandparent: | |
| child_ids = grandparent.get('childrenIds', []) | |
| if user_message['id'] not in child_ids: | |
| child_ids.append(user_message['id']) | |
| await Chats.upsert_message_to_chat_by_id_and_message_id( | |
| chat_id, grandparent_id, {'childrenIds': child_ids} | |
| ) | |
| # Insert chat files from user message if any | |
| user_message_files = user_message.get('files', []) | |
| if user_message_files: | |
| try: | |
| await Chats.insert_chat_files( | |
| chat_id, | |
| user_message.get('id'), | |
| [ | |
| file_item.get('id') | |
| for file_item in user_message_files | |
| if file_item.get('type') == 'file' | |
| ], | |
| user.id, | |
| ) | |
| except Exception as e: | |
| log.debug(f'Error inserting chat files: {e}') | |
| pass | |
| # Save ALL assistant placeholders | |
| user_message_id = metadata.get('user_message_id') | |
| all_assistant_ids = [assistant_id for assistant_id in message_ids.values() if assistant_id] | |
| # Link user message → all assistant messages (childrenIds) | |
| if user_message_id and all_assistant_ids: | |
| existing_user_message = await Chats.get_message_by_id_and_message_id(chat_id, user_message_id) | |
| if existing_user_message: | |
| child_ids = existing_user_message.get('childrenIds', []) | |
| for assistant_id in all_assistant_ids: | |
| if assistant_id not in child_ids: | |
| child_ids.append(assistant_id) | |
| await Chats.upsert_message_to_chat_by_id_and_message_id( | |
| chat_id, | |
| user_message_id, | |
| {'childrenIds': child_ids}, | |
| ) | |
| # Save each assistant placeholder | |
| for target_model_id, assistant_message_id in message_ids.items(): | |
| if assistant_message_id: | |
| await Chats.upsert_message_to_chat_by_id_and_message_id( | |
| chat_id, | |
| assistant_message_id, | |
| { | |
| 'id': assistant_message_id, | |
| 'parentId': user_message_id, | |
| 'childrenIds': [], | |
| 'role': 'assistant', | |
| 'content': '', | |
| 'done': False, | |
| 'model': target_model_id, | |
| 'timestamp': int(time.time()), | |
| }, | |
| ) | |
| request.state.metadata = metadata | |
| form_data['metadata'] = metadata | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| log.warning(f'Error processing chat metadata: {e}') | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=str(e), | |
| ) | |
| async def process_chat(request, form_data, user, metadata, model, tasks=None): | |
| try: | |
| form_data, metadata, events = await process_chat_payload(request, form_data, user, metadata, model) | |
| response = await chat_completion_handler(request, form_data, user) | |
| # When the upstream provider returns an error (e.g. HTTP 400 | |
| # content-filter, quota exceeded), generate_chat_completion | |
| # returns a JSONResponse instead of raising. Detect this and | |
| # raise so the except-block below emits chat:message:error + | |
| # chat:tasks:cancel, unblocking the frontend. | |
| if isinstance(response, JSONResponse) and response.status_code >= 400: | |
| try: | |
| error_body = json.loads(response.body.decode('utf-8', 'replace')) | |
| detail = error_body.get('error', error_body) if isinstance(error_body, dict) else error_body | |
| if isinstance(detail, dict): | |
| detail = detail.get('message', detail.get('detail', str(detail))) | |
| except Exception: | |
| detail = f'Provider returned HTTP {response.status_code}' | |
| raise Exception(detail) | |
| ctx = await build_chat_response_context(request, form_data, user, model, metadata, tasks, events) | |
| return await process_chat_response(response, ctx) | |
| except asyncio.CancelledError: | |
| log.info('Chat processing was cancelled') | |
| try: | |
| async def emit_cancel_event(): | |
| event_emitter = await get_event_emitter(metadata) | |
| if event_emitter: | |
| await event_emitter({'type': 'chat:tasks:cancel'}) | |
| await asyncio.shield(emit_cancel_event()) | |
| except Exception: | |
| pass | |
| raise # re-raise to ensure proper task cancellation handling | |
| except Exception as e: | |
| error_detail = e.detail if isinstance(e, HTTPException) else str(e) | |
| log.error('Error processing chat payload: %s', error_detail) | |
| if metadata.get('chat_id') and metadata.get('message_id'): | |
| # Update the chat message with the error | |
| try: | |
| if not metadata['chat_id'].startswith('local:'): | |
| await Chats.upsert_message_to_chat_by_id_and_message_id( | |
| metadata['chat_id'], | |
| metadata['message_id'], | |
| { | |
| 'parentId': metadata.get('user_message_id', None), | |
| 'error': {'content': error_detail}, | |
| }, | |
| ) | |
| event_emitter = await get_event_emitter(metadata) | |
| if event_emitter: | |
| await event_emitter( | |
| { | |
| 'type': 'chat:message:error', | |
| 'data': {'error': {'content': error_detail}}, | |
| } | |
| ) | |
| await event_emitter( | |
| {'type': 'chat:tasks:cancel'}, | |
| ) | |
| except Exception: | |
| pass | |
| else: | |
| # No chat_id/message_id → legacy/direct API path with no | |
| # WebSocket error channel. We must surface the error as | |
| # a proper HTTP response; without this the function would | |
| # return None which FastAPI serializes as null. #23924 | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=error_detail, | |
| ) | |
| finally: | |
| # MCP cleanup — MUST run in the SAME asyncio task as | |
| # connect() because the MCP SDK's streamablehttp_client | |
| # uses anyio task groups whose cancel scopes enforce | |
| # same-task exit. Do NOT wrap in asyncio.shield() or | |
| # asyncio.wait_for() — both create a new task. | |
| try: | |
| if mcp_clients := metadata.get('mcp_clients'): | |
| for client in reversed(list(mcp_clients.values())): | |
| try: | |
| await client.disconnect() | |
| except Exception as e: | |
| log.debug(f'Error disconnecting MCP client: {e}') | |
| except asyncio.CancelledError: | |
| # Let the client close asynchronously by GC | |
| pass | |
| except Exception as e: | |
| log.debug(f'Error cleaning up MCP clients: {e}') | |
| except asyncio.CancelledError: | |
| pass | |
| try: | |
| if metadata.get('chat_id'): | |
| async def emit_inactive_event(): | |
| try: | |
| event_emitter = await get_event_emitter(metadata, update_db=False) | |
| if event_emitter: | |
| await event_emitter({'type': 'chat:active', 'data': {'active': False}}) | |
| except Exception: | |
| pass | |
| try: | |
| # Shield the event emission so it finishes even if the main task is cancelled | |
| await asyncio.shield(emit_inactive_event()) | |
| except asyncio.CancelledError: | |
| pass | |
| except Exception: | |
| pass | |
| # Fan out: one task per model | |
| if metadata.get('session_id') and metadata.get('chat_id'): | |
| task_ids = [] | |
| chat_id = metadata['chat_id'] | |
| for idx, (target_model_id, assistant_message_id) in enumerate(message_ids.items()): | |
| if not assistant_message_id: | |
| continue | |
| # Per-model metadata: own message_id + model | |
| per_model_metadata = { | |
| **metadata, | |
| 'message_id': assistant_message_id, | |
| } | |
| # Per-model form_data: own model | |
| model_form_data = { | |
| **form_data, | |
| 'model': target_model_id, | |
| 'metadata': per_model_metadata, | |
| } | |
| # Resolve the model object for this specific model | |
| resolved_model = request.app.state.MODELS.get(target_model_id, model) | |
| # Only the first model runs title/tags generation; | |
| # subsequent models only run follow-ups. | |
| task_id, _ = await create_task( | |
| request.app.state.redis, | |
| process_chat( | |
| request, | |
| model_form_data, | |
| user, | |
| per_model_metadata, | |
| resolved_model, | |
| tasks | |
| if idx == 0 | |
| else { | |
| k: v | |
| for k, v in (tasks or {}).items() | |
| if k not in (TASKS.TITLE_GENERATION, TASKS.TAGS_GENERATION) | |
| } | |
| or None, | |
| ), | |
| id=chat_id, | |
| ) | |
| task_ids.append(task_id) | |
| # Emit chat:active=true | |
| if task_ids: | |
| event_emitter = await get_event_emitter( | |
| {**metadata, 'message_id': list(message_ids.values())[0]}, | |
| update_db=False, | |
| ) | |
| if event_emitter: | |
| await event_emitter({'type': 'chat:active', 'data': {'active': True}}) | |
| return { | |
| 'status': True, | |
| 'task_ids': task_ids, | |
| 'chat_id': chat_id, | |
| } | |
| else: | |
| # Legacy/direct: single model, synchronous | |
| metadata['message_id'] = list(message_ids.values())[0] | |
| return await process_chat(request, form_data, user, metadata, model, tasks) | |
| # Alias for chat_completion (Legacy) | |
| generate_chat_completions = chat_completion | |
| generate_chat_completion = chat_completion | |
| # Expose as app.state so internal callers (e.g. automations) can | |
| # use the full pipeline without importing from main.py (avoids circular deps). | |
| app.state.CHAT_COMPLETION_HANDLER = chat_completion | |
| ################################## | |
| # | |
| # Anthropic Messages API Compatible Endpoint | |
| # | |
| ################################## | |
| from open_webui.utils.anthropic import ( | |
| convert_anthropic_to_openai_payload, | |
| convert_openai_to_anthropic_response, | |
| openai_stream_to_anthropic_stream, | |
| ) | |
| # Anthropic Messages API compatible endpoint | |
| async def generate_messages( | |
| request: Request, | |
| form_data: dict, | |
| user=Depends(get_verified_user), | |
| ): | |
| """ | |
| Anthropic Messages API compatible endpoint. | |
| Accepts the Anthropic Messages API format, converts internally to OpenAI | |
| Chat Completions format, routes through the existing chat completion | |
| pipeline, then converts the response back to Anthropic Messages format. | |
| Supports both streaming and non-streaming requests. | |
| All models configured in Open WebUI are accessible via this endpoint. | |
| Authentication: Supports both standard Authorization header and | |
| Anthropic's x-api-key header (via middleware translation). | |
| """ | |
| # Convert Anthropic payload to OpenAI format | |
| requested_model = form_data.get('model', '') | |
| openai_payload = convert_anthropic_to_openai_payload(form_data) | |
| # Route through the existing chat_completion handler | |
| response = await chat_completion(request, openai_payload, user) | |
| # Convert response back to Anthropic format | |
| if isinstance(response, StreamingResponse): | |
| # Streaming response: wrap the generator to convert SSE format | |
| return StreamingResponse( | |
| openai_stream_to_anthropic_stream(response.body_iterator, model=requested_model), | |
| media_type='text/event-stream', | |
| headers={ | |
| 'Cache-Control': 'no-cache', | |
| 'Connection': 'keep-alive', | |
| }, | |
| ) | |
| elif isinstance(response, dict): | |
| return convert_openai_to_anthropic_response(response, model=requested_model) | |
| else: | |
| # Passthrough for error responses (JSONResponse, PlainTextResponse, etc.) | |
| return response | |
| async def chat_completed(request: Request, form_data: dict, user=Depends(get_verified_user)): | |
| """Deprecated: outlet filters now run inline during chat completion. | |
| Kept for backward compatibility with external integrations.""" | |
| try: | |
| model_item = form_data.pop('model_item', {}) | |
| if model_item.get('direct', False): | |
| request.state.direct = True | |
| request.state.model = model_item | |
| return await chat_completed_handler(request, form_data, user) | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=str(e), | |
| ) | |
| async def chat_action(request: Request, action_id: str, form_data: dict, user=Depends(get_verified_user)): | |
| try: | |
| model_item = form_data.pop('model_item', {}) | |
| if model_item.get('direct', False): | |
| request.state.direct = True | |
| request.state.model = model_item | |
| return await chat_action_handler(request, action_id, form_data, user) | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=str(e), | |
| ) | |
| async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_admin_user)): | |
| try: | |
| result = await stop_task(request.app.state.redis, task_id) | |
| return result | |
| except ValueError as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| async def list_tasks_endpoint(request: Request, user=Depends(get_admin_user)): | |
| return {'tasks': await list_tasks(request.app.state.redis)} | |
| async def list_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): | |
| if chat_id.startswith('local:'): | |
| socket_id = chat_id[len('local:') :] | |
| owner_id = get_user_id_from_session_pool(socket_id) | |
| if owner_id != user.id and user.role != 'admin': | |
| return {'task_ids': []} | |
| else: | |
| chat = await Chats.get_chat_by_id(chat_id) | |
| if chat is None or (chat.user_id != user.id and user.role != 'admin'): | |
| return {'task_ids': []} | |
| task_ids = await list_task_ids_by_item_id(request.app.state.redis, chat_id) | |
| log.debug(f'Task IDs for chat {chat_id}: {task_ids}') | |
| return {'task_ids': task_ids} | |
| async def stop_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): | |
| if chat_id.startswith('local:'): | |
| socket_id = chat_id[len('local:') :] | |
| owner_id = get_user_id_from_session_pool(socket_id) | |
| if owner_id != user.id and user.role != 'admin': | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) | |
| else: | |
| chat = await Chats.get_chat_by_id(chat_id) | |
| if chat is None or (chat.user_id != user.id and user.role != 'admin'): | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) | |
| result = await stop_item_tasks(request.app.state.redis, chat_id) | |
| return result | |
| ################################## | |
| # | |
| # Config Endpoints | |
| # | |
| ################################## | |
| async def get_app_config(request: Request): | |
| user = None | |
| token = None | |
| auth_header = request.headers.get('Authorization') | |
| if auth_header: | |
| cred = get_http_authorization_cred(auth_header) | |
| if cred: | |
| token = cred.credentials | |
| if not token and 'token' in request.cookies: | |
| token = request.cookies.get('token') | |
| if token: | |
| try: | |
| data = decode_token(token) | |
| except Exception as e: | |
| log.debug(e) | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail='Invalid token', | |
| ) | |
| if data is not None and 'id' in data: | |
| user = await Users.get_user_by_id(data['id']) | |
| user_count = await Users.get_num_users() | |
| onboarding = False | |
| if user is None: | |
| onboarding = user_count == 0 | |
| return { | |
| **({'onboarding': True} if onboarding else {}), | |
| 'status': True, | |
| 'name': app.state.WEBUI_NAME, | |
| 'version': VERSION, | |
| 'default_locale': str(DEFAULT_LOCALE), | |
| 'oauth': {'providers': {name: config.get('name', name) for name, config in OAUTH_PROVIDERS.items()}}, | |
| 'features': { | |
| 'auth': WEBUI_AUTH, | |
| 'auth_trusted_header': bool(app.state.AUTH_TRUSTED_EMAIL_HEADER), | |
| 'enable_signup_password_confirmation': ENABLE_SIGNUP_PASSWORD_CONFIRMATION, | |
| 'enable_ldap': app.state.config.ENABLE_LDAP, | |
| 'enable_api_keys': app.state.config.ENABLE_API_KEYS, | |
| 'enable_signup': app.state.config.ENABLE_SIGNUP, | |
| 'enable_login_form': app.state.config.ENABLE_LOGIN_FORM, | |
| 'enable_password_change_form': app.state.config.ENABLE_PASSWORD_CHANGE_FORM, | |
| 'enable_websocket': ENABLE_WEBSOCKET_SUPPORT, | |
| 'enable_version_update_check': ENABLE_VERSION_UPDATE_CHECK, | |
| 'enable_public_active_users_count': ENABLE_PUBLIC_ACTIVE_USERS_COUNT, | |
| 'enable_easter_eggs': ENABLE_EASTER_EGGS, | |
| **( | |
| { | |
| 'enable_direct_connections': app.state.config.ENABLE_DIRECT_CONNECTIONS, | |
| 'enable_folders': app.state.config.ENABLE_FOLDERS, | |
| 'folder_max_file_count': app.state.config.FOLDER_MAX_FILE_COUNT, | |
| 'enable_channels': app.state.config.ENABLE_CHANNELS, | |
| 'enable_calendar': app.state.config.ENABLE_CALENDAR, | |
| 'enable_automations': app.state.config.ENABLE_AUTOMATIONS, | |
| 'enable_notes': app.state.config.ENABLE_NOTES, | |
| 'enable_web_search': app.state.config.ENABLE_WEB_SEARCH, | |
| 'enable_code_execution': app.state.config.ENABLE_CODE_EXECUTION, | |
| 'enable_code_interpreter': app.state.config.ENABLE_CODE_INTERPRETER, | |
| 'enable_image_generation': app.state.config.ENABLE_IMAGE_GENERATION, | |
| 'enable_autocomplete_generation': app.state.config.ENABLE_AUTOCOMPLETE_GENERATION, | |
| 'enable_community_sharing': app.state.config.ENABLE_COMMUNITY_SHARING, | |
| 'enable_message_rating': app.state.config.ENABLE_MESSAGE_RATING, | |
| 'enable_user_webhooks': app.state.config.ENABLE_USER_WEBHOOKS, | |
| 'enable_user_status': app.state.config.ENABLE_USER_STATUS, | |
| 'enable_admin_export': ENABLE_ADMIN_EXPORT, | |
| 'enable_admin_chat_access': ENABLE_ADMIN_CHAT_ACCESS, | |
| 'enable_admin_analytics': ENABLE_ADMIN_ANALYTICS, | |
| 'enable_google_drive_integration': app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, | |
| 'enable_onedrive_integration': app.state.config.ENABLE_ONEDRIVE_INTEGRATION, | |
| 'enable_memories': app.state.config.ENABLE_MEMORIES, | |
| **( | |
| { | |
| 'enable_onedrive_personal': ENABLE_ONEDRIVE_PERSONAL, | |
| 'enable_onedrive_business': ENABLE_ONEDRIVE_BUSINESS, | |
| } | |
| if app.state.config.ENABLE_ONEDRIVE_INTEGRATION | |
| else {} | |
| ), | |
| } | |
| if user is not None | |
| else {} | |
| ), | |
| }, | |
| **( | |
| { | |
| 'default_models': app.state.config.DEFAULT_MODELS, | |
| 'default_pinned_models': app.state.config.DEFAULT_PINNED_MODELS, | |
| 'default_prompt_suggestions': app.state.config.DEFAULT_PROMPT_SUGGESTIONS, | |
| 'user_count': user_count, | |
| 'code': { | |
| 'engine': app.state.config.CODE_EXECUTION_ENGINE, | |
| 'interpreter_engine': app.state.config.CODE_INTERPRETER_ENGINE, | |
| }, | |
| 'audio': { | |
| 'tts': { | |
| 'engine': app.state.config.TTS_ENGINE, | |
| 'voice': app.state.config.TTS_VOICE, | |
| 'split_on': app.state.config.TTS_SPLIT_ON, | |
| }, | |
| 'stt': { | |
| 'engine': app.state.config.STT_ENGINE, | |
| }, | |
| }, | |
| 'file': { | |
| 'max_size': app.state.config.FILE_MAX_SIZE, | |
| 'max_count': app.state.config.FILE_MAX_COUNT, | |
| 'image_compression': { | |
| 'width': app.state.config.FILE_IMAGE_COMPRESSION_WIDTH, | |
| 'height': app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT, | |
| }, | |
| }, | |
| 'permissions': {**app.state.config.USER_PERMISSIONS}, | |
| 'google_drive': { | |
| 'client_id': GOOGLE_DRIVE_CLIENT_ID.value, | |
| 'api_key': GOOGLE_DRIVE_API_KEY.value, | |
| }, | |
| 'onedrive': { | |
| 'client_id_personal': ONEDRIVE_CLIENT_ID_PERSONAL, | |
| 'client_id_business': ONEDRIVE_CLIENT_ID_BUSINESS, | |
| 'sharepoint_url': ONEDRIVE_SHAREPOINT_URL.value, | |
| 'sharepoint_tenant_id': ONEDRIVE_SHAREPOINT_TENANT_ID.value, | |
| }, | |
| 'ui': { | |
| 'pending_user_overlay_title': app.state.config.PENDING_USER_OVERLAY_TITLE, | |
| 'pending_user_overlay_content': app.state.config.PENDING_USER_OVERLAY_CONTENT, | |
| 'response_watermark': app.state.config.RESPONSE_WATERMARK, | |
| }, | |
| 'license_metadata': app.state.LICENSE_METADATA, | |
| **( | |
| { | |
| 'active_entries': app.state.USER_COUNT, | |
| } | |
| if user.role == 'admin' | |
| else {} | |
| ), | |
| } | |
| if user is not None and (user.role in ['admin', 'user']) | |
| else { | |
| **( | |
| { | |
| 'ui': { | |
| 'pending_user_overlay_title': app.state.config.PENDING_USER_OVERLAY_TITLE, | |
| 'pending_user_overlay_content': app.state.config.PENDING_USER_OVERLAY_CONTENT, | |
| } | |
| } | |
| if user and user.role == 'pending' | |
| else {} | |
| ), | |
| **( | |
| { | |
| 'metadata': { | |
| 'login_footer': app.state.LICENSE_METADATA.get('login_footer', ''), | |
| 'auth_logo_position': app.state.LICENSE_METADATA.get('auth_logo_position', ''), | |
| } | |
| } | |
| if app.state.LICENSE_METADATA | |
| else {} | |
| ), | |
| } | |
| ), | |
| } | |
| class UrlForm(BaseModel): | |
| url: str | |
| async def get_webhook_url(user=Depends(get_admin_user)): | |
| return { | |
| 'url': app.state.config.WEBHOOK_URL, | |
| } | |
| async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): | |
| app.state.config.WEBHOOK_URL = form_data.url | |
| app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL | |
| return {'url': app.state.config.WEBHOOK_URL} | |
| async def get_app_version(): | |
| return { | |
| 'version': VERSION, | |
| 'deployment_id': DEPLOYMENT_ID, | |
| } | |
| async def get_app_latest_release_version(user=Depends(get_verified_user)): | |
| if not ENABLE_VERSION_UPDATE_CHECK: | |
| log.debug(f'Version update check is disabled, returning current version as latest version') | |
| return {'current': VERSION, 'latest': VERSION} | |
| try: | |
| timeout = aiohttp.ClientTimeout(total=1) | |
| async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session: | |
| async with session.get( | |
| 'https://api.github.com/repos/open-webui/open-webui/releases/latest', | |
| ssl=AIOHTTP_CLIENT_SESSION_SSL, | |
| ) as response: | |
| response.raise_for_status() | |
| data = await response.json() | |
| latest_version = data['tag_name'] | |
| return {'current': VERSION, 'latest': latest_version[1:]} | |
| except Exception as e: | |
| log.debug(e) | |
| return {'current': VERSION, 'latest': VERSION} | |
| async def get_app_changelog(): | |
| return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5} | |
| async def get_current_usage(user=Depends(get_verified_user)): | |
| """ | |
| Get current usage statistics for Open WebUI. | |
| This is an experimental endpoint and subject to change. | |
| """ | |
| try: | |
| # If public visibility is disabled, only allow admins to access this endpoint | |
| if not ENABLE_PUBLIC_ACTIVE_USERS_COUNT and user.role != 'admin': | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail='Access denied. Only administrators can view usage statistics.', | |
| ) | |
| return { | |
| 'model_ids': get_models_in_use(), | |
| 'user_count': await Users.get_active_user_count(), | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| log.error(f'Error getting usage statistics: {e}') | |
| raise HTTPException(status_code=500, detail='Internal Server Error') | |
| ############################ | |
| # OAuth Login & Callback | |
| ############################ | |
| # Initialize OAuth client manager with any MCP tool servers using OAuth 2.1 | |
| if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0: | |
| for tool_server_connection in app.state.config.TOOL_SERVER_CONNECTIONS: | |
| if tool_server_connection.get('type', 'openapi') == 'mcp': | |
| server_id = tool_server_connection.get('info', {}).get('id') | |
| auth_type = tool_server_connection.get('auth_type', 'none') | |
| if server_id and auth_type in ('oauth_2.1', 'oauth_2.1_static'): | |
| try: | |
| oauth_client_info = resolve_oauth_client_info(tool_server_connection) | |
| app.state.oauth_client_manager.add_client( | |
| f'mcp:{server_id}', | |
| OAuthClientInformationFull(**oauth_client_info), | |
| ) | |
| except Exception as e: | |
| log.error(f'Error adding OAuth client for MCP tool server {server_id}: {e}') | |
| pass | |
| try: | |
| if ENABLE_STAR_SESSIONS_MIDDLEWARE: | |
| redis_session_store = RedisStore( | |
| url=REDIS_URL, | |
| prefix=(f'{REDIS_KEY_PREFIX}:session:' if REDIS_KEY_PREFIX else 'session:'), | |
| ) | |
| app.add_middleware(SessionAutoloadMiddleware) | |
| app.add_middleware( | |
| StarSessionsMiddleware, | |
| store=redis_session_store, | |
| cookie_name='owui-session', | |
| cookie_same_site=WEBUI_SESSION_COOKIE_SAME_SITE, | |
| cookie_https_only=WEBUI_SESSION_COOKIE_SECURE, | |
| ) | |
| log.info('Using Redis for session') | |
| else: | |
| raise ValueError('No Redis URL provided') | |
| except Exception as e: | |
| app.add_middleware( | |
| SessionMiddleware, | |
| secret_key=WEBUI_SECRET_KEY, | |
| session_cookie='owui-session', | |
| same_site=WEBUI_SESSION_COOKIE_SAME_SITE, | |
| https_only=WEBUI_SESSION_COOKIE_SECURE, | |
| ) | |
| async def register_client(request, client_id: str) -> bool: | |
| server_type, server_id = client_id.split(':', 1) | |
| connection = None | |
| connection_idx = None | |
| for idx, conn in enumerate(request.app.state.config.TOOL_SERVER_CONNECTIONS or []): | |
| if conn.get('type', 'openapi') == server_type: | |
| info = conn.get('info', {}) | |
| if info.get('id') == server_id: | |
| connection = conn | |
| connection_idx = idx | |
| break | |
| if connection is None or connection_idx is None: | |
| log.warning(f'Unable to locate MCP tool server configuration for client {client_id} during re-registration') | |
| return False | |
| server_url = connection.get('url') | |
| auth_type = connection.get('auth_type', 'none') | |
| oauth_server_key = (connection.get('config') or {}).get('oauth_server_key') | |
| try: | |
| if auth_type == 'oauth_2.1_static': | |
| # Static credentials: rebuild from admin-provided credentials + fresh metadata | |
| info = connection.get('info', {}) | |
| oauth_client_id = info.get('oauth_client_id') or '' | |
| oauth_client_secret = info.get('oauth_client_secret') or '' | |
| if not oauth_client_id or not oauth_client_secret: | |
| # Fall back to blob for backward compatibility | |
| existing_client_info = info.get('oauth_client_info', '') | |
| if not existing_client_info: | |
| log.error(f'No stored OAuth client info for static client {client_id}') | |
| return False | |
| existing_data = decrypt_data(existing_client_info) | |
| oauth_client_id = oauth_client_id or existing_data.get('client_id', '') | |
| oauth_client_secret = oauth_client_secret or existing_data.get('client_secret', '') | |
| oauth_client_info = await get_oauth_client_info_with_static_credentials( | |
| request, | |
| client_id, | |
| server_url, | |
| oauth_client_id=oauth_client_id, | |
| oauth_client_secret=oauth_client_secret, | |
| ) | |
| else: | |
| oauth_client_info = await get_oauth_client_info_with_dynamic_client_registration( | |
| request, | |
| client_id, | |
| server_url, | |
| oauth_server_key, | |
| ) | |
| except Exception as e: | |
| log.error(f'OAuth client re-registration failed for {client_id}: {e}') | |
| return False | |
| try: | |
| connections = request.app.state.config.TOOL_SERVER_CONNECTIONS | |
| connections[connection_idx] = { | |
| **connection, | |
| 'info': { | |
| **connection.get('info', {}), | |
| 'oauth_client_info': encrypt_data(oauth_client_info.model_dump(mode='json')), | |
| }, | |
| } | |
| # Re-assign the full list to trigger AppConfig.__setattr__ → PersistentConfig.save() | |
| # (in-place list mutation via list[idx] = ... does not trigger __setattr__) | |
| request.app.state.config.TOOL_SERVER_CONNECTIONS = connections | |
| except Exception as e: | |
| log.error(f'Failed to persist updated OAuth client info for tool server {client_id}: {e}') | |
| return False | |
| oauth_client_manager.remove_client(client_id) | |
| oauth_client_manager.add_client(client_id, oauth_client_info) | |
| log.info(f'Re-registered OAuth client {client_id} for tool server') | |
| return True | |
| async def oauth_client_authorize( | |
| client_id: str, | |
| request: Request, | |
| response: Response, | |
| user=Depends(get_verified_user), | |
| ): | |
| # ensure_valid_client_registration | |
| client = oauth_client_manager.get_client(client_id) | |
| client_info = oauth_client_manager.get_client_info(client_id) | |
| if client is None or client_info is None: | |
| raise HTTPException(status.HTTP_404_NOT_FOUND) | |
| if not await oauth_client_manager._preflight_authorization_url(client, client_info): | |
| log.info( | |
| 'Detected invalid OAuth client %s; attempting re-registration', | |
| client_id, | |
| ) | |
| registered = await register_client(request, client_id) | |
| if not registered: | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail='Failed to re-register OAuth client', | |
| ) | |
| client = oauth_client_manager.get_client(client_id) | |
| client_info = oauth_client_manager.get_client_info(client_id) | |
| if client is None or client_info is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail='OAuth client unavailable after re-registration', | |
| ) | |
| if not await oauth_client_manager._preflight_authorization_url(client, client_info): | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail='OAuth client registration is still invalid after re-registration', | |
| ) | |
| return await oauth_client_manager.handle_authorize(request, client_id=client_id) | |
| async def oauth_client_callback( | |
| client_id: str, | |
| request: Request, | |
| response: Response, | |
| user=Depends(get_verified_user), | |
| ): | |
| return await oauth_client_manager.handle_callback( | |
| request, | |
| client_id=client_id, | |
| user_id=user.id if user else None, | |
| response=response, | |
| ) | |
| async def oauth_login(provider: str, request: Request): | |
| return await oauth_manager.handle_login(request, provider) | |
| # OAuth login logic is as follows: | |
| # 1. Attempt to find a user with matching subject ID, tied to the provider | |
| # 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth | |
| # - This is considered insecure in general, as OAuth providers do not always verify email addresses | |
| # 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user | |
| # - Email addresses are considered unique, so we fail registration if the email address is already taken | |
| # Legacy endpoint | |
| async def oauth_login_callback( | |
| provider: str, | |
| request: Request, | |
| response: Response, | |
| db: AsyncSession = Depends(get_async_session), | |
| ): | |
| return await oauth_manager.handle_callback(request, provider, response, db=db) | |
| ############################ | |
| # OIDC Back-Channel Logout | |
| ############################ | |
| async def oauth_backchannel_logout( | |
| request: Request, | |
| db: AsyncSession = Depends(get_async_session), | |
| ): | |
| if not ENABLE_OAUTH_BACKCHANNEL_LOGOUT: | |
| raise HTTPException(status_code=404) | |
| return await oauth_manager.handle_backchannel_logout(request, db=db) | |
| async def get_manifest_json(): | |
| if app.state.EXTERNAL_PWA_MANIFEST_URL: | |
| session = await get_session() | |
| async with session.get( | |
| app.state.EXTERNAL_PWA_MANIFEST_URL, | |
| ssl=AIOHTTP_CLIENT_SESSION_SSL, | |
| ) as r: | |
| r.raise_for_status() | |
| return await r.json() | |
| else: | |
| return { | |
| 'name': app.state.WEBUI_NAME, | |
| 'short_name': app.state.WEBUI_NAME, | |
| 'description': f'{app.state.WEBUI_NAME} is an open, extensible, user-friendly interface for AI that adapts to your workflow.', | |
| 'start_url': '/', | |
| 'display': 'standalone', | |
| 'background_color': '#343541', | |
| 'icons': [ | |
| { | |
| 'src': '/static/logo.png', | |
| 'type': 'image/png', | |
| 'sizes': '500x500', | |
| 'purpose': 'any', | |
| }, | |
| { | |
| 'src': '/static/logo.png', | |
| 'type': 'image/png', | |
| 'sizes': '500x500', | |
| 'purpose': 'maskable', | |
| }, | |
| ], | |
| 'share_target': { | |
| 'action': '/', | |
| 'method': 'GET', | |
| 'params': {'text': 'shared'}, | |
| }, | |
| } | |
| async def get_opensearch_xml(): | |
| xml_content = rf""" | |
| <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/"> | |
| <ShortName>{app.state.WEBUI_NAME}</ShortName> | |
| <Description>Search {app.state.WEBUI_NAME}</Description> | |
| <InputEncoding>UTF-8</InputEncoding> | |
| <Image width="16" height="16" type="image/x-icon">{app.state.config.WEBUI_URL}/static/favicon.png</Image> | |
| <Url type="text/html" method="get" template="{app.state.config.WEBUI_URL}/?q={'{searchTerms}'}"/> | |
| <moz:SearchForm>{app.state.config.WEBUI_URL}</moz:SearchForm> | |
| </OpenSearchDescription> | |
| """ | |
| return Response(content=xml_content, media_type='application/xml') | |
| async def healthcheck(): | |
| return {'status': True} | |
| async def readiness_check(): | |
| """ | |
| Returns 200 only when the application is ready to accept traffic. | |
| """ | |
| # Ensure application startup work has completed | |
| if not getattr(app.state, 'startup_complete', False): | |
| log.info('Readiness check failed: startup not complete') | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail='Startup not complete', | |
| ) | |
| # Check database connectivity | |
| try: | |
| ScopedSession.execute(text('SELECT 1;')).all() | |
| except Exception as e: | |
| log.warning(f'Readiness check DB ping failed: {e!r}') | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail='Database not ready', | |
| ) | |
| # Check Redis connectivity if configured | |
| redis = app.state.redis | |
| if redis is not None: | |
| try: | |
| pong = await redis.ping() | |
| if pong is False: | |
| raise Exception('Redis PING returned False') | |
| except Exception as e: | |
| log.warning(f'Readiness check Redis ping failed: {e!r}') | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail='Redis not ready', | |
| ) | |
| return {'status': True} | |
| async def healthcheck_with_db(): | |
| ScopedSession.execute(text('SELECT 1;')).all() | |
| return {'status': True} | |
| app.mount('/static', StaticFiles(directory=STATIC_DIR), name='static') | |
| async def serve_cache_file( | |
| path: str, | |
| user=Depends(get_verified_user), | |
| ): | |
| file_path = os.path.abspath(os.path.join(CACHE_DIR, path)) | |
| # prevent path traversal | |
| if not file_path.startswith(os.path.abspath(CACHE_DIR)): | |
| raise HTTPException(status_code=404, detail='File not found') | |
| if not os.path.isfile(file_path): | |
| raise HTTPException(status_code=404, detail='File not found') | |
| return FileResponse(file_path) | |
| def swagger_ui_html(*args, **kwargs): | |
| return get_swagger_ui_html( | |
| *args, | |
| **kwargs, | |
| swagger_js_url='/static/swagger-ui/swagger-ui-bundle.js', | |
| swagger_css_url='/static/swagger-ui/swagger-ui.css', | |
| swagger_favicon_url='/static/swagger-ui/favicon.png', | |
| ) | |
| applications.get_swagger_ui_html = swagger_ui_html | |
| if os.path.exists(FRONTEND_BUILD_DIR): | |
| mimetypes.add_type('text/javascript', '.js') | |
| app.mount( | |
| '/', | |
| SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True), | |
| name='spa-static-files', | |
| ) | |
| else: | |
| log.warning(f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only.") | |