config.py 24.4 KB
Newer Older
1
import os
2
3
import sys
import logging
Tang Ziya's avatar
Tang Ziya committed
4
5
import importlib.metadata
import pkgutil
6
import chromadb
Timothy J. Baek's avatar
Timothy J. Baek committed
7
from chromadb import Settings
8
from base64 import b64encode
9
from bs4 import BeautifulSoup
10
from typing import TypeVar, Generic, Union
11

Timothy J. Baek's avatar
Timothy J. Baek committed
12
from pathlib import Path
Timothy J. Baek's avatar
Timothy J. Baek committed
13
import json
Timothy J. Baek's avatar
Timothy J. Baek committed
14
15
import yaml

Timothy J. Baek's avatar
Timothy J. Baek committed
16
import markdown
17
18
19
20
21
import requests
import shutil

from secrets import token_bytes
from constants import ERROR_MESSAGES
Timothy J. Baek's avatar
Timothy J. Baek committed
22

Timothy J. Baek's avatar
Timothy J. Baek committed
23
24
25
26
####################################
# Load .env file
####################################

27
28
29
BACKEND_DIR = Path(__file__).parent  # the path containing this file
BASE_DIR = BACKEND_DIR.parent  # the path containing the backend/

Timothy J. Baek's avatar
Timothy J. Baek committed
30
31
32
try:
    from dotenv import load_dotenv, find_dotenv

33
    load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
Timothy J. Baek's avatar
Timothy J. Baek committed
34
35
36
except ImportError:
    print("dotenv not installed, skipping...")

37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
####################################
# LOGGING
####################################

log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]

GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
if GLOBAL_LOG_LEVEL in log_levels:
    logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
else:
    GLOBAL_LOG_LEVEL = "INFO"

log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")

log_sources = [
    "AUDIO",
    "COMFYUI",
    "CONFIG",
    "DB",
    "IMAGES",
    "LITELLM",
    "MAIN",
    "MODELS",
    "OLLAMA",
    "OPENAI",
    "RAG",
    "WEBHOOK",
]

SRC_LOG_LEVELS = {}

for source in log_sources:
    log_env_var = source + "_LOG_LEVEL"
    SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
    if SRC_LOG_LEVELS[source] not in log_levels:
        SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
    log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")

log.setLevel(SRC_LOG_LEVELS["CONFIG"])

79
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
Timothy J. Baek's avatar
Timothy J. Baek committed
80
81
82
if WEBUI_NAME != "Open WebUI":
    WEBUI_NAME += " (Open WebUI)"

83
84
WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")

85
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
Timothy J. Baek's avatar
Timothy J. Baek committed
86

87

Timothy J. Baek's avatar
Timothy J. Baek committed
88
####################################
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
89
# ENV (dev,test,prod)
Timothy J. Baek's avatar
Timothy J. Baek committed
90
91
####################################

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
92
ENV = os.environ.get("ENV", "dev")
Timothy J. Baek's avatar
Timothy J. Baek committed
93

Timothy J. Baek's avatar
Timothy J. Baek committed
94
try:
95
    PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
Timothy J. Baek's avatar
Timothy J. Baek committed
96
except:
Tang Ziya's avatar
Tang Ziya committed
97
98
99
100
    try:
        PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
    except importlib.metadata.PackageNotFoundError:
        PACKAGE_DATA = {"version": "0.0.0"}
Timothy J. Baek's avatar
Timothy J. Baek committed
101
102
103

VERSION = PACKAGE_DATA["version"]

Timothy J. Baek's avatar
Timothy J. Baek committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# Function to parse each section
def parse_section(section):
    items = []
    for li in section.find_all("li"):
        # Extract raw HTML string
        raw_html = str(li)

        # Extract text without HTML tags
        text = li.get_text(separator=" ", strip=True)

        # Split into title and content
        parts = text.split(": ", 1)
        title = parts[0].strip() if len(parts) > 1 else ""
        content = parts[1].strip() if len(parts) > 1 else text

        items.append({"title": title, "content": content, "raw": raw_html})
    return items


try:
125
    changelog_content = (BASE_DIR / "CHANGELOG.md").read_text()
Timothy J. Baek's avatar
Timothy J. Baek committed
126
except:
Tang Ziya's avatar
Tang Ziya committed
127
128
    changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()

Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

# Convert markdown content to HTML
html_content = markdown.markdown(changelog_content)

# Parse the HTML content
soup = BeautifulSoup(html_content, "html.parser")

# Initialize JSON structure
changelog_json = {}

# Iterate over each version
for version in soup.find_all("h2"):
    version_number = version.get_text().strip().split(" - ")[0][1:-1]  # Remove brackets
    date = version.get_text().strip().split(" - ")[1]

    version_data = {"date": date}

    # Find the next sibling that is a h3 tag (section title)
    current = version.find_next_sibling()

    while current and current.name != "h2":
        if current.name == "h3":
            section_title = current.get_text().lower()  # e.g., "added", "fixed"
            section_items = parse_section(current.find_next_sibling("ul"))
            version_data[section_title] = section_items

        # Move to the next element
        current = current.find_next_sibling()

    changelog_json[version_number] = version_data

160

Timothy J. Baek's avatar
Timothy J. Baek committed
161
162
CHANGELOG = changelog_json

163

164
####################################
165
# WEBUI_BUILD_HASH
166
167
####################################

168
WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
169

170
171
172
173
####################################
# DATA/FRONTEND BUILD DIR
####################################

174
175
DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
176
177

try:
178
    CONFIG_DATA = json.loads((DATA_DIR / "config.json").read_text())
179
180
181
except:
    CONFIG_DATA = {}

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209

####################################
# Config helpers
####################################


def save_config():
    try:
        with open(f"{DATA_DIR}/config.json", "w") as f:
            json.dump(CONFIG_DATA, f, indent="\t")
    except Exception as e:
        log.exception(e)


def get_config_value(config_path: str):
    path_parts = config_path.split(".")
    cur_config = CONFIG_DATA
    for key in path_parts:
        if key in cur_config:
            cur_config = cur_config[key]
        else:
            return None
    return cur_config


T = TypeVar("T")


Timothy J. Baek's avatar
Timothy J. Baek committed
210
class PersistentConfig(Generic[T]):
211
212
213
214
215
216
217
218
219
220
221
222
223
224
    def __init__(self, env_name: str, config_path: str, env_value: T):
        self.env_name = env_name
        self.config_path = config_path
        self.env_value = env_value
        self.config_value = get_config_value(config_path)
        if self.config_value is not None:
            log.info(f"'{env_name}' loaded from config.json")
            self.value = self.config_value
        else:
            self.value = env_value

    def __str__(self):
        return str(self.value)

225
226
227
    @property
    def __dict__(self):
        raise TypeError(
Timothy J. Baek's avatar
Timothy J. Baek committed
228
            "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
229
230
231
232
233
        )

    def __getattribute__(self, item):
        if item == "__dict__":
            raise TypeError(
Timothy J. Baek's avatar
Timothy J. Baek committed
234
                "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
235
236
237
            )
        return super().__getattribute__(item)

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
    def save(self):
        # Don't save if the value is the same as the env value and the config value
        if self.env_value == self.value:
            if self.config_value == self.value:
                return
        log.info(f"Saving '{self.env_name}' to config.json")
        path_parts = self.config_path.split(".")
        config = CONFIG_DATA
        for key in path_parts[:-1]:
            if key not in config:
                config[key] = {}
            config = config[key]
        config[path_parts[-1]] = self.value
        save_config()
        self.config_value = self.value


255
class AppConfig:
Timothy J. Baek's avatar
Timothy J. Baek committed
256
    _state: dict[str, PersistentConfig]
257
258
259
260
261

    def __init__(self):
        super().__setattr__("_state", {})

    def __setattr__(self, key, value):
Timothy J. Baek's avatar
Timothy J. Baek committed
262
        if isinstance(value, PersistentConfig):
263
264
265
266
267
268
269
            self._state[key] = value
        else:
            self._state[key].value = value
            self._state[key].save()

    def __getattr__(self, key):
        return self._state[key].value
270
271
272
273
274
275
276
277
278
279


####################################
# WEBUI_AUTH (Required for security)
####################################

WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
    "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
Timothy J. Baek's avatar
Timothy J. Baek committed
280
JWT_EXPIRES_IN = PersistentConfig(
281
282
283
    "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
)

284
285
286
287
####################################
# Static DIR
####################################

288
STATIC_DIR = Path(os.getenv("STATIC_DIR", BACKEND_DIR / "static")).resolve()
289

290
291
292
frontend_favicon = FRONTEND_BUILD_DIR / "favicon.png"
if frontend_favicon.exists():
    shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
293
294
else:
    logging.warning(f"Frontend favicon not found at {frontend_favicon}")
295
296
297
298
299
300

####################################
# CUSTOM_NAME
####################################

CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
Timothy J. Baek's avatar
Timothy J. Baek committed
301

302
if CUSTOM_NAME:
Timothy J. Baek's avatar
Timothy J. Baek committed
303
304
305
306
307
    try:
        r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
        data = r.json()
        if r.ok:
            if "logo" in data:
308
                WEBUI_FAVICON_URL = url = (
Timothy J. Baek's avatar
Timothy J. Baek committed
309
310
311
312
313
314
315
                    f"https://api.openwebui.com{data['logo']}"
                    if data["logo"][0] == "/"
                    else data["logo"]
                )

                r = requests.get(url, stream=True)
                if r.status_code == 200:
316
                    with open(f"{STATIC_DIR}/favicon.png", "wb") as f:
Timothy J. Baek's avatar
Timothy J. Baek committed
317
318
319
320
321
                        r.raw.decode_content = True
                        shutil.copyfileobj(r.raw, f)

            WEBUI_NAME = data["name"]
    except Exception as e:
322
        log.exception(e)
Timothy J. Baek's avatar
Timothy J. Baek committed
323
        pass
324

325

326
####################################
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
327
# File Upload DIR
328
329
####################################

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
330
331
UPLOAD_DIR = f"{DATA_DIR}/uploads"
Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
Timothy J. Baek's avatar
Timothy J. Baek committed
332

333

Timothy J. Baek's avatar
Timothy J. Baek committed
334
335
336
337
338
339
340
####################################
# Cache DIR
####################################

CACHE_DIR = f"{DATA_DIR}/cache"
Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)

341

342
343
344
345
####################################
# Docs DIR
####################################

346
DOCS_DIR = os.getenv("DOCS_DIR", f"{DATA_DIR}/docs")
347
Path(DOCS_DIR).mkdir(parents=True, exist_ok=True)
Timothy J. Baek's avatar
Timothy J. Baek committed
348

Timothy J. Baek's avatar
Timothy J. Baek committed
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377

####################################
# LITELLM_CONFIG
####################################


def create_config_file(file_path):
    directory = os.path.dirname(file_path)

    # Check if directory exists, if not, create it
    if not os.path.exists(directory):
        os.makedirs(directory)

    # Data to write into the YAML file
    config_data = {
        "general_settings": {},
        "litellm_settings": {},
        "model_list": [],
        "router_settings": {},
    }

    # Write data to YAML file
    with open(file_path, "w") as file:
        yaml.dump(config_data, file)


LITELLM_CONFIG_PATH = f"{DATA_DIR}/litellm/config.yaml"

if not os.path.exists(LITELLM_CONFIG_PATH):
378
    log.info("Config file doesn't exist. Creating...")
Timothy J. Baek's avatar
Timothy J. Baek committed
379
    create_config_file(LITELLM_CONFIG_PATH)
380
    log.info("Config file created successfully.")
Timothy J. Baek's avatar
Timothy J. Baek committed
381

382

383
####################################
Timothy J. Baek's avatar
Timothy J. Baek committed
384
# OLLAMA_BASE_URL
385
386
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
387
388
389
390
391
392
393

ENABLE_OLLAMA_API = PersistentConfig(
    "ENABLE_OLLAMA_API",
    "ollama.enable",
    os.environ.get("ENABLE_OLLAMA_API", "True").lower() == "true",
)

394
395
396
OLLAMA_API_BASE_URL = os.environ.get(
    "OLLAMA_API_BASE_URL", "http://localhost:11434/api"
)
Timothy J. Baek's avatar
Timothy J. Baek committed
397

398
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
Timothy J. Baek's avatar
Timothy J. Baek committed
399
K8S_FLAG = os.environ.get("K8S_FLAG", "")
Jannik Streidl's avatar
Jannik Streidl committed
400
USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
401

402
if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
403
404
405
406
407
408
    OLLAMA_BASE_URL = (
        OLLAMA_API_BASE_URL[:-4]
        if OLLAMA_API_BASE_URL.endswith("/api")
        else OLLAMA_API_BASE_URL
    )

Timothy J. Baek's avatar
Timothy J. Baek committed
409
if ENV == "prod":
410
    if OLLAMA_BASE_URL == "/ollama" and not K8S_FLAG:
Jannik Streidl's avatar
Jannik Streidl committed
411
        if USE_OLLAMA_DOCKER.lower() == "true":
412
            # if you use all-in-one docker container (Open WebUI + Ollama)
Jannik Streidl's avatar
Jannik Streidl committed
413
            # with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
414
            OLLAMA_BASE_URL = "http://localhost:11434"
415
        else:
416
            OLLAMA_BASE_URL = "http://host.docker.internal:11434"
Timothy J. Baek's avatar
Timothy J. Baek committed
417
    elif K8S_FLAG:
418
        OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
Timothy J. Baek's avatar
Timothy J. Baek committed
419

420

421
422
423
OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL

424
OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
Timothy J. Baek's avatar
Timothy J. Baek committed
425
OLLAMA_BASE_URLS = PersistentConfig(
426
427
    "OLLAMA_BASE_URLS", "ollama.base_urls", OLLAMA_BASE_URLS
)
428

Timothy J. Baek's avatar
Timothy J. Baek committed
429
430
431
432
####################################
# OPENAI_API
####################################

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
433
434
435
436
437
438
439
440

ENABLE_OPENAI_API = PersistentConfig(
    "ENABLE_OPENAI_API",
    "openai.enable",
    os.environ.get("ENABLE_OPENAI_API", "True").lower() == "true",
)


Timothy J. Baek's avatar
Timothy J. Baek committed
441
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
442
443
OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")

444

445
446
if OPENAI_API_BASE_URL == "":
    OPENAI_API_BASE_URL = "https://api.openai.com/v1"
Timothy J. Baek's avatar
Timothy J. Baek committed
447

Timothy J. Baek's avatar
Timothy J. Baek committed
448
449
450
OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "")
OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY

Timothy J. Baek's avatar
Timothy J. Baek committed
451
OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
Timothy J. Baek's avatar
Timothy J. Baek committed
452
453
454
OPENAI_API_KEYS = PersistentConfig(
    "OPENAI_API_KEYS", "openai.api_keys", OPENAI_API_KEYS
)
Timothy J. Baek's avatar
Timothy J. Baek committed
455
456
457
458
459
460

OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "")
OPENAI_API_BASE_URLS = (
    OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
)

Timothy J. Baek's avatar
Timothy J. Baek committed
461
462
463
464
OPENAI_API_BASE_URLS = [
    url.strip() if url != "" else "https://api.openai.com/v1"
    for url in OPENAI_API_BASE_URLS.split(";")
]
Timothy J. Baek's avatar
Timothy J. Baek committed
465
OPENAI_API_BASE_URLS = PersistentConfig(
466
467
    "OPENAI_API_BASE_URLS", "openai.api_base_urls", OPENAI_API_BASE_URLS
)
468

Timothy J. Baek's avatar
Timothy J. Baek committed
469
OPENAI_API_KEY = ""
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
470
471

try:
472
473
    OPENAI_API_KEY = OPENAI_API_KEYS.value[
        OPENAI_API_BASE_URLS.value.index("https://api.openai.com/v1")
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
474
475
476
477
    ]
except:
    pass

Timothy J. Baek's avatar
Timothy J. Baek committed
478
479
OPENAI_API_BASE_URL = "https://api.openai.com/v1"

480
481
482
483
####################################
# WEBUI
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
484
ENABLE_SIGNUP = PersistentConfig(
485
486
487
488
489
490
491
492
    "ENABLE_SIGNUP",
    "ui.enable_signup",
    (
        False
        if not WEBUI_AUTH
        else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
    ),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
493
DEFAULT_MODELS = PersistentConfig(
494
    "DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
495
)
Timothy J. Baek's avatar
Timothy J. Baek committed
496

Timothy J. Baek's avatar
Timothy J. Baek committed
497
DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
498
499
500
    "DEFAULT_PROMPT_SUGGESTIONS",
    "ui.prompt_suggestions",
    [
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        {
            "title": ["Help me study", "vocabulary for a college entrance exam"],
            "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
        },
        {
            "title": ["Give me ideas", "for what to do with my kids' art"],
            "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.",
        },
        {
            "title": ["Tell me a fun fact", "about the Roman Empire"],
            "content": "Tell me a random fun fact about the Roman Empire",
        },
        {
            "title": ["Show me a code snippet", "of a website's sticky header"],
            "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript.",
        },
Timothy J. Baek's avatar
Timothy J. Baek committed
517
518
519
520
521
522
523
524
525
526
527
        {
            "title": [
                "Explain options trading",
                "if I'm familiar with buying and selling stocks",
            ],
            "content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks.",
        },
        {
            "title": ["Overcome procrastination", "give me tips"],
            "content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
        },
528
    ],
529
)
Timothy J. Baek's avatar
Timothy J. Baek committed
530

Timothy J. Baek's avatar
Timothy J. Baek committed
531
DEFAULT_USER_ROLE = PersistentConfig(
532
533
534
535
    "DEFAULT_USER_ROLE",
    "ui.default_user_role",
    os.getenv("DEFAULT_USER_ROLE", "pending"),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
536

537
538
USER_PERMISSIONS_CHAT_DELETION = (
    os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
539
)
540

Timothy J. Baek's avatar
Timothy J. Baek committed
541
USER_PERMISSIONS = PersistentConfig(
542
543
544
545
    "USER_PERMISSIONS",
    "ui.user_permissions",
    {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}},
)
Timothy J. Baek's avatar
Timothy J. Baek committed
546

Timothy J. Baek's avatar
Timothy J. Baek committed
547
ENABLE_MODEL_FILTER = PersistentConfig(
548
549
550
551
    "ENABLE_MODEL_FILTER",
    "model_filter.enable",
    os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
)
552
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
Timothy J. Baek's avatar
Timothy J. Baek committed
553
MODEL_FILTER_LIST = PersistentConfig(
554
555
556
557
    "MODEL_FILTER_LIST",
    "model_filter.list",
    [model.strip() for model in MODEL_FILTER_LIST.split(";")],
)
558

Timothy J. Baek's avatar
Timothy J. Baek committed
559
WEBHOOK_URL = PersistentConfig(
560
561
    "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
)
562

563
ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
564

Timothy J. Baek's avatar
Timothy J. Baek committed
565
####################################
566
# WEBUI_SECRET_KEY
Timothy J. Baek's avatar
Timothy J. Baek committed
567
568
####################################

569
570
WEBUI_SECRET_KEY = os.environ.get(
    "WEBUI_SECRET_KEY",
Timothy J. Baek's avatar
Timothy J. Baek committed
571
572
573
    os.environ.get(
        "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
    ),  # DEPRECATED: remove at next major version
574
)
575

576
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
Timothy J. Baek's avatar
Timothy J. Baek committed
577
    raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
578
579
580
581
582

####################################
# RAG
####################################

583
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
584
585
586
587
588
589
590
591
592
593
594
595
596
CHROMA_TENANT = os.environ.get("CHROMA_TENANT", chromadb.DEFAULT_TENANT)
CHROMA_DATABASE = os.environ.get("CHROMA_DATABASE", chromadb.DEFAULT_DATABASE)
CHROMA_HTTP_HOST = os.environ.get("CHROMA_HTTP_HOST", "")
CHROMA_HTTP_PORT = int(os.environ.get("CHROMA_HTTP_PORT", "8000"))
# Comma-separated list of header=value pairs
CHROMA_HTTP_HEADERS = os.environ.get("CHROMA_HTTP_HEADERS", "")
if CHROMA_HTTP_HEADERS:
    CHROMA_HTTP_HEADERS = dict(
        [pair.split("=") for pair in CHROMA_HTTP_HEADERS.split(",")]
    )
else:
    CHROMA_HTTP_HEADERS = None
CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
597
# 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)
598

Timothy J. Baek's avatar
Timothy J. Baek committed
599
RAG_TOP_K = PersistentConfig(
600
601
    "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "5"))
)
Timothy J. Baek's avatar
Timothy J. Baek committed
602
RAG_RELEVANCE_THRESHOLD = PersistentConfig(
603
604
605
    "RAG_RELEVANCE_THRESHOLD",
    "rag.relevance_threshold",
    float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")),
Timothy J. Baek's avatar
Timothy J. Baek committed
606
)
607

Timothy J. Baek's avatar
Timothy J. Baek committed
608
ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
609
610
611
612
    "ENABLE_RAG_HYBRID_SEARCH",
    "rag.enable_hybrid_search",
    os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
)
613

Timothy J. Baek's avatar
Timothy J. Baek committed
614
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
615
616
617
    "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
    "rag.enable_web_loader_ssl_verification",
    os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
618
619
)

Timothy J. Baek's avatar
Timothy J. Baek committed
620
RAG_EMBEDDING_ENGINE = PersistentConfig(
621
622
623
624
    "RAG_EMBEDDING_ENGINE",
    "rag.embedding_engine",
    os.environ.get("RAG_EMBEDDING_ENGINE", ""),
)
625

Timothy J. Baek's avatar
Timothy J. Baek committed
626
PDF_EXTRACT_IMAGES = PersistentConfig(
627
628
629
630
    "PDF_EXTRACT_IMAGES",
    "rag.pdf_extract_images",
    os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
)
631

Timothy J. Baek's avatar
Timothy J. Baek committed
632
RAG_EMBEDDING_MODEL = PersistentConfig(
633
634
635
    "RAG_EMBEDDING_MODEL",
    "rag.embedding_model",
    os.environ.get("RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
636
)
637
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL.value}"),
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
638

639
640
641
642
RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
    os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
)

643
644
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
    os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
Steven Kreitzer's avatar
Steven Kreitzer committed
645
646
)

Timothy J. Baek's avatar
Timothy J. Baek committed
647
RAG_RERANKING_MODEL = PersistentConfig(
648
649
650
651
652
653
    "RAG_RERANKING_MODEL",
    "rag.reranking_model",
    os.environ.get("RAG_RERANKING_MODEL", ""),
)
if RAG_RERANKING_MODEL.value != "":
    log.info(f"Reranking model set: {RAG_RERANKING_MODEL.value}"),
Steven Kreitzer's avatar
Steven Kreitzer committed
654

655
656
657
658
RAG_RERANKING_MODEL_AUTO_UPDATE = (
    os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true"
)

Steven Kreitzer's avatar
Steven Kreitzer committed
659
660
RAG_RERANKING_MODEL_TRUST_REMOTE_CODE = (
    os.environ.get("RAG_RERANKING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
661
)
662

663

664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
if CHROMA_HTTP_HOST != "":
    CHROMA_CLIENT = chromadb.HttpClient(
        host=CHROMA_HTTP_HOST,
        port=CHROMA_HTTP_PORT,
        headers=CHROMA_HTTP_HEADERS,
        ssl=CHROMA_HTTP_SSL,
        tenant=CHROMA_TENANT,
        database=CHROMA_DATABASE,
        settings=Settings(allow_reset=True, anonymized_telemetry=False),
    )
else:
    CHROMA_CLIENT = chromadb.PersistentClient(
        path=CHROMA_DATA_PATH,
        settings=Settings(allow_reset=True, anonymized_telemetry=False),
        tenant=CHROMA_TENANT,
        database=CHROMA_DATABASE,
    )
Timothy J. Baek's avatar
Timothy J. Baek committed
681

682

683
684
685
686
687
688
689
690
# device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")

if USE_CUDA.lower() == "true":
    DEVICE_TYPE = "cuda"
else:
    DEVICE_TYPE = "cpu"

Timothy J. Baek's avatar
Timothy J. Baek committed
691
CHUNK_SIZE = PersistentConfig(
692
693
    "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1500"))
)
Timothy J. Baek's avatar
Timothy J. Baek committed
694
CHUNK_OVERLAP = PersistentConfig(
695
696
697
698
    "CHUNK_OVERLAP",
    "rag.chunk_overlap",
    int(os.environ.get("CHUNK_OVERLAP", "100")),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
699

700
DEFAULT_RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags.
Timothy J. Baek's avatar
Timothy J. Baek committed
701
702
703
704
705
706
707
708
709
<context>
    [context]
</context>

When answer to user:
- If you don't know, just say that you don't know.
- If you don't know when you are not sure, ask for clarification.
Avoid mentioning that you obtained the information from the context.
And answer according to the language of the user's question.
710

Timothy J. Baek's avatar
Timothy J. Baek committed
711
712
713
Given the context information, answer the query.
Query: [query]"""

Timothy J. Baek's avatar
Timothy J. Baek committed
714
RAG_TEMPLATE = PersistentConfig(
715
716
717
718
    "RAG_TEMPLATE",
    "rag.template",
    os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
)
719

Timothy J. Baek's avatar
Timothy J. Baek committed
720
RAG_OPENAI_API_BASE_URL = PersistentConfig(
721
722
723
724
    "RAG_OPENAI_API_BASE_URL",
    "rag.openai_api_base_url",
    os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
725
RAG_OPENAI_API_KEY = PersistentConfig(
726
727
728
729
    "RAG_OPENAI_API_KEY",
    "rag.openai_api_key",
    os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
730

731
732
733
ENABLE_RAG_LOCAL_WEB_FETCH = (
    os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true"
)
734

Timothy J. Baek's avatar
Timothy J. Baek committed
735
YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
736
737
738
739
    "YOUTUBE_LOADER_LANGUAGE",
    "rag.youtube_loader_language",
    os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
)
740

Timothy J. Baek's avatar
Timothy J. Baek committed
741
742
743
####################################
# Transcribe
####################################
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
744
745
746

WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
747
748
749
WHISPER_MODEL_AUTO_UPDATE = (
    os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
)
Timothy J. Baek's avatar
Timothy J. Baek committed
750

751

Timothy J. Baek's avatar
Timothy J. Baek committed
752
753
754
755
####################################
# Images
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
756
IMAGE_GENERATION_ENGINE = PersistentConfig(
757
758
759
760
    "IMAGE_GENERATION_ENGINE",
    "image_generation.engine",
    os.getenv("IMAGE_GENERATION_ENGINE", ""),
)
761

Timothy J. Baek's avatar
Timothy J. Baek committed
762
ENABLE_IMAGE_GENERATION = PersistentConfig(
763
764
765
766
    "ENABLE_IMAGE_GENERATION",
    "image_generation.enable",
    os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true",
)
Timothy J. Baek's avatar
Timothy J. Baek committed
767
AUTOMATIC1111_BASE_URL = PersistentConfig(
768
769
770
    "AUTOMATIC1111_BASE_URL",
    "image_generation.automatic1111.base_url",
    os.getenv("AUTOMATIC1111_BASE_URL", ""),
771
)
Timothy J. Baek's avatar
Timothy J. Baek committed
772

Timothy J. Baek's avatar
Timothy J. Baek committed
773
COMFYUI_BASE_URL = PersistentConfig(
774
775
776
777
    "COMFYUI_BASE_URL",
    "image_generation.comfyui.base_url",
    os.getenv("COMFYUI_BASE_URL", ""),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
778

Timothy J. Baek's avatar
Timothy J. Baek committed
779
IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
780
781
782
783
    "IMAGES_OPENAI_API_BASE_URL",
    "image_generation.openai.api_base_url",
    os.getenv("IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
784
IMAGES_OPENAI_API_KEY = PersistentConfig(
785
786
787
    "IMAGES_OPENAI_API_KEY",
    "image_generation.openai.api_key",
    os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY),
Timothy J. Baek's avatar
Timothy J. Baek committed
788
)
Timothy J. Baek's avatar
Timothy J. Baek committed
789

Timothy J. Baek's avatar
Timothy J. Baek committed
790
IMAGE_SIZE = PersistentConfig(
791
792
    "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
)
793

Timothy J. Baek's avatar
Timothy J. Baek committed
794
IMAGE_STEPS = PersistentConfig(
795
796
    "IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
)
797

Timothy J. Baek's avatar
Timothy J. Baek committed
798
IMAGE_GENERATION_MODEL = PersistentConfig(
799
800
801
802
    "IMAGE_GENERATION_MODEL",
    "image_generation.model",
    os.getenv("IMAGE_GENERATION_MODEL", ""),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
803

Timothy J. Baek's avatar
Timothy J. Baek committed
804
805
806
807
####################################
# Audio
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
808
AUDIO_OPENAI_API_BASE_URL = PersistentConfig(
809
810
811
812
    "AUDIO_OPENAI_API_BASE_URL",
    "audio.openai.api_base_url",
    os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
813
AUDIO_OPENAI_API_KEY = PersistentConfig(
814
815
816
817
    "AUDIO_OPENAI_API_KEY",
    "audio.openai.api_key",
    os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
818
AUDIO_OPENAI_API_MODEL = PersistentConfig(
819
820
821
822
    "AUDIO_OPENAI_API_MODEL",
    "audio.openai.api_model",
    os.getenv("AUDIO_OPENAI_API_MODEL", "tts-1"),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
823
AUDIO_OPENAI_API_VOICE = PersistentConfig(
824
825
826
827
    "AUDIO_OPENAI_API_VOICE",
    "audio.openai.api_voice",
    os.getenv("AUDIO_OPENAI_API_VOICE", "alloy"),
)
828
829
830
831
832

####################################
# LiteLLM
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
833
834
835

ENABLE_LITELLM = os.environ.get("ENABLE_LITELLM", "True").lower() == "true"

836
837
838
LITELLM_PROXY_PORT = int(os.getenv("LITELLM_PROXY_PORT", "14365"))
if LITELLM_PROXY_PORT < 0 or LITELLM_PROXY_PORT > 65535:
    raise ValueError("Invalid port number for LITELLM_PROXY_PORT")
839
LITELLM_PROXY_HOST = os.getenv("LITELLM_PROXY_HOST", "127.0.0.1")
840

841

842
843
844
845
846
####################################
# Database
####################################

DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")