config.py 24.3 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
166
167
168
169
####################################
# WEBUI_VERSION
####################################

WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100")

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
####################################

387
388
389
OLLAMA_API_BASE_URL = os.environ.get(
    "OLLAMA_API_BASE_URL", "http://localhost:11434/api"
)
Timothy J. Baek's avatar
Timothy J. Baek committed
390

391
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
Timothy J. Baek's avatar
Timothy J. Baek committed
392
K8S_FLAG = os.environ.get("K8S_FLAG", "")
Jannik Streidl's avatar
Jannik Streidl committed
393
USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
394

395
if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
396
397
398
399
400
401
    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
402
if ENV == "prod":
403
    if OLLAMA_BASE_URL == "/ollama" and not K8S_FLAG:
Jannik Streidl's avatar
Jannik Streidl committed
404
        if USE_OLLAMA_DOCKER.lower() == "true":
405
            # if you use all-in-one docker container (Open WebUI + Ollama)
Jannik Streidl's avatar
Jannik Streidl committed
406
            # with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
407
            OLLAMA_BASE_URL = "http://localhost:11434"
408
        else:
409
            OLLAMA_BASE_URL = "http://host.docker.internal:11434"
Timothy J. Baek's avatar
Timothy J. Baek committed
410
    elif K8S_FLAG:
411
        OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
Timothy J. Baek's avatar
Timothy J. Baek committed
412

413

414
415
416
OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL

417
OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
Timothy J. Baek's avatar
Timothy J. Baek committed
418
OLLAMA_BASE_URLS = PersistentConfig(
419
420
    "OLLAMA_BASE_URLS", "ollama.base_urls", OLLAMA_BASE_URLS
)
421

Timothy J. Baek's avatar
Timothy J. Baek committed
422
423
424
425
####################################
# OPENAI_API
####################################

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
426
427
428
429
430
431
432
433

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
434
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
435
436
OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")

437

438
439
if OPENAI_API_BASE_URL == "":
    OPENAI_API_BASE_URL = "https://api.openai.com/v1"
Timothy J. Baek's avatar
Timothy J. Baek committed
440

Timothy J. Baek's avatar
Timothy J. Baek committed
441
442
443
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
444
OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
Timothy J. Baek's avatar
Timothy J. Baek committed
445
446
447
OPENAI_API_KEYS = PersistentConfig(
    "OPENAI_API_KEYS", "openai.api_keys", OPENAI_API_KEYS
)
Timothy J. Baek's avatar
Timothy J. Baek committed
448
449
450
451
452
453

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
454
455
456
457
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
458
OPENAI_API_BASE_URLS = PersistentConfig(
459
460
    "OPENAI_API_BASE_URLS", "openai.api_base_urls", OPENAI_API_BASE_URLS
)
461

Timothy J. Baek's avatar
Timothy J. Baek committed
462
OPENAI_API_KEY = ""
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
463
464

try:
465
466
    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
467
468
469
470
    ]
except:
    pass

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

473
474
475
476
####################################
# WEBUI
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
477
ENABLE_SIGNUP = PersistentConfig(
478
479
480
481
482
483
484
485
    "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
486
DEFAULT_MODELS = PersistentConfig(
487
    "DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
488
)
Timothy J. Baek's avatar
Timothy J. Baek committed
489

Timothy J. Baek's avatar
Timothy J. Baek committed
490
DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
491
492
493
    "DEFAULT_PROMPT_SUGGESTIONS",
    "ui.prompt_suggestions",
    [
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
        {
            "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
510
511
512
513
514
515
516
517
518
519
520
        {
            "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?",
        },
521
    ],
522
)
Timothy J. Baek's avatar
Timothy J. Baek committed
523

Timothy J. Baek's avatar
Timothy J. Baek committed
524
DEFAULT_USER_ROLE = PersistentConfig(
525
526
527
528
    "DEFAULT_USER_ROLE",
    "ui.default_user_role",
    os.getenv("DEFAULT_USER_ROLE", "pending"),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
529

530
531
USER_PERMISSIONS_CHAT_DELETION = (
    os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
532
)
533

Timothy J. Baek's avatar
Timothy J. Baek committed
534
USER_PERMISSIONS = PersistentConfig(
535
536
537
538
    "USER_PERMISSIONS",
    "ui.user_permissions",
    {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}},
)
Timothy J. Baek's avatar
Timothy J. Baek committed
539

Timothy J. Baek's avatar
Timothy J. Baek committed
540
ENABLE_MODEL_FILTER = PersistentConfig(
541
542
543
544
    "ENABLE_MODEL_FILTER",
    "model_filter.enable",
    os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
)
545
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
Timothy J. Baek's avatar
Timothy J. Baek committed
546
MODEL_FILTER_LIST = PersistentConfig(
547
548
549
550
    "MODEL_FILTER_LIST",
    "model_filter.list",
    [model.strip() for model in MODEL_FILTER_LIST.split(";")],
)
551

Timothy J. Baek's avatar
Timothy J. Baek committed
552
WEBHOOK_URL = PersistentConfig(
553
554
    "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
)
555

556
ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
557

Timothy J. Baek's avatar
Timothy J. Baek committed
558
####################################
559
# WEBUI_SECRET_KEY
Timothy J. Baek's avatar
Timothy J. Baek committed
560
561
####################################

562
563
WEBUI_SECRET_KEY = os.environ.get(
    "WEBUI_SECRET_KEY",
Timothy J. Baek's avatar
Timothy J. Baek committed
564
565
566
    os.environ.get(
        "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
    ),  # DEPRECATED: remove at next major version
567
)
568

569
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
Timothy J. Baek's avatar
Timothy J. Baek committed
570
    raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
571
572
573
574
575

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

576
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
577
578
579
580
581
582
583
584
585
586
587
588
589
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"
590
# 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)
591

Timothy J. Baek's avatar
Timothy J. Baek committed
592
RAG_TOP_K = PersistentConfig(
593
594
    "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "5"))
)
Timothy J. Baek's avatar
Timothy J. Baek committed
595
RAG_RELEVANCE_THRESHOLD = PersistentConfig(
596
597
598
    "RAG_RELEVANCE_THRESHOLD",
    "rag.relevance_threshold",
    float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")),
Timothy J. Baek's avatar
Timothy J. Baek committed
599
)
600

Timothy J. Baek's avatar
Timothy J. Baek committed
601
ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
602
603
604
605
    "ENABLE_RAG_HYBRID_SEARCH",
    "rag.enable_hybrid_search",
    os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
)
606

Timothy J. Baek's avatar
Timothy J. Baek committed
607
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
608
609
610
    "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
    "rag.enable_web_loader_ssl_verification",
    os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
611
612
)

Timothy J. Baek's avatar
Timothy J. Baek committed
613
RAG_EMBEDDING_ENGINE = PersistentConfig(
614
615
616
617
    "RAG_EMBEDDING_ENGINE",
    "rag.embedding_engine",
    os.environ.get("RAG_EMBEDDING_ENGINE", ""),
)
618

Timothy J. Baek's avatar
Timothy J. Baek committed
619
PDF_EXTRACT_IMAGES = PersistentConfig(
620
621
622
623
    "PDF_EXTRACT_IMAGES",
    "rag.pdf_extract_images",
    os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
)
624

Timothy J. Baek's avatar
Timothy J. Baek committed
625
RAG_EMBEDDING_MODEL = PersistentConfig(
626
627
628
    "RAG_EMBEDDING_MODEL",
    "rag.embedding_model",
    os.environ.get("RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
629
)
630
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL.value}"),
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
631

632
633
634
635
RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
    os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
)

636
637
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
    os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
Steven Kreitzer's avatar
Steven Kreitzer committed
638
639
)

Timothy J. Baek's avatar
Timothy J. Baek committed
640
RAG_RERANKING_MODEL = PersistentConfig(
641
642
643
644
645
646
    "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
647

648
649
650
651
RAG_RERANKING_MODEL_AUTO_UPDATE = (
    os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true"
)

Steven Kreitzer's avatar
Steven Kreitzer committed
652
653
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
654
)
655

656

657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
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
674

675

676
677
678
679
680
681
682
683
# 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
684
CHUNK_SIZE = PersistentConfig(
685
686
    "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1500"))
)
Timothy J. Baek's avatar
Timothy J. Baek committed
687
CHUNK_OVERLAP = PersistentConfig(
688
689
690
691
    "CHUNK_OVERLAP",
    "rag.chunk_overlap",
    int(os.environ.get("CHUNK_OVERLAP", "100")),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
692

693
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
694
695
696
697
698
699
700
701
702
<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.
703

Timothy J. Baek's avatar
Timothy J. Baek committed
704
705
706
Given the context information, answer the query.
Query: [query]"""

Timothy J. Baek's avatar
Timothy J. Baek committed
707
RAG_TEMPLATE = PersistentConfig(
708
709
710
711
    "RAG_TEMPLATE",
    "rag.template",
    os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
)
712

Timothy J. Baek's avatar
Timothy J. Baek committed
713
RAG_OPENAI_API_BASE_URL = PersistentConfig(
714
715
716
717
    "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
718
RAG_OPENAI_API_KEY = PersistentConfig(
719
720
721
722
    "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
723

724
725
726
ENABLE_RAG_LOCAL_WEB_FETCH = (
    os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true"
)
727

Timothy J. Baek's avatar
Timothy J. Baek committed
728
YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
729
730
731
732
    "YOUTUBE_LOADER_LANGUAGE",
    "rag.youtube_loader_language",
    os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
)
733

Timothy J. Baek's avatar
Timothy J. Baek committed
734
735
736
####################################
# Transcribe
####################################
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
737
738
739

WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
740
741
742
WHISPER_MODEL_AUTO_UPDATE = (
    os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
)
Timothy J. Baek's avatar
Timothy J. Baek committed
743

744

Timothy J. Baek's avatar
Timothy J. Baek committed
745
746
747
748
####################################
# Images
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
749
IMAGE_GENERATION_ENGINE = PersistentConfig(
750
751
752
753
    "IMAGE_GENERATION_ENGINE",
    "image_generation.engine",
    os.getenv("IMAGE_GENERATION_ENGINE", ""),
)
754

Timothy J. Baek's avatar
Timothy J. Baek committed
755
ENABLE_IMAGE_GENERATION = PersistentConfig(
756
757
758
759
    "ENABLE_IMAGE_GENERATION",
    "image_generation.enable",
    os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true",
)
Timothy J. Baek's avatar
Timothy J. Baek committed
760
AUTOMATIC1111_BASE_URL = PersistentConfig(
761
762
763
    "AUTOMATIC1111_BASE_URL",
    "image_generation.automatic1111.base_url",
    os.getenv("AUTOMATIC1111_BASE_URL", ""),
764
)
Timothy J. Baek's avatar
Timothy J. Baek committed
765

Timothy J. Baek's avatar
Timothy J. Baek committed
766
COMFYUI_BASE_URL = PersistentConfig(
767
768
769
770
    "COMFYUI_BASE_URL",
    "image_generation.comfyui.base_url",
    os.getenv("COMFYUI_BASE_URL", ""),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
771

Timothy J. Baek's avatar
Timothy J. Baek committed
772
IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
773
774
775
776
    "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
777
IMAGES_OPENAI_API_KEY = PersistentConfig(
778
779
780
    "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
781
)
Timothy J. Baek's avatar
Timothy J. Baek committed
782

Timothy J. Baek's avatar
Timothy J. Baek committed
783
IMAGE_SIZE = PersistentConfig(
784
785
    "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
)
786

Timothy J. Baek's avatar
Timothy J. Baek committed
787
IMAGE_STEPS = PersistentConfig(
788
789
    "IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
)
790

Timothy J. Baek's avatar
Timothy J. Baek committed
791
IMAGE_GENERATION_MODEL = PersistentConfig(
792
793
794
795
    "IMAGE_GENERATION_MODEL",
    "image_generation.model",
    os.getenv("IMAGE_GENERATION_MODEL", ""),
)
Timothy J. Baek's avatar
Timothy J. Baek committed
796

Timothy J. Baek's avatar
Timothy J. Baek committed
797
798
799
800
####################################
# Audio
####################################

Timothy J. Baek's avatar
Timothy J. Baek committed
801
AUDIO_OPENAI_API_BASE_URL = PersistentConfig(
802
803
804
805
    "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
806
AUDIO_OPENAI_API_KEY = PersistentConfig(
807
808
809
810
    "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
811
AUDIO_OPENAI_API_MODEL = PersistentConfig(
812
813
814
815
    "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
816
AUDIO_OPENAI_API_VOICE = PersistentConfig(
817
818
819
820
    "AUDIO_OPENAI_API_VOICE",
    "audio.openai.api_voice",
    os.getenv("AUDIO_OPENAI_API_VOICE", "alloy"),
)
821
822
823
824
825

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

Timothy J. Baek's avatar
Timothy J. Baek committed
826
827
828

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

829
830
831
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")
832
LITELLM_PROXY_HOST = os.getenv("LITELLM_PROXY_HOST", "127.0.0.1")
833

834

835
836
837
838
839
####################################
# Database
####################################

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