diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 7061fd2528..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" ) 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/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/images.py b/backend/open_webui/routers/images.py index ca949fd936..fc1c1b9a5a 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -303,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( @@ -472,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: diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index e3210ae5f3..ab35a673fc 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -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 @@ -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}" @@ -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/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/src/lib/apis/index.ts b/src/lib/apis/index.ts index 66572f20ce..d10db74f8c 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1593,7 +1593,7 @@ export interface ModelMeta { profile_image_url?: string; } -export interface ModelParams { } +export interface ModelParams {} export type GlobalModelConfig = ModelConfig[]; diff --git a/src/lib/components/admin/Evaluations/FeedbackModal.svelte b/src/lib/components/admin/Evaluations/FeedbackModal.svelte index 3469e7b26b..f6242327e9 100644 --- a/src/lib/components/admin/Evaluations/FeedbackModal.svelte +++ b/src/lib/components/admin/Evaluations/FeedbackModal.svelte @@ -37,26 +37,12 @@
-
-
{$i18n.t('Rating')}
- -
- {selectedFeedback?.data?.details?.rating ?? '-'} -
-
-
-
{$i18n.t('Reason')}
- -
- {selectedFeedback?.data?.reason || '-'} -
-
- -
+
{#if selectedFeedback?.data?.tags && selectedFeedback?.data?.tags.length}
{#each selectedFeedback?.data?.tags as tag} - {tag}{tag} {/each}
@@ -64,7 +50,23 @@ - {/if}
-
+ +
+
{$i18n.t('Rating')}
+ +
+ {selectedFeedback?.data?.details?.rating ?? '-'} +
+
+
+
{$i18n.t('Reason')}
+ +
+ {selectedFeedback?.data?.reason || '-'} +
+
+ +
{#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 d34ba33972..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 @@