diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 5ab64f0668..5927cbae9d 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -405,14 +405,28 @@ CODE_INTERPRETER_PROMPT_TEMPLATE = os.getenv('CODE_INTERPRETER_PROMPT_TEMPLATE', CODE_INTERPRETER_JUPYTER_URL = os.getenv('CODE_INTERPRETER_JUPYTER_URL', os.getenv('CODE_EXECUTION_JUPYTER_URL', '')) -CODE_INTERPRETER_JUPYTER_AUTH = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH', os.getenv('CODE_EXECUTION_JUPYTER_AUTH', ''), ) +CODE_INTERPRETER_JUPYTER_AUTH = os.getenv( + 'CODE_INTERPRETER_JUPYTER_AUTH', + os.getenv('CODE_EXECUTION_JUPYTER_AUTH', ''), +) -CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN', os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', ''), ) +CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = os.getenv( + 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN', + os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', ''), +) -CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD', os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', ''), ) +CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = os.getenv( + 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD', + os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', ''), +) -CODE_INTERPRETER_JUPYTER_TIMEOUT = int( os.getenv( 'CODE_INTERPRETER_JUPYTER_TIMEOUT', os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'), ) ) +CODE_INTERPRETER_JUPYTER_TIMEOUT = int( + os.getenv( + 'CODE_INTERPRETER_JUPYTER_TIMEOUT', + os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'), + ) +) CODE_INTERPRETER_BLOCKED_MODULES = [ library.strip() for library in os.getenv('CODE_INTERPRETER_BLOCKED_MODULES', '').split(',') if library.strip() @@ -834,7 +848,9 @@ DATALAB_MARKER_PAGINATE = os.getenv('DATALAB_MARKER_PAGINATE', 'false').lower() DATALAB_MARKER_STRIP_EXISTING_OCR = os.getenv('DATALAB_MARKER_STRIP_EXISTING_OCR', 'false').lower() == 'true' -DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = os.getenv('DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', 'false').lower() == 'true' +DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = ( + os.getenv('DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', 'false').lower() == 'true' +) DATALAB_MARKER_FORMAT_LINES = os.getenv('DATALAB_MARKER_FORMAT_LINES', 'false').lower() == 'true' @@ -910,7 +926,9 @@ RAG_HYBRID_BM25_WEIGHT = float(os.getenv('RAG_HYBRID_BM25_WEIGHT', '0.5')) ENABLE_RAG_HYBRID_SEARCH = os.getenv('ENABLE_RAG_HYBRID_SEARCH', '').lower() == 'true' -ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = os.getenv('ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', 'False').lower() == 'true' +ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = ( + os.getenv('ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', 'False').lower() == 'true' +) RAG_FULL_CONTEXT = os.getenv('RAG_FULL_CONTEXT', 'False').lower() == 'true' @@ -920,12 +938,18 @@ RAG_FILE_MAX_SIZE = int(os.getenv('RAG_FILE_MAX_SIZE')) if os.getenv('RAG_FILE_M RAG_FILE_CONTENT_SEARCH_MAX_CHARS = int(os.getenv('RAG_FILE_CONTENT_SEARCH_MAX_CHARS', str(64 * 1024 * 1024))) -FILE_IMAGE_COMPRESSION_WIDTH = int(os.getenv('FILE_IMAGE_COMPRESSION_WIDTH')) if os.getenv('FILE_IMAGE_COMPRESSION_WIDTH') else None +FILE_IMAGE_COMPRESSION_WIDTH = ( + int(os.getenv('FILE_IMAGE_COMPRESSION_WIDTH')) if os.getenv('FILE_IMAGE_COMPRESSION_WIDTH') else None +) -FILE_IMAGE_COMPRESSION_HEIGHT = int(os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT')) if os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT') else None +FILE_IMAGE_COMPRESSION_HEIGHT = ( + int(os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT')) if os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT') else None +) -RAG_ALLOWED_FILE_EXTENSIONS = [ext.strip() for ext in os.getenv('RAG_ALLOWED_FILE_EXTENSIONS', '').split(',') if ext.strip()] +RAG_ALLOWED_FILE_EXTENSIONS = [ + ext.strip() for ext in os.getenv('RAG_ALLOWED_FILE_EXTENSIONS', '').split(',') if ext.strip() +] RAG_EMBEDDING_ENGINE = os.getenv('RAG_EMBEDDING_ENGINE', '') @@ -944,7 +968,9 @@ RAG_EMBEDDING_MODEL_AUTO_UPDATE = ( RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = os.getenv('RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE', 'True').lower() == 'true' -RAG_EMBEDDING_BATCH_SIZE = int(os.getenv('RAG_EMBEDDING_BATCH_SIZE') or os.getenv('RAG_EMBEDDING_OPENAI_BATCH_SIZE', '1')) +RAG_EMBEDDING_BATCH_SIZE = int( + os.getenv('RAG_EMBEDDING_BATCH_SIZE') or os.getenv('RAG_EMBEDDING_OPENAI_BATCH_SIZE', '1') +) ENABLE_ASYNC_EMBEDDING = os.getenv('ENABLE_ASYNC_EMBEDDING', 'True').lower() == 'true' @@ -1081,7 +1107,9 @@ WEB_SEARCH_CONFIRMATION_CONTENT = os.getenv( WEB_SEARCH_ENGINE = os.getenv('WEB_SEARCH_ENGINE', '') -BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = os.getenv('BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true' +BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = ( + os.getenv('BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true' +) BYPASS_WEB_SEARCH_WEB_LOADER = os.getenv('BYPASS_WEB_SEARCH_WEB_LOADER', 'False').lower() == 'true' @@ -1105,7 +1133,9 @@ WEB_SEARCH_DOMAIN_FILTER_LIST = web_search_domain_filter_list WEB_SEARCH_CONCURRENT_REQUESTS = int(os.getenv('WEB_SEARCH_CONCURRENT_REQUESTS', '0')) -WEB_FETCH_MAX_CONTENT_LENGTH = int(os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH')) if os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH') else None +WEB_FETCH_MAX_CONTENT_LENGTH = ( + int(os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH')) if os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH') else None +) WEB_LOADER_ENGINE = os.getenv('WEB_LOADER_ENGINE', '') @@ -1230,7 +1260,7 @@ YANDEX_WEB_SEARCH_API_KEY = os.getenv('YANDEX_WEB_SEARCH_API_KEY', '') YANDEX_WEB_SEARCH_CONFIG = os.getenv('YANDEX_WEB_SEARCH_CONFIG', '') -YOUCOM_API_KEY = os.getenv('YOUCOM_API_KEY', os.getenv('YDC_API_KEY','')) +YOUCOM_API_KEY = os.getenv('YOUCOM_API_KEY', os.getenv('YDC_API_KEY', '')) LINKUP_API_KEY = os.getenv('LINKUP_API_KEY', '') @@ -1483,9 +1513,20 @@ AUDIO_STT_ENGINE = os.getenv('AUDIO_STT_ENGINE', '') AUDIO_STT_MODEL = os.getenv('AUDIO_STT_MODEL', '') -AUDIO_STT_SUPPORTED_CONTENT_TYPES = [ content_type.strip() for content_type in os.getenv('AUDIO_STT_SUPPORTED_CONTENT_TYPES', '').split(',') if content_type.strip() ] +AUDIO_STT_SUPPORTED_CONTENT_TYPES = [ + content_type.strip() + for content_type in os.getenv('AUDIO_STT_SUPPORTED_CONTENT_TYPES', '').split(',') + if content_type.strip() +] -AUDIO_STT_ALLOWED_EXTENSIONS = [ ext.strip() for ext in os.getenv( 'AUDIO_STT_ALLOWED_EXTENSIONS', 'mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg', ).split(',') if ext.strip() ] +AUDIO_STT_ALLOWED_EXTENSIONS = [ + ext.strip() + for ext in os.getenv( + 'AUDIO_STT_ALLOWED_EXTENSIONS', + 'mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg', + ).split(',') + if ext.strip() +] AUDIO_STT_AZURE_API_KEY = os.getenv('AUDIO_STT_AZURE_API_KEY', '') @@ -1530,7 +1571,9 @@ AUDIO_TTS_AZURE_SPEECH_REGION = os.getenv('AUDIO_TTS_AZURE_SPEECH_REGION', '') AUDIO_TTS_AZURE_SPEECH_BASE_URL = os.getenv('AUDIO_TTS_AZURE_SPEECH_BASE_URL', '') -AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = os.getenv('AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', 'audio-24khz-160kbitrate-mono-mp3') +AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = os.getenv( + 'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', 'audio-24khz-160kbitrate-mono-mp3' +) AUDIO_TTS_MISTRAL_API_KEY = os.getenv('AUDIO_TTS_MISTRAL_API_KEY', '') @@ -1728,9 +1771,7 @@ USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = ( os.getenv('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' ) -USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = ( - os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true' -) +USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true' USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = ( @@ -2312,7 +2353,13 @@ Responses from models: {{responses}}""" ENABLE_API_KEYS = os.getenv('ENABLE_API_KEYS', 'False').lower() == 'true' -ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = os.getenv( 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', os.getenv('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'), ).lower() == 'true' +ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ( + os.getenv( + 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', + os.getenv('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'), + ).lower() + == 'true' +) API_KEYS_ALLOWED_ENDPOINTS = os.getenv('API_KEYS_ALLOWED_ENDPOINTS', os.getenv('API_KEY_ALLOWED_ENDPOINTS', '')) @@ -2368,7 +2415,10 @@ MICROSOFT_CLIENT_TENANT_ID = os.getenv('MICROSOFT_CLIENT_TENANT_ID', '') MICROSOFT_CLIENT_LOGIN_BASE_URL = os.getenv('MICROSOFT_CLIENT_LOGIN_BASE_URL', 'https://login.microsoftonline.com') -MICROSOFT_CLIENT_PICTURE_URL = os.getenv( 'MICROSOFT_CLIENT_PICTURE_URL', 'https://graph.microsoft.com/v1.0/me/photo/$value', ) +MICROSOFT_CLIENT_PICTURE_URL = os.getenv( + 'MICROSOFT_CLIENT_PICTURE_URL', + 'https://graph.microsoft.com/v1.0/me/photo/$value', +) MICROSOFT_OAUTH_SCOPE = os.getenv('MICROSOFT_OAUTH_SCOPE', 'openid email profile') @@ -2441,9 +2491,15 @@ OAUTH_ROLES_CLAIM = os.getenv('OAUTH_ROLES_CLAIM', 'roles') OAUTH_ROLES_SEPARATOR = os.getenv('OAUTH_ROLES_SEPARATOR', ',') -OAUTH_ALLOWED_ROLES = [ role.strip() for role in os.getenv('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split(OAUTH_ROLES_SEPARATOR) if role ] +OAUTH_ALLOWED_ROLES = [ + role.strip() + for role in os.getenv('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split(OAUTH_ROLES_SEPARATOR) + if role +] -OAUTH_ADMIN_ROLES = [role.strip() for role in os.getenv('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role] +OAUTH_ADMIN_ROLES = [ + role.strip() for role in os.getenv('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role +] OAUTH_ALLOWED_DOMAINS = [domain.strip() for domain in os.getenv('OAUTH_ALLOWED_DOMAINS', '*').split(',')] @@ -2540,11 +2596,7 @@ def load_oauth_providers(): 'sub_claim': 'id', } - if ( - OAUTH_CLIENT_ID - and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD) - and OPENID_PROVIDER_URL - ): + if OAUTH_CLIENT_ID and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD) and OPENID_PROVIDER_URL: def oidc_oauth_register(oauth: OAuth): client_kwargs = { diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 3857267447..e4b1e57b32 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -599,9 +599,7 @@ except (ValueError, TypeError): # SSL verification for tool server connections specifically. # Accepts "True", "False", or a path to a CA bundle file. # When "True", falls back to AIOHTTP_CLIENT_SSL_CERT_FILE if set. -AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env( - os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True') -) +AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env(os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True')) AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.getenv('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '') diff --git a/backend/open_webui/events.py b/backend/open_webui/events.py index 873af84af7..1e87d912bd 100644 --- a/backend/open_webui/events.py +++ b/backend/open_webui/events.py @@ -42,177 +42,581 @@ class EventDefinition(BaseModel): class EventDefinitions(BaseModel): model_config = ConfigDict(frozen=True) - SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(name='system.startup.started', description='Application startup began.', message='Startup started') - SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(name='system.startup.completed', description='Application startup completed.', message='Startup completed') - SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition(name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started') - SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition(name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed') - CONFIG_IMPORTED: EventDefinition = EventDefinition(name='config.imported', description='Configuration was imported.', message='Config imported') - CONFIG_UPDATED: EventDefinition = EventDefinition(name='config.updated', description='Configuration was updated.', message='Config updated') - CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition(name='config.webhook.updated', description='Event webhook configuration was updated.', message='Webhook configuration updated') - CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(name='config.connections.updated', description='Connection configuration was updated.', message='Config Connections updated') - CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition(name='config.tool_servers.updated', description='Tool server configuration was updated.', message='Config Tool Servers updated') - CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition(name='config.terminal_servers.updated', description='Terminal server configuration was updated.', message='Config Terminal Servers updated') - CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(name='config.code_execution.updated', description='Code execution configuration was updated.', message='Config Code Execution updated') - CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition(name='config.models.updated', description='Model configuration was updated.', message='Config Models updated') - CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition(name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated') - CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(name='config.suggestions.updated', description='Suggestion configuration was updated.', message='Config Suggestions updated') - AUTH_SIGNUP: EventDefinition = EventDefinition(name='auth.signup', description='A user account was created through signup.', message='User signed up') - AUTH_LOGIN: EventDefinition = EventDefinition(name='auth.login', description='A user successfully logged in.', message='User logged in') - AUTH_LOGOUT: EventDefinition = EventDefinition(name='auth.logout', description='A user logged out.', message='User logged out') - AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition(name='auth.password_changed', description='A user password was changed.', message='Password changed') - AUTH_API_KEY_CREATED: EventDefinition = EventDefinition(name='auth.api_key.created', description='A user API key was created.', message='API key created') - AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted') - AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted') - USER_CREATED: EventDefinition = EventDefinition(name='user.created', description='A user account was created.', message='User created') - USER_UPDATED: EventDefinition = EventDefinition(name='user.updated', description='A user account was updated.', message='User updated') - USER_DELETED: EventDefinition = EventDefinition(name='user.deleted', description='A user account was deleted.', message='User deleted') - USER_ROLE_UPDATED: EventDefinition = EventDefinition(name='user.role_updated', description='A user role was updated.', message='User role updated') - USER_STATUS_UPDATED: EventDefinition = EventDefinition(name='user.status_updated', description='A user status was updated.', message='User status updated') - USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(name='user.settings_updated', description='A user settings object was updated.', message='User settings updated') - USER_PROFILE_UPDATED: EventDefinition = EventDefinition(name='user.profile_updated', description='A user profile was updated.', message='User profile updated') - USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition(name='user.permissions_updated', description='A user permissions object was updated.', message='User permissions updated') - GROUP_CREATED: EventDefinition = EventDefinition(name='group.created', description='A group was created.', message='Group created') - GROUP_UPDATED: EventDefinition = EventDefinition(name='group.updated', description='A group was updated.', message='Group updated') - GROUP_DELETED: EventDefinition = EventDefinition(name='group.deleted', description='A group was deleted.', message='Group deleted') - GROUP_MEMBER_ADDED: EventDefinition = EventDefinition(name='group.member_added', description='A user was added to a group.', message='Group member added') - GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition(name='group.member_removed', description='A user was removed from a group.', message='Group member removed') - CHAT_CREATED: EventDefinition = EventDefinition(name='chat.created', description='A chat was created.', message='Chat created') - CHAT_IMPORTED: EventDefinition = EventDefinition(name='chat.imported', description='A chat was imported.', message='Chat imported') - CHAT_UPDATED: EventDefinition = EventDefinition(name='chat.updated', description='A chat was updated.', message='Chat updated') - CHAT_DELETED: EventDefinition = EventDefinition(name='chat.deleted', description='A chat was deleted.', message='Chat deleted') - CHAT_DELETED_ALL: EventDefinition = EventDefinition(name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all') - CHAT_COMPACTED: EventDefinition = EventDefinition(name='chat.compacted', description='A chat was compacted.', message='Chat compacted') - CHAT_PINNED: EventDefinition = EventDefinition(name='chat.pinned', description='A chat was pinned.', message='Chat pinned') - CHAT_UNPINNED: EventDefinition = EventDefinition(name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned') - CHAT_CLONED: EventDefinition = EventDefinition(name='chat.cloned', description='A chat was cloned.', message='Chat cloned') - CHAT_ARCHIVED: EventDefinition = EventDefinition(name='chat.archived', description='A chat was archived.', message='Chat archived') - CHAT_UNARCHIVED: EventDefinition = EventDefinition(name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived') - CHAT_SHARED: EventDefinition = EventDefinition(name='chat.shared', description='A chat was shared.', message='Chat shared') - CHAT_UNSHARED: EventDefinition = EventDefinition(name='chat.unshared', description='A chat was unshared.', message='Chat unshared') - CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated') - CHAT_TAG_ADDED: EventDefinition = EventDefinition(name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added') - CHAT_TAG_REMOVED: EventDefinition = EventDefinition(name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed') - MESSAGE_CREATED: EventDefinition = EventDefinition(name='message.created', description='A message was created.', message='Message created') - MESSAGE_UPDATED: EventDefinition = EventDefinition(name='message.updated', description='A message was updated.', message='Message updated') - MESSAGE_DELETED: EventDefinition = EventDefinition(name='message.deleted', description='A message was deleted.', message='Message deleted') - MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition(name='message.event_received', description='A message-level event was received.', message='Message event received') - MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition(name='message.reaction_added', description='A reaction was added to a message.', message='Message reaction added') - MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition(name='message.reaction_removed', description='A reaction was removed from a message.', message='Message reaction removed') - MESSAGE_PINNED: EventDefinition = EventDefinition(name='message.pinned', description='A message was pinned.', message='Message pinned') - MESSAGE_UNPINNED: EventDefinition = EventDefinition(name='message.unpinned', description='A message was unpinned.', message='Message unpinned') - CHANNEL_CREATED: EventDefinition = EventDefinition(name='channel.created', description='A channel was created.', message='Channel created') - CHANNEL_UPDATED: EventDefinition = EventDefinition(name='channel.updated', description='A channel was updated.', message='Channel updated') - CHANNEL_DELETED: EventDefinition = EventDefinition(name='channel.deleted', description='A channel was deleted.', message='Channel deleted') - CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition(name='channel.member_added', description='A member was added to a channel.', message='Channel member added') - CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition(name='channel.member_removed', description='A member was removed from a channel.', message='Channel member removed') - CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition(name='channel.member_active_updated', description='A channel member active state was updated.', message='Channel member active updated') - CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition(name='channel.webhook.created', description='A channel incoming webhook was created.', message='Channel Webhook created') - CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition(name='channel.webhook.updated', description='A channel incoming webhook was updated.', message='Channel Webhook updated') - CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition(name='channel.webhook.deleted', description='A channel incoming webhook was deleted.', message='Channel Webhook deleted') - FILE_UPLOADED: EventDefinition = EventDefinition(name='file.uploaded', description='A file was uploaded.', message='File uploaded') - FILE_CONTENT_UPDATED: EventDefinition = EventDefinition(name='file.content_updated', description='File content was updated.', message='File content updated') - FILE_RENAMED: EventDefinition = EventDefinition(name='file.renamed', description='A file was renamed.', message='File renamed') - FILE_DELETED: EventDefinition = EventDefinition(name='file.deleted', description='A file was deleted.', message='File deleted') - FILE_DELETED_ALL: EventDefinition = EventDefinition(name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all') - FOLDER_CREATED: EventDefinition = EventDefinition(name='folder.created', description='A folder was created.', message='Folder created') - FOLDER_UPDATED: EventDefinition = EventDefinition(name='folder.updated', description='A folder was updated.', message='Folder updated') - FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition(name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated') - FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition(name='folder.access_updated', description='Folder access was updated.', message='Folder access updated') - FOLDER_DELETED: EventDefinition = EventDefinition(name='folder.deleted', description='A folder was deleted.', message='Folder deleted') - NOTE_CREATED: EventDefinition = EventDefinition(name='note.created', description='A note was created.', message='Note created') - NOTE_UPDATED: EventDefinition = EventDefinition(name='note.updated', description='A note was updated.', message='Note updated') - NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition(name='note.access_updated', description='Note access was updated.', message='Note access updated') - NOTE_PINNED: EventDefinition = EventDefinition(name='note.pinned', description='A note was pinned.', message='Note pinned') - NOTE_UNPINNED: EventDefinition = EventDefinition(name='note.unpinned', description='A note was unpinned.', message='Note unpinned') - NOTE_DELETED: EventDefinition = EventDefinition(name='note.deleted', description='A note was deleted.', message='Note deleted') - MEMORY_CREATED: EventDefinition = EventDefinition(name='memory.created', description='A memory was created.', message='Memory created') - MEMORY_UPDATED: EventDefinition = EventDefinition(name='memory.updated', description='A memory was updated.', message='Memory updated') - MEMORY_DELETED: EventDefinition = EventDefinition(name='memory.deleted', description='A memory was deleted.', message='Memory deleted') - MEMORY_RESET: EventDefinition = EventDefinition(name='memory.reset', description='A memory was reset.', message='Memory reset') - KNOWLEDGE_CREATED: EventDefinition = EventDefinition(name='knowledge.created', description='A knowledge was created.', message='Knowledge created') - KNOWLEDGE_UPDATED: EventDefinition = EventDefinition(name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated') - KNOWLEDGE_DELETED: EventDefinition = EventDefinition(name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted') - KNOWLEDGE_RESET: EventDefinition = EventDefinition(name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset') - KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition(name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed') - KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition(name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated') - KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition(name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added') - KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition(name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated') - KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition(name='knowledge.file.removed', description='A file was removed from a knowledge base.', message='Knowledge File removed') - KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition(name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved') - KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition(name='knowledge.directory.created', description='A knowledge directory was created.', message='Knowledge Directory created') - KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition(name='knowledge.directory.updated', description='A knowledge directory was updated.', message='Knowledge Directory updated') - KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition(name='knowledge.directory.deleted', description='A knowledge directory was deleted.', message='Knowledge Directory deleted') - KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition(name='knowledge.external_connection.created', description='A knowledge external connection was created.', message='Knowledge External Connection created') - KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition(name='knowledge.external_connection.updated', description='A knowledge external connection was updated.', message='Knowledge External Connection updated') - KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition(name='knowledge.external_connection.deleted', description='A knowledge external connection was deleted.', message='Knowledge External Connection deleted') - RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition(name='retrieval.content.processed', description='Retrieval content was processed.', message='Retrieval Content processed') - RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition(name='retrieval.collection.deleted', description='A retrieval collection was deleted.', message='Retrieval Collection deleted') - RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition(name='retrieval.vector_db.reset', description='The retrieval vector database was reset.', message='Retrieval Vector Db reset') - RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition(name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset') - MODEL_CREATED: EventDefinition = EventDefinition(name='model.created', description='A model was created.', message='Model created') - MODEL_IMPORTED: EventDefinition = EventDefinition(name='model.imported', description='A model was imported.', message='Model imported') - MODEL_SYNCED: EventDefinition = EventDefinition(name='model.synced', description='A model was synced.', message='Model synced') - MODEL_UPDATED: EventDefinition = EventDefinition(name='model.updated', description='A model was updated.', message='Model updated') - MODEL_DELETED: EventDefinition = EventDefinition(name='model.deleted', description='A model was deleted.', message='Model deleted') - MODEL_ENABLED: EventDefinition = EventDefinition(name='model.enabled', description='A model was enabled.', message='Model enabled') - MODEL_DISABLED: EventDefinition = EventDefinition(name='model.disabled', description='A model was disabled.', message='Model disabled') - MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition(name='model.access_updated', description='Model access was updated.', message='Model access updated') - MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition(name='model.provider_config.updated', description='Model provider configuration was updated.', message='Model Provider Config updated') - MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition(name='model.provider_model.created', description='A provider model was created.', message='Provider model created') - MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition(name='model.provider_model.deleted', description='A provider model was deleted.', message='Provider model deleted') - FUNCTION_CREATED: EventDefinition = EventDefinition(name='function.created', description='A function was created.', message='Function created') - FUNCTION_UPDATED: EventDefinition = EventDefinition(name='function.updated', description='A function was updated.', message='Function updated') - FUNCTION_DELETED: EventDefinition = EventDefinition(name='function.deleted', description='A function was deleted.', message='Function deleted') - FUNCTION_ENABLED: EventDefinition = EventDefinition(name='function.enabled', description='A function was enabled.', message='Function enabled') - FUNCTION_DISABLED: EventDefinition = EventDefinition(name='function.disabled', description='A function was disabled.', message='Function disabled') - FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition(name='function.valves_updated', description='Function valves were updated.', message='Function valves updated') - TOOL_CREATED: EventDefinition = EventDefinition(name='tool.created', description='A tool was created.', message='Tool created') - TOOL_UPDATED: EventDefinition = EventDefinition(name='tool.updated', description='A tool was updated.', message='Tool updated') - TOOL_DELETED: EventDefinition = EventDefinition(name='tool.deleted', description='A tool was deleted.', message='Tool deleted') - TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition(name='tool.access_updated', description='Tool access was updated.', message='Tool access updated') - TOOL_VALVES_UPDATED: EventDefinition = EventDefinition(name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated') - SKILL_CREATED: EventDefinition = EventDefinition(name='skill.created', description='A skill was created.', message='Skill created') - SKILL_UPDATED: EventDefinition = EventDefinition(name='skill.updated', description='A skill was updated.', message='Skill updated') - SKILL_DELETED: EventDefinition = EventDefinition(name='skill.deleted', description='A skill was deleted.', message='Skill deleted') - SKILL_ENABLED: EventDefinition = EventDefinition(name='skill.enabled', description='A skill was enabled.', message='Skill enabled') - SKILL_DISABLED: EventDefinition = EventDefinition(name='skill.disabled', description='A skill was disabled.', message='Skill disabled') - PROMPT_CREATED: EventDefinition = EventDefinition(name='prompt.created', description='A prompt was created.', message='Prompt created') - PROMPT_UPDATED: EventDefinition = EventDefinition(name='prompt.updated', description='A prompt was updated.', message='Prompt updated') - PROMPT_DELETED: EventDefinition = EventDefinition(name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted') - PROMPT_ENABLED: EventDefinition = EventDefinition(name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled') - PROMPT_DISABLED: EventDefinition = EventDefinition(name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled') - PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition(name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated') - PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition(name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated') - PIPELINE_UPLOADED: EventDefinition = EventDefinition(name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded') - PIPELINE_ADDED: EventDefinition = EventDefinition(name='pipeline.added', description='A pipeline was added.', message='Pipeline added') - PIPELINE_DELETED: EventDefinition = EventDefinition(name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted') - PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition(name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated') - CALENDAR_CREATED: EventDefinition = EventDefinition(name='calendar.created', description='A calendar was created.', message='Calendar created') - CALENDAR_UPDATED: EventDefinition = EventDefinition(name='calendar.updated', description='A calendar was updated.', message='Calendar updated') - CALENDAR_DELETED: EventDefinition = EventDefinition(name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted') - CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition(name='calendar.default_updated', description='The default calendar was updated.', message='Calendar default updated') - CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition(name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created') - CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition(name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated') - CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition(name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted') - CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition(name='calendar.event.rsvp_updated', description='A calendar event RSVP was updated.', message='Calendar Event rsvp updated') - AUTOMATION_CREATED: EventDefinition = EventDefinition(name='automation.created', description='An automation was created.', message='Automation created') - AUTOMATION_UPDATED: EventDefinition = EventDefinition(name='automation.updated', description='An automation was updated.', message='Automation updated') - AUTOMATION_ENABLED: EventDefinition = EventDefinition(name='automation.enabled', description='An automation was enabled.', message='Automation enabled') - AUTOMATION_DISABLED: EventDefinition = EventDefinition(name='automation.disabled', description='An automation was disabled.', message='Automation disabled') - AUTOMATION_DELETED: EventDefinition = EventDefinition(name='automation.deleted', description='An automation was deleted.', message='Automation deleted') - AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition(name='automation.run_started', description='An automation run started.', message='Automation run started') - AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition(name='automation.run_completed', description='An automation run completed.', message='Automation run completed') - AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition(name='automation.run_failed', description='An automation run failed.', message='Automation run failed') - FEEDBACK_CREATED: EventDefinition = EventDefinition(name='feedback.created', description='A feedback was created.', message='Feedback created') - FEEDBACK_UPDATED: EventDefinition = EventDefinition(name='feedback.updated', description='A feedback was updated.', message='Feedback updated') - FEEDBACK_DELETED: EventDefinition = EventDefinition(name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted') - FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition(name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all') - IMAGE_GENERATED: EventDefinition = EventDefinition(name='image.generated', description='An image was generated.', message='Image generated') - IMAGE_EDITED: EventDefinition = EventDefinition(name='image.edited', description='An image was edited.', message='Image edited') - AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition(name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested') - AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition(name='audio.transcription_requested', description='Audio transcription was requested.', message='Transcription requested') - TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition(name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened') - TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition(name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed') + SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition( + name='system.startup.started', description='Application startup began.', message='Startup started' + ) + SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition( + name='system.startup.completed', description='Application startup completed.', message='Startup completed' + ) + SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition( + name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started' + ) + SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition( + name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed' + ) + CONFIG_IMPORTED: EventDefinition = EventDefinition( + name='config.imported', description='Configuration was imported.', message='Config imported' + ) + CONFIG_UPDATED: EventDefinition = EventDefinition( + name='config.updated', description='Configuration was updated.', message='Config updated' + ) + CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition( + name='config.webhook.updated', + description='Event webhook configuration was updated.', + message='Webhook configuration updated', + ) + CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition( + name='config.connections.updated', + description='Connection configuration was updated.', + message='Config Connections updated', + ) + CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition( + name='config.tool_servers.updated', + description='Tool server configuration was updated.', + message='Config Tool Servers updated', + ) + CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition( + name='config.terminal_servers.updated', + description='Terminal server configuration was updated.', + message='Config Terminal Servers updated', + ) + CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition( + name='config.code_execution.updated', + description='Code execution configuration was updated.', + message='Config Code Execution updated', + ) + CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition( + name='config.models.updated', description='Model configuration was updated.', message='Config Models updated' + ) + CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition( + name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated' + ) + CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition( + name='config.suggestions.updated', + description='Suggestion configuration was updated.', + message='Config Suggestions updated', + ) + AUTH_SIGNUP: EventDefinition = EventDefinition( + name='auth.signup', description='A user account was created through signup.', message='User signed up' + ) + AUTH_LOGIN: EventDefinition = EventDefinition( + name='auth.login', description='A user successfully logged in.', message='User logged in' + ) + AUTH_LOGOUT: EventDefinition = EventDefinition( + name='auth.logout', description='A user logged out.', message='User logged out' + ) + AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition( + name='auth.password_changed', description='A user password was changed.', message='Password changed' + ) + AUTH_API_KEY_CREATED: EventDefinition = EventDefinition( + name='auth.api_key.created', description='A user API key was created.', message='API key created' + ) + AUTH_API_KEY_DELETED: EventDefinition = EventDefinition( + name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted' + ) + AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition( + name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted' + ) + USER_CREATED: EventDefinition = EventDefinition( + name='user.created', description='A user account was created.', message='User created' + ) + USER_UPDATED: EventDefinition = EventDefinition( + name='user.updated', description='A user account was updated.', message='User updated' + ) + USER_DELETED: EventDefinition = EventDefinition( + name='user.deleted', description='A user account was deleted.', message='User deleted' + ) + USER_ROLE_UPDATED: EventDefinition = EventDefinition( + name='user.role_updated', description='A user role was updated.', message='User role updated' + ) + USER_STATUS_UPDATED: EventDefinition = EventDefinition( + name='user.status_updated', description='A user status was updated.', message='User status updated' + ) + USER_SETTINGS_UPDATED: EventDefinition = EventDefinition( + name='user.settings_updated', description='A user settings object was updated.', message='User settings updated' + ) + USER_PROFILE_UPDATED: EventDefinition = EventDefinition( + name='user.profile_updated', description='A user profile was updated.', message='User profile updated' + ) + USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition( + name='user.permissions_updated', + description='A user permissions object was updated.', + message='User permissions updated', + ) + GROUP_CREATED: EventDefinition = EventDefinition( + name='group.created', description='A group was created.', message='Group created' + ) + GROUP_UPDATED: EventDefinition = EventDefinition( + name='group.updated', description='A group was updated.', message='Group updated' + ) + GROUP_DELETED: EventDefinition = EventDefinition( + name='group.deleted', description='A group was deleted.', message='Group deleted' + ) + GROUP_MEMBER_ADDED: EventDefinition = EventDefinition( + name='group.member_added', description='A user was added to a group.', message='Group member added' + ) + GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition( + name='group.member_removed', description='A user was removed from a group.', message='Group member removed' + ) + CHAT_CREATED: EventDefinition = EventDefinition( + name='chat.created', description='A chat was created.', message='Chat created' + ) + CHAT_IMPORTED: EventDefinition = EventDefinition( + name='chat.imported', description='A chat was imported.', message='Chat imported' + ) + CHAT_UPDATED: EventDefinition = EventDefinition( + name='chat.updated', description='A chat was updated.', message='Chat updated' + ) + CHAT_DELETED: EventDefinition = EventDefinition( + name='chat.deleted', description='A chat was deleted.', message='Chat deleted' + ) + CHAT_DELETED_ALL: EventDefinition = EventDefinition( + name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all' + ) + CHAT_COMPACTED: EventDefinition = EventDefinition( + name='chat.compacted', description='A chat was compacted.', message='Chat compacted' + ) + CHAT_PINNED: EventDefinition = EventDefinition( + name='chat.pinned', description='A chat was pinned.', message='Chat pinned' + ) + CHAT_UNPINNED: EventDefinition = EventDefinition( + name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned' + ) + CHAT_CLONED: EventDefinition = EventDefinition( + name='chat.cloned', description='A chat was cloned.', message='Chat cloned' + ) + CHAT_ARCHIVED: EventDefinition = EventDefinition( + name='chat.archived', description='A chat was archived.', message='Chat archived' + ) + CHAT_UNARCHIVED: EventDefinition = EventDefinition( + name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived' + ) + CHAT_SHARED: EventDefinition = EventDefinition( + name='chat.shared', description='A chat was shared.', message='Chat shared' + ) + CHAT_UNSHARED: EventDefinition = EventDefinition( + name='chat.unshared', description='A chat was unshared.', message='Chat unshared' + ) + CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition( + name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated' + ) + CHAT_TAG_ADDED: EventDefinition = EventDefinition( + name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added' + ) + CHAT_TAG_REMOVED: EventDefinition = EventDefinition( + name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed' + ) + MESSAGE_CREATED: EventDefinition = EventDefinition( + name='message.created', description='A message was created.', message='Message created' + ) + MESSAGE_UPDATED: EventDefinition = EventDefinition( + name='message.updated', description='A message was updated.', message='Message updated' + ) + MESSAGE_DELETED: EventDefinition = EventDefinition( + name='message.deleted', description='A message was deleted.', message='Message deleted' + ) + MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition( + name='message.event_received', + description='A message-level event was received.', + message='Message event received', + ) + MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition( + name='message.reaction_added', + description='A reaction was added to a message.', + message='Message reaction added', + ) + MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition( + name='message.reaction_removed', + description='A reaction was removed from a message.', + message='Message reaction removed', + ) + MESSAGE_PINNED: EventDefinition = EventDefinition( + name='message.pinned', description='A message was pinned.', message='Message pinned' + ) + MESSAGE_UNPINNED: EventDefinition = EventDefinition( + name='message.unpinned', description='A message was unpinned.', message='Message unpinned' + ) + CHANNEL_CREATED: EventDefinition = EventDefinition( + name='channel.created', description='A channel was created.', message='Channel created' + ) + CHANNEL_UPDATED: EventDefinition = EventDefinition( + name='channel.updated', description='A channel was updated.', message='Channel updated' + ) + CHANNEL_DELETED: EventDefinition = EventDefinition( + name='channel.deleted', description='A channel was deleted.', message='Channel deleted' + ) + CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition( + name='channel.member_added', description='A member was added to a channel.', message='Channel member added' + ) + CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition( + name='channel.member_removed', + description='A member was removed from a channel.', + message='Channel member removed', + ) + CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition( + name='channel.member_active_updated', + description='A channel member active state was updated.', + message='Channel member active updated', + ) + CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition( + name='channel.webhook.created', + description='A channel incoming webhook was created.', + message='Channel Webhook created', + ) + CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition( + name='channel.webhook.updated', + description='A channel incoming webhook was updated.', + message='Channel Webhook updated', + ) + CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition( + name='channel.webhook.deleted', + description='A channel incoming webhook was deleted.', + message='Channel Webhook deleted', + ) + FILE_UPLOADED: EventDefinition = EventDefinition( + name='file.uploaded', description='A file was uploaded.', message='File uploaded' + ) + FILE_CONTENT_UPDATED: EventDefinition = EventDefinition( + name='file.content_updated', description='File content was updated.', message='File content updated' + ) + FILE_RENAMED: EventDefinition = EventDefinition( + name='file.renamed', description='A file was renamed.', message='File renamed' + ) + FILE_DELETED: EventDefinition = EventDefinition( + name='file.deleted', description='A file was deleted.', message='File deleted' + ) + FILE_DELETED_ALL: EventDefinition = EventDefinition( + name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all' + ) + FOLDER_CREATED: EventDefinition = EventDefinition( + name='folder.created', description='A folder was created.', message='Folder created' + ) + FOLDER_UPDATED: EventDefinition = EventDefinition( + name='folder.updated', description='A folder was updated.', message='Folder updated' + ) + FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition( + name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated' + ) + FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='folder.access_updated', description='Folder access was updated.', message='Folder access updated' + ) + FOLDER_DELETED: EventDefinition = EventDefinition( + name='folder.deleted', description='A folder was deleted.', message='Folder deleted' + ) + NOTE_CREATED: EventDefinition = EventDefinition( + name='note.created', description='A note was created.', message='Note created' + ) + NOTE_UPDATED: EventDefinition = EventDefinition( + name='note.updated', description='A note was updated.', message='Note updated' + ) + NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='note.access_updated', description='Note access was updated.', message='Note access updated' + ) + NOTE_PINNED: EventDefinition = EventDefinition( + name='note.pinned', description='A note was pinned.', message='Note pinned' + ) + NOTE_UNPINNED: EventDefinition = EventDefinition( + name='note.unpinned', description='A note was unpinned.', message='Note unpinned' + ) + NOTE_DELETED: EventDefinition = EventDefinition( + name='note.deleted', description='A note was deleted.', message='Note deleted' + ) + MEMORY_CREATED: EventDefinition = EventDefinition( + name='memory.created', description='A memory was created.', message='Memory created' + ) + MEMORY_UPDATED: EventDefinition = EventDefinition( + name='memory.updated', description='A memory was updated.', message='Memory updated' + ) + MEMORY_DELETED: EventDefinition = EventDefinition( + name='memory.deleted', description='A memory was deleted.', message='Memory deleted' + ) + MEMORY_RESET: EventDefinition = EventDefinition( + name='memory.reset', description='A memory was reset.', message='Memory reset' + ) + KNOWLEDGE_CREATED: EventDefinition = EventDefinition( + name='knowledge.created', description='A knowledge was created.', message='Knowledge created' + ) + KNOWLEDGE_UPDATED: EventDefinition = EventDefinition( + name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated' + ) + KNOWLEDGE_DELETED: EventDefinition = EventDefinition( + name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted' + ) + KNOWLEDGE_RESET: EventDefinition = EventDefinition( + name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset' + ) + KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition( + name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed' + ) + KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated' + ) + KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition( + name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added' + ) + KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition( + name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated' + ) + KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition( + name='knowledge.file.removed', + description='A file was removed from a knowledge base.', + message='Knowledge File removed', + ) + KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition( + name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved' + ) + KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition( + name='knowledge.directory.created', + description='A knowledge directory was created.', + message='Knowledge Directory created', + ) + KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition( + name='knowledge.directory.updated', + description='A knowledge directory was updated.', + message='Knowledge Directory updated', + ) + KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition( + name='knowledge.directory.deleted', + description='A knowledge directory was deleted.', + message='Knowledge Directory deleted', + ) + KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition( + name='knowledge.external_connection.created', + description='A knowledge external connection was created.', + message='Knowledge External Connection created', + ) + KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition( + name='knowledge.external_connection.updated', + description='A knowledge external connection was updated.', + message='Knowledge External Connection updated', + ) + KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition( + name='knowledge.external_connection.deleted', + description='A knowledge external connection was deleted.', + message='Knowledge External Connection deleted', + ) + RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition( + name='retrieval.content.processed', + description='Retrieval content was processed.', + message='Retrieval Content processed', + ) + RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition( + name='retrieval.collection.deleted', + description='A retrieval collection was deleted.', + message='Retrieval Collection deleted', + ) + RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition( + name='retrieval.vector_db.reset', + description='The retrieval vector database was reset.', + message='Retrieval Vector Db reset', + ) + RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition( + name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset' + ) + MODEL_CREATED: EventDefinition = EventDefinition( + name='model.created', description='A model was created.', message='Model created' + ) + MODEL_IMPORTED: EventDefinition = EventDefinition( + name='model.imported', description='A model was imported.', message='Model imported' + ) + MODEL_SYNCED: EventDefinition = EventDefinition( + name='model.synced', description='A model was synced.', message='Model synced' + ) + MODEL_UPDATED: EventDefinition = EventDefinition( + name='model.updated', description='A model was updated.', message='Model updated' + ) + MODEL_DELETED: EventDefinition = EventDefinition( + name='model.deleted', description='A model was deleted.', message='Model deleted' + ) + MODEL_ENABLED: EventDefinition = EventDefinition( + name='model.enabled', description='A model was enabled.', message='Model enabled' + ) + MODEL_DISABLED: EventDefinition = EventDefinition( + name='model.disabled', description='A model was disabled.', message='Model disabled' + ) + MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='model.access_updated', description='Model access was updated.', message='Model access updated' + ) + MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition( + name='model.provider_config.updated', + description='Model provider configuration was updated.', + message='Model Provider Config updated', + ) + MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition( + name='model.provider_model.created', + description='A provider model was created.', + message='Provider model created', + ) + MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition( + name='model.provider_model.deleted', + description='A provider model was deleted.', + message='Provider model deleted', + ) + FUNCTION_CREATED: EventDefinition = EventDefinition( + name='function.created', description='A function was created.', message='Function created' + ) + FUNCTION_UPDATED: EventDefinition = EventDefinition( + name='function.updated', description='A function was updated.', message='Function updated' + ) + FUNCTION_DELETED: EventDefinition = EventDefinition( + name='function.deleted', description='A function was deleted.', message='Function deleted' + ) + FUNCTION_ENABLED: EventDefinition = EventDefinition( + name='function.enabled', description='A function was enabled.', message='Function enabled' + ) + FUNCTION_DISABLED: EventDefinition = EventDefinition( + name='function.disabled', description='A function was disabled.', message='Function disabled' + ) + FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition( + name='function.valves_updated', description='Function valves were updated.', message='Function valves updated' + ) + TOOL_CREATED: EventDefinition = EventDefinition( + name='tool.created', description='A tool was created.', message='Tool created' + ) + TOOL_UPDATED: EventDefinition = EventDefinition( + name='tool.updated', description='A tool was updated.', message='Tool updated' + ) + TOOL_DELETED: EventDefinition = EventDefinition( + name='tool.deleted', description='A tool was deleted.', message='Tool deleted' + ) + TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='tool.access_updated', description='Tool access was updated.', message='Tool access updated' + ) + TOOL_VALVES_UPDATED: EventDefinition = EventDefinition( + name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated' + ) + SKILL_CREATED: EventDefinition = EventDefinition( + name='skill.created', description='A skill was created.', message='Skill created' + ) + SKILL_UPDATED: EventDefinition = EventDefinition( + name='skill.updated', description='A skill was updated.', message='Skill updated' + ) + SKILL_DELETED: EventDefinition = EventDefinition( + name='skill.deleted', description='A skill was deleted.', message='Skill deleted' + ) + SKILL_ENABLED: EventDefinition = EventDefinition( + name='skill.enabled', description='A skill was enabled.', message='Skill enabled' + ) + SKILL_DISABLED: EventDefinition = EventDefinition( + name='skill.disabled', description='A skill was disabled.', message='Skill disabled' + ) + PROMPT_CREATED: EventDefinition = EventDefinition( + name='prompt.created', description='A prompt was created.', message='Prompt created' + ) + PROMPT_UPDATED: EventDefinition = EventDefinition( + name='prompt.updated', description='A prompt was updated.', message='Prompt updated' + ) + PROMPT_DELETED: EventDefinition = EventDefinition( + name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted' + ) + PROMPT_ENABLED: EventDefinition = EventDefinition( + name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled' + ) + PROMPT_DISABLED: EventDefinition = EventDefinition( + name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled' + ) + PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition( + name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated' + ) + PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition( + name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated' + ) + PIPELINE_UPLOADED: EventDefinition = EventDefinition( + name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded' + ) + PIPELINE_ADDED: EventDefinition = EventDefinition( + name='pipeline.added', description='A pipeline was added.', message='Pipeline added' + ) + PIPELINE_DELETED: EventDefinition = EventDefinition( + name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted' + ) + PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition( + name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated' + ) + CALENDAR_CREATED: EventDefinition = EventDefinition( + name='calendar.created', description='A calendar was created.', message='Calendar created' + ) + CALENDAR_UPDATED: EventDefinition = EventDefinition( + name='calendar.updated', description='A calendar was updated.', message='Calendar updated' + ) + CALENDAR_DELETED: EventDefinition = EventDefinition( + name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted' + ) + CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition( + name='calendar.default_updated', + description='The default calendar was updated.', + message='Calendar default updated', + ) + CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition( + name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created' + ) + CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition( + name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated' + ) + CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition( + name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted' + ) + CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition( + name='calendar.event.rsvp_updated', + description='A calendar event RSVP was updated.', + message='Calendar Event rsvp updated', + ) + AUTOMATION_CREATED: EventDefinition = EventDefinition( + name='automation.created', description='An automation was created.', message='Automation created' + ) + AUTOMATION_UPDATED: EventDefinition = EventDefinition( + name='automation.updated', description='An automation was updated.', message='Automation updated' + ) + AUTOMATION_ENABLED: EventDefinition = EventDefinition( + name='automation.enabled', description='An automation was enabled.', message='Automation enabled' + ) + AUTOMATION_DISABLED: EventDefinition = EventDefinition( + name='automation.disabled', description='An automation was disabled.', message='Automation disabled' + ) + AUTOMATION_DELETED: EventDefinition = EventDefinition( + name='automation.deleted', description='An automation was deleted.', message='Automation deleted' + ) + AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition( + name='automation.run_started', description='An automation run started.', message='Automation run started' + ) + AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition( + name='automation.run_completed', description='An automation run completed.', message='Automation run completed' + ) + AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition( + name='automation.run_failed', description='An automation run failed.', message='Automation run failed' + ) + FEEDBACK_CREATED: EventDefinition = EventDefinition( + name='feedback.created', description='A feedback was created.', message='Feedback created' + ) + FEEDBACK_UPDATED: EventDefinition = EventDefinition( + name='feedback.updated', description='A feedback was updated.', message='Feedback updated' + ) + FEEDBACK_DELETED: EventDefinition = EventDefinition( + name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted' + ) + FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition( + name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all' + ) + IMAGE_GENERATED: EventDefinition = EventDefinition( + name='image.generated', description='An image was generated.', message='Image generated' + ) + IMAGE_EDITED: EventDefinition = EventDefinition( + name='image.edited', description='An image was edited.', message='Image edited' + ) + AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition( + name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested' + ) + AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition( + name='audio.transcription_requested', + description='Audio transcription was requested.', + message='Transcription requested', + ) + TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition( + name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened' + ) + TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition( + name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed' + ) EVENTS = EventDefinitions() @@ -232,6 +636,7 @@ def get_event_catalog() -> list[dict[str, str]]: for definition in EVENT_DEFINITIONS ] + SENSITIVE_KEYS = { 'password', 'hashed_password', @@ -369,10 +774,7 @@ async def event_target_matches( from open_webui.models.groups import Groups groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids)) - user_group_ids = { - user_id: {group.id for group in groups} - for user_id, groups in groups_by_user.items() - } + user_group_ids = {user_id: {group.id for group in groups} for user_id, groups in groups_by_user.items()} return any(group_ids.intersection(target_group_ids) for group_ids in user_group_ids.values()) @@ -577,7 +979,7 @@ def build_event( subject=_sanitize(subject) if subject else None, data=_sanitize(data or {}), message=message, -) + ) async def dispatch_webhook_event(app: Any, event: Event) -> None: @@ -652,9 +1054,7 @@ async def dispatch_event_functions(app: Any, event: Event, request: Any | None = '__app__': app, '__request__': request, } - params = { - key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters - } + params = {key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters} if inspect.iscoroutinefunction(handler): await handler(**params) diff --git a/backend/open_webui/migrations/versions/3ff2c63645b8_reshape_config_to_per_key_rows.py b/backend/open_webui/migrations/versions/3ff2c63645b8_reshape_config_to_per_key_rows.py index 167862d709..1bf3c9bb45 100644 --- a/backend/open_webui/migrations/versions/3ff2c63645b8_reshape_config_to_per_key_rows.py +++ b/backend/open_webui/migrations/versions/3ff2c63645b8_reshape_config_to_per_key_rows.py @@ -24,421 +24,419 @@ depends_on: Union[str, Sequence[str], None] = None # Maps every dot-notation blob path to its legacy env/config key name. # Built from the legacy persistent config declarations in config.py. BLOB_PATH_TO_KEY = { - "audio.stt.allowed_extensions": "AUDIO_STT_ALLOWED_EXTENSIONS", - "audio.stt.azure.api_key": "AUDIO_STT_AZURE_API_KEY", - "audio.stt.azure.base_url": "AUDIO_STT_AZURE_BASE_URL", - "audio.stt.azure.locales": "AUDIO_STT_AZURE_LOCALES", - "audio.stt.azure.max_speakers": "AUDIO_STT_AZURE_MAX_SPEAKERS", - "audio.stt.azure.region": "AUDIO_STT_AZURE_REGION", - "audio.stt.deepgram.api_key": "DEEPGRAM_API_KEY", - "audio.stt.engine": "AUDIO_STT_ENGINE", - "audio.stt.mistral.api_base_url": "AUDIO_STT_MISTRAL_API_BASE_URL", - "audio.stt.mistral.api_key": "AUDIO_STT_MISTRAL_API_KEY", - "audio.stt.mistral.use_chat_completions": "AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS", - "audio.stt.model": "AUDIO_STT_MODEL", - "audio.stt.openai.api_base_url": "AUDIO_STT_OPENAI_API_BASE_URL", - "audio.stt.openai.api_key": "AUDIO_STT_OPENAI_API_KEY", - "audio.stt.supported_content_types": "AUDIO_STT_SUPPORTED_CONTENT_TYPES", - "audio.stt.whisper_model": "WHISPER_MODEL", - "audio.tts.api_key": "AUDIO_TTS_API_KEY", - "audio.tts.azure.speech_base_url": "AUDIO_TTS_AZURE_SPEECH_BASE_URL", - "audio.tts.azure.speech_output_format": "AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT", - "audio.tts.azure.speech_region": "AUDIO_TTS_AZURE_SPEECH_REGION", - "audio.tts.engine": "AUDIO_TTS_ENGINE", - "audio.tts.mistral.api_base_url": "AUDIO_TTS_MISTRAL_API_BASE_URL", - "audio.tts.mistral.api_key": "AUDIO_TTS_MISTRAL_API_KEY", - "audio.tts.model": "AUDIO_TTS_MODEL", - "audio.tts.openai.api_base_url": "AUDIO_TTS_OPENAI_API_BASE_URL", - "audio.tts.openai.api_key": "AUDIO_TTS_OPENAI_API_KEY", - "audio.tts.openai.params": "AUDIO_TTS_OPENAI_PARAMS", - "audio.tts.split_on": "AUDIO_TTS_SPLIT_ON", - "audio.tts.voice": "AUDIO_TTS_VOICE", - "auth.admin.email": "ADMIN_EMAIL", - "auth.admin.show": "SHOW_ADMIN_DETAILS", - "auth.api_key.allowed_endpoints": "API_KEYS_ALLOWED_ENDPOINTS", - "auth.api_key.endpoint_restrictions": "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS", - "auth.enable_api_keys": "ENABLE_API_KEYS", - "auth.jwt_expiry": "JWT_EXPIRES_IN", - "automations.enable": "ENABLE_AUTOMATIONS", - "automations.max_count": "AUTOMATION_MAX_COUNT", - "automations.min_interval": "AUTOMATION_MIN_INTERVAL", - "calendar.enable": "ENABLE_CALENDAR", - "channels.enable": "ENABLE_CHANNELS", - "code_execution.enable": "ENABLE_CODE_EXECUTION", - "code_execution.engine": "CODE_EXECUTION_ENGINE", - "code_execution.jupyter.auth": "CODE_EXECUTION_JUPYTER_AUTH", - "code_execution.jupyter.auth_password": "CODE_EXECUTION_JUPYTER_AUTH_PASSWORD", - "code_execution.jupyter.auth_token": "CODE_EXECUTION_JUPYTER_AUTH_TOKEN", - "code_execution.jupyter.timeout": "CODE_EXECUTION_JUPYTER_TIMEOUT", - "code_execution.jupyter.url": "CODE_EXECUTION_JUPYTER_URL", - "code_interpreter.enable": "ENABLE_CODE_INTERPRETER", - "code_interpreter.engine": "CODE_INTERPRETER_ENGINE", - "code_interpreter.jupyter.auth": "CODE_INTERPRETER_JUPYTER_AUTH", - "code_interpreter.jupyter.auth_password": "CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD", - "code_interpreter.jupyter.auth_token": "CODE_INTERPRETER_JUPYTER_AUTH_TOKEN", - "code_interpreter.jupyter.timeout": "CODE_INTERPRETER_JUPYTER_TIMEOUT", - "code_interpreter.jupyter.url": "CODE_INTERPRETER_JUPYTER_URL", - "code_interpreter.prompt_template": "CODE_INTERPRETER_PROMPT_TEMPLATE", - "direct.enable": "ENABLE_DIRECT_CONNECTIONS", - "evaluation.arena.enable": "ENABLE_EVALUATION_ARENA_MODELS", - "evaluation.arena.models": "EVALUATION_ARENA_MODELS", - "file.image_compression_height": "FILE_IMAGE_COMPRESSION_HEIGHT", - "file.image_compression_width": "FILE_IMAGE_COMPRESSION_WIDTH", - "folders.enable": "ENABLE_FOLDERS", - "folders.max_file_count": "FOLDER_MAX_FILE_COUNT", - "google_drive.api_key": "GOOGLE_DRIVE_API_KEY", - "google_drive.client_id": "GOOGLE_DRIVE_CLIENT_ID", - "google_drive.enable": "ENABLE_GOOGLE_DRIVE_INTEGRATION", - "image_generation.automatic1111.api_auth": "AUTOMATIC1111_API_AUTH", - "image_generation.automatic1111.api_params": "AUTOMATIC1111_PARAMS", - "image_generation.automatic1111.base_url": "AUTOMATIC1111_BASE_URL", - "image_generation.comfyui.api_key": "COMFYUI_API_KEY", - "image_generation.comfyui.base_url": "COMFYUI_BASE_URL", - "image_generation.comfyui.nodes": "COMFYUI_WORKFLOW_NODES", - "image_generation.comfyui.workflow": "COMFYUI_WORKFLOW", - "image_generation.enable": "ENABLE_IMAGE_GENERATION", - "image_generation.engine": "IMAGE_GENERATION_ENGINE", - "image_generation.gemini.api_base_url": "IMAGES_GEMINI_API_BASE_URL", - "image_generation.gemini.api_key": "IMAGES_GEMINI_API_KEY", - "image_generation.gemini.endpoint_method": "IMAGES_GEMINI_ENDPOINT_METHOD", - "image_generation.model": "IMAGE_GENERATION_MODEL", - "image_generation.openai.api_base_url": "IMAGES_OPENAI_API_BASE_URL", - "image_generation.openai.api_key": "IMAGES_OPENAI_API_KEY", - "image_generation.openai.api_version": "IMAGES_OPENAI_API_VERSION", - "image_generation.openai.params": "IMAGES_OPENAI_API_PARAMS", - "image_generation.prompt.enable": "ENABLE_IMAGE_PROMPT_GENERATION", - "image_generation.size": "IMAGE_SIZE", - "image_generation.steps": "IMAGE_STEPS", - "images.edit.comfyui.api_key": "IMAGES_EDIT_COMFYUI_API_KEY", - "images.edit.comfyui.base_url": "IMAGES_EDIT_COMFYUI_BASE_URL", - "images.edit.comfyui.nodes": "IMAGES_EDIT_COMFYUI_WORKFLOW_NODES", - "images.edit.comfyui.workflow": "IMAGES_EDIT_COMFYUI_WORKFLOW", - "images.edit.enable": "ENABLE_IMAGE_EDIT", - "images.edit.engine": "IMAGE_EDIT_ENGINE", - "images.edit.gemini.api_base_url": "IMAGES_EDIT_GEMINI_API_BASE_URL", - "images.edit.gemini.api_key": "IMAGES_EDIT_GEMINI_API_KEY", - "images.edit.model": "IMAGE_EDIT_MODEL", - "images.edit.openai.api_base_url": "IMAGES_EDIT_OPENAI_API_BASE_URL", - "images.edit.openai.api_key": "IMAGES_EDIT_OPENAI_API_KEY", - "images.edit.openai.api_version": "IMAGES_EDIT_OPENAI_API_VERSION", - "images.edit.size": "IMAGE_EDIT_SIZE", - "ldap.enable": "ENABLE_LDAP", - "ldap.group.enable_creation": "ENABLE_LDAP_GROUP_CREATION", - "ldap.group.enable_management": "ENABLE_LDAP_GROUP_MANAGEMENT", - "ldap.server.app_dn": "LDAP_APP_DN", - "ldap.server.app_password": "LDAP_APP_PASSWORD", - "ldap.server.attribute_for_groups": "LDAP_ATTRIBUTE_FOR_GROUPS", - "ldap.server.attribute_for_mail": "LDAP_ATTRIBUTE_FOR_MAIL", - "ldap.server.attribute_for_username": "LDAP_ATTRIBUTE_FOR_USERNAME", - "ldap.server.ca_cert_file": "LDAP_CA_CERT_FILE", - "ldap.server.ciphers": "LDAP_CIPHERS", - "ldap.server.host": "LDAP_SERVER_HOST", - "ldap.server.label": "LDAP_SERVER_LABEL", - "ldap.server.port": "LDAP_SERVER_PORT", - "ldap.server.search_filter": "LDAP_SEARCH_FILTER", - "ldap.server.use_tls": "LDAP_USE_TLS", - "ldap.server.users_dn": "LDAP_SEARCH_BASE", - "ldap.server.validate_cert": "LDAP_VALIDATE_CERT", - "memories.enable": "ENABLE_MEMORIES", - "models.base_models_cache": "ENABLE_BASE_MODELS_CACHE", - "models.default_metadata": "DEFAULT_MODEL_METADATA", - "models.default_params": "DEFAULT_MODEL_PARAMS", - "notes.enable": "ENABLE_NOTES", + 'audio.stt.allowed_extensions': 'AUDIO_STT_ALLOWED_EXTENSIONS', + 'audio.stt.azure.api_key': 'AUDIO_STT_AZURE_API_KEY', + 'audio.stt.azure.base_url': 'AUDIO_STT_AZURE_BASE_URL', + 'audio.stt.azure.locales': 'AUDIO_STT_AZURE_LOCALES', + 'audio.stt.azure.max_speakers': 'AUDIO_STT_AZURE_MAX_SPEAKERS', + 'audio.stt.azure.region': 'AUDIO_STT_AZURE_REGION', + 'audio.stt.deepgram.api_key': 'DEEPGRAM_API_KEY', + 'audio.stt.engine': 'AUDIO_STT_ENGINE', + 'audio.stt.mistral.api_base_url': 'AUDIO_STT_MISTRAL_API_BASE_URL', + 'audio.stt.mistral.api_key': 'AUDIO_STT_MISTRAL_API_KEY', + 'audio.stt.mistral.use_chat_completions': 'AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS', + 'audio.stt.model': 'AUDIO_STT_MODEL', + 'audio.stt.openai.api_base_url': 'AUDIO_STT_OPENAI_API_BASE_URL', + 'audio.stt.openai.api_key': 'AUDIO_STT_OPENAI_API_KEY', + 'audio.stt.supported_content_types': 'AUDIO_STT_SUPPORTED_CONTENT_TYPES', + 'audio.stt.whisper_model': 'WHISPER_MODEL', + 'audio.tts.api_key': 'AUDIO_TTS_API_KEY', + 'audio.tts.azure.speech_base_url': 'AUDIO_TTS_AZURE_SPEECH_BASE_URL', + 'audio.tts.azure.speech_output_format': 'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', + 'audio.tts.azure.speech_region': 'AUDIO_TTS_AZURE_SPEECH_REGION', + 'audio.tts.engine': 'AUDIO_TTS_ENGINE', + 'audio.tts.mistral.api_base_url': 'AUDIO_TTS_MISTRAL_API_BASE_URL', + 'audio.tts.mistral.api_key': 'AUDIO_TTS_MISTRAL_API_KEY', + 'audio.tts.model': 'AUDIO_TTS_MODEL', + 'audio.tts.openai.api_base_url': 'AUDIO_TTS_OPENAI_API_BASE_URL', + 'audio.tts.openai.api_key': 'AUDIO_TTS_OPENAI_API_KEY', + 'audio.tts.openai.params': 'AUDIO_TTS_OPENAI_PARAMS', + 'audio.tts.split_on': 'AUDIO_TTS_SPLIT_ON', + 'audio.tts.voice': 'AUDIO_TTS_VOICE', + 'auth.admin.email': 'ADMIN_EMAIL', + 'auth.admin.show': 'SHOW_ADMIN_DETAILS', + 'auth.api_key.allowed_endpoints': 'API_KEYS_ALLOWED_ENDPOINTS', + 'auth.api_key.endpoint_restrictions': 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', + 'auth.enable_api_keys': 'ENABLE_API_KEYS', + 'auth.jwt_expiry': 'JWT_EXPIRES_IN', + 'automations.enable': 'ENABLE_AUTOMATIONS', + 'automations.max_count': 'AUTOMATION_MAX_COUNT', + 'automations.min_interval': 'AUTOMATION_MIN_INTERVAL', + 'calendar.enable': 'ENABLE_CALENDAR', + 'channels.enable': 'ENABLE_CHANNELS', + 'code_execution.enable': 'ENABLE_CODE_EXECUTION', + 'code_execution.engine': 'CODE_EXECUTION_ENGINE', + 'code_execution.jupyter.auth': 'CODE_EXECUTION_JUPYTER_AUTH', + 'code_execution.jupyter.auth_password': 'CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', + 'code_execution.jupyter.auth_token': 'CODE_EXECUTION_JUPYTER_AUTH_TOKEN', + 'code_execution.jupyter.timeout': 'CODE_EXECUTION_JUPYTER_TIMEOUT', + 'code_execution.jupyter.url': 'CODE_EXECUTION_JUPYTER_URL', + 'code_interpreter.enable': 'ENABLE_CODE_INTERPRETER', + 'code_interpreter.engine': 'CODE_INTERPRETER_ENGINE', + 'code_interpreter.jupyter.auth': 'CODE_INTERPRETER_JUPYTER_AUTH', + 'code_interpreter.jupyter.auth_password': 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD', + 'code_interpreter.jupyter.auth_token': 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN', + 'code_interpreter.jupyter.timeout': 'CODE_INTERPRETER_JUPYTER_TIMEOUT', + 'code_interpreter.jupyter.url': 'CODE_INTERPRETER_JUPYTER_URL', + 'code_interpreter.prompt_template': 'CODE_INTERPRETER_PROMPT_TEMPLATE', + 'direct.enable': 'ENABLE_DIRECT_CONNECTIONS', + 'evaluation.arena.enable': 'ENABLE_EVALUATION_ARENA_MODELS', + 'evaluation.arena.models': 'EVALUATION_ARENA_MODELS', + 'file.image_compression_height': 'FILE_IMAGE_COMPRESSION_HEIGHT', + 'file.image_compression_width': 'FILE_IMAGE_COMPRESSION_WIDTH', + 'folders.enable': 'ENABLE_FOLDERS', + 'folders.max_file_count': 'FOLDER_MAX_FILE_COUNT', + 'google_drive.api_key': 'GOOGLE_DRIVE_API_KEY', + 'google_drive.client_id': 'GOOGLE_DRIVE_CLIENT_ID', + 'google_drive.enable': 'ENABLE_GOOGLE_DRIVE_INTEGRATION', + 'image_generation.automatic1111.api_auth': 'AUTOMATIC1111_API_AUTH', + 'image_generation.automatic1111.api_params': 'AUTOMATIC1111_PARAMS', + 'image_generation.automatic1111.base_url': 'AUTOMATIC1111_BASE_URL', + 'image_generation.comfyui.api_key': 'COMFYUI_API_KEY', + 'image_generation.comfyui.base_url': 'COMFYUI_BASE_URL', + 'image_generation.comfyui.nodes': 'COMFYUI_WORKFLOW_NODES', + 'image_generation.comfyui.workflow': 'COMFYUI_WORKFLOW', + 'image_generation.enable': 'ENABLE_IMAGE_GENERATION', + 'image_generation.engine': 'IMAGE_GENERATION_ENGINE', + 'image_generation.gemini.api_base_url': 'IMAGES_GEMINI_API_BASE_URL', + 'image_generation.gemini.api_key': 'IMAGES_GEMINI_API_KEY', + 'image_generation.gemini.endpoint_method': 'IMAGES_GEMINI_ENDPOINT_METHOD', + 'image_generation.model': 'IMAGE_GENERATION_MODEL', + 'image_generation.openai.api_base_url': 'IMAGES_OPENAI_API_BASE_URL', + 'image_generation.openai.api_key': 'IMAGES_OPENAI_API_KEY', + 'image_generation.openai.api_version': 'IMAGES_OPENAI_API_VERSION', + 'image_generation.openai.params': 'IMAGES_OPENAI_API_PARAMS', + 'image_generation.prompt.enable': 'ENABLE_IMAGE_PROMPT_GENERATION', + 'image_generation.size': 'IMAGE_SIZE', + 'image_generation.steps': 'IMAGE_STEPS', + 'images.edit.comfyui.api_key': 'IMAGES_EDIT_COMFYUI_API_KEY', + 'images.edit.comfyui.base_url': 'IMAGES_EDIT_COMFYUI_BASE_URL', + 'images.edit.comfyui.nodes': 'IMAGES_EDIT_COMFYUI_WORKFLOW_NODES', + 'images.edit.comfyui.workflow': 'IMAGES_EDIT_COMFYUI_WORKFLOW', + 'images.edit.enable': 'ENABLE_IMAGE_EDIT', + 'images.edit.engine': 'IMAGE_EDIT_ENGINE', + 'images.edit.gemini.api_base_url': 'IMAGES_EDIT_GEMINI_API_BASE_URL', + 'images.edit.gemini.api_key': 'IMAGES_EDIT_GEMINI_API_KEY', + 'images.edit.model': 'IMAGE_EDIT_MODEL', + 'images.edit.openai.api_base_url': 'IMAGES_EDIT_OPENAI_API_BASE_URL', + 'images.edit.openai.api_key': 'IMAGES_EDIT_OPENAI_API_KEY', + 'images.edit.openai.api_version': 'IMAGES_EDIT_OPENAI_API_VERSION', + 'images.edit.size': 'IMAGE_EDIT_SIZE', + 'ldap.enable': 'ENABLE_LDAP', + 'ldap.group.enable_creation': 'ENABLE_LDAP_GROUP_CREATION', + 'ldap.group.enable_management': 'ENABLE_LDAP_GROUP_MANAGEMENT', + 'ldap.server.app_dn': 'LDAP_APP_DN', + 'ldap.server.app_password': 'LDAP_APP_PASSWORD', + 'ldap.server.attribute_for_groups': 'LDAP_ATTRIBUTE_FOR_GROUPS', + 'ldap.server.attribute_for_mail': 'LDAP_ATTRIBUTE_FOR_MAIL', + 'ldap.server.attribute_for_username': 'LDAP_ATTRIBUTE_FOR_USERNAME', + 'ldap.server.ca_cert_file': 'LDAP_CA_CERT_FILE', + 'ldap.server.ciphers': 'LDAP_CIPHERS', + 'ldap.server.host': 'LDAP_SERVER_HOST', + 'ldap.server.label': 'LDAP_SERVER_LABEL', + 'ldap.server.port': 'LDAP_SERVER_PORT', + 'ldap.server.search_filter': 'LDAP_SEARCH_FILTER', + 'ldap.server.use_tls': 'LDAP_USE_TLS', + 'ldap.server.users_dn': 'LDAP_SEARCH_BASE', + 'ldap.server.validate_cert': 'LDAP_VALIDATE_CERT', + 'memories.enable': 'ENABLE_MEMORIES', + 'models.base_models_cache': 'ENABLE_BASE_MODELS_CACHE', + 'models.default_metadata': 'DEFAULT_MODEL_METADATA', + 'models.default_params': 'DEFAULT_MODEL_PARAMS', + 'notes.enable': 'ENABLE_NOTES', # OAuth — direct paths - "oauth.admin_roles": "OAUTH_ADMIN_ROLES", - "oauth.allowed_domains": "OAUTH_ALLOWED_DOMAINS", - "oauth.allowed_roles": "OAUTH_ALLOWED_ROLES", - "oauth.audience": "OAUTH_AUDIENCE", - "oauth.auto_redirect": "OAUTH_AUTO_REDIRECT", - "oauth.blocked_groups": "OAUTH_BLOCKED_GROUPS", - "oauth.client.timeout": "OAUTH_CLIENT_TIMEOUT", - "oauth.enable_group_creation": "ENABLE_OAUTH_GROUP_CREATION", - "oauth.enable_group_mapping": "ENABLE_OAUTH_GROUP_MANAGEMENT", - "oauth.enable_role_mapping": "ENABLE_OAUTH_ROLE_MANAGEMENT", - "oauth.enable_signup": "ENABLE_OAUTH_SIGNUP", - "oauth.group_default_share": "OAUTH_GROUP_DEFAULT_SHARE", - "oauth.merge_accounts_by_email": "OAUTH_MERGE_ACCOUNTS_BY_EMAIL", - "oauth.refresh_token_include_scope": "OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE", - "oauth.roles_claim": "OAUTH_ROLES_CLAIM", - "oauth.update_email_on_login": "OAUTH_UPDATE_EMAIL_ON_LOGIN", - "oauth.update_name_on_login": "OAUTH_UPDATE_NAME_ON_LOGIN", - "oauth.update_picture_on_login": "OAUTH_UPDATE_PICTURE_ON_LOGIN", + 'oauth.admin_roles': 'OAUTH_ADMIN_ROLES', + 'oauth.allowed_domains': 'OAUTH_ALLOWED_DOMAINS', + 'oauth.allowed_roles': 'OAUTH_ALLOWED_ROLES', + 'oauth.audience': 'OAUTH_AUDIENCE', + 'oauth.auto_redirect': 'OAUTH_AUTO_REDIRECT', + 'oauth.blocked_groups': 'OAUTH_BLOCKED_GROUPS', + 'oauth.client.timeout': 'OAUTH_CLIENT_TIMEOUT', + 'oauth.enable_group_creation': 'ENABLE_OAUTH_GROUP_CREATION', + 'oauth.enable_group_mapping': 'ENABLE_OAUTH_GROUP_MANAGEMENT', + 'oauth.enable_role_mapping': 'ENABLE_OAUTH_ROLE_MANAGEMENT', + 'oauth.enable_signup': 'ENABLE_OAUTH_SIGNUP', + 'oauth.group_default_share': 'OAUTH_GROUP_DEFAULT_SHARE', + 'oauth.merge_accounts_by_email': 'OAUTH_MERGE_ACCOUNTS_BY_EMAIL', + 'oauth.refresh_token_include_scope': 'OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', + 'oauth.roles_claim': 'OAUTH_ROLES_CLAIM', + 'oauth.update_email_on_login': 'OAUTH_UPDATE_EMAIL_ON_LOGIN', + 'oauth.update_name_on_login': 'OAUTH_UPDATE_NAME_ON_LOGIN', + 'oauth.update_picture_on_login': 'OAUTH_UPDATE_PICTURE_ON_LOGIN', # OAuth — generic provider paths - "oauth.client_id": "OAUTH_CLIENT_ID", - "oauth.client_secret": "OAUTH_CLIENT_SECRET", - "oauth.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD", - "oauth.email_claim": "OAUTH_EMAIL_CLAIM", - "oauth.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT", - "oauth.group_claim": "OAUTH_GROUP_CLAIM", - "oauth.picture_claim": "OAUTH_PICTURE_CLAIM", - "oauth.provider_name": "OAUTH_PROVIDER_NAME", - "oauth.provider_url": "OPENID_PROVIDER_URL", - "oauth.redirect_uri": "OPENID_REDIRECT_URI", - "oauth.scopes": "OAUTH_SCOPES", - "oauth.sub_claim": "OAUTH_SUB_CLAIM", - "oauth.timeout": "OAUTH_TIMEOUT", - "oauth.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD", - "oauth.username_claim": "OAUTH_USERNAME_CLAIM", + 'oauth.client_id': 'OAUTH_CLIENT_ID', + 'oauth.client_secret': 'OAUTH_CLIENT_SECRET', + 'oauth.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD', + 'oauth.email_claim': 'OAUTH_EMAIL_CLAIM', + 'oauth.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT', + 'oauth.group_claim': 'OAUTH_GROUP_CLAIM', + 'oauth.picture_claim': 'OAUTH_PICTURE_CLAIM', + 'oauth.provider_name': 'OAUTH_PROVIDER_NAME', + 'oauth.provider_url': 'OPENID_PROVIDER_URL', + 'oauth.redirect_uri': 'OPENID_REDIRECT_URI', + 'oauth.scopes': 'OAUTH_SCOPES', + 'oauth.sub_claim': 'OAUTH_SUB_CLAIM', + 'oauth.timeout': 'OAUTH_TIMEOUT', + 'oauth.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', + 'oauth.username_claim': 'OAUTH_USERNAME_CLAIM', # OAuth — OIDC nested paths (flattened) - "oauth.oidc.avatar_claim": "OAUTH_PICTURE_CLAIM", - "oauth.oidc.client_id": "OAUTH_CLIENT_ID", - "oauth.oidc.client_secret": "OAUTH_CLIENT_SECRET", - "oauth.oidc.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD", - "oauth.oidc.email_claim": "OAUTH_EMAIL_CLAIM", - "oauth.oidc.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT", - "oauth.oidc.group_claim": "OAUTH_GROUP_CLAIM", # renamed from OAUTH_GROUPS_CLAIM - "oauth.oidc.oauth_timeout": "OAUTH_TIMEOUT", - "oauth.oidc.provider_name": "OAUTH_PROVIDER_NAME", - "oauth.oidc.provider_url": "OPENID_PROVIDER_URL", - "oauth.oidc.redirect_uri": "OPENID_REDIRECT_URI", - "oauth.oidc.scopes": "OAUTH_SCOPES", - "oauth.oidc.sub_claim": "OAUTH_SUB_CLAIM", - "oauth.oidc.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD", - "oauth.oidc.username_claim": "OAUTH_USERNAME_CLAIM", + 'oauth.oidc.avatar_claim': 'OAUTH_PICTURE_CLAIM', + 'oauth.oidc.client_id': 'OAUTH_CLIENT_ID', + 'oauth.oidc.client_secret': 'OAUTH_CLIENT_SECRET', + 'oauth.oidc.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD', + 'oauth.oidc.email_claim': 'OAUTH_EMAIL_CLAIM', + 'oauth.oidc.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT', + 'oauth.oidc.group_claim': 'OAUTH_GROUP_CLAIM', # renamed from OAUTH_GROUPS_CLAIM + 'oauth.oidc.oauth_timeout': 'OAUTH_TIMEOUT', + 'oauth.oidc.provider_name': 'OAUTH_PROVIDER_NAME', + 'oauth.oidc.provider_url': 'OPENID_PROVIDER_URL', + 'oauth.oidc.redirect_uri': 'OPENID_REDIRECT_URI', + 'oauth.oidc.scopes': 'OAUTH_SCOPES', + 'oauth.oidc.sub_claim': 'OAUTH_SUB_CLAIM', + 'oauth.oidc.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', + 'oauth.oidc.username_claim': 'OAUTH_USERNAME_CLAIM', # OAuth — provider-specific - "oauth.feishu.client_id": "FEISHU_CLIENT_ID", - "oauth.feishu.client_secret": "FEISHU_CLIENT_SECRET", - "oauth.feishu.redirect_uri": "FEISHU_REDIRECT_URI", - "oauth.feishu.scope": "FEISHU_OAUTH_SCOPE", - "oauth.github.client_id": "GITHUB_CLIENT_ID", - "oauth.github.client_secret": "GITHUB_CLIENT_SECRET", - "oauth.github.redirect_uri": "GITHUB_CLIENT_REDIRECT_URI", - "oauth.github.scope": "GITHUB_CLIENT_SCOPE", - "oauth.google.client_id": "GOOGLE_CLIENT_ID", - "oauth.google.client_secret": "GOOGLE_CLIENT_SECRET", - "oauth.google.redirect_uri": "GOOGLE_REDIRECT_URI", - "oauth.google.scope": "GOOGLE_OAUTH_SCOPE", - "oauth.microsoft.client_id": "MICROSOFT_CLIENT_ID", - "oauth.microsoft.client_secret": "MICROSOFT_CLIENT_SECRET", - "oauth.microsoft.login_base_url": "MICROSOFT_CLIENT_LOGIN_BASE_URL", - "oauth.microsoft.picture_url": "MICROSOFT_CLIENT_PICTURE_URL", - "oauth.microsoft.redirect_uri": "MICROSOFT_REDIRECT_URI", - "oauth.microsoft.scope": "MICROSOFT_OAUTH_SCOPE", - "oauth.microsoft.tenant_id": "MICROSOFT_CLIENT_TENANT_ID", + 'oauth.feishu.client_id': 'FEISHU_CLIENT_ID', + 'oauth.feishu.client_secret': 'FEISHU_CLIENT_SECRET', + 'oauth.feishu.redirect_uri': 'FEISHU_REDIRECT_URI', + 'oauth.feishu.scope': 'FEISHU_OAUTH_SCOPE', + 'oauth.github.client_id': 'GITHUB_CLIENT_ID', + 'oauth.github.client_secret': 'GITHUB_CLIENT_SECRET', + 'oauth.github.redirect_uri': 'GITHUB_CLIENT_REDIRECT_URI', + 'oauth.github.scope': 'GITHUB_CLIENT_SCOPE', + 'oauth.google.client_id': 'GOOGLE_CLIENT_ID', + 'oauth.google.client_secret': 'GOOGLE_CLIENT_SECRET', + 'oauth.google.redirect_uri': 'GOOGLE_REDIRECT_URI', + 'oauth.google.scope': 'GOOGLE_OAUTH_SCOPE', + 'oauth.microsoft.client_id': 'MICROSOFT_CLIENT_ID', + 'oauth.microsoft.client_secret': 'MICROSOFT_CLIENT_SECRET', + 'oauth.microsoft.login_base_url': 'MICROSOFT_CLIENT_LOGIN_BASE_URL', + 'oauth.microsoft.picture_url': 'MICROSOFT_CLIENT_PICTURE_URL', + 'oauth.microsoft.redirect_uri': 'MICROSOFT_REDIRECT_URI', + 'oauth.microsoft.scope': 'MICROSOFT_OAUTH_SCOPE', + 'oauth.microsoft.tenant_id': 'MICROSOFT_CLIENT_TENANT_ID', # Ollama / OpenAI - "ollama.api_configs": "OLLAMA_API_CONFIGS", - "ollama.base_urls": "OLLAMA_BASE_URLS", - "ollama.enable": "ENABLE_OLLAMA_API", - "onedrive.enable": "ENABLE_ONEDRIVE_INTEGRATION", - "onedrive.sharepoint_tenant_id": "ONEDRIVE_SHAREPOINT_TENANT_ID", - "onedrive.sharepoint_url": "ONEDRIVE_SHAREPOINT_URL", - "openai.api_base_urls": "OPENAI_API_BASE_URLS", - "openai.api_configs": "OPENAI_API_CONFIGS", - "openai.api_keys": "OPENAI_API_KEYS", - "openai.enable": "ENABLE_OPENAI_API", + 'ollama.api_configs': 'OLLAMA_API_CONFIGS', + 'ollama.base_urls': 'OLLAMA_BASE_URLS', + 'ollama.enable': 'ENABLE_OLLAMA_API', + 'onedrive.enable': 'ENABLE_ONEDRIVE_INTEGRATION', + 'onedrive.sharepoint_tenant_id': 'ONEDRIVE_SHAREPOINT_TENANT_ID', + 'onedrive.sharepoint_url': 'ONEDRIVE_SHAREPOINT_URL', + 'openai.api_base_urls': 'OPENAI_API_BASE_URLS', + 'openai.api_configs': 'OPENAI_API_CONFIGS', + 'openai.api_keys': 'OPENAI_API_KEYS', + 'openai.enable': 'ENABLE_OPENAI_API', # RAG - "rag.content_extraction_engine": "CONTENT_EXTRACTION_ENGINE", - "rag.datalab_marker_use_llm": "DATALAB_MARKER_USE_LLM", - "rag.mistral_ocr_api_base_url": "MISTRAL_OCR_API_BASE_URL", - "rag.azure_openai.api_key": "RAG_AZURE_OPENAI_API_KEY", - "rag.azure_openai.api_version": "RAG_AZURE_OPENAI_API_VERSION", - "rag.azure_openai.base_url": "RAG_AZURE_OPENAI_BASE_URL", - "rag.bypass_embedding_and_retrieval": "BYPASS_EMBEDDING_AND_RETRIEVAL", - "rag.chunk_min_size_target": "CHUNK_MIN_SIZE_TARGET", - "rag.chunk_overlap": "CHUNK_OVERLAP", - "rag.chunk_size": "CHUNK_SIZE", - "rag.datalab_marker_additional_config": "DATALAB_MARKER_ADDITIONAL_CONFIG", - "rag.datalab_marker_api_base_url": "DATALAB_MARKER_API_BASE_URL", - "rag.datalab_marker_api_key": "DATALAB_MARKER_API_KEY", - "rag.datalab_marker_disable_image_extraction": "DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION", - "rag.datalab_marker_force_ocr": "DATALAB_MARKER_FORCE_OCR", - "rag.datalab_marker_format_lines": "DATALAB_MARKER_FORMAT_LINES", - "rag.datalab_marker_output_format": "DATALAB_MARKER_OUTPUT_FORMAT", - "rag.datalab_marker_paginate": "DATALAB_MARKER_PAGINATE", - "rag.datalab_marker_skip_cache": "DATALAB_MARKER_SKIP_CACHE", - "rag.datalab_marker_strip_existing_ocr": "DATALAB_MARKER_STRIP_EXISTING_OCR", - "rag.docling_api_key": "DOCLING_API_KEY", - "rag.docling_params": "DOCLING_PARAMS", - "rag.docling_server_url": "DOCLING_SERVER_URL", - "rag.document_intelligence_endpoint": "DOCUMENT_INTELLIGENCE_ENDPOINT", - "rag.document_intelligence_key": "DOCUMENT_INTELLIGENCE_KEY", - "rag.document_intelligence_model": "DOCUMENT_INTELLIGENCE_MODEL", - "rag.embedding_batch_size": "RAG_EMBEDDING_BATCH_SIZE", - "rag.embedding_concurrent_requests": "RAG_EMBEDDING_CONCURRENT_REQUESTS", - "rag.embedding_engine": "RAG_EMBEDDING_ENGINE", - "rag.embedding_model": "RAG_EMBEDDING_MODEL", - "rag.enable_async_embedding": "ENABLE_ASYNC_EMBEDDING", - "rag.enable_hybrid_search": "ENABLE_RAG_HYBRID_SEARCH", - "rag.enable_hybrid_search_enriched_texts": "ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS", - "rag.enable_markdown_header_text_splitter": "ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER", - "rag.external_document_loader_api_key": "EXTERNAL_DOCUMENT_LOADER_API_KEY", - "rag.external_document_loader_url": "EXTERNAL_DOCUMENT_LOADER_URL", - "rag.external_reranker_api_key": "RAG_EXTERNAL_RERANKER_API_KEY", - "rag.external_reranker_timeout": "RAG_EXTERNAL_RERANKER_TIMEOUT", - "rag.external_reranker_url": "RAG_EXTERNAL_RERANKER_URL", - "rag.file.allowed_extensions": "RAG_ALLOWED_FILE_EXTENSIONS", - "rag.file.max_count": "RAG_FILE_MAX_COUNT", - "rag.file.max_size": "RAG_FILE_MAX_SIZE", - "rag.full_context": "RAG_FULL_CONTEXT", - "rag.hybrid_bm25_weight": "RAG_HYBRID_BM25_WEIGHT", - "rag.mineru_api_key": "MINERU_API_KEY", - "rag.mineru_api_mode": "MINERU_API_MODE", - "rag.mineru_api_timeout": "MINERU_API_TIMEOUT", - "rag.mineru_api_url": "MINERU_API_URL", - "rag.mineru_file_extensions": "MINERU_FILE_EXTENSIONS", - "rag.mineru_params": "MINERU_PARAMS", - "rag.mistral_ocr_api_key": "MISTRAL_OCR_API_KEY", - "rag.ollama.key": "RAG_OLLAMA_API_KEY", - "rag.ollama.url": "RAG_OLLAMA_BASE_URL", - "rag.openai_api_base_url": "RAG_OPENAI_API_BASE_URL", - "rag.openai_api_key": "RAG_OPENAI_API_KEY", - "rag.paddleocr_vl_base_url": "PADDLEOCR_VL_BASE_URL", - "rag.paddleocr_vl_token": "PADDLEOCR_VL_TOKEN", - "rag.pdf_extract_images": "PDF_EXTRACT_IMAGES", - "rag.pdf_loader_mode": "PDF_LOADER_MODE", - "rag.relevance_threshold": "RAG_RELEVANCE_THRESHOLD", - "rag.reranking_batch_size": "RAG_RERANKING_BATCH_SIZE", - "rag.reranking_engine": "RAG_RERANKING_ENGINE", - "rag.reranking_model": "RAG_RERANKING_MODEL", - "rag.template": "RAG_TEMPLATE", - "rag.text_splitter": "RAG_TEXT_SPLITTER", - "rag.tika_server_url": "TIKA_SERVER_URL", - "rag.tiktoken_encoding_name": "TIKTOKEN_ENCODING_NAME", - "rag.top_k": "RAG_TOP_K", - "rag.top_k_reranker": "RAG_TOP_K_RERANKER", + 'rag.content_extraction_engine': 'CONTENT_EXTRACTION_ENGINE', + 'rag.datalab_marker_use_llm': 'DATALAB_MARKER_USE_LLM', + 'rag.mistral_ocr_api_base_url': 'MISTRAL_OCR_API_BASE_URL', + 'rag.azure_openai.api_key': 'RAG_AZURE_OPENAI_API_KEY', + 'rag.azure_openai.api_version': 'RAG_AZURE_OPENAI_API_VERSION', + 'rag.azure_openai.base_url': 'RAG_AZURE_OPENAI_BASE_URL', + 'rag.bypass_embedding_and_retrieval': 'BYPASS_EMBEDDING_AND_RETRIEVAL', + 'rag.chunk_min_size_target': 'CHUNK_MIN_SIZE_TARGET', + 'rag.chunk_overlap': 'CHUNK_OVERLAP', + 'rag.chunk_size': 'CHUNK_SIZE', + 'rag.datalab_marker_additional_config': 'DATALAB_MARKER_ADDITIONAL_CONFIG', + 'rag.datalab_marker_api_base_url': 'DATALAB_MARKER_API_BASE_URL', + 'rag.datalab_marker_api_key': 'DATALAB_MARKER_API_KEY', + 'rag.datalab_marker_disable_image_extraction': 'DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', + 'rag.datalab_marker_force_ocr': 'DATALAB_MARKER_FORCE_OCR', + 'rag.datalab_marker_format_lines': 'DATALAB_MARKER_FORMAT_LINES', + 'rag.datalab_marker_output_format': 'DATALAB_MARKER_OUTPUT_FORMAT', + 'rag.datalab_marker_paginate': 'DATALAB_MARKER_PAGINATE', + 'rag.datalab_marker_skip_cache': 'DATALAB_MARKER_SKIP_CACHE', + 'rag.datalab_marker_strip_existing_ocr': 'DATALAB_MARKER_STRIP_EXISTING_OCR', + 'rag.docling_api_key': 'DOCLING_API_KEY', + 'rag.docling_params': 'DOCLING_PARAMS', + 'rag.docling_server_url': 'DOCLING_SERVER_URL', + 'rag.document_intelligence_endpoint': 'DOCUMENT_INTELLIGENCE_ENDPOINT', + 'rag.document_intelligence_key': 'DOCUMENT_INTELLIGENCE_KEY', + 'rag.document_intelligence_model': 'DOCUMENT_INTELLIGENCE_MODEL', + 'rag.embedding_batch_size': 'RAG_EMBEDDING_BATCH_SIZE', + 'rag.embedding_concurrent_requests': 'RAG_EMBEDDING_CONCURRENT_REQUESTS', + 'rag.embedding_engine': 'RAG_EMBEDDING_ENGINE', + 'rag.embedding_model': 'RAG_EMBEDDING_MODEL', + 'rag.enable_async_embedding': 'ENABLE_ASYNC_EMBEDDING', + 'rag.enable_hybrid_search': 'ENABLE_RAG_HYBRID_SEARCH', + 'rag.enable_hybrid_search_enriched_texts': 'ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', + 'rag.enable_markdown_header_text_splitter': 'ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER', + 'rag.external_document_loader_api_key': 'EXTERNAL_DOCUMENT_LOADER_API_KEY', + 'rag.external_document_loader_url': 'EXTERNAL_DOCUMENT_LOADER_URL', + 'rag.external_reranker_api_key': 'RAG_EXTERNAL_RERANKER_API_KEY', + 'rag.external_reranker_timeout': 'RAG_EXTERNAL_RERANKER_TIMEOUT', + 'rag.external_reranker_url': 'RAG_EXTERNAL_RERANKER_URL', + 'rag.file.allowed_extensions': 'RAG_ALLOWED_FILE_EXTENSIONS', + 'rag.file.max_count': 'RAG_FILE_MAX_COUNT', + 'rag.file.max_size': 'RAG_FILE_MAX_SIZE', + 'rag.full_context': 'RAG_FULL_CONTEXT', + 'rag.hybrid_bm25_weight': 'RAG_HYBRID_BM25_WEIGHT', + 'rag.mineru_api_key': 'MINERU_API_KEY', + 'rag.mineru_api_mode': 'MINERU_API_MODE', + 'rag.mineru_api_timeout': 'MINERU_API_TIMEOUT', + 'rag.mineru_api_url': 'MINERU_API_URL', + 'rag.mineru_file_extensions': 'MINERU_FILE_EXTENSIONS', + 'rag.mineru_params': 'MINERU_PARAMS', + 'rag.mistral_ocr_api_key': 'MISTRAL_OCR_API_KEY', + 'rag.ollama.key': 'RAG_OLLAMA_API_KEY', + 'rag.ollama.url': 'RAG_OLLAMA_BASE_URL', + 'rag.openai_api_base_url': 'RAG_OPENAI_API_BASE_URL', + 'rag.openai_api_key': 'RAG_OPENAI_API_KEY', + 'rag.paddleocr_vl_base_url': 'PADDLEOCR_VL_BASE_URL', + 'rag.paddleocr_vl_token': 'PADDLEOCR_VL_TOKEN', + 'rag.pdf_extract_images': 'PDF_EXTRACT_IMAGES', + 'rag.pdf_loader_mode': 'PDF_LOADER_MODE', + 'rag.relevance_threshold': 'RAG_RELEVANCE_THRESHOLD', + 'rag.reranking_batch_size': 'RAG_RERANKING_BATCH_SIZE', + 'rag.reranking_engine': 'RAG_RERANKING_ENGINE', + 'rag.reranking_model': 'RAG_RERANKING_MODEL', + 'rag.template': 'RAG_TEMPLATE', + 'rag.text_splitter': 'RAG_TEXT_SPLITTER', + 'rag.tika_server_url': 'TIKA_SERVER_URL', + 'rag.tiktoken_encoding_name': 'TIKTOKEN_ENCODING_NAME', + 'rag.top_k': 'RAG_TOP_K', + 'rag.top_k_reranker': 'RAG_TOP_K_RERANKER', # RAG — Web - "rag.web.fetch.max_content_length": "WEB_FETCH_MAX_CONTENT_LENGTH", - "rag.web.loader.concurrent_requests": "WEB_LOADER_CONCURRENT_REQUESTS", - "rag.web.loader.engine": "WEB_LOADER_ENGINE", - "rag.web.loader.external_web_loader_api_key": "EXTERNAL_WEB_LOADER_API_KEY", - "rag.web.loader.external_web_loader_url": "EXTERNAL_WEB_LOADER_URL", - "rag.web.loader.firecrawl_api_key": "FIRECRAWL_API_KEY", - "rag.web.loader.firecrawl_api_url": "FIRECRAWL_API_BASE_URL", - "rag.web.loader.firecrawl_timeout": "FIRECRAWL_TIMEOUT", - "rag.web.loader.playwright_timeout": "PLAYWRIGHT_TIMEOUT", - "rag.web.loader.playwright_ws_url": "PLAYWRIGHT_WS_URL", - "rag.web.loader.ssl_verification": "ENABLE_WEB_LOADER_SSL_VERIFICATION", - "rag.web.loader.timeout": "WEB_LOADER_TIMEOUT", - "rag.web.search.azure_ai_search_api_key": "AZURE_AI_SEARCH_API_KEY", - "rag.web.search.azure_ai_search_endpoint": "AZURE_AI_SEARCH_ENDPOINT", - "rag.web.search.azure_ai_search_index_name": "AZURE_AI_SEARCH_INDEX_NAME", - "rag.web.search.bing_search_v7_endpoint": "BING_SEARCH_V7_ENDPOINT", - "rag.web.search.bing_search_v7_subscription_key": "BING_SEARCH_V7_SUBSCRIPTION_KEY", - "rag.web.search.bocha_search_api_key": "BOCHA_SEARCH_API_KEY", - "rag.web.search.brave_search_api_key": "BRAVE_SEARCH_API_KEY", - "rag.web.search.brave_search_context_tokens": "BRAVE_SEARCH_CONTEXT_TOKENS", - "rag.web.search.bypass_embedding_and_retrieval": "BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL", - "rag.web.search.bypass_web_loader": "BYPASS_WEB_SEARCH_WEB_LOADER", - "rag.web.search.concurrent_requests": "WEB_SEARCH_CONCURRENT_REQUESTS", - "rag.web.search.ddgs_backend": "DDGS_BACKEND", - "rag.web.search.domain.filter_list": "WEB_SEARCH_DOMAIN_FILTER_LIST", - "rag.web.search.enable": "ENABLE_WEB_SEARCH", - "rag.web.search.engine": "WEB_SEARCH_ENGINE", - "rag.web.search.exa_api_key": "EXA_API_KEY", - "rag.web.search.external_web_search_api_key": "EXTERNAL_WEB_SEARCH_API_KEY", - "rag.web.search.external_web_search_url": "EXTERNAL_WEB_SEARCH_URL", - "rag.web.search.google_pse_api_key": "GOOGLE_PSE_API_KEY", - "rag.web.search.google_pse_engine_id": "GOOGLE_PSE_ENGINE_ID", - "rag.web.search.jina_api_base_url": "JINA_API_BASE_URL", - "rag.web.search.jina_api_key": "JINA_API_KEY", - "rag.web.search.kagi_search_api_key": "KAGI_SEARCH_API_KEY", - "rag.web.search.linkup_api_key": "LINKUP_API_KEY", - "rag.web.search.linkup_search_params": "LINKUP_SEARCH_PARAMS", - "rag.web.search.mojeek_search_api_key": "MOJEEK_SEARCH_API_KEY", - "rag.web.search.ollama_cloud_api_key": "OLLAMA_CLOUD_WEB_SEARCH_API_KEY", - "rag.web.search.perplexity_api_key": "PERPLEXITY_API_KEY", - "rag.web.search.perplexity_model": "PERPLEXITY_MODEL", - "rag.web.search.perplexity_search_api_url": "PERPLEXITY_SEARCH_API_URL", - "rag.web.search.perplexity_search_context_usage": "PERPLEXITY_SEARCH_CONTEXT_USAGE", - "rag.web.search.result_count": "WEB_SEARCH_RESULT_COUNT", - "rag.web.search.searchapi_api_key": "SEARCHAPI_API_KEY", - "rag.web.search.searchapi_engine": "SEARCHAPI_ENGINE", - "rag.web.search.searxng_language": "SEARXNG_LANGUAGE", - "rag.web.search.searxng_query_url": "SEARXNG_QUERY_URL", - "rag.web.search.serpapi_api_key": "SERPAPI_API_KEY", - "rag.web.search.serpapi_engine": "SERPAPI_ENGINE", - "rag.web.search.serper_api_key": "SERPER_API_KEY", - "rag.web.search.serply_api_key": "SERPLY_API_KEY", - "rag.web.search.serpstack_api_key": "SERPSTACK_API_KEY", - "rag.web.search.serpstack_https": "SERPSTACK_HTTPS", - "rag.web.search.sougou_api_sid": "SOUGOU_API_SID", - "rag.web.search.sougou_api_sk": "SOUGOU_API_SK", - "rag.web.search.tavily_api_key": "TAVILY_API_KEY", - "rag.web.search.tavily_extract_depth": "TAVILY_EXTRACT_DEPTH", - "rag.web.search.trust_env": "WEB_SEARCH_TRUST_ENV", - "rag.web.search.yacy_password": "YACY_PASSWORD", - "rag.web.search.yacy_query_url": "YACY_QUERY_URL", - "rag.web.search.yacy_username": "YACY_USERNAME", - "rag.web.search.yandex_web_search_api_key": "YANDEX_WEB_SEARCH_API_KEY", - "rag.web.search.yandex_web_search_config": "YANDEX_WEB_SEARCH_CONFIG", - "rag.web.search.yandex_web_search_url": "YANDEX_WEB_SEARCH_URL", - "rag.web.search.youcom_api_key": "YOUCOM_API_KEY", - "rag.youtube_loader_language": "YOUTUBE_LOADER_LANGUAGE", - "rag.youtube_loader_proxy_url": "YOUTUBE_LOADER_PROXY_URL", + 'rag.web.fetch.max_content_length': 'WEB_FETCH_MAX_CONTENT_LENGTH', + 'rag.web.loader.concurrent_requests': 'WEB_LOADER_CONCURRENT_REQUESTS', + 'rag.web.loader.engine': 'WEB_LOADER_ENGINE', + 'rag.web.loader.external_web_loader_api_key': 'EXTERNAL_WEB_LOADER_API_KEY', + 'rag.web.loader.external_web_loader_url': 'EXTERNAL_WEB_LOADER_URL', + 'rag.web.loader.firecrawl_api_key': 'FIRECRAWL_API_KEY', + 'rag.web.loader.firecrawl_api_url': 'FIRECRAWL_API_BASE_URL', + 'rag.web.loader.firecrawl_timeout': 'FIRECRAWL_TIMEOUT', + 'rag.web.loader.playwright_timeout': 'PLAYWRIGHT_TIMEOUT', + 'rag.web.loader.playwright_ws_url': 'PLAYWRIGHT_WS_URL', + 'rag.web.loader.ssl_verification': 'ENABLE_WEB_LOADER_SSL_VERIFICATION', + 'rag.web.loader.timeout': 'WEB_LOADER_TIMEOUT', + 'rag.web.search.azure_ai_search_api_key': 'AZURE_AI_SEARCH_API_KEY', + 'rag.web.search.azure_ai_search_endpoint': 'AZURE_AI_SEARCH_ENDPOINT', + 'rag.web.search.azure_ai_search_index_name': 'AZURE_AI_SEARCH_INDEX_NAME', + 'rag.web.search.bing_search_v7_endpoint': 'BING_SEARCH_V7_ENDPOINT', + 'rag.web.search.bing_search_v7_subscription_key': 'BING_SEARCH_V7_SUBSCRIPTION_KEY', + 'rag.web.search.bocha_search_api_key': 'BOCHA_SEARCH_API_KEY', + 'rag.web.search.brave_search_api_key': 'BRAVE_SEARCH_API_KEY', + 'rag.web.search.brave_search_context_tokens': 'BRAVE_SEARCH_CONTEXT_TOKENS', + 'rag.web.search.bypass_embedding_and_retrieval': 'BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', + 'rag.web.search.bypass_web_loader': 'BYPASS_WEB_SEARCH_WEB_LOADER', + 'rag.web.search.concurrent_requests': 'WEB_SEARCH_CONCURRENT_REQUESTS', + 'rag.web.search.ddgs_backend': 'DDGS_BACKEND', + 'rag.web.search.domain.filter_list': 'WEB_SEARCH_DOMAIN_FILTER_LIST', + 'rag.web.search.enable': 'ENABLE_WEB_SEARCH', + 'rag.web.search.engine': 'WEB_SEARCH_ENGINE', + 'rag.web.search.exa_api_key': 'EXA_API_KEY', + 'rag.web.search.external_web_search_api_key': 'EXTERNAL_WEB_SEARCH_API_KEY', + 'rag.web.search.external_web_search_url': 'EXTERNAL_WEB_SEARCH_URL', + 'rag.web.search.google_pse_api_key': 'GOOGLE_PSE_API_KEY', + 'rag.web.search.google_pse_engine_id': 'GOOGLE_PSE_ENGINE_ID', + 'rag.web.search.jina_api_base_url': 'JINA_API_BASE_URL', + 'rag.web.search.jina_api_key': 'JINA_API_KEY', + 'rag.web.search.kagi_search_api_key': 'KAGI_SEARCH_API_KEY', + 'rag.web.search.linkup_api_key': 'LINKUP_API_KEY', + 'rag.web.search.linkup_search_params': 'LINKUP_SEARCH_PARAMS', + 'rag.web.search.mojeek_search_api_key': 'MOJEEK_SEARCH_API_KEY', + 'rag.web.search.ollama_cloud_api_key': 'OLLAMA_CLOUD_WEB_SEARCH_API_KEY', + 'rag.web.search.perplexity_api_key': 'PERPLEXITY_API_KEY', + 'rag.web.search.perplexity_model': 'PERPLEXITY_MODEL', + 'rag.web.search.perplexity_search_api_url': 'PERPLEXITY_SEARCH_API_URL', + 'rag.web.search.perplexity_search_context_usage': 'PERPLEXITY_SEARCH_CONTEXT_USAGE', + 'rag.web.search.result_count': 'WEB_SEARCH_RESULT_COUNT', + 'rag.web.search.searchapi_api_key': 'SEARCHAPI_API_KEY', + 'rag.web.search.searchapi_engine': 'SEARCHAPI_ENGINE', + 'rag.web.search.searxng_language': 'SEARXNG_LANGUAGE', + 'rag.web.search.searxng_query_url': 'SEARXNG_QUERY_URL', + 'rag.web.search.serpapi_api_key': 'SERPAPI_API_KEY', + 'rag.web.search.serpapi_engine': 'SERPAPI_ENGINE', + 'rag.web.search.serper_api_key': 'SERPER_API_KEY', + 'rag.web.search.serply_api_key': 'SERPLY_API_KEY', + 'rag.web.search.serpstack_api_key': 'SERPSTACK_API_KEY', + 'rag.web.search.serpstack_https': 'SERPSTACK_HTTPS', + 'rag.web.search.sougou_api_sid': 'SOUGOU_API_SID', + 'rag.web.search.sougou_api_sk': 'SOUGOU_API_SK', + 'rag.web.search.tavily_api_key': 'TAVILY_API_KEY', + 'rag.web.search.tavily_extract_depth': 'TAVILY_EXTRACT_DEPTH', + 'rag.web.search.trust_env': 'WEB_SEARCH_TRUST_ENV', + 'rag.web.search.yacy_password': 'YACY_PASSWORD', + 'rag.web.search.yacy_query_url': 'YACY_QUERY_URL', + 'rag.web.search.yacy_username': 'YACY_USERNAME', + 'rag.web.search.yandex_web_search_api_key': 'YANDEX_WEB_SEARCH_API_KEY', + 'rag.web.search.yandex_web_search_config': 'YANDEX_WEB_SEARCH_CONFIG', + 'rag.web.search.yandex_web_search_url': 'YANDEX_WEB_SEARCH_URL', + 'rag.web.search.youcom_api_key': 'YOUCOM_API_KEY', + 'rag.youtube_loader_language': 'YOUTUBE_LOADER_LANGUAGE', + 'rag.youtube_loader_proxy_url': 'YOUTUBE_LOADER_PROXY_URL', # Tasks - "task.autocomplete.enable": "ENABLE_AUTOCOMPLETE_GENERATION", - "task.autocomplete.input_max_length": "AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH", - "task.autocomplete.prompt_template": "AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE", - "task.follow_up.enable": "ENABLE_FOLLOW_UP_GENERATION", - "task.follow_up.prompt_template": "FOLLOW_UP_GENERATION_PROMPT_TEMPLATE", - "task.image.prompt_template": "IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE", - "task.model.default": "TASK_MODEL", - "task.model.external": "TASK_MODEL_EXTERNAL", - "task.query.prompt_template": "QUERY_GENERATION_PROMPT_TEMPLATE", - "task.query.retrieval.enable": "ENABLE_RETRIEVAL_QUERY_GENERATION", - "task.query.search.enable": "ENABLE_SEARCH_QUERY_GENERATION", - "task.tags.enable": "ENABLE_TAGS_GENERATION", - "task.tags.prompt_template": "TAGS_GENERATION_PROMPT_TEMPLATE", - "task.title.enable": "ENABLE_TITLE_GENERATION", - "task.title.prompt_template": "TITLE_GENERATION_PROMPT_TEMPLATE", - "task.tools.prompt_template": "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE", - "task.voice.prompt.enable": "ENABLE_VOICE_MODE_PROMPT", - "task.voice.prompt_template": "VOICE_MODE_PROMPT_TEMPLATE", + 'task.autocomplete.enable': 'ENABLE_AUTOCOMPLETE_GENERATION', + 'task.autocomplete.input_max_length': 'AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', + 'task.autocomplete.prompt_template': 'AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', + 'task.follow_up.enable': 'ENABLE_FOLLOW_UP_GENERATION', + 'task.follow_up.prompt_template': 'FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', + 'task.image.prompt_template': 'IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', + 'task.model.default': 'TASK_MODEL', + 'task.model.external': 'TASK_MODEL_EXTERNAL', + 'task.query.prompt_template': 'QUERY_GENERATION_PROMPT_TEMPLATE', + 'task.query.retrieval.enable': 'ENABLE_RETRIEVAL_QUERY_GENERATION', + 'task.query.search.enable': 'ENABLE_SEARCH_QUERY_GENERATION', + 'task.tags.enable': 'ENABLE_TAGS_GENERATION', + 'task.tags.prompt_template': 'TAGS_GENERATION_PROMPT_TEMPLATE', + 'task.title.enable': 'ENABLE_TITLE_GENERATION', + 'task.title.prompt_template': 'TITLE_GENERATION_PROMPT_TEMPLATE', + 'task.tools.prompt_template': 'TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', + 'task.voice.prompt.enable': 'ENABLE_VOICE_MODE_PROMPT', + 'task.voice.prompt_template': 'VOICE_MODE_PROMPT_TEMPLATE', # Misc - "terminal_server.connections": "TERMINAL_SERVER_CONNECTIONS", - "tool_server.connections": "TOOL_SERVER_CONNECTIONS", - "ui.banners": "WEBUI_BANNERS", - "ui.default_group_id": "DEFAULT_GROUP_ID", - "ui.default_locale": "DEFAULT_LOCALE", - "ui.default_models": "DEFAULT_MODELS", - "ui.default_pinned_models": "DEFAULT_PINNED_MODELS", - "ui.default_user_role": "DEFAULT_USER_ROLE", - "ui.enable_community_sharing": "ENABLE_COMMUNITY_SHARING", - "ui.enable_login_form": "ENABLE_LOGIN_FORM", - "ui.enable_message_rating": "ENABLE_MESSAGE_RATING", - "ui.enable_password_change_form": "ENABLE_PASSWORD_CHANGE_FORM", - "ui.enable_signup": "ENABLE_SIGNUP", - "ui.enable_user_webhooks": "ENABLE_USER_WEBHOOKS", - "ui.model_order_list": "MODEL_ORDER_LIST", - "ui.pending_user_overlay_content": "PENDING_USER_OVERLAY_CONTENT", - "ui.pending_user_overlay_title": "PENDING_USER_OVERLAY_TITLE", - "ui.prompt_suggestions": "DEFAULT_PROMPT_SUGGESTIONS", - "ui.watermark": "RESPONSE_WATERMARK", - "user.permissions": "USER_PERMISSIONS", - "users.enable_status": "ENABLE_USER_STATUS", - "webhook_url": "WEBHOOK_URL", - "webui.url": "WEBUI_URL", + 'terminal_server.connections': 'TERMINAL_SERVER_CONNECTIONS', + 'tool_server.connections': 'TOOL_SERVER_CONNECTIONS', + 'ui.banners': 'WEBUI_BANNERS', + 'ui.default_group_id': 'DEFAULT_GROUP_ID', + 'ui.default_locale': 'DEFAULT_LOCALE', + 'ui.default_models': 'DEFAULT_MODELS', + 'ui.default_pinned_models': 'DEFAULT_PINNED_MODELS', + 'ui.default_user_role': 'DEFAULT_USER_ROLE', + 'ui.enable_community_sharing': 'ENABLE_COMMUNITY_SHARING', + 'ui.enable_login_form': 'ENABLE_LOGIN_FORM', + 'ui.enable_message_rating': 'ENABLE_MESSAGE_RATING', + 'ui.enable_password_change_form': 'ENABLE_PASSWORD_CHANGE_FORM', + 'ui.enable_signup': 'ENABLE_SIGNUP', + 'ui.enable_user_webhooks': 'ENABLE_USER_WEBHOOKS', + 'ui.model_order_list': 'MODEL_ORDER_LIST', + 'ui.pending_user_overlay_content': 'PENDING_USER_OVERLAY_CONTENT', + 'ui.pending_user_overlay_title': 'PENDING_USER_OVERLAY_TITLE', + 'ui.prompt_suggestions': 'DEFAULT_PROMPT_SUGGESTIONS', + 'ui.watermark': 'RESPONSE_WATERMARK', + 'user.permissions': 'USER_PERMISSIONS', + 'users.enable_status': 'ENABLE_USER_STATUS', + 'webhook_url': 'WEBHOOK_URL', + 'webui.url': 'WEBUI_URL', } STORAGE_KEY_REWRITES = { - "oauth.refresh_token_include_scope": "oauth.refresh_token.include_scope", - - "rag.openai_api_base_url": "rag.openai.api_base_url", - "rag.openai_api_key": "rag.openai.api_key", - "rag.ollama.url": "rag.ollama.base_url", - "rag.ollama.key": "rag.ollama.api_key", - "oauth.oidc.avatar_claim": "oauth.picture_claim", - "oauth.oidc.client_id": "oauth.client_id", - "oauth.oidc.client_secret": "oauth.client_secret", - "oauth.oidc.code_challenge_method": "oauth.code_challenge_method", - "oauth.oidc.email_claim": "oauth.email_claim", - "oauth.oidc.end_session_endpoint": "oauth.end_session_endpoint", - "oauth.oidc.group_claim": "oauth.group_claim", - "oauth.oidc.oauth_timeout": "oauth.timeout", - "oauth.oidc.provider_name": "oauth.provider_name", - "oauth.oidc.provider_url": "oauth.provider_url", - "oauth.oidc.redirect_uri": "oauth.redirect_uri", - "oauth.oidc.scopes": "oauth.scopes", - "oauth.oidc.sub_claim": "oauth.sub_claim", - "oauth.oidc.token_endpoint_auth_method": "oauth.token_endpoint_auth_method", - "oauth.oidc.username_claim": "oauth.username_claim", + 'oauth.refresh_token_include_scope': 'oauth.refresh_token.include_scope', + 'rag.openai_api_base_url': 'rag.openai.api_base_url', + 'rag.openai_api_key': 'rag.openai.api_key', + 'rag.ollama.url': 'rag.ollama.base_url', + 'rag.ollama.key': 'rag.ollama.api_key', + 'oauth.oidc.avatar_claim': 'oauth.picture_claim', + 'oauth.oidc.client_id': 'oauth.client_id', + 'oauth.oidc.client_secret': 'oauth.client_secret', + 'oauth.oidc.code_challenge_method': 'oauth.code_challenge_method', + 'oauth.oidc.email_claim': 'oauth.email_claim', + 'oauth.oidc.end_session_endpoint': 'oauth.end_session_endpoint', + 'oauth.oidc.group_claim': 'oauth.group_claim', + 'oauth.oidc.oauth_timeout': 'oauth.timeout', + 'oauth.oidc.provider_name': 'oauth.provider_name', + 'oauth.oidc.provider_url': 'oauth.provider_url', + 'oauth.oidc.redirect_uri': 'oauth.redirect_uri', + 'oauth.oidc.scopes': 'oauth.scopes', + 'oauth.oidc.sub_claim': 'oauth.sub_claim', + 'oauth.oidc.token_endpoint_auth_method': 'oauth.token_endpoint_auth_method', + 'oauth.oidc.username_claim': 'oauth.username_claim', } LEGACY_KEY_TO_STORAGE_KEY = { - legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path) - for blob_path, legacy_key in BLOB_PATH_TO_KEY.items() + legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path) for blob_path, legacy_key in BLOB_PATH_TO_KEY.items() } @@ -466,7 +464,9 @@ def upgrade() -> None: conn = op.get_bind() inspector = sa.inspect(conn) table_names = set(inspector.get_table_names()) - config_columns = {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set() + config_columns = ( + {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set() + ) has_old_config = {'id', 'data'}.issubset(config_columns) has_new_config = {'key', 'value'}.issubset(config_columns) @@ -481,9 +481,7 @@ def upgrade() -> None: blob_data = {} if has_old_config: try: - result = conn.execute( - sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1) - ) + result = conn.execute(sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1)) row = result.fetchone() if row and row[0]: raw = row[0] @@ -538,10 +536,7 @@ def upgrade() -> None: now = int(time.time()) op.bulk_insert( new_config, - [ - {'key': k, 'value': v, 'updated_at': now} - for k, v in rows.items() - ], + [{'key': k, 'value': v, 'updated_at': now} for k, v in rows.items()], ) @@ -557,7 +552,9 @@ def downgrade() -> None: op.rename_table('config_old', 'config') return - config_columns = {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set() + config_columns = ( + {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set() + ) has_per_key_config = {'key', 'value'}.issubset(config_columns) blob_data = {} diff --git a/backend/open_webui/models/feedbacks.py b/backend/open_webui/models/feedbacks.py index 5f971b55eb..0bf1a6a139 100644 --- a/backend/open_webui/models/feedbacks.py +++ b/backend/open_webui/models/feedbacks.py @@ -222,11 +222,7 @@ class FeedbackTable: ) -> FeedbackListResponse: async with get_async_db_context(db) as db: stmt = select(Feedback, User).join(User, Feedback.user_id == User.id) - count_stmt = ( - select(func.count(Feedback.id)) - .select_from(Feedback) - .join(User, Feedback.user_id == User.id) - ) + count_stmt = select(func.count(Feedback.id)).select_from(Feedback).join(User, Feedback.user_id == User.id) if filter: # Apply model_id filter (exact match) diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index 18f60a43f7..a06a5c51f8 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -144,9 +144,7 @@ class FolderTable: except Exception: return None - async def get_folder_by_id( - self, id: str, db: Optional[AsyncSession] = None - ) -> Optional[FolderModel]: + async def get_folder_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[FolderModel]: """Fetch folder by ID only (no user_id filter). Used for shared access.""" try: async with get_async_db_context(db) as db: @@ -159,8 +157,7 @@ class FolderTable: return None async def get_shared_folder_ids_for_user( - self, user_id: str, user_group_ids: set[str], - db: Optional[AsyncSession] = None + self, user_id: str, user_group_ids: set[str], db: Optional[AsyncSession] = None ) -> dict[str, str]: """ Returns {folder_id: highest_permission} for all folders shared with user. @@ -175,8 +172,7 @@ class FolderTable: ] if user_group_ids: conditions.append( - and_(AccessGrant.principal_type == 'group', - AccessGrant.principal_id.in_(user_group_ids)) + and_(AccessGrant.principal_type == 'group', AccessGrant.principal_id.in_(user_group_ids)) ) result = await db.execute( select(AccessGrant).filter( diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index a68b689e54..8f0f7e0d1b 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -164,11 +164,7 @@ class FunctionsTable: func_data['updated_at'] = int(time.time()) if func.id in existing_ids: - await db.execute( - update(Function) - .filter_by(id=func.id) - .values(**func_data) - ) + await db.execute(update(Function).filter_by(id=func.id).values(**func_data)) else: new_func = Function(**func_data) db.add(new_func) diff --git a/backend/open_webui/models/memories.py b/backend/open_webui/models/memories.py index de6b9595b1..ad32330f9f 100644 --- a/backend/open_webui/models/memories.py +++ b/backend/open_webui/models/memories.py @@ -210,7 +210,9 @@ class MemoriesTable: ) db.add(memory) await db.flush() - results.append({'action': action, 'status': 'created', 'memory': MemoryModel.model_validate(memory)}) + results.append( + {'action': action, 'status': 'created', 'memory': MemoryModel.model_validate(memory)} + ) elif action == 'replace': memory_id = operation.get('id') @@ -228,7 +230,9 @@ class MemoriesTable: memory.meta = {**(memory.meta or {}), **operation.get('meta')} memory.updated_at = now await db.flush() - results.append({'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)}) + results.append( + {'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)} + ) elif action == 'move': memory_id = operation.get('id') @@ -241,7 +245,9 @@ class MemoriesTable: memory.meta = {**(memory.meta or {}), **operation.get('meta')} memory.updated_at = now await db.flush() - results.append({'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)}) + results.append( + {'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)} + ) elif action == 'remove': memory_id = operation.get('id') diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 0b6c4e080b..dc43dfe1e9 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -540,10 +540,7 @@ class MessageTable: ) grouped[mid][reaction.name]['count'] += 1 - return { - mid: [Reactions(**r) for r in reactions.values()] - for mid, reactions in grouped.items() - } + return {mid: [Reactions(**r) for r in reactions.values()] for mid, reactions in grouped.items()} async def get_thread_reply_counts_by_message_ids( self, ids: list[str], db: Optional[AsyncSession] = None diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index d6309fdf2a..a6468f1876 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -244,9 +244,7 @@ class ToolsTable: try: async with get_async_db_context(db) as db: await db.execute( - update(Tool) - .filter_by(id=id) - .values(valves=encrypt_valves(valves), updated_at=int(time.time())) + update(Tool).filter_by(id=id).values(valves=encrypt_valves(valves), updated_at=int(time.time())) ) await db.commit() return await self.get_tool_by_id(id, db=db) diff --git a/backend/open_webui/retrieval/external.py b/backend/open_webui/retrieval/external.py index 802132b0ba..dcfc575fc8 100644 --- a/backend/open_webui/retrieval/external.py +++ b/backend/open_webui/retrieval/external.py @@ -192,7 +192,7 @@ async def _retrieve_milvus(connection, auth_config, knowledge, query, count, emb } normalized = [] - for hit in (response[0] if response else []): + for hit in response[0] if response else []: item = dict(hit) entity = item.get('entity') or {} result = { diff --git a/backend/open_webui/retrieval/vector/dbs/milvus.py b/backend/open_webui/retrieval/vector/dbs/milvus.py index 1e8439cdaa..b0331e3eea 100644 --- a/backend/open_webui/retrieval/vector/dbs/milvus.py +++ b/backend/open_webui/retrieval/vector/dbs/milvus.py @@ -280,10 +280,7 @@ class MilvusClient(VectorDBBase): for item in items: text = item['text'] or '' if len(text) > MILVUS_TEXT_MAX_LENGTH: - log.warning( - f'Milvus: truncating text id={item["id"]} ' - f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars' - ) + log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars') text = text[:MILVUS_TEXT_MAX_LENGTH] data.append( { @@ -299,10 +296,7 @@ class MilvusClient(VectorDBBase): data=data, ) except MilvusException as e: - log.error( - f'Milvus insert failed for {self.collection_prefix}_{collection_name} ' - f'({len(items)} items): {e}' - ) + log.error(f'Milvus insert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}') raise def upsert(self, collection_name: str, items: list[VectorItem]): @@ -324,10 +318,7 @@ class MilvusClient(VectorDBBase): for item in items: text = item['text'] or '' if len(text) > MILVUS_TEXT_MAX_LENGTH: - log.warning( - f'Milvus: truncating text id={item["id"]} ' - f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars' - ) + log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars') text = text[:MILVUS_TEXT_MAX_LENGTH] data.append( { @@ -343,10 +334,7 @@ class MilvusClient(VectorDBBase): data=data, ) except MilvusException as e: - log.error( - f'Milvus upsert failed for {self.collection_prefix}_{collection_name} ' - f'({len(items)} items): {e}' - ) + log.error(f'Milvus upsert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}') raise def delete( diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index e299aa6511..8310f49a2c 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -292,8 +292,7 @@ async def update_audio_config(request: Request, form_data: AudioConfigUpdateForm if form_data.stt.ENGINE == '': request.app.state.faster_whisper_model = await asyncio.to_thread( - set_faster_whisper_model, - form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE + set_faster_whisper_model, form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE ) else: request.app.state.faster_whisper_model = None @@ -560,9 +559,7 @@ async def speech(request: Request, user=Depends(get_verified_user)): detail=ERROR_MESSAGES.NOT_FOUND, ) - if user.role != 'admin' and not await has_permission( - user.id, 'chat.tts', await Config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -1152,9 +1149,7 @@ async def transcription( language: Optional[str] = Form(None), user=Depends(get_verified_user), ): - if user.role != 'admin' and not await has_permission( - user.id, 'chat.stt', await Config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', await Config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index c3c436e291..9f71ecafb8 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -186,7 +186,8 @@ async def create_session_response( request, EVENTS.AUTH_LOGIN, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', source=source, data={'auth_method': source}, ) @@ -386,7 +387,8 @@ async def update_password( request, EVENTS.AUTH_PASSWORD_CHANGED, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return success else: @@ -1372,9 +1374,7 @@ async def update_oauth_config(request: Request, form_data: OAuthConfigForm, user async def _check_api_key_permission(request: Request, user, db: AsyncSession): if not await Config.get('auth.enable_api_keys') or ( user.role != 'admin' - and not await has_permission( - user.id, 'features.api_keys', await Config.get('user.permissions'), db=db - ) + and not await has_permission(user.id, 'features.api_keys', await Config.get('user.permissions'), db=db) ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -1397,7 +1397,8 @@ async def generate_api_key( request, EVENTS.AUTH_API_KEY_CREATED, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return { 'api_key': api_key, @@ -1418,16 +1419,15 @@ async def delete_api_key( request, EVENTS.AUTH_API_KEY_DELETED, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return success # get api key @router.get('/api_key', response_model=ApiKey) -async def get_api_key( - request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session) -): +async def get_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): await _check_api_key_permission(request, user, db) api_key = await Users.get_user_api_key_by_id(user.id, db=db) if api_key: diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index 0630e83fb2..f7d027bad9 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -266,7 +266,8 @@ async def toggle_automation_by_id( request, EVENTS.AUTOMATION_ENABLED if toggled.is_active else EVENTS.AUTOMATION_DISABLED, actor=user, - subject_id=toggled.id, subject_type='automation', + subject_id=toggled.id, + subject_type='automation', data={'name': toggled.name}, ) return response diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index d640b5f34e..f95be48e84 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -42,9 +42,7 @@ async def check_calendar_permission(request: Request, user): status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED, ) - if user.role != 'admin' and not await has_permission( - user.id, 'features.calendar', config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'features.calendar', config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED, diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 317de94d6c..08d3b1ccb2 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -575,9 +575,7 @@ async def delete_all_user_chats( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): - if user.role == 'user' and not await has_permission( - user.id, 'chat.delete', await Config.get('user.permissions') - ): + if user.role == 'user' and not await has_permission(user.id, 'chat.delete', await Config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -589,7 +587,8 @@ async def delete_all_user_chats( request, EVENTS.CHAT_DELETED_ALL, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return result @@ -1001,7 +1000,8 @@ async def unshare_all_chats( request, EVENTS.CHAT_UNSHARED, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', data={'count': len(chat_ids), 'chat_ids': chat_ids}, ) return result @@ -1215,6 +1215,12 @@ async def update_chat_by_id( chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) if chat: updated_chat = {**chat.chat, **form_data.chat} + if 'history' in form_data.chat: + updated_chat['history'] = Chats.merge_history( + chat.chat.get('history'), + form_data.chat.get('history'), + form_data.deleted_message_ids, + ) # Re-derive content from output for assistant messages so that frontend # edits to output items are reflected in content. Only when output @@ -1222,7 +1228,7 @@ async def update_chat_by_id( # (e.g. a `replace` event or an outlet filter footer) would be reverted. existing_messages = (chat.chat.get('history') or {}).get('messages') or {} for msg_id, msg in updated_chat.get('history', {}).get('messages', {}).items(): - if msg.get('role') == 'assistant' and msg.get('output'): + if isinstance(msg, dict) and msg.get('role') == 'assistant' and msg.get('output'): if msg.get('output') != existing_messages.get(msg_id, {}).get('output'): msg['content'] = serialize_output(msg['output']) @@ -1233,7 +1239,7 @@ async def update_chat_by_id( # history with potential edits, deletions, or new branches. messages = (updated_chat.get('history') or {}).get('messages') or {} if messages: - await Chats.reconcile_messages_by_chat_id(id, user.id, messages) + await Chats.reconcile_messages_by_chat_id(id, user.id, messages, form_data.deleted_message_ids) await publish_event( request, @@ -1470,7 +1476,8 @@ async def pin_chat_by_id( request, EVENTS.CHAT_PINNED if chat.pinned else EVENTS.CHAT_UNPINNED, actor=user, - subject_id=id, subject_type='chat', + subject_id=id, + subject_type='chat', ) return chat else: @@ -1643,7 +1650,8 @@ async def archive_chat_by_id( request, EVENTS.CHAT_ARCHIVED if chat.archived else EVENTS.CHAT_UNARCHIVED, actor=user, - subject_id=id, subject_type='chat', + subject_id=id, + subject_type='chat', ) return ChatResponse(**chat.model_dump()) else: @@ -1660,9 +1668,7 @@ async def share_chat_by_id( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): - if user.role != 'admin' and not await has_permission( - user.id, 'chat.share', await Config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'chat.share', await Config.get('user.permissions')): raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index b9a2099921..533c1bd27f 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -144,7 +144,8 @@ async def set_connections_config( request, EVENTS.CONFIG_CONNECTIONS_UPDATED, actor=user, - subject_id='connections', subject_type='config', + subject_id='connections', + subject_type='config', data=values, ) return values @@ -262,9 +263,7 @@ async def set_tool_servers_config( if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id: try: oauth_client_info = resolve_oauth_client_info(connection) - oauth_client_info = await recover_static_oauth_client_metadata( - connection, oauth_client_info - ) + oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info) oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info) request.app.state.oauth_client_manager.add_client( f'{server_type}:{server_id}', @@ -278,7 +277,8 @@ async def set_tool_servers_config( request, EVENTS.CONFIG_TOOL_SERVERS_UPDATED, actor=user, - subject_id='tool_server.connections', subject_type='config', + subject_id='tool_server.connections', + subject_type='config', data={'count': len(connections), 'types': [connection.get('type', 'openapi') for connection in connections]}, ) return {'TOOL_SERVER_CONNECTIONS': connections} @@ -330,7 +330,8 @@ async def set_terminal_servers_config( request, EVENTS.CONFIG_TERMINAL_SERVERS_UPDATED, actor=user, - subject_id='terminal_server.connections', subject_type='config', + subject_id='terminal_server.connections', + subject_type='config', data={'count': len(connections)}, ) return {'TERMINAL_SERVER_CONNECTIONS': connections} @@ -699,7 +700,8 @@ async def set_code_execution_config( request, EVENTS.CONFIG_CODE_EXECUTION_UPDATED, actor=user, - subject_id='code_execution', subject_type='config', + subject_id='code_execution', + subject_type='config', data={ 'code_execution_enabled': values.get('ENABLE_CODE_EXECUTION'), 'code_execution_engine': values.get('CODE_EXECUTION_ENGINE'), @@ -741,7 +743,8 @@ async def set_models_config(request: Request, form_data: ModelsConfigForm, user= request, EVENTS.CONFIG_MODELS_UPDATED, actor=user, - subject_id='models', subject_type='config', + subject_id='models', + subject_type='config', data={ 'default_models': values.get('DEFAULT_MODELS'), 'default_pinned_models': values.get('DEFAULT_PINNED_MODELS'), @@ -773,7 +776,8 @@ async def set_default_suggestions( request, EVENTS.CONFIG_SUGGESTIONS_UPDATED, actor=user, - subject_id='ui.prompt_suggestions', subject_type='config', + subject_id='ui.prompt_suggestions', + subject_type='config', data={'count': len(suggestions or [])}, ) return suggestions @@ -801,7 +805,8 @@ async def set_banners( request, EVENTS.CONFIG_BANNERS_UPDATED, actor=user, - subject_id='ui.banners', subject_type='config', + subject_id='ui.banners', + subject_type='config', data={'count': len(banners or [])}, ) return banners diff --git a/backend/open_webui/routers/evaluations.py b/backend/open_webui/routers/evaluations.py index f1247d5a00..fd18843435 100644 --- a/backend/open_webui/routers/evaluations.py +++ b/backend/open_webui/routers/evaluations.py @@ -373,7 +373,8 @@ async def delete_feedbacks( request, EVENTS.FEEDBACK_DELETED_ALL, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return success diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 4559cf60e5..33e7dc1220 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -270,9 +270,7 @@ async def upload_file( result_meta = result.meta or {} result_content_type = ( - result_meta.get('content_type') - if isinstance(result_meta, dict) - else getattr(result_meta, 'content_type', None) + result_meta.get('content_type') if isinstance(result_meta, dict) else getattr(result_meta, 'content_type', None) ) await publish_event( request, @@ -532,7 +530,9 @@ async def count_files( @router.delete('/all') -async def delete_all_files(request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): +async def delete_all_files( + request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session) +): result = await Files.delete_all_files(db=db) if result: try: diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index 71ee9b9cf0..f048bbbf75 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -182,9 +182,7 @@ async def get_shared_folders( groups = await Groups.get_groups_by_member_id(user.id, db=db) group_ids = {g.id for g in groups} - folder_perms = await Folders.get_shared_folder_ids_for_user( - user.id, group_ids, db=db - ) + folder_perms = await Folders.get_shared_folder_ids_for_user(user.id, group_ids, db=db) # Filter out folders owned by the user results = [] @@ -199,28 +197,30 @@ async def get_shared_folders( owner = await Users.get_user_by_id(folder.user_id, db=db) owner_cache[folder.user_id] = owner.name if owner else 'Unknown' - results.append({ - **folder.model_dump(), - 'owner_name': owner_cache[folder.user_id], - 'permission': permission, - }) + results.append( + { + **folder.model_dump(), + 'owner_name': owner_cache[folder.user_id], + 'permission': permission, + } + ) # Also include child folders of shared folders (inheritance) shared_root_ids = {r['id'] for r in results} for root_id in list(shared_root_ids): root_folder = await Folders.get_folder_by_id(root_id, db=db) if root_folder: - children = await Folders.get_children_folders_by_id_and_user_id( - root_id, root_folder.user_id, db=db - ) + children = await Folders.get_children_folders_by_id_and_user_id(root_id, root_folder.user_id, db=db) if children: for child in children: if child.id not in {r['id'] for r in results}: - results.append({ - **child.model_dump(), - 'owner_name': owner_cache.get(child.user_id, 'Unknown'), - 'permission': folder_perms.get(root_id, 'read'), - }) + results.append( + { + **child.model_dump(), + 'owner_name': owner_cache.get(child.user_id, 'Unknown'), + 'permission': folder_perms.get(root_id, 'read'), + } + ) return results @@ -231,7 +231,9 @@ async def get_shared_folders( @router.get('/{id}', response_model=None) -async def get_folder_by_id(request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_folder_by_id( + request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): await check_folders_permission(request, user, db=db) folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db) if folder: @@ -443,7 +445,8 @@ async def update_folder_access_by_id( form_data.access_grants = await filter_allowed_access_grants( await Config.get('user.permissions'), - user.id, user.role, + user.id, + user.role, form_data.access_grants, None, db=db, @@ -509,10 +512,7 @@ async def get_shared_folder_chats( chat['owner_name'] = owner_cache[uid] return { - 'chats': [ - {**chat, 'readonly': chat['user_id'] != user.id} - for chat in chats - ], + 'chats': [{**chat, 'readonly': chat['user_id'] != user.id} for chat in chats], 'folder_permission': 'write' if has_write else 'read', } diff --git a/backend/open_webui/routers/functions.py b/backend/open_webui/routers/functions.py index 7219209bf3..8917bb37f0 100644 --- a/backend/open_webui/routers/functions.py +++ b/backend/open_webui/routers/functions.py @@ -291,7 +291,8 @@ async def toggle_function_by_id( request, EVENTS.FUNCTION_ENABLED if function.is_active else EVENTS.FUNCTION_DISABLED, actor=user, - subject_id=function.id, subject_type='function', + subject_id=function.id, + subject_type='function', data={'type': function.type, 'name': function.name}, ) return function diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 1625a34237..78247c0b7e 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -201,25 +201,12 @@ async def set_image_model(request: Request, model: str): async def get_image_model(request): image_config = await get_image_config() if image_config.IMAGE_GENERATION_ENGINE == 'openai': - return ( - image_config.IMAGE_GENERATION_MODEL - if image_config.IMAGE_GENERATION_MODEL - else 'dall-e-2' - ) + return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'dall-e-2' elif image_config.IMAGE_GENERATION_ENGINE == 'gemini': - return ( - image_config.IMAGE_GENERATION_MODEL - if image_config.IMAGE_GENERATION_MODEL - else 'imagen-3.0-generate-002' - ) + return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'imagen-3.0-generate-002' elif image_config.IMAGE_GENERATION_ENGINE == 'comfyui': - return ( - image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else '' - ) - elif ( - image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' - or image_config.IMAGE_GENERATION_ENGINE == '' - ): + return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else '' + elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '': try: session = await get_session() async with session.get( @@ -433,10 +420,7 @@ async def get_models(request: Request, user=Depends(get_verified_user)): info['CheckpointLoaderSimple']['input']['required']['ckpt_name'][0], ) ) - elif ( - image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' - or image_config.IMAGE_GENERATION_ENGINE == '' - ): + elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '': session = await get_session() async with session.get( url=f'{image_config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models', @@ -592,7 +576,8 @@ async def generate_images(request: Request, form_data: CreateImageForm, user=Dep request, EVENTS.IMAGE_GENERATED, actor=user, - subject_id=None, subject_type='image', + subject_id=None, + subject_type='image', data={ 'model': form_data.model, 'size': form_data.size, @@ -658,11 +643,7 @@ async def image_generations( ) else {'response_format': 'b64_json'} ), - **( - {} - if not image_config.IMAGES_OPENAI_API_PARAMS - else image_config.IMAGES_OPENAI_API_PARAMS - ), + **({} if not image_config.IMAGES_OPENAI_API_PARAMS else image_config.IMAGES_OPENAI_API_PARAMS), } session = await get_session() @@ -803,10 +784,7 @@ async def image_generations( ) images.append({'url': url}) return images - elif ( - image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' - or image_config.IMAGE_GENERATION_ENGINE == '' - ): + elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '': if form_data.model: await set_image_model(request, form_data.model) @@ -892,7 +870,8 @@ async def edit_images(request: Request, form_data: EditImageForm, user=Depends(g request, EVENTS.IMAGE_EDITED, actor=user, - subject_id=None, subject_type='image', + subject_id=None, + subject_type='image', data={ 'model': form_data.model, 'size': form_data.size, diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index 8a60f79495..6d7ac4e9f2 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -570,7 +570,9 @@ async def _set_external_connections(connections: list[dict]) -> None: await Config.upsert({EXTERNAL_KNOWLEDGE_CONNECTIONS_CONFIG_KEY: connections}) -def _external_connection_dict(form_data: ExternalKnowledgeConnectionForm, user_id: str, id: Optional[str] = None) -> dict: +def _external_connection_dict( + form_data: ExternalKnowledgeConnectionForm, user_id: str, id: Optional[str] = None +) -> dict: provider, config = _validate_external_connection_form(form_data) now = int(time.time()) return { @@ -1703,9 +1705,7 @@ async def delete_knowledge_by_id( connection_id = (knowledge.meta or {}).get('external', {}).get('connection_id') if connection_id: connections = [ - connection - for connection in await _get_external_connections() - if connection.get('id') != connection_id + connection for connection in await _get_external_connections() if connection.get('id') != connection_id ] await _set_external_connections(connections) else: diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py index 971c4de51f..8e6d50f39a 100644 --- a/backend/open_webui/routers/memories.py +++ b/backend/open_webui/routers/memories.py @@ -39,9 +39,7 @@ async def check_memories_permission(user): detail=ERROR_MESSAGES.NOT_FOUND, ) - if user.role != 'admin' and not await has_permission( - user.id, 'features.memories', config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'features.memories', config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -431,7 +429,8 @@ async def reset_memory_from_vector_db( request, EVENTS.MEMORY_RESET, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', data={'count': len(memories)}, ) return True @@ -461,7 +460,8 @@ async def delete_memory_by_user_id( request, EVENTS.MEMORY_DELETED, actor=user, - subject_id=user.id, subject_type='user', + subject_id=user.id, + subject_type='user', ) return True diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index 2af08b5b20..59c025055e 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -654,7 +654,8 @@ async def toggle_model_by_id( request, EVENTS.MODEL_ENABLED if model.is_active else EVENTS.MODEL_DISABLED, actor=user, - subject_id=model.id, subject_type='model', + subject_id=model.id, + subject_type='model', data={'name': model.name}, ) return model @@ -867,7 +868,9 @@ async def delete_model_by_id( @router.delete('/delete/all', response_model=bool) -async def delete_all_models(request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): +async def delete_all_models( + request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session) +): result = await Models.delete_all_models(db=db) if result: await publish_event(request, EVENTS.MODEL_DELETED, actor=user, subject_type='model') diff --git a/backend/open_webui/routers/notes.py b/backend/open_webui/routers/notes.py index c477e03eca..477558e423 100644 --- a/backend/open_webui/routers/notes.py +++ b/backend/open_webui/routers/notes.py @@ -479,7 +479,8 @@ async def pin_note_by_id( request, EVENTS.NOTE_PINNED if note.is_pinned else EVENTS.NOTE_UNPINNED, actor=user, - subject_id=note.id, subject_type='note', + subject_id=note.id, + subject_type='note', ) return note diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index bb7ebad899..f7019fbf3a 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -356,9 +356,7 @@ async def update_config(request: Request, form_data: OpenAIConfigForm, user=Depe @router.post('/audio/speech') async def speech(request: Request, user=Depends(get_verified_user)): - if user.role != 'admin' and not await has_permission( - user.id, 'chat.tts', await Config.get('user.permissions') - ): + if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, diff --git a/backend/open_webui/routers/prompts.py b/backend/open_webui/routers/prompts.py index 373323a59c..0e3c9c6ee4 100644 --- a/backend/open_webui/routers/prompts.py +++ b/backend/open_webui/routers/prompts.py @@ -532,7 +532,8 @@ async def toggle_prompt_active( request, EVENTS.PROMPT_ENABLED if result.is_active else EVENTS.PROMPT_DISABLED, actor=user, - subject_id=result.id, subject_type='prompt', + subject_id=result.id, + subject_type='prompt', data={'name': result.name, 'command': result.command}, ) return result diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 2d80b1472e..8647562600 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -196,9 +196,7 @@ def get_rf( ) except Exception as e: log.error(f'ExternalReranking: {e}') - raise Exception( - ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model') - ) + raise Exception(ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model')) else: import sentence_transformers import torch @@ -516,9 +514,7 @@ async def unload_embedding_model(request: Request): @router.post('/embedding/update') async def update_embedding_config(request: Request, form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)): config = await get_retrieval_config() - log.info( - f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}' - ) + log.info(f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}') await unload_embedding_model(request) try: config.RAG_EMBEDDING_ENGINE = form_data.RAG_EMBEDDING_ENGINE @@ -574,9 +570,7 @@ async def update_embedding_config(request: Request, form_data: EmbeddingModelUpd ), config.RAG_EMBEDDING_BATCH_SIZE, azure_api_version=( - config.RAG_AZURE_OPENAI_API_VERSION - if config.RAG_EMBEDDING_ENGINE == 'azure_openai' - else None + config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None ), enable_async=config.ENABLE_ASYNC_EMBEDDING, concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS, @@ -933,9 +927,7 @@ class ConfigForm(BaseModel): async def update_rag_config(request: Request, form_data: ConfigForm, user=Depends(get_admin_user)): # RAG settings config = await get_retrieval_config() - config.RAG_TEMPLATE = ( - form_data.RAG_TEMPLATE if form_data.RAG_TEMPLATE is not None else config.RAG_TEMPLATE - ) + config.RAG_TEMPLATE = form_data.RAG_TEMPLATE if form_data.RAG_TEMPLATE is not None else config.RAG_TEMPLATE config.TOP_K = form_data.TOP_K if form_data.TOP_K is not None else config.TOP_K config.BYPASS_EMBEDDING_AND_RETRIEVAL = ( form_data.BYPASS_EMBEDDING_AND_RETRIEVAL @@ -943,9 +935,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else config.BYPASS_EMBEDDING_AND_RETRIEVAL ) config.RAG_FULL_CONTEXT = ( - form_data.RAG_FULL_CONTEXT - if form_data.RAG_FULL_CONTEXT is not None - else config.RAG_FULL_CONTEXT + form_data.RAG_FULL_CONTEXT if form_data.RAG_FULL_CONTEXT is not None else config.RAG_FULL_CONTEXT ) # Hybrid search settings @@ -960,18 +950,12 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else config.ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS ) - config.TOP_K_RERANKER = ( - form_data.TOP_K_RERANKER if form_data.TOP_K_RERANKER is not None else config.TOP_K_RERANKER - ) + config.TOP_K_RERANKER = form_data.TOP_K_RERANKER if form_data.TOP_K_RERANKER is not None else config.TOP_K_RERANKER config.RELEVANCE_THRESHOLD = ( - form_data.RELEVANCE_THRESHOLD - if form_data.RELEVANCE_THRESHOLD is not None - else config.RELEVANCE_THRESHOLD + form_data.RELEVANCE_THRESHOLD if form_data.RELEVANCE_THRESHOLD is not None else config.RELEVANCE_THRESHOLD ) config.HYBRID_BM25_WEIGHT = ( - form_data.HYBRID_BM25_WEIGHT - if form_data.HYBRID_BM25_WEIGHT is not None - else config.HYBRID_BM25_WEIGHT + form_data.HYBRID_BM25_WEIGHT if form_data.HYBRID_BM25_WEIGHT is not None else config.HYBRID_BM25_WEIGHT ) # Content extraction settings @@ -981,9 +965,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else config.CONTENT_EXTRACTION_ENGINE ) config.PDF_EXTRACT_IMAGES = ( - form_data.PDF_EXTRACT_IMAGES - if form_data.PDF_EXTRACT_IMAGES is not None - else config.PDF_EXTRACT_IMAGES + form_data.PDF_EXTRACT_IMAGES if form_data.PDF_EXTRACT_IMAGES is not None else config.PDF_EXTRACT_IMAGES ) config.PDF_LOADER_MODE = ( form_data.PDF_LOADER_MODE if form_data.PDF_LOADER_MODE is not None else config.PDF_LOADER_MODE @@ -1062,16 +1044,12 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend form_data.TIKA_SERVER_URL if form_data.TIKA_SERVER_URL is not None else config.TIKA_SERVER_URL ) config.DOCLING_SERVER_URL = ( - form_data.DOCLING_SERVER_URL - if form_data.DOCLING_SERVER_URL is not None - else config.DOCLING_SERVER_URL + form_data.DOCLING_SERVER_URL if form_data.DOCLING_SERVER_URL is not None else config.DOCLING_SERVER_URL ) config.DOCLING_API_KEY = ( form_data.DOCLING_API_KEY if form_data.DOCLING_API_KEY is not None else config.DOCLING_API_KEY ) - config.DOCLING_PARAMS = ( - form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS - ) + config.DOCLING_PARAMS = form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS config.DOCUMENT_INTELLIGENCE_ENDPOINT = ( form_data.DOCUMENT_INTELLIGENCE_ENDPOINT if form_data.DOCUMENT_INTELLIGENCE_ENDPOINT is not None @@ -1094,39 +1072,25 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else config.MISTRAL_OCR_API_BASE_URL ) config.MISTRAL_OCR_API_KEY = ( - form_data.MISTRAL_OCR_API_KEY - if form_data.MISTRAL_OCR_API_KEY is not None - else config.MISTRAL_OCR_API_KEY + form_data.MISTRAL_OCR_API_KEY if form_data.MISTRAL_OCR_API_KEY is not None else config.MISTRAL_OCR_API_KEY ) config.PADDLEOCR_VL_BASE_URL = ( - form_data.PADDLEOCR_VL_BASE_URL - if form_data.PADDLEOCR_VL_BASE_URL is not None - else config.PADDLEOCR_VL_BASE_URL + form_data.PADDLEOCR_VL_BASE_URL if form_data.PADDLEOCR_VL_BASE_URL is not None else config.PADDLEOCR_VL_BASE_URL ) config.PADDLEOCR_VL_TOKEN = ( - form_data.PADDLEOCR_VL_TOKEN - if form_data.PADDLEOCR_VL_TOKEN is not None - else config.PADDLEOCR_VL_TOKEN + form_data.PADDLEOCR_VL_TOKEN if form_data.PADDLEOCR_VL_TOKEN is not None else config.PADDLEOCR_VL_TOKEN ) # MinerU settings config.MINERU_API_MODE = ( form_data.MINERU_API_MODE if form_data.MINERU_API_MODE is not None else config.MINERU_API_MODE ) - config.MINERU_API_URL = ( - form_data.MINERU_API_URL if form_data.MINERU_API_URL is not None else config.MINERU_API_URL - ) - config.MINERU_API_KEY = ( - form_data.MINERU_API_KEY if form_data.MINERU_API_KEY is not None else config.MINERU_API_KEY - ) + config.MINERU_API_URL = form_data.MINERU_API_URL if form_data.MINERU_API_URL is not None else config.MINERU_API_URL + config.MINERU_API_KEY = form_data.MINERU_API_KEY if form_data.MINERU_API_KEY is not None else config.MINERU_API_KEY config.MINERU_API_TIMEOUT = ( - form_data.MINERU_API_TIMEOUT - if form_data.MINERU_API_TIMEOUT is not None - else config.MINERU_API_TIMEOUT - ) - config.MINERU_PARAMS = ( - form_data.MINERU_PARAMS if form_data.MINERU_PARAMS is not None else config.MINERU_PARAMS + form_data.MINERU_API_TIMEOUT if form_data.MINERU_API_TIMEOUT is not None else config.MINERU_API_TIMEOUT ) + config.MINERU_PARAMS = form_data.MINERU_PARAMS if form_data.MINERU_PARAMS is not None else config.MINERU_PARAMS config.MINERU_FILE_EXTENSIONS = ( form_data.MINERU_FILE_EXTENSIONS if form_data.MINERU_FILE_EXTENSIONS is not None @@ -1147,9 +1111,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend if torch.cuda.is_available(): torch.cuda.empty_cache() config.RAG_RERANKING_ENGINE = ( - form_data.RAG_RERANKING_ENGINE - if form_data.RAG_RERANKING_ENGINE is not None - else config.RAG_RERANKING_ENGINE + form_data.RAG_RERANKING_ENGINE if form_data.RAG_RERANKING_ENGINE is not None else config.RAG_RERANKING_ENGINE ) config.RAG_EXTERNAL_RERANKER_URL = ( @@ -1176,21 +1138,14 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else config.RAG_RERANKING_BATCH_SIZE ) - log.info( - f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}' - ) + log.info(f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}') try: config.RAG_RERANKING_MODEL = ( - form_data.RAG_RERANKING_MODEL - if form_data.RAG_RERANKING_MODEL is not None - else config.RAG_RERANKING_MODEL + form_data.RAG_RERANKING_MODEL if form_data.RAG_RERANKING_MODEL is not None else config.RAG_RERANKING_MODEL ) try: - if ( - config.ENABLE_RAG_HYBRID_SEARCH - and not config.BYPASS_EMBEDDING_AND_RETRIEVAL - ): + if config.ENABLE_RAG_HYBRID_SEARCH and not config.BYPASS_EMBEDDING_AND_RETRIEVAL: request.app.state.rf = get_rf( config.RAG_RERANKING_ENGINE, config.RAG_RERANKING_MODEL, @@ -1216,25 +1171,17 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend ) # Chunking settings - config.TEXT_SPLITTER = ( - form_data.TEXT_SPLITTER if form_data.TEXT_SPLITTER is not None else config.TEXT_SPLITTER - ) + config.TEXT_SPLITTER = form_data.TEXT_SPLITTER if form_data.TEXT_SPLITTER is not None else config.TEXT_SPLITTER config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER = ( form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER if form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER is not None else config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER ) - config.CHUNK_SIZE = ( - form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE - ) + config.CHUNK_SIZE = form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE config.CHUNK_MIN_SIZE_TARGET = ( - form_data.CHUNK_MIN_SIZE_TARGET - if form_data.CHUNK_MIN_SIZE_TARGET is not None - else config.CHUNK_MIN_SIZE_TARGET - ) - config.CHUNK_OVERLAP = ( - form_data.CHUNK_OVERLAP if form_data.CHUNK_OVERLAP is not None else config.CHUNK_OVERLAP + form_data.CHUNK_MIN_SIZE_TARGET if form_data.CHUNK_MIN_SIZE_TARGET is not None else config.CHUNK_MIN_SIZE_TARGET ) + config.CHUNK_OVERLAP = form_data.CHUNK_OVERLAP if form_data.CHUNK_OVERLAP is not None else config.CHUNK_OVERLAP config.RAG_TOKENIZER_MODEL = ( form_data.RAG_TOKENIZER_MODEL.strip() if form_data.RAG_TOKENIZER_MODEL is not None @@ -1287,9 +1234,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend config.WEB_FETCH_MAX_CONTENT_LENGTH = form_data.web.WEB_FETCH_MAX_CONTENT_LENGTH config.WEB_LOADER_CONCURRENT_REQUESTS = form_data.web.WEB_LOADER_CONCURRENT_REQUESTS config.WEB_SEARCH_DOMAIN_FILTER_LIST = form_data.web.WEB_SEARCH_DOMAIN_FILTER_LIST - config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = ( - form_data.web.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL - ) + config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = form_data.web.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL config.BYPASS_WEB_SEARCH_WEB_LOADER = form_data.web.BYPASS_WEB_SEARCH_WEB_LOADER config.OLLAMA_CLOUD_WEB_SEARCH_API_KEY = form_data.web.OLLAMA_CLOUD_WEB_SEARCH_API_KEY config.SEARXNG_QUERY_URL = form_data.web.SEARXNG_QUERY_URL @@ -1802,9 +1747,7 @@ def save_docs_to_vector_db( ), config.RAG_EMBEDDING_BATCH_SIZE, azure_api_version=( - config.RAG_AZURE_OPENAI_API_VERSION - if config.RAG_EMBEDDING_ENGINE == 'azure_openai' - else None + config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None ), enable_async=config.ENABLE_ASYNC_EMBEDDING, concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS, @@ -1997,7 +1940,8 @@ async def process_file( request, EVENTS.RETRIEVAL_CONTENT_PROCESSED, actor=user, - subject_id=file.id, subject_type='file', + subject_id=file.id, + subject_type='file', data={'collection_name': None, 'filename': file.filename}, ) return { @@ -2056,7 +2000,8 @@ async def process_file( request, EVENTS.RETRIEVAL_CONTENT_PROCESSED, actor=user, - subject_id=file.id, subject_type='file', + subject_id=file.id, + subject_type='file', data={'collection_name': collection_name, 'filename': file.filename}, ) return { @@ -2131,7 +2076,8 @@ async def process_text( request, EVENTS.RETRIEVAL_CONTENT_PROCESSED, actor=user, - subject_id=collection_name, subject_type='retrieval.collection', + subject_id=collection_name, + subject_type='retrieval.collection', data={'name': form_data.name, 'content_preview': text_content[:300]}, ) return { @@ -2451,11 +2397,7 @@ async def search_web(request: Request, engine: str, query: str, user=None) -> li config.WEB_SEARCH_DOMAIN_FILTER_LIST, ) elif engine == 'azure': - if ( - config.AZURE_AI_SEARCH_API_KEY - and config.AZURE_AI_SEARCH_ENDPOINT - and config.AZURE_AI_SEARCH_INDEX_NAME - ): + if config.AZURE_AI_SEARCH_API_KEY and config.AZURE_AI_SEARCH_ENDPOINT and config.AZURE_AI_SEARCH_INDEX_NAME: return await asyncio.to_thread( search_azure, config.AZURE_AI_SEARCH_API_KEY, @@ -2570,9 +2512,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - if user.role != 'admin' and not await has_permission( - user.id, 'features.web_search', config.USER_PERMISSIONS - ): + if user.role != 'admin' and not await has_permission(user.id, 'features.web_search', config.USER_PERMISSIONS): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -2980,7 +2920,8 @@ async def reset_upload_dir(request: Request, user=Depends(get_admin_user)) -> bo request, EVENTS.RETRIEVAL_UPLOADS_RESET, actor=user, - subject_id='all', subject_type='file.uploads', + subject_id='all', + subject_type='file.uploads', ) return True @@ -3117,7 +3058,8 @@ async def process_files_batch( request, EVENTS.RETRIEVAL_CONTENT_PROCESSED, actor=user, - subject_id=collection_name, subject_type='retrieval.collection', + subject_id=collection_name, + subject_type='retrieval.collection', data={ 'count': len([item for item in file_results if item.status == 'completed']), 'errors': len(file_errors), diff --git a/backend/open_webui/routers/skills.py b/backend/open_webui/routers/skills.py index ba452b3167..9af2d81573 100644 --- a/backend/open_webui/routers/skills.py +++ b/backend/open_webui/routers/skills.py @@ -159,12 +159,8 @@ async def create_new_skill( db: AsyncSession = Depends(get_async_session), ): if user.role != 'admin' and not ( - await has_permission( - user.id, 'workspace.skills', await Config.get('user.permissions'), db=db - ) - or await has_permission( - user.id, 'workspace.skills_import', await Config.get('user.permissions'), db=db - ) + await has_permission(user.id, 'workspace.skills', await Config.get('user.permissions'), db=db) + or await has_permission(user.id, 'workspace.skills_import', await Config.get('user.permissions'), db=db) ): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index c87805a78f..c830753b5a 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -76,8 +76,7 @@ async def get_tools( for tool in await Tools.get_tools(defer_content=True, db=db): tool_module = tools_cache.get(tool.id) has_user_valves = ( - hasattr(tool_module, 'UserValves') if tool_module - else (tool.meta.has_user_valves if tool.meta else False) + hasattr(tool_module, 'UserValves') if tool_module else (tool.meta.has_user_valves if tool.meta else False) ) tools.append( ToolUserResponse( diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index f93a11fc4c..d15708941a 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -280,7 +280,8 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi request, EVENTS.USER_PERMISSIONS_UPDATED, actor=user, - subject_id='user.permissions', subject_type='config', + subject_id='user.permissions', + subject_type='config', ) return user_permissions @@ -691,7 +692,8 @@ async def update_user_by_id( request, EVENTS.AUTH_PASSWORD_CHANGED, actor=session_user, - subject_id=user_id, subject_type='user', + subject_id=user_id, + subject_type='user', source='admin', ) return updated_user diff --git a/backend/open_webui/utils/context_compaction.py b/backend/open_webui/utils/context_compaction.py index 3c6dd819ec..e20f6b9cdc 100644 --- a/backend/open_webui/utils/context_compaction.py +++ b/backend/open_webui/utils/context_compaction.py @@ -54,10 +54,7 @@ async def compact_messages_for_request( messages, previous_summary = _apply_latest_summary_checkpoint(messages) token_threshold = _resolve_token_threshold(config['token_threshold'], metadata) - if ( - not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold) - or len(messages) <= 3 - ): + if not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold) or len(messages) <= 3: return messages, previous_summary, False boundary = _find_compaction_boundary(messages) diff --git a/backend/open_webui/utils/memory.py b/backend/open_webui/utils/memory.py index 180462f860..6d96c4cab7 100644 --- a/backend/open_webui/utils/memory.py +++ b/backend/open_webui/utils/memory.py @@ -355,9 +355,9 @@ async def add_memory_context(request, form_data: dict, user, model: dict | None seen_ids.add(memory_id) content = str(doc) - if metadata.get('path') and content.startswith(f"{metadata.get('path')}\n"): + if metadata.get('path') and content.startswith(f'{metadata.get("path")}\n'): content = content[len(metadata.get('path')) + 1 :] - label = f"{metadata.get('path')}: {content}" if metadata.get('path') else content + label = f'{metadata.get("path")}: {content}' if metadata.get('path') else content sections[Memories.normalize_memory_type(metadata.get('type'))].append(label) parts = [] diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 829f1e5d6a..0d2b0860d4 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1476,6 +1476,7 @@ async def chat_completion_tools_handler( return body, {'sources': sources} + async def chat_web_search_handler(request: Request, form_data: dict, extra_params: dict, user): event_emitter = extra_params['__event_emitter__'] await event_emitter( @@ -3636,9 +3637,7 @@ async def non_streaming_chat_response_handler(response, ctx): 'end_tag': '', 'attributes': {'type': 'reasoning_content'}, 'content': ( - [{'type': 'output_text', 'text': reasoning_content}] - if reasoning_content - else [] + [{'type': 'output_text', 'text': reasoning_content}] if reasoning_content else [] ), 'summary': None, } @@ -4148,19 +4147,19 @@ async def streaming_chat_response_handler(response, ctx): # error events so frontend and DB paths still receive them. try: raw_obj = json.loads(data) - raw_error = raw_obj.get("error") if isinstance(raw_obj, dict) else None + raw_error = raw_obj.get('error') if isinstance(raw_obj, dict) else None if raw_error: try: Chats.upsert_message_to_chat_by_id_and_message_id( - metadata["chat_id"], - metadata["message_id"], + metadata['chat_id'], + metadata['message_id'], { - "error": {"content": raw_error}, + 'error': {'content': raw_error}, }, ) except Exception: pass - await event_emitter({ "type": "chat:completion", "data": { "error": raw_error } }) + await event_emitter({'type': 'chat:completion', 'data': {'error': raw_error}}) except Exception: pass continue @@ -4390,9 +4389,9 @@ async def streaming_chat_response_handler(response, ctx): current_response_tool_call['function']['name'] = delta_name if delta_arguments: - current_response_tool_call['function'][ - 'arguments' - ] += delta_arguments + current_response_tool_call['function']['arguments'] += ( + delta_arguments + ) # Emit pending tool calls in real-time if response_tool_calls: diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index af7c7e0ae9..509663fcfb 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -254,12 +254,7 @@ def convert_output_to_messages( def flush_pending(): nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details - if ( - not pending_content - and not pending_tool_calls - and not pending_reasoning - and not pending_reasoning_details - ): + if not pending_content and not pending_tool_calls and not pending_reasoning and not pending_reasoning_details: return message = { diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index ca8179ea37..39e139b053 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -54,16 +54,8 @@ async def fetch_openai_models(request: Request, user: UserModel = None): async def get_all_base_models(request: Request, user: UserModel = None): config = await Config.get_many('openai.enable', 'ollama.enable') - openai_task = ( - fetch_openai_models(request, user) - if config.get('openai.enable') - else asyncio.sleep(0, result=[]) - ) - ollama_task = ( - fetch_ollama_models(request, user) - if config.get('ollama.enable') - else asyncio.sleep(0, result=[]) - ) + openai_task = fetch_openai_models(request, user) if config.get('openai.enable') else asyncio.sleep(0, result=[]) + ollama_task = fetch_ollama_models(request, user) if config.get('ollama.enable') else asyncio.sleep(0, result=[]) function_task = get_function_models(request) openai_models, ollama_models, function_models = await asyncio.gather(openai_task, ollama_task, function_task) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 30085a29d7..67b66d1a0d 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -178,10 +178,7 @@ def _default_value(value): async def get_oauth_runtime_config() -> SimpleNamespace: keys = [key for key, _default in OAUTH_RUNTIME_CONFIG.values()] stored = await Config.get_many(*keys) - values = { - name: stored.get(key, _default_value(default)) - for name, (key, default) in OAUTH_RUNTIME_CONFIG.items() - } + values = {name: stored.get(key, _default_value(default)) for name, (key, default) in OAUTH_RUNTIME_CONFIG.items()} return SimpleNamespace(**values) @@ -715,10 +712,7 @@ def apply_connection_oauth_options(connection: dict, oauth_client_info: dict) -> def scope_has_resource_indicator(scope: str | None) -> bool: if not scope: return False - return any( - scope_value.startswith(('https://', 'http://', 'api://')) - for scope_value in scope.split() - ) + return any(scope_value.startswith(('https://', 'http://', 'api://')) for scope_value in scope.split()) def should_send_oauth_resource(client_info: OAuthClientInformationFull | None) -> bool: @@ -852,9 +846,7 @@ class OAuthClientManager: try: oauth_client_info = resolve_oauth_client_info(connection) - oauth_client_info = await recover_static_oauth_client_metadata( - connection, oauth_client_info - ) + oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info) oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info) return self.add_client(expected_client_id, OAuthClientInformationFull(**oauth_client_info))['client'] except Exception as e: diff --git a/backend/open_webui/utils/plugin.py b/backend/open_webui/utils/plugin.py index 9d463083e3..53531c4350 100644 --- a/backend/open_webui/utils/plugin.py +++ b/backend/open_webui/utils/plugin.py @@ -217,9 +217,7 @@ async def load_tool_module_by_id(tool_id, content=None): # Install required packages found within the frontmatter. # Runs `pip install` via subprocess, which can take a long time; # offload to a thread so it doesn't block the event loop. - await asyncio.to_thread( - install_frontmatter_requirements, frontmatter.get('requirements', '') - ) + await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', '')) module_name = f'tool_{tool_id}' module = types.ModuleType(module_name) @@ -264,9 +262,7 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non else: frontmatter = extract_frontmatter(content) # `pip install` via subprocess can block for a long time; offload it. - await asyncio.to_thread( - install_frontmatter_requirements, frontmatter.get('requirements', '') - ) + await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', '')) module_name = f'function_{function_id}' module = types.ModuleType(module_name) @@ -468,8 +464,6 @@ async def install_tool_and_function_dependencies(): all_dependencies += f'{dependencies}, ' # `pip install` via subprocess can block for a long time; offload it. - await asyncio.to_thread( - install_frontmatter_requirements, all_dependencies.strip(', ') - ) + await asyncio.to_thread(install_frontmatter_requirements, all_dependencies.strip(', ')) except Exception as e: log.error(f'Error installing requirements: {e}') diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index a61caa02bf..6c33882c50 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -618,19 +618,11 @@ async def get_builtin_tools( builtin_functions.append(execute_code) # Notes tools - search, view, create, and update user's notes - if ( - is_builtin_tool_enabled('notes') - and config.get('notes.enable') - and await has_user_permission('notes') - ): + if is_builtin_tool_enabled('notes') and config.get('notes.enable') and await has_user_permission('notes'): builtin_functions.extend([search_notes, view_note, write_note, replace_note_content]) # Channels tools - search channels and messages - if ( - is_builtin_tool_enabled('channels') - and config.get('channels.enable') - and await has_user_permission('channels') - ): + if is_builtin_tool_enabled('channels') and config.get('channels.enable') and await has_user_permission('channels'): builtin_functions.extend( [ search_channels, @@ -659,11 +651,7 @@ async def get_builtin_tools( ) # Calendar tools - search/create/update/delete events - if ( - is_builtin_tool_enabled('calendar') - and config.get('calendar.enable') - and await has_user_permission('calendar') - ): + if is_builtin_tool_enabled('calendar') and config.get('calendar.enable') and await has_user_permission('calendar'): builtin_functions.extend( [search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event] ) diff --git a/backend/open_webui/utils/webhook.py b/backend/open_webui/utils/webhook.py index 1c3bfd22ba..d3026ffac6 100644 --- a/backend/open_webui/utils/webhook.py +++ b/backend/open_webui/utils/webhook.py @@ -27,9 +27,7 @@ def _event_text(message: str, description: str | None = None, event_data: dict | return '\n'.join(lines) -async def post_webhook( - name: str, url: str, message: str, event_data: dict, description: str | None = None -) -> bool: +async def post_webhook(name: str, url: str, message: str, event_data: dict, description: str | None = None) -> bool: try: log.debug(f'post_webhook: {url}, {message}, {event_data}') # Block private-IP / loopback / cloud-metadata targets — the URL is diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 7c0bbf0623..721800fbab 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -1156,7 +1156,12 @@ export const getChatAccessGrants = async (token: string, id: string) => { return res; }; -export const updateChatById = async (token: string, id: string, chat: object) => { +export const updateChatById = async ( + token: string, + id: string, + chat: object, + deletedMessageIds: string[] = [] +) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { @@ -1167,7 +1172,8 @@ export const updateChatById = async (token: string, id: string, chat: object) => ...(token && { authorization: `Bearer ${token}` }) }, body: JSON.stringify({ - chat: chat + chat: chat, + ...(deletedMessageIds.length && { deleted_message_ids: deletedMessageIds }) }) }) .then(async (res) => { diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 49b7ffb67a..36efe44e33 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -512,7 +512,7 @@ } }; - const showMessage = async (message, scroll = true) => { + const showMessage = async (message, scroll = true, save = true) => { const _chatId = JSON.parse(JSON.stringify($chatId)); let _messageId = JSON.parse(JSON.stringify(message.id)); @@ -545,7 +545,9 @@ await tick(); await tick(); - saveChatHandler(_chatId, history); + if (save) { + saveChatHandler(_chatId, history); + } }; const updateLastReadAt = (id) => { diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index d525e5a8a4..283bfd44a4 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -35,6 +35,7 @@ export let atSelectedModel; let messages = []; + let deletedMessageIds = new Set(); export let setInputText: Function = () => {}; @@ -168,10 +169,16 @@ if (!$temporaryChatEnabled) { history = history; await tick(); - const res = await updateChatById(localStorage.token, chatId, { - history: history, - messages: messages - }); + const res = await updateChatById( + localStorage.token, + chatId, + { + history: history, + messages: messages + }, + Array.from(deletedMessageIds) + ); + deletedMessageIds.clear(); // Refresh local message content from backend (e.g. re-derived via serialize_output) if (res?.chat?.history?.messages) { @@ -461,9 +468,11 @@ // Delete the message and its children [messageId, ...childMessageIds].forEach((id) => { delete history.messages[id]; + deletedMessageIds.add(id); }); - showMessage({ id: parentMessageId }, false); + showMessage({ id: parentMessageId }, false, false); + await updateChat(); }; const triggerScroll = () => {