Skip to content

Commit 7a7a257

Browse files
authored
Merge pull request open-webui#21314 from open-webui/dev
0.8.0
2 parents 2b26355 + 9fc1658 commit 7a7a257

337 files changed

Lines changed: 29882 additions & 6369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pull_request_template.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
<!--
2+
⚠️ CRITICAL CHECKS FOR CONTRIBUTORS (READ, DON'T DELETE) ⚠️
3+
1. Target the `dev` branch. PRs targeting `main` will be automatically closed.
4+
2. Do NOT delete the CLA section at the bottom. It is required for the bot to accept your PR.
5+
-->
6+
17
# Pull Request Checklist
28

39
### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) to discuss your idea/fix with the community before creating a pull request, and describe your changes before submitting a pull request.
@@ -6,14 +12,16 @@ This is to ensure large feature PRs are discussed with the community first, befo
612

713
**Before submitting, make sure you've checked the following:**
814

9-
- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. **Not targeting the `dev` branch will lead to immediate closure of the PR.**
15+
- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. **PRs targeting `main` will be immediately closed.**
1016
- [ ] **Description:** Provide a concise description of the changes made in this pull request down below.
1117
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
12-
- [ ] **Documentation:** If necessary, update relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs) like environment variables, the tutorials, or other documentation sources.
13-
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
14-
- [ ] **Testing:** Perform manual tests to **verify the implemented fix/feature works as intended AND does not break any other functionality**. Take this as an opportunity to **make screenshots of the feature/fix and include it in the PR description**.
18+
- [ ] **Documentation:** Add docs in [Open WebUI Docs Repository](https://github.com/open-webui/docs). Document user-facing behavior, environment variables, public APIs/interfaces, or deployment steps.
19+
- [ ] **Dependencies:** Are there any new or upgraded dependencies? If so, explain why, update the changelog/docs, and include any compatibility notes. Actually run the code/function that uses updated library to ensure it doesn't crash.
20+
- [ ] **Testing:** Perform manual tests to **verify the implemented fix/feature works as intended AND does not break any other functionality**. Include reproducible steps to demonstrate the issue before the fix. Test edge cases (URL encoding, HTML entities, types). Take this as an opportunity to **make screenshots of the feature/fix and include them in the PR description**.
1521
- [ ] **Agentic AI Code:** Confirm this Pull Request is **not written by any AI Agent** or has at least **gone through additional human review AND manual testing**. If any AI Agent is the co-author of this PR, it may lead to immediate closure of the PR.
1622
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
23+
- [ ] **Design & Architecture:** Prefer smart defaults over adding new settings; use local state for ephemeral UI logic. Open a Discussion for major architectural or UX changes.
24+
- [ ] **Git Hygiene:** Keep PRs atomic (one logical change). Clean up commits and rebase on `dev` to ensure no unrelated commits (e.g. from `main`) are included. Push updates to the existing PR branch instead of closing and reopening.
1725
- [ ] **Title Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
1826
- **BREAKING CHANGE**: Significant changes that may affect compatibility
1927
- **build**: Changes that affect the build system or external dependencies
@@ -76,6 +84,12 @@ This is to ensure large feature PRs are discussed with the community first, befo
7684

7785
### Contributor License Agreement
7886

87+
<!--
88+
🚨 DO NOT DELETE THE TEXT BELOW 🚨
89+
Keep the "Contributor License Agreement" confirmation text intact.
90+
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.
91+
-->
92+
7993
By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
8094

8195
> [!NOTE]

CHANGELOG.md

Lines changed: 150 additions & 0 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ RUN apt-get update && \
128128
apt-get install -y --no-install-recommends \
129129
git build-essential pandoc gcc netcat-openbsd curl jq \
130130
python3-dev \
131-
ffmpeg libsm6 libxext6 \
131+
ffmpeg libsm6 libxext6 zstd \
132132
&& rm -rf /var/lib/apt/lists/*
133133

134134
# install python dependencies
@@ -143,6 +143,7 @@ RUN pip3 install --no-cache-dir uv && \
143143
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
144144
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
145145
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
146+
python -c "import nltk; nltk.download('punkt_tab')"; \
146147
else \
147148
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
148149
uv pip install --system -r requirements.txt --no-cache-dir && \
@@ -151,6 +152,7 @@ RUN pip3 install --no-cache-dir uv && \
151152
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
152153
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
153154
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
155+
python -c "import nltk; nltk.download('punkt_tab')"; \
154156
fi; \
155157
fi; \
156158
mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ && \

backend/open_webui/config.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import logging
33
import os
44
import shutil
5+
import socket
56
import base64
7+
from concurrent.futures import ThreadPoolExecutor
68
import redis
79

810
from datetime import datetime
@@ -257,16 +259,16 @@ def __setattr__(self, key, value):
257259
self._state[key].value = value
258260
self._state[key].save()
259261

260-
if self._redis:
262+
if self._redis and ENABLE_PERSISTENT_CONFIG:
261263
redis_key = f"{self._redis_key_prefix}:config:{key}"
262264
self._redis.set(redis_key, json.dumps(self._state[key].value))
263265

264266
def __getattr__(self, key):
265267
if key not in self._state:
266268
raise AttributeError(f"Config key '{key}' not found")
267269

268-
# If Redis is available, check for an updated value
269-
if self._redis:
270+
# If Redis is available and persistent config is enabled, check for an updated value
271+
if self._redis and ENABLE_PERSISTENT_CONFIG:
270272
redis_key = f"{self._redis_key_prefix}:config:{key}"
271273
redis_value = self._redis.get(redis_key)
272274

@@ -1015,6 +1017,39 @@ def feishu_oauth_register(oauth: OAuth):
10151017
OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
10161018

10171019

1020+
def _resolve_ollama_base_url(url: str) -> str:
1021+
"""If the default Ollama port (11434) is unreachable, try the fallback port (12434)."""
1022+
1023+
def reachable(host: str, port: int) -> bool:
1024+
try:
1025+
with socket.create_connection((host, port), timeout=1.0):
1026+
return True
1027+
except (OSError, TimeoutError):
1028+
return False
1029+
1030+
host = urlparse(url).hostname or "localhost"
1031+
1032+
with ThreadPoolExecutor(max_workers=2) as pool:
1033+
default = pool.submit(reachable, host, 11434)
1034+
fallback = pool.submit(reachable, host, 12434)
1035+
1036+
if not default.result() and fallback.result():
1037+
url = url.replace(":11434", ":12434")
1038+
log.info(f"Ollama port 11434 unreachable on {host}, falling back to 12434")
1039+
elif not default.result():
1040+
log.info(f"Ollama ports 11434 and 12434 both unreachable on {host}")
1041+
1042+
return url
1043+
1044+
1045+
# Auto-resolve Ollama port when no explicit URL was provided by the user.
1046+
# The Dockerfile default is "/ollama" which the block above rewrites to :11434.
1047+
if os.environ.get("OLLAMA_BASE_URL", "") in ("", "/ollama") and not os.environ.get(
1048+
"OLLAMA_BASE_URLS", ""
1049+
):
1050+
OLLAMA_BASE_URL = _resolve_ollama_base_url(OLLAMA_BASE_URL)
1051+
1052+
10181053
OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
10191054
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
10201055

@@ -1265,6 +1300,11 @@ def feishu_oauth_register(oauth: OAuth):
12651300
os.environ.get("USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS", "False").lower() == "true"
12661301
)
12671302

1303+
USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS = (
1304+
os.environ.get("USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS", "False").lower()
1305+
== "true"
1306+
)
1307+
12681308
USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT = (
12691309
os.environ.get("USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT", "False").lower()
12701310
== "true"
@@ -1486,6 +1526,7 @@ def feishu_oauth_register(oauth: OAuth):
14861526
"knowledge": USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS,
14871527
"prompts": USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS,
14881528
"tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS,
1529+
"skills": USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS,
14891530
"models_import": USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT,
14901531
"models_export": USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT,
14911532
"prompts_import": USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT,
@@ -2246,9 +2287,15 @@ class BannerModel(BaseModel):
22462287
QDRANT_COLLECTION_PREFIX = os.environ.get("QDRANT_COLLECTION_PREFIX", "open-webui")
22472288

22482289
WEAVIATE_HTTP_HOST = os.environ.get("WEAVIATE_HTTP_HOST", "")
2290+
WEAVIATE_GRPC_HOST = os.environ.get("WEAVIATE_GRPC_HOST", "")
22492291
WEAVIATE_HTTP_PORT = int(os.environ.get("WEAVIATE_HTTP_PORT", "8080"))
22502292
WEAVIATE_GRPC_PORT = int(os.environ.get("WEAVIATE_GRPC_PORT", "50051"))
22512293
WEAVIATE_API_KEY = os.environ.get("WEAVIATE_API_KEY")
2294+
WEAVIATE_HTTP_SECURE = os.environ.get("WEAVIATE_HTTP_SECURE", "false").lower() == "true"
2295+
WEAVIATE_GRPC_SECURE = os.environ.get("WEAVIATE_GRPC_SECURE", "false").lower() == "true"
2296+
WEAVIATE_SKIP_INIT_CHECKS = (
2297+
os.environ.get("WEAVIATE_SKIP_INIT_CHECKS", "false").lower() == "true"
2298+
)
22522299

22532300
# OpenSearch
22542301
OPENSEARCH_URI = os.environ.get("OPENSEARCH_URI", "https://localhost:9200")
@@ -2805,6 +2852,12 @@ class BannerModel(BaseModel):
28052852
os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
28062853
)
28072854

2855+
PDF_LOADER_MODE = PersistentConfig(
2856+
"PDF_LOADER_MODE",
2857+
"rag.pdf_loader_mode",
2858+
os.environ.get("PDF_LOADER_MODE", "page"),
2859+
)
2860+
28082861
RAG_EMBEDDING_MODEL = PersistentConfig(
28092862
"RAG_EMBEDDING_MODEL",
28102863
"rag.embedding_model",
@@ -3400,6 +3453,24 @@ class BannerModel(BaseModel):
34003453
os.environ.get("EXTERNAL_WEB_LOADER_API_KEY", ""),
34013454
)
34023455

3456+
YANDEX_WEB_SEARCH_URL = PersistentConfig(
3457+
"YANDEX_WEB_SEARCH_URL",
3458+
"rag.web.search.yandex_web_search_url",
3459+
os.environ.get("YANDEX_WEB_SEARCH_URL", ""),
3460+
)
3461+
3462+
YANDEX_WEB_SEARCH_API_KEY = PersistentConfig(
3463+
"YANDEX_WEB_SEARCH_API_KEY",
3464+
"rag.web.search.yandex_web_search_api_key",
3465+
os.environ.get("YANDEX_WEB_SEARCH_API_KEY", ""),
3466+
)
3467+
3468+
YANDEX_WEB_SEARCH_CONFIG = PersistentConfig(
3469+
"YANDEX_WEB_SEARCH_CONFIG",
3470+
"rag.web.search.yandex_web_search_config",
3471+
os.environ.get("YANDEX_WEB_SEARCH_CONFIG", ""),
3472+
)
3473+
34033474
####################################
34043475
# Images
34053476
####################################
@@ -3422,6 +3493,16 @@ class BannerModel(BaseModel):
34223493
os.getenv("IMAGE_GENERATION_MODEL", ""),
34233494
)
34243495

3496+
# Regex pattern for models that support IMAGE_SIZE = "auto".
3497+
IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv(
3498+
"IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN", "^gpt-image"
3499+
)
3500+
3501+
# Regex pattern for models that return URLs instead of base64 data.
3502+
IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv(
3503+
"IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN", "^gpt-image"
3504+
)
3505+
34253506
IMAGE_SIZE = PersistentConfig(
34263507
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
34273508
)

backend/open_webui/env.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,35 @@ def parse_section(section):
194194
os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
195195
)
196196

197+
# Header names for user info forwarding (customizable via environment variables)
198+
FORWARD_USER_INFO_HEADER_USER_NAME = os.environ.get(
199+
"FORWARD_USER_INFO_HEADER_USER_NAME", "X-OpenWebUI-User-Name"
200+
)
201+
FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get(
202+
"FORWARD_USER_INFO_HEADER_USER_ID", "X-OpenWebUI-User-Id"
203+
)
204+
FORWARD_USER_INFO_HEADER_USER_EMAIL = os.environ.get(
205+
"FORWARD_USER_INFO_HEADER_USER_EMAIL", "X-OpenWebUI-User-Email"
206+
)
207+
FORWARD_USER_INFO_HEADER_USER_ROLE = os.environ.get(
208+
"FORWARD_USER_INFO_HEADER_USER_ROLE", "X-OpenWebUI-User-Role"
209+
)
210+
211+
# Header name for chat ID forwarding (customizable via environment variable)
212+
FORWARD_SESSION_INFO_HEADER_MESSAGE_ID = os.environ.get(
213+
"FORWARD_SESSION_INFO_HEADER_MESSAGE_ID", "X-OpenWebUI-Message-Id"
214+
)
215+
FORWARD_SESSION_INFO_HEADER_CHAT_ID = os.environ.get(
216+
"FORWARD_SESSION_INFO_HEADER_CHAT_ID", "X-OpenWebUI-Chat-Id"
217+
)
218+
197219
# Experimental feature, may be removed in future
198220
ENABLE_STAR_SESSIONS_MIDDLEWARE = (
199221
os.environ.get("ENABLE_STAR_SESSIONS_MIDDLEWARE", "False").lower() == "true"
200222
)
201223

224+
ENABLE_EASTER_EGGS = os.environ.get("ENABLE_EASTER_EGGS", "True").lower() == "true"
225+
202226
####################################
203227
# WEBUI_BUILD_HASH
204228
####################################
@@ -391,6 +415,18 @@ def parse_section(section):
391415
except ValueError:
392416
REDIS_SOCKET_CONNECT_TIMEOUT = None
393417

418+
REDIS_RECONNECT_DELAY = os.environ.get("REDIS_RECONNECT_DELAY", "")
419+
420+
if REDIS_RECONNECT_DELAY == "":
421+
REDIS_RECONNECT_DELAY = None
422+
else:
423+
try:
424+
REDIS_RECONNECT_DELAY = float(REDIS_RECONNECT_DELAY)
425+
if REDIS_RECONNECT_DELAY < 0:
426+
REDIS_RECONNECT_DELAY = None
427+
except Exception:
428+
REDIS_RECONNECT_DELAY = None
429+
394430
####################################
395431
# UVICORN WORKERS
396432
####################################
@@ -455,6 +491,8 @@ def parse_section(section):
455491
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$"
456492
)
457493

494+
PASSWORD_VALIDATION_HINT = os.environ.get("PASSWORD_VALIDATION_HINT", "")
495+
458496

459497
BYPASS_MODEL_ACCESS_CONTROL = (
460498
os.environ.get("BYPASS_MODEL_ACCESS_CONTROL", "False").lower() == "true"
@@ -519,6 +557,12 @@ def parse_section(section):
519557
"OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY
520558
)
521559

560+
# Token Exchange Configuration
561+
# Allows external apps to exchange OAuth tokens for OpenWebUI tokens
562+
ENABLE_OAUTH_TOKEN_EXCHANGE = (
563+
os.environ.get("ENABLE_OAUTH_TOKEN_EXCHANGE", "False").lower() == "true"
564+
)
565+
522566
####################################
523567
# SCIM Configuration
524568
####################################
@@ -545,15 +589,11 @@ def parse_section(section):
545589

546590
pk = None
547591
if LICENSE_PUBLIC_KEY:
548-
pk = serialization.load_pem_public_key(
549-
f"""
592+
pk = serialization.load_pem_public_key(f"""
550593
-----BEGIN PUBLIC KEY-----
551594
{LICENSE_PUBLIC_KEY}
552595
-----END PUBLIC KEY-----
553-
""".encode(
554-
"utf-8"
555-
)
556-
)
596+
""".encode("utf-8"))
557597

558598

559599
####################################
@@ -673,7 +713,11 @@ def parse_section(section):
673713
os.environ.get("WEBSOCKET_SERVER_LOGGING", "False").lower() == "true"
674714
)
675715
WEBSOCKET_SERVER_ENGINEIO_LOGGING = (
676-
os.environ.get("WEBSOCKET_SERVER_LOGGING", "False").lower() == "true"
716+
os.environ.get(
717+
"WEBSOCKET_SERVER_ENGINEIO_LOGGING",
718+
os.environ.get("WEBSOCKET_SERVER_LOGGING", "False"),
719+
).lower()
720+
== "true"
677721
)
678722
WEBSOCKET_SERVER_PING_TIMEOUT = os.environ.get("WEBSOCKET_SERVER_PING_TIMEOUT", "20")
679723
try:
@@ -739,6 +783,17 @@ def parse_section(section):
739783
)
740784

741785

786+
RAG_EMBEDDING_TIMEOUT = os.environ.get("RAG_EMBEDDING_TIMEOUT", "")
787+
788+
if RAG_EMBEDDING_TIMEOUT == "":
789+
RAG_EMBEDDING_TIMEOUT = None
790+
else:
791+
try:
792+
RAG_EMBEDDING_TIMEOUT = int(RAG_EMBEDDING_TIMEOUT)
793+
except Exception:
794+
RAG_EMBEDDING_TIMEOUT = None
795+
796+
742797
####################################
743798
# SENTENCE TRANSFORMERS
744799
####################################

backend/open_webui/functions.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
get_function_module_from_cache,
3636
)
3737
from open_webui.utils.tools import get_tools
38-
from open_webui.utils.access_control import has_access
3938

4039
from open_webui.env import GLOBAL_LOG_LEVEL
4140

@@ -51,7 +50,6 @@
5150
apply_system_prompt_to_body,
5251
)
5352

54-
5553
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
5654
log = logging.getLogger(__name__)
5755

backend/open_webui/internal/migrations/001_initial_schema.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import peewee as pw
3030
from peewee_migrate import Migrator
3131

32-
3332
with suppress(ImportError):
3433
import playhouse.postgres_ext as pw_pext
3534

backend/open_webui/internal/migrations/002_add_local_sharing.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import peewee as pw
3030
from peewee_migrate import Migrator
3131

32-
3332
with suppress(ImportError):
3433
import playhouse.postgres_ext as pw_pext
3534

backend/open_webui/internal/migrations/003_add_auth_api_key.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import peewee as pw
3030
from peewee_migrate import Migrator
3131

32-
3332
with suppress(ImportError):
3433
import playhouse.postgres_ext as pw_pext
3534

0 commit comments

Comments
 (0)