olipericles commited on
Commit
b56d671
·
1 Parent(s): 0228ca8

Primeiro commit

Browse files
Files changed (3) hide show
  1. .gitignore +8 -0
  2. docker-compose.yaml +46 -0
  3. open-webui/config/config.py +1431 -0
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Ignorar arquivos .env
2
+ .env
3
+
4
+ # Ignorar a pasta langflow/
5
+ langflow/
6
+
7
+ # Ignorar a pasta postgres/
8
+ postgres/
docker-compose.yaml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ webui:
3
+ image: ghcr.io/open-webui/open-webui:main
4
+ pull_policy: always
5
+ restart: always
6
+ volumes:
7
+ - ./open-webui/config/config.py:/app/backend/config.py
8
+ - ./open-webui/images/favicon.png:/app/build/static/favicon.png
9
+ - ./open-webui/images/favicon.png:/app/build/favicon.png
10
+ - ./open-webui/_data:/app/backend/data
11
+ ports:
12
+ - 8080:8080
13
+ env_file:
14
+ - .env
15
+ depends_on:
16
+ - langflow
17
+ - postgres
18
+
19
+ langflow:
20
+ image: langflowai/langflow:latest
21
+ pull_policy: always
22
+ restart: always
23
+ volumes:
24
+ - ./langflow/data:/app/langflow
25
+ ports:
26
+ - 7860:7860
27
+ env_file:
28
+ - .env
29
+ depends_on:
30
+ - postgres
31
+ user: "1000:1000"
32
+
33
+ postgres:
34
+ image: postgres:16
35
+ pull_policy: always
36
+ restart: always
37
+ ports:
38
+ - "5432:5432"
39
+ volumes:
40
+ - ./postgres:/var/lib/postgresql/data
41
+ - ./postgres/initdb:/docker-entrypoint-initdb.d
42
+ env_file:
43
+ - .env
44
+ environment:
45
+ POSTGRES_USER: ${POSTGRES_USER}
46
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
open-webui/config/config.py ADDED
@@ -0,0 +1,1431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import logging
4
+ import importlib.metadata
5
+ import pkgutil
6
+ import chromadb
7
+ from chromadb import Settings
8
+ from bs4 import BeautifulSoup
9
+ from typing import TypeVar, Generic
10
+ from pydantic import BaseModel
11
+ from typing import Optional
12
+
13
+ from pathlib import Path
14
+ import json
15
+ import yaml
16
+
17
+ import markdown
18
+ import requests
19
+ import shutil
20
+
21
+ from constants import ERROR_MESSAGES
22
+
23
+ ####################################
24
+ # Load .env file
25
+ ####################################
26
+
27
+ BACKEND_DIR = Path(__file__).parent # the path containing this file
28
+ BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
29
+
30
+ print(BASE_DIR)
31
+
32
+ try:
33
+ from dotenv import load_dotenv, find_dotenv
34
+
35
+ load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
36
+ except ImportError:
37
+ print("dotenv not installed, skipping...")
38
+
39
+
40
+ ####################################
41
+ # LOGGING
42
+ ####################################
43
+
44
+ log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
45
+
46
+ GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
47
+ if GLOBAL_LOG_LEVEL in log_levels:
48
+ logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
49
+ else:
50
+ GLOBAL_LOG_LEVEL = "INFO"
51
+
52
+ log = logging.getLogger(__name__)
53
+ log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
54
+
55
+ log_sources = [
56
+ "AUDIO",
57
+ "COMFYUI",
58
+ "CONFIG",
59
+ "DB",
60
+ "IMAGES",
61
+ "MAIN",
62
+ "MODELS",
63
+ "OLLAMA",
64
+ "OPENAI",
65
+ "RAG",
66
+ "WEBHOOK",
67
+ ]
68
+
69
+ SRC_LOG_LEVELS = {}
70
+
71
+ for source in log_sources:
72
+ log_env_var = source + "_LOG_LEVEL"
73
+ SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
74
+ if SRC_LOG_LEVELS[source] not in log_levels:
75
+ SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
76
+ log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
77
+
78
+ log.setLevel(SRC_LOG_LEVELS["CONFIG"])
79
+
80
+
81
+ class EndpointFilter(logging.Filter):
82
+ def filter(self, record: logging.LogRecord) -> bool:
83
+ return record.getMessage().find("/health") == -1
84
+
85
+
86
+ # Filter out /endpoint
87
+ logging.getLogger("uvicorn.access").addFilter(EndpointFilter())
88
+
89
+
90
+ WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
91
+ if WEBUI_NAME != "Open WebUI":
92
+ WEBUI_NAME += " (Open WebUI)"
93
+
94
+ WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")
95
+ WEBUI_NAME = "ChatBotando"
96
+ WEBUI_FAVICON_URL = "/app/build/favicon.png"
97
+
98
+
99
+ ####################################
100
+ # ENV (dev,test,prod)
101
+ ####################################
102
+
103
+ ENV = os.environ.get("ENV", "dev")
104
+
105
+ try:
106
+ PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
107
+ except Exception:
108
+ try:
109
+ PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
110
+ except importlib.metadata.PackageNotFoundError:
111
+ PACKAGE_DATA = {"version": "0.0.0"}
112
+
113
+ VERSION = PACKAGE_DATA["version"]
114
+
115
+
116
+ # Function to parse each section
117
+ def parse_section(section):
118
+ items = []
119
+ for li in section.find_all("li"):
120
+ # Extract raw HTML string
121
+ raw_html = str(li)
122
+
123
+ # Extract text without HTML tags
124
+ text = li.get_text(separator=" ", strip=True)
125
+
126
+ # Split into title and content
127
+ parts = text.split(": ", 1)
128
+ title = parts[0].strip() if len(parts) > 1 else ""
129
+ content = parts[1].strip() if len(parts) > 1 else text
130
+
131
+ items.append({"title": title, "content": content, "raw": raw_html})
132
+ return items
133
+
134
+
135
+ try:
136
+ changelog_path = BASE_DIR / "CHANGELOG.md"
137
+ with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
138
+ changelog_content = file.read()
139
+
140
+ except Exception:
141
+ changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
142
+
143
+
144
+ # Convert markdown content to HTML
145
+ html_content = markdown.markdown(changelog_content)
146
+
147
+ # Parse the HTML content
148
+ soup = BeautifulSoup(html_content, "html.parser")
149
+
150
+ # Initialize JSON structure
151
+ changelog_json = {}
152
+
153
+ # Iterate over each version
154
+ for version in soup.find_all("h2"):
155
+ version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
156
+ date = version.get_text().strip().split(" - ")[1]
157
+
158
+ version_data = {"date": date}
159
+
160
+ # Find the next sibling that is a h3 tag (section title)
161
+ current = version.find_next_sibling()
162
+
163
+ while current and current.name != "h2":
164
+ if current.name == "h3":
165
+ section_title = current.get_text().lower() # e.g., "added", "fixed"
166
+ section_items = parse_section(current.find_next_sibling("ul"))
167
+ version_data[section_title] = section_items
168
+
169
+ # Move to the next element
170
+ current = current.find_next_sibling()
171
+
172
+ changelog_json[version_number] = version_data
173
+
174
+
175
+ CHANGELOG = changelog_json
176
+
177
+
178
+ ####################################
179
+ # SAFE_MODE
180
+ ####################################
181
+
182
+ SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
183
+
184
+ ####################################
185
+ # WEBUI_BUILD_HASH
186
+ ####################################
187
+
188
+ WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
189
+
190
+ ####################################
191
+ # DATA/FRONTEND BUILD DIR
192
+ ####################################
193
+
194
+ DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
195
+ FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
196
+
197
+ RESET_CONFIG_ON_START = (
198
+ os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
199
+ )
200
+ if RESET_CONFIG_ON_START:
201
+ try:
202
+ os.remove(f"{DATA_DIR}/config.json")
203
+ with open(f"{DATA_DIR}/config.json", "w") as f:
204
+ f.write("{}")
205
+ except Exception:
206
+ pass
207
+
208
+ try:
209
+ CONFIG_DATA = json.loads((DATA_DIR / "config.json").read_text())
210
+ except Exception:
211
+ CONFIG_DATA = {}
212
+
213
+
214
+ ####################################
215
+ # Config helpers
216
+ ####################################
217
+
218
+
219
+ def save_config():
220
+ try:
221
+ with open(f"{DATA_DIR}/config.json", "w") as f:
222
+ json.dump(CONFIG_DATA, f, indent="\t")
223
+ except Exception as e:
224
+ log.exception(e)
225
+
226
+
227
+ def get_config_value(config_path: str):
228
+ path_parts = config_path.split(".")
229
+ cur_config = CONFIG_DATA
230
+ for key in path_parts:
231
+ if key in cur_config:
232
+ cur_config = cur_config[key]
233
+ else:
234
+ return None
235
+ return cur_config
236
+
237
+
238
+ T = TypeVar("T")
239
+
240
+
241
+ class PersistentConfig(Generic[T]):
242
+ def __init__(self, env_name: str, config_path: str, env_value: T):
243
+ self.env_name = env_name
244
+ self.config_path = config_path
245
+ self.env_value = env_value
246
+ self.config_value = get_config_value(config_path)
247
+ if self.config_value is not None:
248
+ log.info(f"'{env_name}' loaded from config.json")
249
+ self.value = self.config_value
250
+ else:
251
+ self.value = env_value
252
+
253
+ def __str__(self):
254
+ return str(self.value)
255
+
256
+ @property
257
+ def __dict__(self):
258
+ raise TypeError(
259
+ "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
260
+ )
261
+
262
+ def __getattribute__(self, item):
263
+ if item == "__dict__":
264
+ raise TypeError(
265
+ "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
266
+ )
267
+ return super().__getattribute__(item)
268
+
269
+ def save(self):
270
+ # Don't save if the value is the same as the env value and the config value
271
+ if self.env_value == self.value:
272
+ if self.config_value == self.value:
273
+ return
274
+ log.info(f"Saving '{self.env_name}' to config.json")
275
+ path_parts = self.config_path.split(".")
276
+ config = CONFIG_DATA
277
+ for key in path_parts[:-1]:
278
+ if key not in config:
279
+ config[key] = {}
280
+ config = config[key]
281
+ config[path_parts[-1]] = self.value
282
+ save_config()
283
+ self.config_value = self.value
284
+
285
+
286
+ class AppConfig:
287
+ _state: dict[str, PersistentConfig]
288
+
289
+ def __init__(self):
290
+ super().__setattr__("_state", {})
291
+
292
+ def __setattr__(self, key, value):
293
+ if isinstance(value, PersistentConfig):
294
+ self._state[key] = value
295
+ else:
296
+ self._state[key].value = value
297
+ self._state[key].save()
298
+
299
+ def __getattr__(self, key):
300
+ return self._state[key].value
301
+
302
+
303
+ ####################################
304
+ # WEBUI_AUTH (Required for security)
305
+ ####################################
306
+
307
+ WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
308
+ WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
309
+ "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
310
+ )
311
+ WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
312
+ JWT_EXPIRES_IN = PersistentConfig(
313
+ "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
314
+ )
315
+
316
+ ####################################
317
+ # OAuth config
318
+ ####################################
319
+
320
+ ENABLE_OAUTH_SIGNUP = PersistentConfig(
321
+ "ENABLE_OAUTH_SIGNUP",
322
+ "oauth.enable_signup",
323
+ os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true",
324
+ )
325
+
326
+ OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig(
327
+ "OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
328
+ "oauth.merge_accounts_by_email",
329
+ os.environ.get("OAUTH_MERGE_ACCOUNTS_BY_EMAIL", "False").lower() == "true",
330
+ )
331
+
332
+ OAUTH_PROVIDERS = {}
333
+
334
+ GOOGLE_CLIENT_ID = PersistentConfig(
335
+ "GOOGLE_CLIENT_ID",
336
+ "oauth.google.client_id",
337
+ os.environ.get("GOOGLE_CLIENT_ID", ""),
338
+ )
339
+
340
+ GOOGLE_CLIENT_SECRET = PersistentConfig(
341
+ "GOOGLE_CLIENT_SECRET",
342
+ "oauth.google.client_secret",
343
+ os.environ.get("GOOGLE_CLIENT_SECRET", ""),
344
+ )
345
+
346
+ GOOGLE_OAUTH_SCOPE = PersistentConfig(
347
+ "GOOGLE_OAUTH_SCOPE",
348
+ "oauth.google.scope",
349
+ os.environ.get("GOOGLE_OAUTH_SCOPE", "openid email profile"),
350
+ )
351
+
352
+ GOOGLE_REDIRECT_URI = PersistentConfig(
353
+ "GOOGLE_REDIRECT_URI",
354
+ "oauth.google.redirect_uri",
355
+ os.environ.get("GOOGLE_REDIRECT_URI", ""),
356
+ )
357
+
358
+ MICROSOFT_CLIENT_ID = PersistentConfig(
359
+ "MICROSOFT_CLIENT_ID",
360
+ "oauth.microsoft.client_id",
361
+ os.environ.get("MICROSOFT_CLIENT_ID", ""),
362
+ )
363
+
364
+ MICROSOFT_CLIENT_SECRET = PersistentConfig(
365
+ "MICROSOFT_CLIENT_SECRET",
366
+ "oauth.microsoft.client_secret",
367
+ os.environ.get("MICROSOFT_CLIENT_SECRET", ""),
368
+ )
369
+
370
+ MICROSOFT_CLIENT_TENANT_ID = PersistentConfig(
371
+ "MICROSOFT_CLIENT_TENANT_ID",
372
+ "oauth.microsoft.tenant_id",
373
+ os.environ.get("MICROSOFT_CLIENT_TENANT_ID", ""),
374
+ )
375
+
376
+ MICROSOFT_OAUTH_SCOPE = PersistentConfig(
377
+ "MICROSOFT_OAUTH_SCOPE",
378
+ "oauth.microsoft.scope",
379
+ os.environ.get("MICROSOFT_OAUTH_SCOPE", "openid email profile"),
380
+ )
381
+
382
+ MICROSOFT_REDIRECT_URI = PersistentConfig(
383
+ "MICROSOFT_REDIRECT_URI",
384
+ "oauth.microsoft.redirect_uri",
385
+ os.environ.get("MICROSOFT_REDIRECT_URI", ""),
386
+ )
387
+
388
+ OAUTH_CLIENT_ID = PersistentConfig(
389
+ "OAUTH_CLIENT_ID",
390
+ "oauth.oidc.client_id",
391
+ os.environ.get("OAUTH_CLIENT_ID", ""),
392
+ )
393
+
394
+ OAUTH_CLIENT_SECRET = PersistentConfig(
395
+ "OAUTH_CLIENT_SECRET",
396
+ "oauth.oidc.client_secret",
397
+ os.environ.get("OAUTH_CLIENT_SECRET", ""),
398
+ )
399
+
400
+ OPENID_PROVIDER_URL = PersistentConfig(
401
+ "OPENID_PROVIDER_URL",
402
+ "oauth.oidc.provider_url",
403
+ os.environ.get("OPENID_PROVIDER_URL", ""),
404
+ )
405
+
406
+ OPENID_REDIRECT_URI = PersistentConfig(
407
+ "OPENID_REDIRECT_URI",
408
+ "oauth.oidc.redirect_uri",
409
+ os.environ.get("OPENID_REDIRECT_URI", ""),
410
+ )
411
+
412
+ OAUTH_SCOPES = PersistentConfig(
413
+ "OAUTH_SCOPES",
414
+ "oauth.oidc.scopes",
415
+ os.environ.get("OAUTH_SCOPES", "openid email profile"),
416
+ )
417
+
418
+ OAUTH_PROVIDER_NAME = PersistentConfig(
419
+ "OAUTH_PROVIDER_NAME",
420
+ "oauth.oidc.provider_name",
421
+ os.environ.get("OAUTH_PROVIDER_NAME", "SSO"),
422
+ )
423
+
424
+ OAUTH_USERNAME_CLAIM = PersistentConfig(
425
+ "OAUTH_USERNAME_CLAIM",
426
+ "oauth.oidc.username_claim",
427
+ os.environ.get("OAUTH_USERNAME_CLAIM", "name"),
428
+ )
429
+
430
+ OAUTH_PICTURE_CLAIM = PersistentConfig(
431
+ "OAUTH_USERNAME_CLAIM",
432
+ "oauth.oidc.avatar_claim",
433
+ os.environ.get("OAUTH_PICTURE_CLAIM", "picture"),
434
+ )
435
+
436
+ OAUTH_EMAIL_CLAIM = PersistentConfig(
437
+ "OAUTH_EMAIL_CLAIM",
438
+ "oauth.oidc.email_claim",
439
+ os.environ.get("OAUTH_EMAIL_CLAIM", "email"),
440
+ )
441
+
442
+
443
+ def load_oauth_providers():
444
+ OAUTH_PROVIDERS.clear()
445
+ if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value:
446
+ OAUTH_PROVIDERS["google"] = {
447
+ "client_id": GOOGLE_CLIENT_ID.value,
448
+ "client_secret": GOOGLE_CLIENT_SECRET.value,
449
+ "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
450
+ "scope": GOOGLE_OAUTH_SCOPE.value,
451
+ "redirect_uri": GOOGLE_REDIRECT_URI.value,
452
+ }
453
+
454
+ if (
455
+ MICROSOFT_CLIENT_ID.value
456
+ and MICROSOFT_CLIENT_SECRET.value
457
+ and MICROSOFT_CLIENT_TENANT_ID.value
458
+ ):
459
+ OAUTH_PROVIDERS["microsoft"] = {
460
+ "client_id": MICROSOFT_CLIENT_ID.value,
461
+ "client_secret": MICROSOFT_CLIENT_SECRET.value,
462
+ "server_metadata_url": f"https://login.microsoftonline.com/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration",
463
+ "scope": MICROSOFT_OAUTH_SCOPE.value,
464
+ "redirect_uri": MICROSOFT_REDIRECT_URI.value,
465
+ }
466
+
467
+ if (
468
+ OAUTH_CLIENT_ID.value
469
+ and OAUTH_CLIENT_SECRET.value
470
+ and OPENID_PROVIDER_URL.value
471
+ ):
472
+ OAUTH_PROVIDERS["oidc"] = {
473
+ "client_id": OAUTH_CLIENT_ID.value,
474
+ "client_secret": OAUTH_CLIENT_SECRET.value,
475
+ "server_metadata_url": OPENID_PROVIDER_URL.value,
476
+ "scope": OAUTH_SCOPES.value,
477
+ "name": OAUTH_PROVIDER_NAME.value,
478
+ "redirect_uri": OPENID_REDIRECT_URI.value,
479
+ }
480
+
481
+
482
+ load_oauth_providers()
483
+
484
+ ####################################
485
+ # Static DIR
486
+ ####################################
487
+
488
+ STATIC_DIR = Path(os.getenv("STATIC_DIR", BACKEND_DIR / "static")).resolve()
489
+
490
+ frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
491
+
492
+ if frontend_favicon.exists():
493
+ try:
494
+ shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
495
+ except Exception as e:
496
+ logging.error(f"An error occurred: {e}")
497
+ else:
498
+ logging.warning(f"Frontend favicon not found at {frontend_favicon}")
499
+
500
+ frontend_splash = FRONTEND_BUILD_DIR / "static" / "splash.png"
501
+
502
+ if frontend_splash.exists():
503
+ try:
504
+ shutil.copyfile(frontend_splash, STATIC_DIR / "splash.png")
505
+ except Exception as e:
506
+ logging.error(f"An error occurred: {e}")
507
+ else:
508
+ logging.warning(f"Frontend splash not found at {frontend_splash}")
509
+
510
+
511
+ ####################################
512
+ # CUSTOM_NAME
513
+ ####################################
514
+
515
+ CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
516
+
517
+ if CUSTOM_NAME:
518
+ try:
519
+ r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
520
+ data = r.json()
521
+ if r.ok:
522
+ if "logo" in data:
523
+ WEBUI_FAVICON_URL = url = (
524
+ f"https://api.openwebui.com{data['logo']}"
525
+ if data["logo"][0] == "/"
526
+ else data["logo"]
527
+ )
528
+
529
+ r = requests.get(url, stream=True)
530
+ if r.status_code == 200:
531
+ with open(f"{STATIC_DIR}/favicon.png", "wb") as f:
532
+ r.raw.decode_content = True
533
+ shutil.copyfileobj(r.raw, f)
534
+
535
+ if "splash" in data:
536
+ url = (
537
+ f"https://api.openwebui.com{data['splash']}"
538
+ if data["splash"][0] == "/"
539
+ else data["splash"]
540
+ )
541
+
542
+ r = requests.get(url, stream=True)
543
+ if r.status_code == 200:
544
+ with open(f"{STATIC_DIR}/splash.png", "wb") as f:
545
+ r.raw.decode_content = True
546
+ shutil.copyfileobj(r.raw, f)
547
+
548
+ WEBUI_NAME = data["name"]
549
+ except Exception as e:
550
+ log.exception(e)
551
+ pass
552
+
553
+
554
+ ####################################
555
+ # File Upload DIR
556
+ ####################################
557
+
558
+ UPLOAD_DIR = f"{DATA_DIR}/uploads"
559
+ Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
560
+
561
+
562
+ ####################################
563
+ # Cache DIR
564
+ ####################################
565
+
566
+ CACHE_DIR = f"{DATA_DIR}/cache"
567
+ Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
568
+
569
+
570
+ ####################################
571
+ # Docs DIR
572
+ ####################################
573
+
574
+ DOCS_DIR = os.getenv("DOCS_DIR", f"{DATA_DIR}/docs")
575
+ Path(DOCS_DIR).mkdir(parents=True, exist_ok=True)
576
+
577
+
578
+ ####################################
579
+ # Tools DIR
580
+ ####################################
581
+
582
+ TOOLS_DIR = os.getenv("TOOLS_DIR", f"{DATA_DIR}/tools")
583
+ Path(TOOLS_DIR).mkdir(parents=True, exist_ok=True)
584
+
585
+
586
+ ####################################
587
+ # Functions DIR
588
+ ####################################
589
+
590
+ FUNCTIONS_DIR = os.getenv("FUNCTIONS_DIR", f"{DATA_DIR}/functions")
591
+ Path(FUNCTIONS_DIR).mkdir(parents=True, exist_ok=True)
592
+
593
+
594
+ ####################################
595
+ # LITELLM_CONFIG
596
+ ####################################
597
+
598
+
599
+ def create_config_file(file_path):
600
+ directory = os.path.dirname(file_path)
601
+
602
+ # Check if directory exists, if not, create it
603
+ if not os.path.exists(directory):
604
+ os.makedirs(directory)
605
+
606
+ # Data to write into the YAML file
607
+ config_data = {
608
+ "general_settings": {},
609
+ "litellm_settings": {},
610
+ "model_list": [],
611
+ "router_settings": {},
612
+ }
613
+
614
+ # Write data to YAML file
615
+ with open(file_path, "w") as file:
616
+ yaml.dump(config_data, file)
617
+
618
+
619
+ LITELLM_CONFIG_PATH = f"{DATA_DIR}/litellm/config.yaml"
620
+
621
+ # if not os.path.exists(LITELLM_CONFIG_PATH):
622
+ # log.info("Config file doesn't exist. Creating...")
623
+ # create_config_file(LITELLM_CONFIG_PATH)
624
+ # log.info("Config file created successfully.")
625
+
626
+
627
+ ####################################
628
+ # OLLAMA_BASE_URL
629
+ ####################################
630
+
631
+
632
+ ENABLE_OLLAMA_API = PersistentConfig(
633
+ "ENABLE_OLLAMA_API",
634
+ "ollama.enable",
635
+ os.environ.get("ENABLE_OLLAMA_API", "True").lower() == "true",
636
+ )
637
+
638
+ OLLAMA_API_BASE_URL = os.environ.get(
639
+ "OLLAMA_API_BASE_URL", "http://localhost:11434/api"
640
+ )
641
+
642
+ OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
643
+ AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
644
+
645
+ if AIOHTTP_CLIENT_TIMEOUT == "":
646
+ AIOHTTP_CLIENT_TIMEOUT = None
647
+ else:
648
+ try:
649
+ AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
650
+ except Exception:
651
+ AIOHTTP_CLIENT_TIMEOUT = 300
652
+
653
+
654
+ K8S_FLAG = os.environ.get("K8S_FLAG", "")
655
+ USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
656
+
657
+ if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
658
+ OLLAMA_BASE_URL = (
659
+ OLLAMA_API_BASE_URL[:-4]
660
+ if OLLAMA_API_BASE_URL.endswith("/api")
661
+ else OLLAMA_API_BASE_URL
662
+ )
663
+
664
+ if ENV == "prod":
665
+ if OLLAMA_BASE_URL == "/ollama" and not K8S_FLAG:
666
+ if USE_OLLAMA_DOCKER.lower() == "true":
667
+ # if you use all-in-one docker container (Open WebUI + Ollama)
668
+ # with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
669
+ OLLAMA_BASE_URL = "http://localhost:11434"
670
+ else:
671
+ OLLAMA_BASE_URL = "http://host.docker.internal:11434"
672
+ elif K8S_FLAG:
673
+ OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
674
+
675
+
676
+ OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
677
+ OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
678
+
679
+ OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
680
+ OLLAMA_BASE_URLS = PersistentConfig(
681
+ "OLLAMA_BASE_URLS", "ollama.base_urls", OLLAMA_BASE_URLS
682
+ )
683
+
684
+ ####################################
685
+ # OPENAI_API
686
+ ####################################
687
+
688
+
689
+ ENABLE_OPENAI_API = PersistentConfig(
690
+ "ENABLE_OPENAI_API",
691
+ "openai.enable",
692
+ os.environ.get("ENABLE_OPENAI_API", "True").lower() == "true",
693
+ )
694
+
695
+
696
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
697
+ OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
698
+
699
+
700
+ if OPENAI_API_BASE_URL == "":
701
+ OPENAI_API_BASE_URL = "https://api.openai.com/v1"
702
+
703
+ OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "")
704
+ OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY
705
+
706
+ OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
707
+ OPENAI_API_KEYS = PersistentConfig(
708
+ "OPENAI_API_KEYS", "openai.api_keys", OPENAI_API_KEYS
709
+ )
710
+
711
+ OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "")
712
+ OPENAI_API_BASE_URLS = (
713
+ OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
714
+ )
715
+
716
+ OPENAI_API_BASE_URLS = [
717
+ url.strip() if url != "" else "https://api.openai.com/v1"
718
+ for url in OPENAI_API_BASE_URLS.split(";")
719
+ ]
720
+ OPENAI_API_BASE_URLS = PersistentConfig(
721
+ "OPENAI_API_BASE_URLS", "openai.api_base_urls", OPENAI_API_BASE_URLS
722
+ )
723
+
724
+ OPENAI_API_KEY = ""
725
+
726
+ try:
727
+ OPENAI_API_KEY = OPENAI_API_KEYS.value[
728
+ OPENAI_API_BASE_URLS.value.index("https://api.openai.com/v1")
729
+ ]
730
+ except Exception:
731
+ pass
732
+
733
+ OPENAI_API_BASE_URL = "https://api.openai.com/v1"
734
+
735
+ ####################################
736
+ # WEBUI
737
+ ####################################
738
+
739
+ ENABLE_SIGNUP = PersistentConfig(
740
+ "ENABLE_SIGNUP",
741
+ "ui.enable_signup",
742
+ (
743
+ False
744
+ if not WEBUI_AUTH
745
+ else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
746
+ ),
747
+ )
748
+
749
+ ENABLE_LOGIN_FORM = PersistentConfig(
750
+ "ENABLE_LOGIN_FORM",
751
+ "ui.ENABLE_LOGIN_FORM",
752
+ os.environ.get("ENABLE_LOGIN_FORM", "True").lower() == "true",
753
+ )
754
+
755
+ DEFAULT_LOCALE = PersistentConfig(
756
+ "DEFAULT_LOCALE",
757
+ "ui.default_locale",
758
+ os.environ.get("DEFAULT_LOCALE", ""),
759
+ )
760
+
761
+ DEFAULT_MODELS = PersistentConfig(
762
+ "DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
763
+ )
764
+
765
+ DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
766
+ "DEFAULT_PROMPT_SUGGESTIONS",
767
+ "ui.prompt_suggestions",
768
+ [
769
+ {
770
+ "title": ["Help me study", "vocabulary for a college entrance exam"],
771
+ "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
772
+ },
773
+ {
774
+ "title": ["Give me ideas", "for what to do with my kids' art"],
775
+ "content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.",
776
+ },
777
+ {
778
+ "title": ["Tell me a fun fact", "about the Roman Empire"],
779
+ "content": "Tell me a random fun fact about the Roman Empire",
780
+ },
781
+ {
782
+ "title": ["Show me a code snippet", "of a website's sticky header"],
783
+ "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript.",
784
+ },
785
+ {
786
+ "title": [
787
+ "Explain options trading",
788
+ "if I'm familiar with buying and selling stocks",
789
+ ],
790
+ "content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks.",
791
+ },
792
+ {
793
+ "title": ["Overcome procrastination", "give me tips"],
794
+ "content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
795
+ },
796
+ ],
797
+ )
798
+
799
+ DEFAULT_USER_ROLE = PersistentConfig(
800
+ "DEFAULT_USER_ROLE",
801
+ "ui.default_user_role",
802
+ os.getenv("DEFAULT_USER_ROLE", "pending"),
803
+ )
804
+
805
+ USER_PERMISSIONS_CHAT_DELETION = (
806
+ os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
807
+ )
808
+
809
+ USER_PERMISSIONS = PersistentConfig(
810
+ "USER_PERMISSIONS",
811
+ "ui.user_permissions",
812
+ {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}},
813
+ )
814
+
815
+ ENABLE_MODEL_FILTER = PersistentConfig(
816
+ "ENABLE_MODEL_FILTER",
817
+ "model_filter.enable",
818
+ os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
819
+ )
820
+ MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
821
+ MODEL_FILTER_LIST = PersistentConfig(
822
+ "MODEL_FILTER_LIST",
823
+ "model_filter.list",
824
+ [model.strip() for model in MODEL_FILTER_LIST.split(";")],
825
+ )
826
+
827
+ WEBHOOK_URL = PersistentConfig(
828
+ "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
829
+ )
830
+
831
+ ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
832
+
833
+ ENABLE_ADMIN_CHAT_ACCESS = (
834
+ os.environ.get("ENABLE_ADMIN_CHAT_ACCESS", "True").lower() == "true"
835
+ )
836
+
837
+ ENABLE_COMMUNITY_SHARING = PersistentConfig(
838
+ "ENABLE_COMMUNITY_SHARING",
839
+ "ui.enable_community_sharing",
840
+ os.environ.get("ENABLE_COMMUNITY_SHARING", "True").lower() == "true",
841
+ )
842
+
843
+
844
+ class BannerModel(BaseModel):
845
+ id: str
846
+ type: str
847
+ title: Optional[str] = None
848
+ content: str
849
+ dismissible: bool
850
+ timestamp: int
851
+
852
+
853
+ try:
854
+ banners = json.loads(os.environ.get("WEBUI_BANNERS", "[]"))
855
+ banners = [BannerModel(**banner) for banner in banners]
856
+ except Exception as e:
857
+ print(f"Error loading WEBUI_BANNERS: {e}")
858
+ banners = []
859
+
860
+ WEBUI_BANNERS = PersistentConfig("WEBUI_BANNERS", "ui.banners", banners)
861
+
862
+
863
+ SHOW_ADMIN_DETAILS = PersistentConfig(
864
+ "SHOW_ADMIN_DETAILS",
865
+ "auth.admin.show",
866
+ os.environ.get("SHOW_ADMIN_DETAILS", "true").lower() == "true",
867
+ )
868
+
869
+ ADMIN_EMAIL = PersistentConfig(
870
+ "ADMIN_EMAIL",
871
+ "auth.admin.email",
872
+ os.environ.get("ADMIN_EMAIL", None),
873
+ )
874
+
875
+
876
+ ####################################
877
+ # TASKS
878
+ ####################################
879
+
880
+
881
+ TASK_MODEL = PersistentConfig(
882
+ "TASK_MODEL",
883
+ "task.model.default",
884
+ os.environ.get("TASK_MODEL", ""),
885
+ )
886
+
887
+ TASK_MODEL_EXTERNAL = PersistentConfig(
888
+ "TASK_MODEL_EXTERNAL",
889
+ "task.model.external",
890
+ os.environ.get("TASK_MODEL_EXTERNAL", ""),
891
+ )
892
+
893
+ TITLE_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
894
+ "TITLE_GENERATION_PROMPT_TEMPLATE",
895
+ "task.title.prompt_template",
896
+ os.environ.get(
897
+ "TITLE_GENERATION_PROMPT_TEMPLATE",
898
+ """Here is the query:
899
+ {{prompt:middletruncate:8000}}
900
+
901
+ Create a concise, 3-5 word phrase with an emoji as a title for the previous query. Suitable Emojis for the summary can be used to enhance understanding but avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT.
902
+
903
+ Examples of titles:
904
+ 📉 Stock Market Trends
905
+ 🍪 Perfect Chocolate Chip Recipe
906
+ Evolution of Music Streaming
907
+ Remote Work Productivity Tips
908
+ Artificial Intelligence in Healthcare
909
+ 🎮 Video Game Development Insights""",
910
+ ),
911
+ )
912
+
913
+
914
+ SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
915
+ "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE",
916
+ "task.search.prompt_template",
917
+ os.environ.get(
918
+ "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE",
919
+ """You are tasked with generating web search queries. Give me an appropriate query to answer my question for google search. Answer with only the query. Today is {{CURRENT_DATE}}.
920
+
921
+ Question:
922
+ {{prompt:end:4000}}""",
923
+ ),
924
+ )
925
+
926
+ SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD = PersistentConfig(
927
+ "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD",
928
+ "task.search.prompt_length_threshold",
929
+ int(
930
+ os.environ.get(
931
+ "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD",
932
+ 100,
933
+ )
934
+ ),
935
+ )
936
+
937
+ TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
938
+ "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
939
+ "task.tools.prompt_template",
940
+ os.environ.get(
941
+ "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
942
+ """Tools: {{TOOLS}}
943
+ If a function tool doesn't match the query, return an empty string. Else, pick a function tool, fill in the parameters from the function tool's schema, and return it in the format { "name": \"functionName\", "parameters": { "key": "value" } }. Only pick a function if the user asks. Only return the object. Do not return any other text.""",
944
+ ),
945
+ )
946
+
947
+
948
+ ####################################
949
+ # WEBUI_SECRET_KEY
950
+ ####################################
951
+
952
+ WEBUI_SECRET_KEY = os.environ.get(
953
+ "WEBUI_SECRET_KEY",
954
+ os.environ.get(
955
+ "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
956
+ ), # DEPRECATED: remove at next major version
957
+ )
958
+
959
+ WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
960
+ "WEBUI_SESSION_COOKIE_SAME_SITE",
961
+ os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
962
+ )
963
+
964
+ WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
965
+ "WEBUI_SESSION_COOKIE_SECURE",
966
+ os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
967
+ )
968
+
969
+ if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
970
+ raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
971
+
972
+ ####################################
973
+ # RAG document content extraction
974
+ ####################################
975
+
976
+ CONTENT_EXTRACTION_ENGINE = PersistentConfig(
977
+ "CONTENT_EXTRACTION_ENGINE",
978
+ "rag.CONTENT_EXTRACTION_ENGINE",
979
+ os.environ.get("CONTENT_EXTRACTION_ENGINE", "").lower(),
980
+ )
981
+
982
+ TIKA_SERVER_URL = PersistentConfig(
983
+ "TIKA_SERVER_URL",
984
+ "rag.tika_server_url",
985
+ os.getenv("TIKA_SERVER_URL", "http://tika:9998"), # Default for sidecar deployment
986
+ )
987
+
988
+ ####################################
989
+ # RAG
990
+ ####################################
991
+
992
+ CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
993
+ CHROMA_TENANT = os.environ.get("CHROMA_TENANT", chromadb.DEFAULT_TENANT)
994
+ CHROMA_DATABASE = os.environ.get("CHROMA_DATABASE", chromadb.DEFAULT_DATABASE)
995
+ CHROMA_HTTP_HOST = os.environ.get("CHROMA_HTTP_HOST", "")
996
+ CHROMA_HTTP_PORT = int(os.environ.get("CHROMA_HTTP_PORT", "8000"))
997
+ # Comma-separated list of header=value pairs
998
+ CHROMA_HTTP_HEADERS = os.environ.get("CHROMA_HTTP_HEADERS", "")
999
+ if CHROMA_HTTP_HEADERS:
1000
+ CHROMA_HTTP_HEADERS = dict(
1001
+ [pair.split("=") for pair in CHROMA_HTTP_HEADERS.split(",")]
1002
+ )
1003
+ else:
1004
+ CHROMA_HTTP_HEADERS = None
1005
+ CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
1006
+ # this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (sentence-transformers/all-MiniLM-L6-v2)
1007
+
1008
+ RAG_TOP_K = PersistentConfig(
1009
+ "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "5"))
1010
+ )
1011
+ RAG_RELEVANCE_THRESHOLD = PersistentConfig(
1012
+ "RAG_RELEVANCE_THRESHOLD",
1013
+ "rag.relevance_threshold",
1014
+ float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")),
1015
+ )
1016
+
1017
+ ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
1018
+ "ENABLE_RAG_HYBRID_SEARCH",
1019
+ "rag.enable_hybrid_search",
1020
+ os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
1021
+ )
1022
+
1023
+ ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
1024
+ "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
1025
+ "rag.enable_web_loader_ssl_verification",
1026
+ os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
1027
+ )
1028
+
1029
+ RAG_EMBEDDING_ENGINE = PersistentConfig(
1030
+ "RAG_EMBEDDING_ENGINE",
1031
+ "rag.embedding_engine",
1032
+ os.environ.get("RAG_EMBEDDING_ENGINE", ""),
1033
+ )
1034
+
1035
+ PDF_EXTRACT_IMAGES = PersistentConfig(
1036
+ "PDF_EXTRACT_IMAGES",
1037
+ "rag.pdf_extract_images",
1038
+ os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
1039
+ )
1040
+
1041
+ RAG_EMBEDDING_MODEL = PersistentConfig(
1042
+ "RAG_EMBEDDING_MODEL",
1043
+ "rag.embedding_model",
1044
+ os.environ.get("RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
1045
+ )
1046
+ log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL.value}")
1047
+
1048
+ RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
1049
+ os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
1050
+ )
1051
+
1052
+ RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
1053
+ os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
1054
+ )
1055
+
1056
+ RAG_EMBEDDING_OPENAI_BATCH_SIZE = PersistentConfig(
1057
+ "RAG_EMBEDDING_OPENAI_BATCH_SIZE",
1058
+ "rag.embedding_openai_batch_size",
1059
+ os.environ.get("RAG_EMBEDDING_OPENAI_BATCH_SIZE", 1),
1060
+ )
1061
+
1062
+ RAG_RERANKING_MODEL = PersistentConfig(
1063
+ "RAG_RERANKING_MODEL",
1064
+ "rag.reranking_model",
1065
+ os.environ.get("RAG_RERANKING_MODEL", ""),
1066
+ )
1067
+ if RAG_RERANKING_MODEL.value != "":
1068
+ log.info(f"Reranking model set: {RAG_RERANKING_MODEL.value}")
1069
+
1070
+ RAG_RERANKING_MODEL_AUTO_UPDATE = (
1071
+ os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true"
1072
+ )
1073
+
1074
+ RAG_RERANKING_MODEL_TRUST_REMOTE_CODE = (
1075
+ os.environ.get("RAG_RERANKING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
1076
+ )
1077
+
1078
+
1079
+ if CHROMA_HTTP_HOST != "":
1080
+ CHROMA_CLIENT = chromadb.HttpClient(
1081
+ host=CHROMA_HTTP_HOST,
1082
+ port=CHROMA_HTTP_PORT,
1083
+ headers=CHROMA_HTTP_HEADERS,
1084
+ ssl=CHROMA_HTTP_SSL,
1085
+ tenant=CHROMA_TENANT,
1086
+ database=CHROMA_DATABASE,
1087
+ settings=Settings(allow_reset=True, anonymized_telemetry=False),
1088
+ )
1089
+ else:
1090
+ CHROMA_CLIENT = chromadb.PersistentClient(
1091
+ path=CHROMA_DATA_PATH,
1092
+ settings=Settings(allow_reset=True, anonymized_telemetry=False),
1093
+ tenant=CHROMA_TENANT,
1094
+ database=CHROMA_DATABASE,
1095
+ )
1096
+
1097
+
1098
+ # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
1099
+ USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
1100
+
1101
+ if USE_CUDA.lower() == "true":
1102
+ DEVICE_TYPE = "cuda"
1103
+ else:
1104
+ DEVICE_TYPE = "cpu"
1105
+
1106
+ CHUNK_SIZE = PersistentConfig(
1107
+ "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1500"))
1108
+ )
1109
+ CHUNK_OVERLAP = PersistentConfig(
1110
+ "CHUNK_OVERLAP",
1111
+ "rag.chunk_overlap",
1112
+ int(os.environ.get("CHUNK_OVERLAP", "100")),
1113
+ )
1114
+
1115
+ DEFAULT_RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags.
1116
+ <context>
1117
+ [context]
1118
+ </context>
1119
+
1120
+ When answer to user:
1121
+ - If you don't know, just say that you don't know.
1122
+ - If you don't know when you are not sure, ask for clarification.
1123
+ Avoid mentioning that you obtained the information from the context.
1124
+ And answer according to the language of the user's question.
1125
+
1126
+ Given the context information, answer the query.
1127
+ Query: [query]"""
1128
+
1129
+ RAG_TEMPLATE = PersistentConfig(
1130
+ "RAG_TEMPLATE",
1131
+ "rag.template",
1132
+ os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
1133
+ )
1134
+
1135
+ RAG_OPENAI_API_BASE_URL = PersistentConfig(
1136
+ "RAG_OPENAI_API_BASE_URL",
1137
+ "rag.openai_api_base_url",
1138
+ os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
1139
+ )
1140
+ RAG_OPENAI_API_KEY = PersistentConfig(
1141
+ "RAG_OPENAI_API_KEY",
1142
+ "rag.openai_api_key",
1143
+ os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY),
1144
+ )
1145
+
1146
+ ENABLE_RAG_LOCAL_WEB_FETCH = (
1147
+ os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true"
1148
+ )
1149
+
1150
+ YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
1151
+ "YOUTUBE_LOADER_LANGUAGE",
1152
+ "rag.youtube_loader_language",
1153
+ os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
1154
+ )
1155
+
1156
+
1157
+ ENABLE_RAG_WEB_SEARCH = PersistentConfig(
1158
+ "ENABLE_RAG_WEB_SEARCH",
1159
+ "rag.web.search.enable",
1160
+ os.getenv("ENABLE_RAG_WEB_SEARCH", "False").lower() == "true",
1161
+ )
1162
+
1163
+ RAG_WEB_SEARCH_ENGINE = PersistentConfig(
1164
+ "RAG_WEB_SEARCH_ENGINE",
1165
+ "rag.web.search.engine",
1166
+ os.getenv("RAG_WEB_SEARCH_ENGINE", ""),
1167
+ )
1168
+
1169
+ # You can provide a list of your own websites to filter after performing a web search.
1170
+ # This ensures the highest level of safety and reliability of the information sources.
1171
+ RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig(
1172
+ "RAG_WEB_SEARCH_DOMAIN_FILTER_LIST",
1173
+ "rag.rag.web.search.domain.filter_list",
1174
+ [
1175
+ # "wikipedia.com",
1176
+ # "wikimedia.org",
1177
+ # "wikidata.org",
1178
+ ],
1179
+ )
1180
+
1181
+ SEARXNG_QUERY_URL = PersistentConfig(
1182
+ "SEARXNG_QUERY_URL",
1183
+ "rag.web.search.searxng_query_url",
1184
+ os.getenv("SEARXNG_QUERY_URL", ""),
1185
+ )
1186
+
1187
+ GOOGLE_PSE_API_KEY = PersistentConfig(
1188
+ "GOOGLE_PSE_API_KEY",
1189
+ "rag.web.search.google_pse_api_key",
1190
+ os.getenv("GOOGLE_PSE_API_KEY", ""),
1191
+ )
1192
+
1193
+ GOOGLE_PSE_ENGINE_ID = PersistentConfig(
1194
+ "GOOGLE_PSE_ENGINE_ID",
1195
+ "rag.web.search.google_pse_engine_id",
1196
+ os.getenv("GOOGLE_PSE_ENGINE_ID", ""),
1197
+ )
1198
+
1199
+ BRAVE_SEARCH_API_KEY = PersistentConfig(
1200
+ "BRAVE_SEARCH_API_KEY",
1201
+ "rag.web.search.brave_search_api_key",
1202
+ os.getenv("BRAVE_SEARCH_API_KEY", ""),
1203
+ )
1204
+
1205
+ SERPSTACK_API_KEY = PersistentConfig(
1206
+ "SERPSTACK_API_KEY",
1207
+ "rag.web.search.serpstack_api_key",
1208
+ os.getenv("SERPSTACK_API_KEY", ""),
1209
+ )
1210
+
1211
+ SERPSTACK_HTTPS = PersistentConfig(
1212
+ "SERPSTACK_HTTPS",
1213
+ "rag.web.search.serpstack_https",
1214
+ os.getenv("SERPSTACK_HTTPS", "True").lower() == "true",
1215
+ )
1216
+
1217
+ SERPER_API_KEY = PersistentConfig(
1218
+ "SERPER_API_KEY",
1219
+ "rag.web.search.serper_api_key",
1220
+ os.getenv("SERPER_API_KEY", ""),
1221
+ )
1222
+
1223
+ SERPLY_API_KEY = PersistentConfig(
1224
+ "SERPLY_API_KEY",
1225
+ "rag.web.search.serply_api_key",
1226
+ os.getenv("SERPLY_API_KEY", ""),
1227
+ )
1228
+
1229
+ TAVILY_API_KEY = PersistentConfig(
1230
+ "TAVILY_API_KEY",
1231
+ "rag.web.search.tavily_api_key",
1232
+ os.getenv("TAVILY_API_KEY", ""),
1233
+ )
1234
+
1235
+ RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
1236
+ "RAG_WEB_SEARCH_RESULT_COUNT",
1237
+ "rag.web.search.result_count",
1238
+ int(os.getenv("RAG_WEB_SEARCH_RESULT_COUNT", "3")),
1239
+ )
1240
+
1241
+ RAG_WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig(
1242
+ "RAG_WEB_SEARCH_CONCURRENT_REQUESTS",
1243
+ "rag.web.search.concurrent_requests",
1244
+ int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
1245
+ )
1246
+
1247
+
1248
+ ####################################
1249
+ # Transcribe
1250
+ ####################################
1251
+
1252
+ WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
1253
+ WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
1254
+ WHISPER_MODEL_AUTO_UPDATE = (
1255
+ os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
1256
+ )
1257
+
1258
+
1259
+ ####################################
1260
+ # Images
1261
+ ####################################
1262
+
1263
+ IMAGE_GENERATION_ENGINE = PersistentConfig(
1264
+ "IMAGE_GENERATION_ENGINE",
1265
+ "image_generation.engine",
1266
+ os.getenv("IMAGE_GENERATION_ENGINE", ""),
1267
+ )
1268
+
1269
+ ENABLE_IMAGE_GENERATION = PersistentConfig(
1270
+ "ENABLE_IMAGE_GENERATION",
1271
+ "image_generation.enable",
1272
+ os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true",
1273
+ )
1274
+ AUTOMATIC1111_BASE_URL = PersistentConfig(
1275
+ "AUTOMATIC1111_BASE_URL",
1276
+ "image_generation.automatic1111.base_url",
1277
+ os.getenv("AUTOMATIC1111_BASE_URL", ""),
1278
+ )
1279
+ AUTOMATIC1111_API_AUTH = PersistentConfig(
1280
+ "AUTOMATIC1111_API_AUTH",
1281
+ "image_generation.automatic1111.api_auth",
1282
+ os.getenv("AUTOMATIC1111_API_AUTH", ""),
1283
+ )
1284
+
1285
+ COMFYUI_BASE_URL = PersistentConfig(
1286
+ "COMFYUI_BASE_URL",
1287
+ "image_generation.comfyui.base_url",
1288
+ os.getenv("COMFYUI_BASE_URL", ""),
1289
+ )
1290
+
1291
+ COMFYUI_CFG_SCALE = PersistentConfig(
1292
+ "COMFYUI_CFG_SCALE",
1293
+ "image_generation.comfyui.cfg_scale",
1294
+ os.getenv("COMFYUI_CFG_SCALE", ""),
1295
+ )
1296
+
1297
+ COMFYUI_SAMPLER = PersistentConfig(
1298
+ "COMFYUI_SAMPLER",
1299
+ "image_generation.comfyui.sampler",
1300
+ os.getenv("COMFYUI_SAMPLER", ""),
1301
+ )
1302
+
1303
+ COMFYUI_SCHEDULER = PersistentConfig(
1304
+ "COMFYUI_SCHEDULER",
1305
+ "image_generation.comfyui.scheduler",
1306
+ os.getenv("COMFYUI_SCHEDULER", ""),
1307
+ )
1308
+
1309
+ COMFYUI_SD3 = PersistentConfig(
1310
+ "COMFYUI_SD3",
1311
+ "image_generation.comfyui.sd3",
1312
+ os.environ.get("COMFYUI_SD3", "").lower() == "true",
1313
+ )
1314
+
1315
+ COMFYUI_FLUX = PersistentConfig(
1316
+ "COMFYUI_FLUX",
1317
+ "image_generation.comfyui.flux",
1318
+ os.environ.get("COMFYUI_FLUX", "").lower() == "true",
1319
+ )
1320
+
1321
+ COMFYUI_FLUX_WEIGHT_DTYPE = PersistentConfig(
1322
+ "COMFYUI_FLUX_WEIGHT_DTYPE",
1323
+ "image_generation.comfyui.flux_weight_dtype",
1324
+ os.getenv("COMFYUI_FLUX_WEIGHT_DTYPE", ""),
1325
+ )
1326
+
1327
+ COMFYUI_FLUX_FP8_CLIP = PersistentConfig(
1328
+ "COMFYUI_FLUX_FP8_CLIP",
1329
+ "image_generation.comfyui.flux_fp8_clip",
1330
+ os.environ.get("COMFYUI_FLUX_FP8_CLIP", "").lower() == "true",
1331
+ )
1332
+
1333
+ IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
1334
+ "IMAGES_OPENAI_API_BASE_URL",
1335
+ "image_generation.openai.api_base_url",
1336
+ os.getenv("IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
1337
+ )
1338
+ IMAGES_OPENAI_API_KEY = PersistentConfig(
1339
+ "IMAGES_OPENAI_API_KEY",
1340
+ "image_generation.openai.api_key",
1341
+ os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY),
1342
+ )
1343
+
1344
+ IMAGE_SIZE = PersistentConfig(
1345
+ "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
1346
+ )
1347
+
1348
+ IMAGE_STEPS = PersistentConfig(
1349
+ "IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
1350
+ )
1351
+
1352
+ IMAGE_GENERATION_MODEL = PersistentConfig(
1353
+ "IMAGE_GENERATION_MODEL",
1354
+ "image_generation.model",
1355
+ os.getenv("IMAGE_GENERATION_MODEL", ""),
1356
+ )
1357
+
1358
+ ####################################
1359
+ # Audio
1360
+ ####################################
1361
+
1362
+ AUDIO_STT_OPENAI_API_BASE_URL = PersistentConfig(
1363
+ "AUDIO_STT_OPENAI_API_BASE_URL",
1364
+ "audio.stt.openai.api_base_url",
1365
+ os.getenv("AUDIO_STT_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
1366
+ )
1367
+
1368
+ AUDIO_STT_OPENAI_API_KEY = PersistentConfig(
1369
+ "AUDIO_STT_OPENAI_API_KEY",
1370
+ "audio.stt.openai.api_key",
1371
+ os.getenv("AUDIO_STT_OPENAI_API_KEY", OPENAI_API_KEY),
1372
+ )
1373
+
1374
+ AUDIO_STT_ENGINE = PersistentConfig(
1375
+ "AUDIO_STT_ENGINE",
1376
+ "audio.stt.engine",
1377
+ os.getenv("AUDIO_STT_ENGINE", ""),
1378
+ )
1379
+
1380
+ AUDIO_STT_MODEL = PersistentConfig(
1381
+ "AUDIO_STT_MODEL",
1382
+ "audio.stt.model",
1383
+ os.getenv("AUDIO_STT_MODEL", "whisper-1"),
1384
+ )
1385
+
1386
+ AUDIO_TTS_OPENAI_API_BASE_URL = PersistentConfig(
1387
+ "AUDIO_TTS_OPENAI_API_BASE_URL",
1388
+ "audio.tts.openai.api_base_url",
1389
+ os.getenv("AUDIO_TTS_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
1390
+ )
1391
+ AUDIO_TTS_OPENAI_API_KEY = PersistentConfig(
1392
+ "AUDIO_TTS_OPENAI_API_KEY",
1393
+ "audio.tts.openai.api_key",
1394
+ os.getenv("AUDIO_TTS_OPENAI_API_KEY", OPENAI_API_KEY),
1395
+ )
1396
+
1397
+ AUDIO_TTS_API_KEY = PersistentConfig(
1398
+ "AUDIO_TTS_API_KEY",
1399
+ "audio.tts.api_key",
1400
+ os.getenv("AUDIO_TTS_API_KEY", ""),
1401
+ )
1402
+
1403
+ AUDIO_TTS_ENGINE = PersistentConfig(
1404
+ "AUDIO_TTS_ENGINE",
1405
+ "audio.tts.engine",
1406
+ os.getenv("AUDIO_TTS_ENGINE", ""),
1407
+ )
1408
+
1409
+
1410
+ AUDIO_TTS_MODEL = PersistentConfig(
1411
+ "AUDIO_TTS_MODEL",
1412
+ "audio.tts.model",
1413
+ os.getenv("AUDIO_TTS_MODEL", "tts-1"),
1414
+ )
1415
+
1416
+ AUDIO_TTS_VOICE = PersistentConfig(
1417
+ "AUDIO_TTS_VOICE",
1418
+ "audio.tts.voice",
1419
+ os.getenv("AUDIO_TTS_VOICE", "alloy"),
1420
+ )
1421
+
1422
+
1423
+ ####################################
1424
+ # Database
1425
+ ####################################
1426
+
1427
+ DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
1428
+
1429
+ # Replace the postgres:// with postgresql://
1430
+ if "postgres://" in DATABASE_URL:
1431
+ DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")