env.py 23 KB

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