diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 0f7b5611f5..cd0136a5c8 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -199,6 +199,7 @@ CHANGELOG = changelog_json SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true" + #################################### # ENABLE_FORWARD_USER_INFO_HEADERS #################################### @@ -272,15 +273,13 @@ if "postgres://" in DATABASE_URL: DATABASE_SCHEMA = os.environ.get("DATABASE_SCHEMA", None) -DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", 0) +DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", None) -if DATABASE_POOL_SIZE == "": - DATABASE_POOL_SIZE = 0 -else: +if DATABASE_POOL_SIZE != None: try: DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE) except Exception: - DATABASE_POOL_SIZE = 0 + DATABASE_POOL_SIZE = None DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0) @@ -396,6 +395,10 @@ WEBUI_AUTH_COOKIE_SECURE = ( if WEBUI_AUTH and WEBUI_SECRET_KEY == "": raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) +ENABLE_COMPRESSION_MIDDLEWARE = ( + os.environ.get("ENABLE_COMPRESSION_MIDDLEWARE", "True").lower() == "true" +) + ENABLE_WEBSOCKET_SUPPORT = ( os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true" ) @@ -543,6 +546,9 @@ ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get( "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317" ) +OTEL_EXPORTER_OTLP_INSECURE = ( + os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true" +) OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui") OTEL_RESOURCE_ATTRIBUTES = os.environ.get( "OTEL_RESOURCE_ATTRIBUTES", "" @@ -550,6 +556,8 @@ OTEL_RESOURCE_ATTRIBUTES = os.environ.get( OTEL_TRACES_SAMPLER = os.environ.get( "OTEL_TRACES_SAMPLER", "parentbased_always_on" ).lower() +OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "") +OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "") #################################### # TOOLS/FUNCTIONS PIP OPTIONS diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index 840f571cc9..e1ffc1eb27 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -62,6 +62,9 @@ def handle_peewee_migration(DATABASE_URL): except Exception as e: log.error(f"Failed to initialize the database connection: {e}") + log.warning( + "Hint: If your database password contains special characters, you may need to URL-encode it." + ) raise finally: # Properly closing the database connection @@ -81,20 +84,23 @@ if "sqlite" in SQLALCHEMY_DATABASE_URL: SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) else: - if DATABASE_POOL_SIZE > 0: - engine = create_engine( - SQLALCHEMY_DATABASE_URL, - pool_size=DATABASE_POOL_SIZE, - max_overflow=DATABASE_POOL_MAX_OVERFLOW, - pool_timeout=DATABASE_POOL_TIMEOUT, - pool_recycle=DATABASE_POOL_RECYCLE, - pool_pre_ping=True, - poolclass=QueuePool, - ) + if isinstance(DATABASE_POOL_SIZE, int): + if DATABASE_POOL_SIZE > 0: + engine = create_engine( + SQLALCHEMY_DATABASE_URL, + pool_size=DATABASE_POOL_SIZE, + max_overflow=DATABASE_POOL_MAX_OVERFLOW, + pool_timeout=DATABASE_POOL_TIMEOUT, + pool_recycle=DATABASE_POOL_RECYCLE, + pool_pre_ping=True, + poolclass=QueuePool, + ) + else: + engine = create_engine( + SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool + ) else: - engine = create_engine( - SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool - ) + engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) SessionLocal = sessionmaker( diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 544756a6e8..8e37d9e530 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -411,6 +411,7 @@ from open_webui.env import ( WEBUI_AUTH_TRUSTED_EMAIL_HEADER, WEBUI_AUTH_TRUSTED_NAME_HEADER, WEBUI_AUTH_SIGNOUT_REDIRECT_URL, + ENABLE_COMPRESSION_MIDDLEWARE, ENABLE_WEBSOCKET_SUPPORT, BYPASS_MODEL_ACCESS_CONTROL, RESET_CONFIG_ON_START, @@ -1072,7 +1073,9 @@ class RedirectMiddleware(BaseHTTPMiddleware): # Add the middleware to the app -app.add_middleware(CompressMiddleware) +if ENABLE_COMPRESSION_MIDDLEWARE: + app.add_middleware(CompressMiddleware) + app.add_middleware(RedirectMiddleware) app.add_middleware(SecurityHeadersMiddleware) diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py index 8ac878fc22..a91496e8e8 100644 --- a/backend/open_webui/retrieval/loaders/main.py +++ b/backend/open_webui/retrieval/loaders/main.py @@ -14,7 +14,7 @@ from langchain_community.document_loaders import ( TextLoader, UnstructuredEPubLoader, UnstructuredExcelLoader, - UnstructuredMarkdownLoader, + UnstructuredODTLoader, UnstructuredPowerPointLoader, UnstructuredRSTLoader, UnstructuredXMLLoader, @@ -389,6 +389,8 @@ class Loader: loader = UnstructuredPowerPointLoader(file_path) elif file_ext == "msg": loader = OutlookMessageLoader(file_path) + elif file_ext == "odt": + loader = UnstructuredODTLoader(file_path) elif self._is_text_file(file_ext, file_content_type): loader = TextLoader(file_path, autodetect_encoding=True) else: diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 683f42819b..00dd683063 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -7,6 +7,7 @@ import hashlib from concurrent.futures import ThreadPoolExecutor import time +from urllib.parse import quote from huggingface_hub import snapshot_download from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever from langchain_community.retrievers import BM25Retriever @@ -678,10 +679,10 @@ def generate_openai_batch_embeddings( "Authorization": f"Bearer {key}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -727,10 +728,10 @@ def generate_azure_openai_batch_embeddings( "api-key": key, **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -777,10 +778,10 @@ def generate_ollama_batch_embeddings( "Authorization": f"Bearer {key}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 27634cec19..211f2ae859 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -15,6 +15,7 @@ import aiohttp import aiofiles import requests import mimetypes +from urllib.parse import quote from fastapi import ( Depends, @@ -343,10 +344,10 @@ async def speech(request: Request, user=Depends(get_verified_user)): "Authorization": f"Bearer {request.app.state.config.TTS_OPENAI_API_KEY}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -919,14 +920,18 @@ def transcription( ): log.info(f"file.content_type: {file.content_type}") - supported_content_types = request.app.state.config.STT_SUPPORTED_CONTENT_TYPES or [ - "audio/*", - "video/webm", - ] + stt_supported_content_types = getattr( + request.app.state.config, "STT_SUPPORTED_CONTENT_TYPES", [] + ) if not any( fnmatch(file.content_type, content_type) - for content_type in supported_content_types + for content_type in ( + stt_supported_content_types + if stt_supported_content_types + and any(t.strip() for t in stt_supported_content_types) + else ["audio/*", "video/webm"] + ) ): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 60a12db4b3..106f3684a7 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -669,6 +669,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm): @router.get("/signout") async def signout(request: Request, response: Response): response.delete_cookie("token") + response.delete_cookie("oui-session") if ENABLE_OAUTH_SIGNUP.value: oauth_id_token = request.cookies.get("oauth_id_token") diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index b9bb15c7b4..bdf5780fc4 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -155,17 +155,18 @@ def upload_file( if process: try: if file.content_type: - stt_supported_content_types = ( - request.app.state.config.STT_SUPPORTED_CONTENT_TYPES - or [ - "audio/*", - "video/webm", - ] + stt_supported_content_types = getattr( + request.app.state.config, "STT_SUPPORTED_CONTENT_TYPES", [] ) if any( fnmatch(file.content_type, content_type) - for content_type in stt_supported_content_types + for content_type in ( + stt_supported_content_types + if stt_supported_content_types + and any(t.strip() for t in stt_supported_content_types) + else ["audio/*", "video/webm"] + ) ): file_path = Storage.get_file(file_path) result = transcribe(request, file_path, file_metadata) diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 52686a5841..fc1c1b9a5a 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -8,6 +8,7 @@ import re from pathlib import Path from typing import Optional +from urllib.parse import quote import requests from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from open_webui.config import CACHE_DIR @@ -302,8 +303,14 @@ async def update_image_config( ): set_image_model(request, form_data.MODEL) + if (form_data.IMAGE_SIZE == "auto" and form_data.MODEL != 'gpt-image-1'): + raise HTTPException( + status_code=400, + detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (auto is only allowed with gpt-image-1).") + ) + pattern = r"^\d+x\d+$" - if re.match(pattern, form_data.IMAGE_SIZE): + if form_data.IMAGE_SIZE == "auto" or re.match(pattern, form_data.IMAGE_SIZE): request.app.state.config.IMAGE_SIZE = form_data.IMAGE_SIZE else: raise HTTPException( @@ -471,7 +478,14 @@ async def image_generations( form_data: GenerateImageForm, user=Depends(get_verified_user), ): - width, height = tuple(map(int, request.app.state.config.IMAGE_SIZE.split("x"))) + # if IMAGE_SIZE = 'auto', default WidthxHeight to the 512x512 default + # This is only relevant when the user has set IMAGE_SIZE to 'auto' with an + # image model other than gpt-image-1, which is warned about on settings save + width, height = ( + tuple(map(int, request.app.state.config.IMAGE_SIZE.split("x"))) + if 'x' in request.app.state.config.IMAGE_SIZE + else (512, 512) + ) r = None try: @@ -483,10 +497,10 @@ async def image_generations( headers["Content-Type"] = "application/json" if ENABLE_FORWARD_USER_INFO_HEADERS: - headers["X-OpenWebUI-User-Name"] = user.name - headers["X-OpenWebUI-User-Id"] = user.id - headers["X-OpenWebUI-User-Email"] = user.email - headers["X-OpenWebUI-User-Role"] = user.role + headers["X-OpenWebUI-User-Name"] = quote(user.name) + headers["X-OpenWebUI-User-Id"] = quote(user.id) + headers["X-OpenWebUI-User-Email"] = quote(user.email) + headers["X-OpenWebUI-User-Role"] = quote(user.role) data = { "model": ( diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 1353599374..9c1e1fdb00 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -16,6 +16,7 @@ from urllib.parse import urlparse import aiohttp from aiocache import cached import requests +from urllib.parse import quote from open_webui.models.chats import Chats from open_webui.models.users import UserModel @@ -87,10 +88,10 @@ async def send_get_request(url, key=None, user: UserModel = None): **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -138,10 +139,10 @@ async def send_post_request( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -242,10 +243,10 @@ async def verify_connection( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -462,10 +463,10 @@ async def get_ollama_tags( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -824,10 +825,10 @@ async def copy_model( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -890,10 +891,10 @@ async def delete_model( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -949,10 +950,10 @@ async def show_model_info( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1036,10 +1037,10 @@ async def embed( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1123,10 +1124,10 @@ async def embeddings( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 7649271fee..ab35a673fc 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -8,7 +8,7 @@ from typing import Literal, Optional, overload import aiohttp from aiocache import cached import requests - +from urllib.parse import quote from fastapi import Depends, FastAPI, HTTPException, Request, APIRouter from fastapi.middleware.cors import CORSMiddleware @@ -66,10 +66,10 @@ async def send_get_request(url, key=None, user: UserModel = None): **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -225,10 +225,10 @@ async def speech(request: Request, user=Depends(get_verified_user)): ), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -478,10 +478,10 @@ async def get_models( "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -573,10 +573,10 @@ async def verify_connection( "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -633,13 +633,7 @@ async def verify_connection( raise HTTPException(status_code=500, detail=error_detail) -def convert_to_azure_payload( - url, - payload: dict, -): - model = payload.get("model", "") - - # Filter allowed parameters based on Azure OpenAI API +def get_azure_allowed_params(api_version: str) -> set[str]: allowed_params = { "messages", "temperature", @@ -669,6 +663,23 @@ def convert_to_azure_payload( "max_completion_tokens", } + try: + if api_version >= "2024-09-01-preview": + allowed_params.add("stream_options") + except ValueError: + log.debug( + f"Invalid API version {api_version} for Azure OpenAI. Defaulting to allowed parameters." + ) + + return allowed_params + + +def convert_to_azure_payload(url, payload: dict, api_version: str): + model = payload.get("model", "") + + # Filter allowed parameters based on Azure OpenAI API + allowed_params = get_azure_allowed_params(api_version) + # Special handling for o-series models if model.startswith("o") and model.endswith("-mini"): # Convert max_tokens to max_completion_tokens for o-series models @@ -806,10 +817,10 @@ async def generate_chat_completion( ), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -817,8 +828,8 @@ async def generate_chat_completion( } if api_config.get("azure", False): - request_url, payload = convert_to_azure_payload(url, payload) - api_version = api_config.get("api_version", "") or "2023-03-15-preview" + api_version = api_config.get("api_version", "2023-03-15-preview") + request_url, payload = convert_to_azure_payload(url, payload, api_version) headers["api-key"] = key headers["api-version"] = api_version request_url = f"{request_url}/chat/completions?api-version={api_version}" @@ -924,10 +935,10 @@ async def embeddings(request: Request, form_data: dict, user): "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -996,10 +1007,10 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -1007,16 +1018,15 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): } if api_config.get("azure", False): + api_version = api_config.get("api_version", "2023-03-15-preview") headers["api-key"] = key - headers["api-version"] = ( - api_config.get("api_version", "") or "2023-03-15-preview" - ) + headers["api-version"] = api_version payload = json.loads(body) - url, payload = convert_to_azure_payload(url, payload) + url, payload = convert_to_azure_payload(url, payload, api_version) body = json.dumps(payload).encode() - request_url = f"{url}/{path}?api-version={api_config.get('api_version', '2023-03-15-preview')}" + request_url = f"{url}/{path}?api-version={api_version}" else: headers["Authorization"] = f"Bearer {key}" request_url = f"{url}/{path}" diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index ee6f99fbb5..6d888ca990 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -1747,6 +1747,16 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]: ) else: raise Exception("No TAVILY_API_KEY found in environment variables") + elif engine == "exa": + if request.app.state.config.EXA_API_KEY: + return search_exa( + request.app.state.config.EXA_API_KEY, + query, + request.app.state.config.WEB_SEARCH_RESULT_COUNT, + request.app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST, + ) + else: + raise Exception("No EXA_API_KEY found in environment variables") elif engine == "searchapi": if request.app.state.config.SEARCHAPI_API_KEY: return search_searchapi( diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index 268c910e3e..83483f391b 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -419,7 +419,7 @@ async def chat_action(request: Request, action_id: str, form_data: dict, user: A params[key] = value if "__user__" in sig.parameters: - __user__ = (user.model_dump() if isinstance(user, UserModel) else {},) + __user__ = user.model_dump() if isinstance(user, UserModel) else {} try: if hasattr(function_module, "UserValves"): diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index b1e69db264..6bad97b1f4 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -804,7 +804,6 @@ async def process_chat_payload(request, form_data, user, metadata, model): raise e try: - filter_functions = [ Functions.get_function_by_id(filter_id) for filter_id in get_sorted_filter_ids( @@ -1741,7 +1740,7 @@ async def process_chat_response( }, ) - async def stream_body_handler(response): + async def stream_body_handler(response, form_data): nonlocal content nonlocal content_blocks @@ -1770,7 +1769,7 @@ async def process_chat_response( filter_functions=filter_functions, filter_type="stream", form_data=data, - extra_params=extra_params, + extra_params={"__body__": form_data, **extra_params}, ) if data: @@ -2032,7 +2031,7 @@ async def process_chat_response( if response.background: await response.background() - await stream_body_handler(response) + await stream_body_handler(response, form_data) MAX_TOOL_CALL_RETRIES = 10 tool_call_retries = 0 @@ -2181,22 +2180,24 @@ async def process_chat_response( ) try: + new_form_data = { + "model": model_id, + "stream": True, + "tools": form_data["tools"], + "messages": [ + *form_data["messages"], + *convert_content_blocks_to_messages(content_blocks), + ], + } + res = await generate_chat_completion( request, - { - "model": model_id, - "stream": True, - "tools": form_data["tools"], - "messages": [ - *form_data["messages"], - *convert_content_blocks_to_messages(content_blocks), - ], - }, + new_form_data, user, ) if isinstance(res, StreamingResponse): - await stream_body_handler(res) + await stream_body_handler(res, new_form_data) else: break except Exception as e: diff --git a/backend/open_webui/utils/telemetry/setup.py b/backend/open_webui/utils/telemetry/setup.py index 62632cff52..123666d025 100644 --- a/backend/open_webui/utils/telemetry/setup.py +++ b/backend/open_webui/utils/telemetry/setup.py @@ -4,6 +4,7 @@ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExport from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider from sqlalchemy import Engine +from base64 import b64encode from open_webui.utils.telemetry.exporters import LazyBatchSpanProcessor from open_webui.utils.telemetry.instrumentors import Instrumentor @@ -11,7 +12,10 @@ from open_webui.utils.telemetry.metrics import setup_metrics from open_webui.env import ( OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_INSECURE, ENABLE_OTEL_METRICS, + OTEL_BASIC_AUTH_USERNAME, + OTEL_BASIC_AUTH_PASSWORD, ) @@ -22,8 +26,20 @@ def setup(app: FastAPI, db_engine: Engine): resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) ) ) + + # Add basic auth header only if both username and password are not empty + headers = [] + if OTEL_BASIC_AUTH_USERNAME and OTEL_BASIC_AUTH_PASSWORD: + auth_string = f"{OTEL_BASIC_AUTH_USERNAME}:{OTEL_BASIC_AUTH_PASSWORD}" + auth_header = b64encode(auth_string.encode()).decode() + headers = [("authorization", f"Basic {auth_header}")] + # otlp export - exporter = OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT) + exporter = OTLPSpanExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, + insecure=OTEL_EXPORTER_OTLP_INSECURE, + headers=headers, + ) trace.get_tracer_provider().add_span_processor(LazyBatchSpanProcessor(exporter)) Instrumentor(app=app, db_engine=db_engine).instrument() diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index dda2635ec7..02c8b3a86b 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -101,9 +101,6 @@ def get_tools( def make_tool_function(function_name, token, tool_server_data): async def tool_function(**kwargs): - print( - f"Executing tool function {function_name} with params: {kwargs}" - ) return await execute_tool_server( token=token, url=tool_server_data["url"], diff --git a/pyproject.toml b/pyproject.toml index 9a964a8c9b..86693f192e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -139,7 +139,7 @@ requires-python = ">= 3.11, < 3.13.0a1" dynamic = ["version"] classifiers = [ "Development Status :: 4 - Beta", - "License :: OSI Approved :: MIT License", + "License :: Other/Proprietary License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 842edd9c9d..0475df8d07 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -347,6 +347,8 @@ export const userSignOut = async () => { if (error) { throw error; } + + sessionStorage.clear(); return res; }; diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 99b95b0629..d10db74f8c 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1587,6 +1587,7 @@ export interface ModelConfig { } export interface ModelMeta { + toolIds: never[]; description?: string; capabilities?: object; profile_image_url?: string; diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index 1c9ce46e24..fa3cab44c3 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -3,6 +3,7 @@ import { getContext, onMount } from 'svelte'; const i18n = getContext('i18n'); + import { settings } from '$lib/stores'; import Modal from '$lib/components/common/Modal.svelte'; import Plus from '$lib/components/icons/Plus.svelte'; import Minus from '$lib/components/icons/Minus.svelte'; @@ -153,15 +154,16 @@
-
+

{#if edit} {$i18n.t('Edit Connection')} {:else} {$i18n.t('Add Connection')} {/if} -

+
{#if models[selectedModelIdx]?.info?.meta?.user} diff --git a/src/lib/components/chat/Controls/Controls.svelte b/src/lib/components/chat/Controls/Controls.svelte index 915d36f08d..1954493d1d 100644 --- a/src/lib/components/chat/Controls/Controls.svelte +++ b/src/lib/components/chat/Controls/Controls.svelte @@ -9,7 +9,7 @@ import FileItem from '$lib/components/common/FileItem.svelte'; import Collapsible from '$lib/components/common/Collapsible.svelte'; - import { user } from '$lib/stores'; + import { user, settings } from '$lib/stores'; export let models = []; export let chatFiles = []; export let params = {}; @@ -74,7 +74,9 @@