1
0

env.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. import importlib.metadata
  2. import json
  3. import logging
  4. import os
  5. import pkgutil
  6. import sys
  7. import shutil
  8. from uuid import uuid4
  9. from pathlib import Path
  10. from cryptography.hazmat.primitives import serialization
  11. import markdown
  12. from bs4 import BeautifulSoup
  13. from open_webui.constants import ERROR_MESSAGES
  14. ####################################
  15. # Load .env file
  16. ####################################
  17. # Use .resolve() to get the canonical path, removing any '..' or '.' components
  18. ENV_FILE_PATH = Path(__file__).resolve()
  19. # OPEN_WEBUI_DIR should be the directory where env.py resides (open_webui/)
  20. OPEN_WEBUI_DIR = ENV_FILE_PATH.parent
  21. # BACKEND_DIR is the parent of OPEN_WEBUI_DIR (backend/)
  22. BACKEND_DIR = OPEN_WEBUI_DIR.parent
  23. # BASE_DIR is the parent of BACKEND_DIR (open-webui-dev/)
  24. BASE_DIR = BACKEND_DIR.parent
  25. try:
  26. from dotenv import find_dotenv, load_dotenv
  27. load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
  28. except ImportError:
  29. print("dotenv not installed, skipping...")
  30. DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
  31. # device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
  32. USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
  33. if USE_CUDA.lower() == "true":
  34. try:
  35. import torch
  36. assert torch.cuda.is_available(), "CUDA not available"
  37. DEVICE_TYPE = "cuda"
  38. except Exception as e:
  39. cuda_error = (
  40. "Error when testing CUDA but USE_CUDA_DOCKER is true. "
  41. f"Resetting USE_CUDA_DOCKER to false: {e}"
  42. )
  43. os.environ["USE_CUDA_DOCKER"] = "false"
  44. USE_CUDA = "false"
  45. DEVICE_TYPE = "cpu"
  46. else:
  47. DEVICE_TYPE = "cpu"
  48. try:
  49. import torch
  50. if torch.backends.mps.is_available() and torch.backends.mps.is_built():
  51. DEVICE_TYPE = "mps"
  52. except Exception:
  53. pass
  54. ####################################
  55. # LOGGING
  56. ####################################
  57. GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
  58. if GLOBAL_LOG_LEVEL in logging.getLevelNamesMapping():
  59. logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
  60. else:
  61. GLOBAL_LOG_LEVEL = "INFO"
  62. log = logging.getLogger(__name__)
  63. log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
  64. if "cuda_error" in locals():
  65. log.exception(cuda_error)
  66. del cuda_error
  67. log_sources = [
  68. "AUDIO",
  69. "COMFYUI",
  70. "CONFIG",
  71. "DB",
  72. "IMAGES",
  73. "MAIN",
  74. "MODELS",
  75. "OLLAMA",
  76. "OPENAI",
  77. "RAG",
  78. "WEBHOOK",
  79. "SOCKET",
  80. "OAUTH",
  81. ]
  82. SRC_LOG_LEVELS = {}
  83. for source in log_sources:
  84. log_env_var = source + "_LOG_LEVEL"
  85. SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
  86. if SRC_LOG_LEVELS[source] not in logging.getLevelNamesMapping():
  87. SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
  88. log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
  89. log.setLevel(SRC_LOG_LEVELS["CONFIG"])
  90. WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
  91. if WEBUI_NAME != "Open WebUI":
  92. WEBUI_NAME += " (Open WebUI)"
  93. WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
  94. TRUSTED_SIGNATURE_KEY = os.environ.get("TRUSTED_SIGNATURE_KEY", "")
  95. ####################################
  96. # ENV (dev,test,prod)
  97. ####################################
  98. ENV = os.environ.get("ENV", "dev")
  99. FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
  100. if FROM_INIT_PY:
  101. PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
  102. else:
  103. try:
  104. PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
  105. except Exception:
  106. PACKAGE_DATA = {"version": "0.0.0"}
  107. VERSION = PACKAGE_DATA["version"]
  108. INSTANCE_ID = os.environ.get("INSTANCE_ID", str(uuid4()))
  109. # Function to parse each section
  110. def parse_section(section):
  111. items = []
  112. for li in section.find_all("li"):
  113. # Extract raw HTML string
  114. raw_html = str(li)
  115. # Extract text without HTML tags
  116. text = li.get_text(separator=" ", strip=True)
  117. # Split into title and content
  118. parts = text.split(": ", 1)
  119. title = parts[0].strip() if len(parts) > 1 else ""
  120. content = parts[1].strip() if len(parts) > 1 else text
  121. items.append({"title": title, "content": content, "raw": raw_html})
  122. return items
  123. try:
  124. changelog_path = BASE_DIR / "CHANGELOG.md"
  125. with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
  126. changelog_content = file.read()
  127. except Exception:
  128. changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
  129. # Convert markdown content to HTML
  130. html_content = markdown.markdown(changelog_content)
  131. # Parse the HTML content
  132. soup = BeautifulSoup(html_content, "html.parser")
  133. # Initialize JSON structure
  134. changelog_json = {}
  135. # Iterate over each version
  136. for version in soup.find_all("h2"):
  137. version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
  138. date = version.get_text().strip().split(" - ")[1]
  139. version_data = {"date": date}
  140. # Find the next sibling that is a h3 tag (section title)
  141. current = version.find_next_sibling()
  142. while current and current.name != "h2":
  143. if current.name == "h3":
  144. section_title = current.get_text().lower() # e.g., "added", "fixed"
  145. section_items = parse_section(current.find_next_sibling("ul"))
  146. version_data[section_title] = section_items
  147. # Move to the next element
  148. current = current.find_next_sibling()
  149. changelog_json[version_number] = version_data
  150. CHANGELOG = changelog_json
  151. ####################################
  152. # SAFE_MODE
  153. ####################################
  154. SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
  155. ####################################
  156. # ENABLE_FORWARD_USER_INFO_HEADERS
  157. ####################################
  158. ENABLE_FORWARD_USER_INFO_HEADERS = (
  159. os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
  160. )
  161. # Experimental feature, may be removed in future
  162. ENABLE_STAR_SESSIONS_MIDDLEWARE = (
  163. os.environ.get("ENABLE_STAR_SESSIONS_MIDDLEWARE", "False").lower() == "true"
  164. )
  165. ####################################
  166. # WEBUI_BUILD_HASH
  167. ####################################
  168. WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
  169. ####################################
  170. # DATA/FRONTEND BUILD DIR
  171. ####################################
  172. DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
  173. if FROM_INIT_PY:
  174. NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
  175. NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
  176. # Check if the data directory exists in the package directory
  177. if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
  178. log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
  179. for item in DATA_DIR.iterdir():
  180. dest = NEW_DATA_DIR / item.name
  181. if item.is_dir():
  182. shutil.copytree(item, dest, dirs_exist_ok=True)
  183. else:
  184. shutil.copy2(item, dest)
  185. # Zip the data directory
  186. shutil.make_archive(DATA_DIR.parent / "open_webui_data", "zip", DATA_DIR)
  187. # Remove the old data directory
  188. shutil.rmtree(DATA_DIR)
  189. DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
  190. STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static"))
  191. FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
  192. FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
  193. if FROM_INIT_PY:
  194. FRONTEND_BUILD_DIR = Path(
  195. os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
  196. ).resolve()
  197. ####################################
  198. # Database
  199. ####################################
  200. # Check if the file exists
  201. if os.path.exists(f"{DATA_DIR}/ollama.db"):
  202. # Rename the file
  203. os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
  204. log.info("Database migrated from Ollama-WebUI successfully.")
  205. else:
  206. pass
  207. DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
  208. DATABASE_TYPE = os.environ.get("DATABASE_TYPE")
  209. DATABASE_USER = os.environ.get("DATABASE_USER")
  210. DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
  211. DATABASE_CRED = ""
  212. if DATABASE_USER:
  213. DATABASE_CRED += f"{DATABASE_USER}"
  214. if DATABASE_PASSWORD:
  215. DATABASE_CRED += f":{DATABASE_PASSWORD}"
  216. DB_VARS = {
  217. "db_type": DATABASE_TYPE,
  218. "db_cred": DATABASE_CRED,
  219. "db_host": os.environ.get("DATABASE_HOST"),
  220. "db_port": os.environ.get("DATABASE_PORT"),
  221. "db_name": os.environ.get("DATABASE_NAME"),
  222. }
  223. if all(DB_VARS.values()):
  224. DATABASE_URL = f"{DB_VARS['db_type']}://{DB_VARS['db_cred']}@{DB_VARS['db_host']}:{DB_VARS['db_port']}/{DB_VARS['db_name']}"
  225. elif DATABASE_TYPE == "sqlite+sqlcipher" and not os.environ.get("DATABASE_URL"):
  226. # Handle SQLCipher with local file when DATABASE_URL wasn't explicitly set
  227. DATABASE_URL = f"sqlite+sqlcipher:///{DATA_DIR}/webui.db"
  228. # Replace the postgres:// with postgresql://
  229. if "postgres://" in DATABASE_URL:
  230. DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
  231. DATABASE_SCHEMA = os.environ.get("DATABASE_SCHEMA", None)
  232. DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", None)
  233. if DATABASE_POOL_SIZE != None:
  234. try:
  235. DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE)
  236. except Exception:
  237. DATABASE_POOL_SIZE = None
  238. DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0)
  239. if DATABASE_POOL_MAX_OVERFLOW == "":
  240. DATABASE_POOL_MAX_OVERFLOW = 0
  241. else:
  242. try:
  243. DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW)
  244. except Exception:
  245. DATABASE_POOL_MAX_OVERFLOW = 0
  246. DATABASE_POOL_TIMEOUT = os.environ.get("DATABASE_POOL_TIMEOUT", 30)
  247. if DATABASE_POOL_TIMEOUT == "":
  248. DATABASE_POOL_TIMEOUT = 30
  249. else:
  250. try:
  251. DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT)
  252. except Exception:
  253. DATABASE_POOL_TIMEOUT = 30
  254. DATABASE_POOL_RECYCLE = os.environ.get("DATABASE_POOL_RECYCLE", 3600)
  255. if DATABASE_POOL_RECYCLE == "":
  256. DATABASE_POOL_RECYCLE = 3600
  257. else:
  258. try:
  259. DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE)
  260. except Exception:
  261. DATABASE_POOL_RECYCLE = 3600
  262. DATABASE_ENABLE_SQLITE_WAL = (
  263. os.environ.get("DATABASE_ENABLE_SQLITE_WAL", "False").lower() == "true"
  264. )
  265. DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = os.environ.get(
  266. "DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL", None
  267. )
  268. if DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL is not None:
  269. try:
  270. DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = float(
  271. DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL
  272. )
  273. except Exception:
  274. DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = 0.0
  275. RESET_CONFIG_ON_START = (
  276. os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
  277. )
  278. ENABLE_REALTIME_CHAT_SAVE = (
  279. os.environ.get("ENABLE_REALTIME_CHAT_SAVE", "False").lower() == "true"
  280. )
  281. ENABLE_QUERIES_CACHE = os.environ.get("ENABLE_QUERIES_CACHE", "False").lower() == "true"
  282. ####################################
  283. # REDIS
  284. ####################################
  285. REDIS_URL = os.environ.get("REDIS_URL", "")
  286. REDIS_CLUSTER = os.environ.get("REDIS_CLUSTER", "False").lower() == "true"
  287. REDIS_KEY_PREFIX = os.environ.get("REDIS_KEY_PREFIX", "open-webui")
  288. REDIS_SENTINEL_HOSTS = os.environ.get("REDIS_SENTINEL_HOSTS", "")
  289. REDIS_SENTINEL_PORT = os.environ.get("REDIS_SENTINEL_PORT", "26379")
  290. # Maximum number of retries for Redis operations when using Sentinel fail-over
  291. REDIS_SENTINEL_MAX_RETRY_COUNT = os.environ.get("REDIS_SENTINEL_MAX_RETRY_COUNT", "2")
  292. try:
  293. REDIS_SENTINEL_MAX_RETRY_COUNT = int(REDIS_SENTINEL_MAX_RETRY_COUNT)
  294. if REDIS_SENTINEL_MAX_RETRY_COUNT < 1:
  295. REDIS_SENTINEL_MAX_RETRY_COUNT = 2
  296. except ValueError:
  297. REDIS_SENTINEL_MAX_RETRY_COUNT = 2
  298. ####################################
  299. # UVICORN WORKERS
  300. ####################################
  301. # Number of uvicorn worker processes for handling requests
  302. UVICORN_WORKERS = os.environ.get("UVICORN_WORKERS", "1")
  303. try:
  304. UVICORN_WORKERS = int(UVICORN_WORKERS)
  305. if UVICORN_WORKERS < 1:
  306. UVICORN_WORKERS = 1
  307. except ValueError:
  308. UVICORN_WORKERS = 1
  309. log.info(f"Invalid UVICORN_WORKERS value, defaulting to {UVICORN_WORKERS}")
  310. ####################################
  311. # WEBUI_AUTH (Required for security)
  312. ####################################
  313. WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
  314. ENABLE_INITIAL_ADMIN_SIGNUP = (
  315. os.environ.get("ENABLE_INITIAL_ADMIN_SIGNUP", "False").lower() == "true"
  316. )
  317. ENABLE_SIGNUP_PASSWORD_CONFIRMATION = (
  318. os.environ.get("ENABLE_SIGNUP_PASSWORD_CONFIRMATION", "False").lower() == "true"
  319. )
  320. WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
  321. "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
  322. )
  323. WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
  324. WEBUI_AUTH_TRUSTED_GROUPS_HEADER = os.environ.get(
  325. "WEBUI_AUTH_TRUSTED_GROUPS_HEADER", None
  326. )
  327. BYPASS_MODEL_ACCESS_CONTROL = (
  328. os.environ.get("BYPASS_MODEL_ACCESS_CONTROL", "False").lower() == "true"
  329. )
  330. WEBUI_AUTH_SIGNOUT_REDIRECT_URL = os.environ.get(
  331. "WEBUI_AUTH_SIGNOUT_REDIRECT_URL", None
  332. )
  333. ####################################
  334. # WEBUI_SECRET_KEY
  335. ####################################
  336. WEBUI_SECRET_KEY = os.environ.get(
  337. "WEBUI_SECRET_KEY",
  338. os.environ.get(
  339. "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
  340. ), # DEPRECATED: remove at next major version
  341. )
  342. WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
  343. WEBUI_SESSION_COOKIE_SECURE = (
  344. os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
  345. )
  346. WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get(
  347. "WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE
  348. )
  349. WEBUI_AUTH_COOKIE_SECURE = (
  350. os.environ.get(
  351. "WEBUI_AUTH_COOKIE_SECURE",
  352. os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false"),
  353. ).lower()
  354. == "true"
  355. )
  356. if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
  357. raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
  358. ENABLE_COMPRESSION_MIDDLEWARE = (
  359. os.environ.get("ENABLE_COMPRESSION_MIDDLEWARE", "True").lower() == "true"
  360. )
  361. ####################################
  362. # OAUTH Configuration
  363. ####################################
  364. ENABLE_OAUTH_EMAIL_FALLBACK = (
  365. os.environ.get("ENABLE_OAUTH_EMAIL_FALLBACK", "False").lower() == "true"
  366. )
  367. ENABLE_OAUTH_ID_TOKEN_COOKIE = (
  368. os.environ.get("ENABLE_OAUTH_ID_TOKEN_COOKIE", "True").lower() == "true"
  369. )
  370. OAUTH_CLIENT_INFO_ENCRYPTION_KEY = os.environ.get(
  371. "OAUTH_CLIENT_INFO_ENCRYPTION_KEY", WEBUI_SECRET_KEY
  372. )
  373. OAUTH_SESSION_TOKEN_ENCRYPTION_KEY = os.environ.get(
  374. "OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY
  375. )
  376. ####################################
  377. # SCIM Configuration
  378. ####################################
  379. ENABLE_SCIM = (
  380. os.environ.get("ENABLE_SCIM", os.environ.get("SCIM_ENABLED", "False")).lower()
  381. == "true"
  382. )
  383. SCIM_TOKEN = os.environ.get("SCIM_TOKEN", "")
  384. ####################################
  385. # LICENSE_KEY
  386. ####################################
  387. LICENSE_KEY = os.environ.get("LICENSE_KEY", "")
  388. LICENSE_BLOB = None
  389. LICENSE_BLOB_PATH = os.environ.get("LICENSE_BLOB_PATH", DATA_DIR / "l.data")
  390. if LICENSE_BLOB_PATH and os.path.exists(LICENSE_BLOB_PATH):
  391. with open(LICENSE_BLOB_PATH, "rb") as f:
  392. LICENSE_BLOB = f.read()
  393. LICENSE_PUBLIC_KEY = os.environ.get("LICENSE_PUBLIC_KEY", "")
  394. pk = None
  395. if LICENSE_PUBLIC_KEY:
  396. pk = serialization.load_pem_public_key(
  397. f"""
  398. -----BEGIN PUBLIC KEY-----
  399. {LICENSE_PUBLIC_KEY}
  400. -----END PUBLIC KEY-----
  401. """.encode(
  402. "utf-8"
  403. )
  404. )
  405. ####################################
  406. # MODELS
  407. ####################################
  408. MODELS_CACHE_TTL = os.environ.get("MODELS_CACHE_TTL", "1")
  409. if MODELS_CACHE_TTL == "":
  410. MODELS_CACHE_TTL = None
  411. else:
  412. try:
  413. MODELS_CACHE_TTL = int(MODELS_CACHE_TTL)
  414. except Exception:
  415. MODELS_CACHE_TTL = 1
  416. ####################################
  417. # CHAT
  418. ####################################
  419. CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = os.environ.get(
  420. "CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE", "1"
  421. )
  422. if CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE == "":
  423. CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = 1
  424. else:
  425. try:
  426. CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = int(
  427. CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE
  428. )
  429. except Exception:
  430. CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = 1
  431. CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = os.environ.get(
  432. "CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES", "30"
  433. )
  434. if CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES == "":
  435. CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = 30
  436. else:
  437. try:
  438. CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = int(CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES)
  439. except Exception:
  440. CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES = 30
  441. CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE = os.environ.get(
  442. "CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE", ""
  443. )
  444. if CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE == "":
  445. CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE = None
  446. else:
  447. try:
  448. CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE = int(
  449. CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE
  450. )
  451. except Exception:
  452. CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE = None
  453. ####################################
  454. # WEBSOCKET SUPPORT
  455. ####################################
  456. ENABLE_WEBSOCKET_SUPPORT = (
  457. os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
  458. )
  459. WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
  460. WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", REDIS_URL)
  461. WEBSOCKET_REDIS_CLUSTER = (
  462. os.environ.get("WEBSOCKET_REDIS_CLUSTER", str(REDIS_CLUSTER)).lower() == "true"
  463. )
  464. websocket_redis_lock_timeout = os.environ.get("WEBSOCKET_REDIS_LOCK_TIMEOUT", "60")
  465. try:
  466. WEBSOCKET_REDIS_LOCK_TIMEOUT = int(websocket_redis_lock_timeout)
  467. except ValueError:
  468. WEBSOCKET_REDIS_LOCK_TIMEOUT = 60
  469. WEBSOCKET_SENTINEL_HOSTS = os.environ.get("WEBSOCKET_SENTINEL_HOSTS", "")
  470. WEBSOCKET_SENTINEL_PORT = os.environ.get("WEBSOCKET_SENTINEL_PORT", "26379")
  471. AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
  472. if AIOHTTP_CLIENT_TIMEOUT == "":
  473. AIOHTTP_CLIENT_TIMEOUT = None
  474. else:
  475. try:
  476. AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
  477. except Exception:
  478. AIOHTTP_CLIENT_TIMEOUT = 300
  479. AIOHTTP_CLIENT_SESSION_SSL = (
  480. os.environ.get("AIOHTTP_CLIENT_SESSION_SSL", "True").lower() == "true"
  481. )
  482. AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = os.environ.get(
  483. "AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST",
  484. os.environ.get("AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", "10"),
  485. )
  486. if AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST == "":
  487. AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = None
  488. else:
  489. try:
  490. AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = int(AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
  491. except Exception:
  492. AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 10
  493. AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = os.environ.get(
  494. "AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA", "10"
  495. )
  496. if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA == "":
  497. AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = None
  498. else:
  499. try:
  500. AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = int(
  501. AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
  502. )
  503. except Exception:
  504. AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = 10
  505. AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = (
  506. os.environ.get("AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL", "True").lower() == "true"
  507. )
  508. ####################################
  509. # SENTENCE TRANSFORMERS
  510. ####################################
  511. SENTENCE_TRANSFORMERS_BACKEND = os.environ.get("SENTENCE_TRANSFORMERS_BACKEND", "")
  512. if SENTENCE_TRANSFORMERS_BACKEND == "":
  513. SENTENCE_TRANSFORMERS_BACKEND = "torch"
  514. SENTENCE_TRANSFORMERS_MODEL_KWARGS = os.environ.get(
  515. "SENTENCE_TRANSFORMERS_MODEL_KWARGS", ""
  516. )
  517. if SENTENCE_TRANSFORMERS_MODEL_KWARGS == "":
  518. SENTENCE_TRANSFORMERS_MODEL_KWARGS = None
  519. else:
  520. try:
  521. SENTENCE_TRANSFORMERS_MODEL_KWARGS = json.loads(
  522. SENTENCE_TRANSFORMERS_MODEL_KWARGS
  523. )
  524. except Exception:
  525. SENTENCE_TRANSFORMERS_MODEL_KWARGS = None
  526. SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND = os.environ.get(
  527. "SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND", ""
  528. )
  529. if SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND == "":
  530. SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND = "torch"
  531. SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = os.environ.get(
  532. "SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS", ""
  533. )
  534. if SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS == "":
  535. SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = None
  536. else:
  537. try:
  538. SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = json.loads(
  539. SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS
  540. )
  541. except Exception:
  542. SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS = None
  543. ####################################
  544. # OFFLINE_MODE
  545. ####################################
  546. ENABLE_VERSION_UPDATE_CHECK = (
  547. os.environ.get("ENABLE_VERSION_UPDATE_CHECK", "true").lower() == "true"
  548. )
  549. OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
  550. if OFFLINE_MODE:
  551. os.environ["HF_HUB_OFFLINE"] = "1"
  552. ENABLE_VERSION_UPDATE_CHECK = False
  553. ####################################
  554. # AUDIT LOGGING
  555. ####################################
  556. # Where to store log file
  557. # Defaults to the DATA_DIR/audit.log. To set AUDIT_LOGS_FILE_PATH you need to
  558. # provide the whole path, like: /app/audit.log
  559. AUDIT_LOGS_FILE_PATH = os.getenv("AUDIT_LOGS_FILE_PATH", f"{DATA_DIR}/audit.log")
  560. # Maximum size of a file before rotating into a new log file
  561. AUDIT_LOG_FILE_ROTATION_SIZE = os.getenv("AUDIT_LOG_FILE_ROTATION_SIZE", "10MB")
  562. # Comma separated list of logger names to use for audit logging
  563. # Default is "uvicorn.access" which is the access log for Uvicorn
  564. # You can add more logger names to this list if you want to capture more logs
  565. AUDIT_UVICORN_LOGGER_NAMES = os.getenv(
  566. "AUDIT_UVICORN_LOGGER_NAMES", "uvicorn.access"
  567. ).split(",")
  568. # METADATA | REQUEST | REQUEST_RESPONSE
  569. AUDIT_LOG_LEVEL = os.getenv("AUDIT_LOG_LEVEL", "NONE").upper()
  570. try:
  571. MAX_BODY_LOG_SIZE = int(os.environ.get("MAX_BODY_LOG_SIZE") or 2048)
  572. except ValueError:
  573. MAX_BODY_LOG_SIZE = 2048
  574. # Comma separated list for urls to exclude from audit
  575. AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders").split(
  576. ","
  577. )
  578. AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
  579. AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
  580. ####################################
  581. # OPENTELEMETRY
  582. ####################################
  583. ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
  584. ENABLE_OTEL_TRACES = os.environ.get("ENABLE_OTEL_TRACES", "False").lower() == "true"
  585. ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true"
  586. ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true"
  587. OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
  588. "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
  589. )
  590. OTEL_METRICS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
  591. "OTEL_METRICS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
  592. )
  593. OTEL_LOGS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
  594. "OTEL_LOGS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
  595. )
  596. OTEL_EXPORTER_OTLP_INSECURE = (
  597. os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
  598. )
  599. OTEL_METRICS_EXPORTER_OTLP_INSECURE = (
  600. os.environ.get(
  601. "OTEL_METRICS_EXPORTER_OTLP_INSECURE", str(OTEL_EXPORTER_OTLP_INSECURE)
  602. ).lower()
  603. == "true"
  604. )
  605. OTEL_LOGS_EXPORTER_OTLP_INSECURE = (
  606. os.environ.get(
  607. "OTEL_LOGS_EXPORTER_OTLP_INSECURE", str(OTEL_EXPORTER_OTLP_INSECURE)
  608. ).lower()
  609. == "true"
  610. )
  611. OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui")
  612. OTEL_RESOURCE_ATTRIBUTES = os.environ.get(
  613. "OTEL_RESOURCE_ATTRIBUTES", ""
  614. ) # e.g. key1=val1,key2=val2
  615. OTEL_TRACES_SAMPLER = os.environ.get(
  616. "OTEL_TRACES_SAMPLER", "parentbased_always_on"
  617. ).lower()
  618. OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "")
  619. OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "")
  620. OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get(
  621. "OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
  622. )
  623. OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get(
  624. "OTEL_METRICS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
  625. )
  626. OTEL_LOGS_BASIC_AUTH_USERNAME = os.environ.get(
  627. "OTEL_LOGS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
  628. )
  629. OTEL_LOGS_BASIC_AUTH_PASSWORD = os.environ.get(
  630. "OTEL_LOGS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
  631. )
  632. OTEL_OTLP_SPAN_EXPORTER = os.environ.get(
  633. "OTEL_OTLP_SPAN_EXPORTER", "grpc"
  634. ).lower() # grpc or http
  635. OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get(
  636. "OTEL_METRICS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
  637. ).lower() # grpc or http
  638. OTEL_LOGS_OTLP_SPAN_EXPORTER = os.environ.get(
  639. "OTEL_LOGS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
  640. ).lower() # grpc or http
  641. ####################################
  642. # TOOLS/FUNCTIONS PIP OPTIONS
  643. ####################################
  644. PIP_OPTIONS = os.getenv("PIP_OPTIONS", "").split()
  645. PIP_PACKAGE_INDEX_OPTIONS = os.getenv("PIP_PACKAGE_INDEX_OPTIONS", "").split()
  646. ####################################
  647. # PROGRESSIVE WEB APP OPTIONS
  648. ####################################
  649. EXTERNAL_PWA_MANIFEST_URL = os.environ.get("EXTERNAL_PWA_MANIFEST_URL")