Coverage for open_webui/config.py: 75%
1172 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 05:07 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 05:07 +0000
1from __future__ import annotations
3import base64
4import logging
5import os
6import shutil
7import socket
8from concurrent.futures import ThreadPoolExecutor
9from datetime import datetime
10from pathlib import Path
11from typing import Optional, Union
12from urllib.parse import urlparse
14import redis
15import requests
16from authlib.integrations.starlette_client import OAuth
17from pydantic import BaseModel
19from open_webui.env import (
20 USE_SLIM,
21 DATA_DIR,
22 DATABASE_URL,
23 ENABLE_ADMIN_CHAT_ACCESS,
24 ENABLE_DB_MIGRATIONS,
25 ENV,
26 FRONTEND_BUILD_DIR,
27 OFFLINE_MODE,
28 OPEN_WEBUI_DIR,
29 REDIS_KEY_PREFIX,
30 REDIS_SENTINEL_HOSTS,
31 REDIS_SENTINEL_PORT,
32 REDIS_URL,
33 WEBUI_AUTH,
34 WEBUI_FAVICON_URL,
35 WEBUI_NAME,
36 log,
37)
38from open_webui.models.config import Config
39from open_webui.utils.json_codec import JSONCodec
42async def seed_registered_defaults():
43 await Config.rename_prefix('rag.web', 'web')
44 await Config.repair_config_rows()
45 await Config.seed_defaults(DEFAULT_CONFIG)
48async def async_reset_config():
49 await Config.clear()
52class EndpointFilter(logging.Filter):
53 def filter(self, record: logging.LogRecord) -> bool:
54 return record.getMessage().find('/health') == -1
57logging.getLogger('uvicorn.access').addFilter(EndpointFilter())
59####################################
60# Initialization
61####################################
64def run_migrations():
65 log.info('Running migrations')
66 try:
67 from alembic import command
68 from alembic.config import Config as AlembicConfig
70 alembic_cfg = AlembicConfig(OPEN_WEBUI_DIR / 'alembic.ini')
72 migrations_path = OPEN_WEBUI_DIR / 'migrations'
73 alembic_cfg.set_main_option('script_location', str(migrations_path))
75 command.upgrade(alembic_cfg, 'head')
76 except Exception as e:
77 log.exception(f'Error running migrations: {e}')
78 raise
81if ENABLE_DB_MIGRATIONS: 81 ↛ 85line 81 didn't jump to line 85 because the condition on line 81 was always true
82 run_migrations()
85async def import_legacy_config_json():
86 """Migrate legacy config.json → database on first run."""
87 if not os.path.exists(f'{DATA_DIR}/config.json'): 87 ↛ 89line 87 didn't jump to line 89 because the condition on line 87 was always true
88 return
89 with open(f'{DATA_DIR}/config.json', 'r') as _f:
90 await Config.upsert(JSONCodec.loads(_f.read()))
91 os.rename(f'{DATA_DIR}/config.json', f'{DATA_DIR}/old_config.json')
94####################################
95# Static DIR
96####################################
98STATIC_DIR = Path(os.getenv('STATIC_DIR', OPEN_WEBUI_DIR / 'static')).resolve()
100try:
101 if STATIC_DIR.exists(): 101 ↛ 111line 101 didn't jump to line 111 because the condition on line 101 was always true
102 for item in STATIC_DIR.iterdir():
103 if item.is_file() or item.is_symlink():
104 try:
105 item.unlink()
106 except Exception as e:
107 pass
108except Exception as e:
109 pass
111for file_path in (FRONTEND_BUILD_DIR / 'static').glob('**/*'):
112 if file_path.is_file(): 112 ↛ 111line 112 didn't jump to line 111 because the condition on line 112 was always true
113 target_path = STATIC_DIR / file_path.relative_to((FRONTEND_BUILD_DIR / 'static'))
114 target_path.parent.mkdir(parents=True, exist_ok=True)
115 try:
116 shutil.copyfile(file_path, target_path)
117 except Exception as e:
118 logging.error(f'An error occurred: {e}')
120# LICENSE covers copied Open WebUI logo/favicon assets.
121# Do not alter, remove, obscure, or replace them except as LICENSE permits:
122# https://docs.openwebui.com/license.
123frontend_favicon = FRONTEND_BUILD_DIR / 'static' / 'favicon.png'
125if frontend_favicon.exists(): 125 ↛ 131line 125 didn't jump to line 131 because the condition on line 125 was always true
126 try:
127 shutil.copyfile(frontend_favicon, STATIC_DIR / 'favicon.png')
128 except Exception as e:
129 logging.error(f'An error occurred: {e}')
131frontend_splash = FRONTEND_BUILD_DIR / 'static' / 'splash.png'
133if frontend_splash.exists(): 133 ↛ 139line 133 didn't jump to line 139 because the condition on line 133 was always true
134 try:
135 shutil.copyfile(frontend_splash, STATIC_DIR / 'splash.png')
136 except Exception as e:
137 logging.error(f'An error occurred: {e}')
139frontend_loader = FRONTEND_BUILD_DIR / 'static' / 'loader.js'
141if frontend_loader.exists(): 141 ↛ 150line 141 didn't jump to line 150 because the condition on line 141 was always true
142 try:
143 shutil.copyfile(frontend_loader, STATIC_DIR / 'loader.js')
144 except Exception as e:
145 logging.error(f'An error occurred: {e}')
148# --- Storage Provider ---
150STORAGE_PROVIDER = os.getenv('STORAGE_PROVIDER', 'local') # defaults to local, s3
151STORAGE_LOCAL_CACHE = os.getenv('STORAGE_LOCAL_CACHE', 'true').lower() == 'true'
153S3_ACCESS_KEY_ID = os.getenv('S3_ACCESS_KEY_ID', None)
154S3_SECRET_ACCESS_KEY = os.getenv('S3_SECRET_ACCESS_KEY', None)
155S3_REGION_NAME = os.getenv('S3_REGION_NAME', None)
156S3_BUCKET_NAME = os.getenv('S3_BUCKET_NAME', None)
157S3_KEY_PREFIX = os.getenv('S3_KEY_PREFIX', None)
158S3_ENDPOINT_URL = os.getenv('S3_ENDPOINT_URL', None)
159S3_USE_ACCELERATE_ENDPOINT = os.getenv('S3_USE_ACCELERATE_ENDPOINT', 'false').lower() == 'true'
160S3_ADDRESSING_STYLE = os.getenv('S3_ADDRESSING_STYLE', None)
161S3_ENABLE_TAGGING = os.getenv('S3_ENABLE_TAGGING', 'false').lower() == 'true'
163GCS_BUCKET_NAME = os.getenv('GCS_BUCKET_NAME', None)
164GOOGLE_APPLICATION_CREDENTIALS_JSON = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON', None)
166AZURE_STORAGE_ENDPOINT = os.getenv('AZURE_STORAGE_ENDPOINT', None)
167AZURE_STORAGE_CONTAINER_NAME = os.getenv('AZURE_STORAGE_CONTAINER_NAME', None)
168AZURE_STORAGE_KEY = os.getenv('AZURE_STORAGE_KEY', None)
170####################################
171# File Upload DIR
172####################################
174UPLOAD_DIR = DATA_DIR / 'uploads'
175UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
178####################################
179# Cache DIR
180####################################
182CACHE_DIR = DATA_DIR / 'cache'
183CACHE_DIR.mkdir(parents=True, exist_ok=True)
186####################################
187# CUSTOM_NAME (Legacy)
188####################################
190# LICENSE covers this legacy Open WebUI branding path.
191# Do not alter, remove, obscure, or replace it except as LICENSE permits:
192# https://docs.openwebui.com/license.
193CUSTOM_NAME = os.getenv('CUSTOM_NAME', '')
195if CUSTOM_NAME: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 try:
197 r = requests.get(f'https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}')
198 data = r.json()
199 if r.ok:
200 if 'logo' in data:
201 WEBUI_FAVICON_URL = url = (
202 f'https://api.openwebui.com{data["logo"]}' if data['logo'][0] == '/' else data['logo']
203 )
205 r = requests.get(url, stream=True)
206 if r.status_code == 200:
207 with open(f'{STATIC_DIR}/favicon.png', 'wb') as f:
208 r.raw.decode_content = True
209 shutil.copyfileobj(r.raw, f)
211 if 'splash' in data:
212 url = f'https://api.openwebui.com{data["splash"]}' if data['splash'][0] == '/' else data['splash']
214 r = requests.get(url, stream=True)
215 if r.status_code == 200:
216 with open(f'{STATIC_DIR}/splash.png', 'wb') as f:
217 r.raw.decode_content = True
218 shutil.copyfileobj(r.raw, f)
220 WEBUI_NAME = data['name']
221 except Exception as e:
222 log.exception(e)
223 pass
226####################################
227# DIRECT CONNECTIONS
228####################################
230ENABLE_DIRECT_CONNECTIONS = os.getenv('ENABLE_DIRECT_CONNECTIONS', 'False').lower() == 'true'
231ENABLE_DIRECT_INTEGRATIONS = os.getenv('ENABLE_DIRECT_INTEGRATIONS', 'False').lower() == 'true'
233####################################
234# OLLAMA_BASE_URL
235####################################
237ENABLE_OLLAMA_API = os.getenv('ENABLE_OLLAMA_API', 'True').lower() == 'true'
239OLLAMA_API_BASE_URL = os.getenv('OLLAMA_API_BASE_URL', 'http://localhost:11434/api')
241OLLAMA_BASE_URL = os.getenv('OLLAMA_BASE_URL', '')
242if OLLAMA_BASE_URL: 242 ↛ 247line 242 didn't jump to line 247 because the condition on line 242 was always true
243 # Remove trailing slash
244 OLLAMA_BASE_URL = OLLAMA_BASE_URL[:-1] if OLLAMA_BASE_URL.endswith('/') else OLLAMA_BASE_URL
247K8S_FLAG = os.getenv('K8S_FLAG', '')
248USE_OLLAMA_DOCKER = os.getenv('USE_OLLAMA_DOCKER', 'false')
250if OLLAMA_BASE_URL == '' and OLLAMA_API_BASE_URL != '': 250 ↛ 251line 250 didn't jump to line 251 because the condition on line 250 was never true
251 OLLAMA_BASE_URL = OLLAMA_API_BASE_URL[:-4] if OLLAMA_API_BASE_URL.endswith('/api') else OLLAMA_API_BASE_URL
253if ENV == 'prod': 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 if OLLAMA_BASE_URL == '/ollama' and not K8S_FLAG:
255 if USE_OLLAMA_DOCKER.lower() == 'true':
256 # if you use all-in-one docker container (Open WebUI + Ollama)
257 # with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
258 OLLAMA_BASE_URL = 'http://localhost:11434'
259 else:
260 OLLAMA_BASE_URL = 'http://host.docker.internal:11434'
261 elif K8S_FLAG:
262 OLLAMA_BASE_URL = 'http://ollama-service.open-webui.svc.cluster.local:11434'
265def _resolve_ollama_base_url(url: str) -> str:
266 """If the default Ollama port (11434) is unreachable, try the fallback port (12434)."""
268 def reachable(host: str, port: int) -> bool:
269 try:
270 with socket.create_connection((host, port), timeout=1.0):
271 return True
272 except (OSError, TimeoutError):
273 return False
275 host = urlparse(url).hostname or 'localhost'
277 with ThreadPoolExecutor(max_workers=2) as pool:
278 default = pool.submit(reachable, host, 11434)
279 fallback = pool.submit(reachable, host, 12434)
281 if not default.result() and fallback.result(): 281 ↛ 282line 281 didn't jump to line 282 because the condition on line 281 was never true
282 url = url.replace(':11434', ':12434')
283 log.info('Ollama port 11434 unreachable on %s, falling back to 12434', host)
284 elif not default.result(): 284 ↛ 287line 284 didn't jump to line 287 because the condition on line 284 was always true
285 log.info('Ollama ports 11434 and 12434 both unreachable on %s', host)
287 return url
290# Auto-resolve Ollama port when no explicit URL was provided by the user.
291# The Dockerfile default is "/ollama" which the block above rewrites to :11434.
292if os.getenv('OLLAMA_BASE_URL', '') in ('', '/ollama') and not os.getenv('OLLAMA_BASE_URLS', ''): 292 ↛ 296line 292 didn't jump to line 296 because the condition on line 292 was always true
293 OLLAMA_BASE_URL = _resolve_ollama_base_url(OLLAMA_BASE_URL)
296OLLAMA_BASE_URLS = os.getenv('OLLAMA_BASE_URLS', '')
297OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != '' else OLLAMA_BASE_URL
299OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(';')]
300OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
302OLLAMA_API_CONFIGS = {}
303_ollama_api_configs = os.getenv('OLLAMA_API_CONFIGS', '')
304if _ollama_api_configs: 304 ↛ 305line 304 didn't jump to line 305 because the condition on line 304 was never true
305 try:
306 parsed = JSONCodec.loads(_ollama_api_configs)
307 if isinstance(parsed, dict):
308 OLLAMA_API_CONFIGS = parsed
309 else:
310 log.warning('OLLAMA_API_CONFIGS must be a JSON object, ignoring')
311 except (JSONCodec.JSONDecodeError, TypeError):
312 log.warning('OLLAMA_API_CONFIGS is not valid JSON, ignoring')
314####################################
315# OPENAI_API
316####################################
319ENABLE_OPENAI_API = os.getenv('ENABLE_OPENAI_API', 'True').lower() == 'true'
322OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', '')
323OPENAI_API_BASE_URL = os.getenv('OPENAI_API_BASE_URL', '')
325GEMINI_API_KEY = os.getenv('GEMINI_API_KEY', '')
326GEMINI_API_BASE_URL = os.getenv('GEMINI_API_BASE_URL', '')
329if OPENAI_API_BASE_URL == '': 329 ↛ 332line 329 didn't jump to line 332 because the condition on line 329 was always true
330 OPENAI_API_BASE_URL = 'https://api.openai.com/v1'
331else:
332 if OPENAI_API_BASE_URL.endswith('/'):
333 OPENAI_API_BASE_URL = OPENAI_API_BASE_URL[:-1]
335OPENAI_API_KEYS = os.getenv('OPENAI_API_KEYS', '')
336OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != '' else OPENAI_API_KEY
338OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(';')]
339OPENAI_API_KEYS = OPENAI_API_KEYS
341OPENAI_API_BASE_URLS = os.getenv('OPENAI_API_BASE_URLS', '')
342OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != '' else OPENAI_API_BASE_URL
344OPENAI_API_BASE_URLS = [
345 url.strip() if url != '' else 'https://api.openai.com/v1' for url in OPENAI_API_BASE_URLS.split(';')
346]
347OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
349OPENAI_API_CONFIGS = {}
350_openai_api_configs = os.getenv('OPENAI_API_CONFIGS', '')
351if _openai_api_configs: 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true
352 try:
353 parsed = JSONCodec.loads(_openai_api_configs)
354 if isinstance(parsed, dict):
355 OPENAI_API_CONFIGS = parsed
356 else:
357 log.warning('OPENAI_API_CONFIGS must be a JSON object, ignoring')
358 except (JSONCodec.JSONDecodeError, TypeError):
359 log.warning('OPENAI_API_CONFIGS is not valid JSON, ignoring')
361# Get the actual OpenAI API key based on the base URL
362OPENAI_API_KEY = ''
363try:
364 OPENAI_API_KEY = OPENAI_API_KEYS[OPENAI_API_BASE_URLS.index('https://api.openai.com/v1')]
365except Exception:
366 pass
367OPENAI_API_BASE_URL = 'https://api.openai.com/v1'
370####################################
371# MODELS
372####################################
374ENABLE_BASE_MODELS_CACHE = os.getenv('ENABLE_BASE_MODELS_CACHE', 'False').lower() == 'true'
377####################################
378# TOOL_SERVERS
379####################################
381try:
382 tool_server_connections = JSONCodec.loads(os.getenv('TOOL_SERVER_CONNECTIONS', '[]'))
383except Exception as e:
384 log.exception(f'Error loading TOOL_SERVER_CONNECTIONS: {e}')
385 tool_server_connections = []
388TOOL_SERVER_CONNECTIONS = tool_server_connections
390OAUTH_CLIENT_TIMEOUT = os.getenv('OAUTH_CLIENT_TIMEOUT', '')
392####################################
393# TERMINAL_SERVER
394####################################
396terminal_server_connections = JSONCodec.loads(os.getenv('TERMINAL_SERVER_CONNECTIONS', '[]'))
398TERMINAL_SERVER_CONNECTIONS = terminal_server_connections
400try:
401 TERMINAL_PROXY_HEADERS = JSONCodec.loads(os.getenv('TERMINAL_PROXY_HEADERS', '{}'))
402except Exception:
403 TERMINAL_PROXY_HEADERS = {}
405####################################
406# Code Interpreter
407####################################
409ENABLE_CODE_EXECUTION = os.getenv('ENABLE_CODE_EXECUTION', 'True').lower() == 'true'
411CODE_EXECUTION_ENGINE = os.getenv('CODE_EXECUTION_ENGINE', 'pyodide')
413CODE_EXECUTION_JUPYTER_URL = os.getenv('CODE_EXECUTION_JUPYTER_URL', '')
415CODE_EXECUTION_JUPYTER_AUTH = os.getenv('CODE_EXECUTION_JUPYTER_AUTH', '')
417CODE_EXECUTION_JUPYTER_AUTH_TOKEN = os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', '')
420CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', '')
422CODE_EXECUTION_JUPYTER_TIMEOUT = int(os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'))
424ENABLE_CODE_INTERPRETER = os.getenv('ENABLE_CODE_INTERPRETER', 'True').lower() == 'true'
426ENABLE_MEMORIES = os.getenv('ENABLE_MEMORIES', 'True').lower() == 'true'
427ENABLE_MEMORY_SYSTEM_CONTEXT = os.getenv('ENABLE_MEMORY_SYSTEM_CONTEXT', 'True').lower() == 'true'
428ENABLE_MEMORY_BACKGROUND_REVIEW = os.getenv('ENABLE_MEMORY_BACKGROUND_REVIEW', 'False').lower() == 'true'
429MEMORIES_REVIEW_INTERVAL_TURNS = int(os.getenv('MEMORIES_REVIEW_INTERVAL_TURNS', '10'))
430MEMORIES_USER_CHAR_LIMIT = int(os.getenv('MEMORIES_USER_CHAR_LIMIT', '2000'))
431MEMORIES_CONTEXT_CHAR_LIMIT = int(os.getenv('MEMORIES_CONTEXT_CHAR_LIMIT', '2000'))
433CODE_INTERPRETER_ENGINE = os.getenv('CODE_INTERPRETER_ENGINE', 'pyodide')
435CODE_INTERPRETER_PROMPT_TEMPLATE = os.getenv('CODE_INTERPRETER_PROMPT_TEMPLATE', '')
437CODE_INTERPRETER_JUPYTER_URL = os.getenv('CODE_INTERPRETER_JUPYTER_URL', os.getenv('CODE_EXECUTION_JUPYTER_URL', ''))
439CODE_INTERPRETER_JUPYTER_AUTH = os.getenv(
440 'CODE_INTERPRETER_JUPYTER_AUTH',
441 os.getenv('CODE_EXECUTION_JUPYTER_AUTH', ''),
442)
444CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = os.getenv(
445 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN',
446 os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', ''),
447)
450CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = os.getenv(
451 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD',
452 os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', ''),
453)
455CODE_INTERPRETER_JUPYTER_TIMEOUT = int(
456 os.getenv(
457 'CODE_INTERPRETER_JUPYTER_TIMEOUT',
458 os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'),
459 )
460)
462CODE_INTERPRETER_BLOCKED_MODULES = [
463 library.strip() for library in os.getenv('CODE_INTERPRETER_BLOCKED_MODULES', '').split(',') if library.strip()
464]
466DEFAULT_CODE_INTERPRETER_PROMPT = """
467#### Code Interpreter
469You have access to a Python code interpreter via: `<code_interpreter type="code" lang="python"></code_interpreter>`
471- The Python shell runs directly in the user's browser for fast execution of analysis, calculations, or problem-solving. Use it in this response.
472- You can use a wide array of libraries for data manipulation, visualization, API calls, or any computational task. Think outside the box and harness Python's full potential.
473- **You must enclose your code within `<code_interpreter type="code" lang="python">` XML tags** and stop right away. If you don't, the code won't execute.
474- Do NOT use triple backticks (```py ... ```) inside the XML tags — that is markdown formatting, not executable Python code.
475- **Always print meaningful outputs** (results, tables, summaries, visuals). Avoid implicit outputs; use explicit print statements.
476- After obtaining output, **provide a concise analysis, interpretation, or next steps** to help the user understand the findings.
477- If results are unclear or unexpected, refine the code and re-execute. Iterate until you deliver meaningful insights.
478- **If a link to an image, audio, or any file appears in the output, display it exactly as-is** in your response so the user can access it. Do not modify the link.
479- Respond in the chat's primary language. Default to English if multilingual.
481Ensure the code interpreter is effectively utilized to achieve the highest-quality analysis for the user."""
483# Appended to the code interpreter prompt only when engine is pyodide (not jupyter)
484CODE_INTERPRETER_PYODIDE_PROMPT = """
486##### Pyodide Environment
488- This Python environment runs via Pyodide in the browser. **Do not install packages** — `pip install`, `subprocess`, and `micropip.install()` are not available.
489- If a required library is unavailable, use an alternative approach with available modules. Do not attempt to install anything.
491##### Persistent File System
493- User-uploaded files are available at `/mnt/uploads/`. When the user asks you to work with their files, read from this directory.
494- You can also write output files to `/mnt/uploads/` so the user can access and download them from the file browser.
495- The file system persists across code executions within the same session.
496- Use `import os; os.listdir('/mnt/uploads')` to discover available files."""
499####################################
500# Vector Database
501####################################
503VECTOR_DB = os.getenv('VECTOR_DB', 'pgvector' if USE_SLIM else 'chroma')
505# Chroma
506CHROMA_DATA_PATH = f'{DATA_DIR}/vector_db'
508if VECTOR_DB == 'chroma' and not USE_SLIM: 508 ↛ 528line 508 didn't jump to line 528 because the condition on line 508 was always true
509 import chromadb
511 CHROMA_TENANT = os.getenv('CHROMA_TENANT', chromadb.DEFAULT_TENANT)
512 CHROMA_DATABASE = os.getenv('CHROMA_DATABASE', chromadb.DEFAULT_DATABASE)
513 CHROMA_HTTP_HOST = os.getenv('CHROMA_HTTP_HOST', '')
514 CHROMA_HTTP_PORT = int(os.getenv('CHROMA_HTTP_PORT', '8000'))
515 CHROMA_CLIENT_AUTH_PROVIDER = os.getenv('CHROMA_CLIENT_AUTH_PROVIDER', '')
516 CHROMA_CLIENT_AUTH_CREDENTIALS = os.getenv('CHROMA_CLIENT_AUTH_CREDENTIALS', '')
517 # Comma-separated list of header=value pairs
518 CHROMA_HTTP_HEADERS = os.getenv('CHROMA_HTTP_HEADERS', '')
519 if CHROMA_HTTP_HEADERS: 519 ↛ 520line 519 didn't jump to line 520 because the condition on line 519 was never true
520 CHROMA_HTTP_HEADERS = dict([pair.split('=') for pair in CHROMA_HTTP_HEADERS.split(',')])
521 else:
522 CHROMA_HTTP_HEADERS = None
523 CHROMA_HTTP_SSL = os.getenv('CHROMA_HTTP_SSL', 'false').lower() == 'true'
524# 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)
527# MariaDB Vector (mariadb-vector)
528MARIADB_VECTOR_DB_URL = os.getenv('MARIADB_VECTOR_DB_URL', '').strip()
530MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH = int(
531 os.getenv('MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH', '1536').strip() or '1536'
532)
534# Distance strategy:
535# - cosine => vec_distance_cosine(...)
536# - euclidean => vec_distance_euclidean(...)
537MARIADB_VECTOR_DISTANCE_STRATEGY = os.getenv('MARIADB_VECTOR_DISTANCE_STRATEGY', 'cosine').strip().lower()
539# HNSW M parameter (MariaDB VECTOR INDEX ... M=<int>)
540MARIADB_VECTOR_INDEX_M = int(os.getenv('MARIADB_VECTOR_INDEX_M', '8').strip() or '8')
542# Pooling (MariaDB-Vector)
543MARIADB_VECTOR_POOL_SIZE = os.getenv('MARIADB_VECTOR_POOL_SIZE', None)
545if MARIADB_VECTOR_POOL_SIZE != None: 545 ↛ 546line 545 didn't jump to line 546 because the condition on line 545 was never true
546 try:
547 MARIADB_VECTOR_POOL_SIZE = int(MARIADB_VECTOR_POOL_SIZE)
548 except Exception:
549 MARIADB_VECTOR_POOL_SIZE = None
551MARIADB_VECTOR_POOL_MAX_OVERFLOW = os.getenv('MARIADB_VECTOR_POOL_MAX_OVERFLOW', 0)
553if MARIADB_VECTOR_POOL_MAX_OVERFLOW == '': 553 ↛ 554line 553 didn't jump to line 554 because the condition on line 553 was never true
554 MARIADB_VECTOR_POOL_MAX_OVERFLOW = 0
555else:
556 try:
557 MARIADB_VECTOR_POOL_MAX_OVERFLOW = int(MARIADB_VECTOR_POOL_MAX_OVERFLOW)
558 except Exception:
559 MARIADB_VECTOR_POOL_MAX_OVERFLOW = 0
561MARIADB_VECTOR_POOL_TIMEOUT = os.getenv('MARIADB_VECTOR_POOL_TIMEOUT', 30)
563if MARIADB_VECTOR_POOL_TIMEOUT == '': 563 ↛ 564line 563 didn't jump to line 564 because the condition on line 563 was never true
564 MARIADB_VECTOR_POOL_TIMEOUT = 30
565else:
566 try:
567 MARIADB_VECTOR_POOL_TIMEOUT = int(MARIADB_VECTOR_POOL_TIMEOUT)
568 except Exception:
569 MARIADB_VECTOR_POOL_TIMEOUT = 30
571MARIADB_VECTOR_POOL_RECYCLE = os.getenv('MARIADB_VECTOR_POOL_RECYCLE', 3600)
573if MARIADB_VECTOR_POOL_RECYCLE == '': 573 ↛ 574line 573 didn't jump to line 574 because the condition on line 573 was never true
574 MARIADB_VECTOR_POOL_RECYCLE = 3600
575else:
576 try:
577 MARIADB_VECTOR_POOL_RECYCLE = int(MARIADB_VECTOR_POOL_RECYCLE)
578 except Exception:
579 MARIADB_VECTOR_POOL_RECYCLE = 3600
581ENABLE_MARIADB_VECTOR = True
582if VECTOR_DB == 'mariadb-vector': 582 ↛ 583line 582 didn't jump to line 583 because the condition on line 582 was never true
583 if not MARIADB_VECTOR_DB_URL:
584 ENABLE_MARIADB_VECTOR = False
585 else:
586 try:
587 parsed = urlparse(MARIADB_VECTOR_DB_URL)
588 scheme = (parsed.scheme or '').lower()
589 # Require official driver so VECTOR binds as float32 bytes correctly
590 if scheme != 'mariadb+mariadbconnector':
591 ENABLE_MARIADB_VECTOR = False
592 except Exception:
593 ENABLE_MARIADB_VECTOR = False
596# Milvus
597MILVUS_URI = os.getenv('MILVUS_URI', f'{DATA_DIR}/vector_db/milvus.db')
598MILVUS_DB = os.getenv('MILVUS_DB', 'default')
599MILVUS_TOKEN = os.getenv('MILVUS_TOKEN', None)
600MILVUS_INDEX_TYPE = os.getenv('MILVUS_INDEX_TYPE', 'HNSW')
601MILVUS_METRIC_TYPE = os.getenv('MILVUS_METRIC_TYPE', 'COSINE')
602MILVUS_HNSW_M = int(os.getenv('MILVUS_HNSW_M', '16'))
603MILVUS_HNSW_EFCONSTRUCTION = int(os.getenv('MILVUS_HNSW_EFCONSTRUCTION', '100'))
604MILVUS_IVF_FLAT_NLIST = int(os.getenv('MILVUS_IVF_FLAT_NLIST', '128'))
605MILVUS_DISKANN_MAX_DEGREE = int(os.getenv('MILVUS_DISKANN_MAX_DEGREE', '56'))
606MILVUS_DISKANN_SEARCH_LIST_SIZE = int(os.getenv('MILVUS_DISKANN_SEARCH_LIST_SIZE', '100'))
607ENABLE_MILVUS_MULTITENANCY_MODE = os.getenv('ENABLE_MILVUS_MULTITENANCY_MODE', 'false').lower() == 'true'
608# Hyphens not allowed, need to use underscores in collection names
609MILVUS_COLLECTION_PREFIX = os.getenv('MILVUS_COLLECTION_PREFIX', 'open_webui')
611# Qdrant
612QDRANT_URI = os.getenv('QDRANT_URI', None)
613QDRANT_API_KEY = os.getenv('QDRANT_API_KEY', None)
614QDRANT_ON_DISK = os.getenv('QDRANT_ON_DISK', 'false').lower() == 'true'
615QDRANT_PREFER_GRPC = os.getenv('QDRANT_PREFER_GRPC', 'false').lower() == 'true'
616QDRANT_GRPC_PORT = int(os.getenv('QDRANT_GRPC_PORT', '6334'))
617QDRANT_TIMEOUT = int(os.getenv('QDRANT_TIMEOUT', '5'))
618QDRANT_HNSW_M = int(os.getenv('QDRANT_HNSW_M', '16'))
619ENABLE_QDRANT_MULTITENANCY_MODE = os.getenv('ENABLE_QDRANT_MULTITENANCY_MODE', 'true').lower() == 'true'
620QDRANT_COLLECTION_PREFIX = os.getenv('QDRANT_COLLECTION_PREFIX', 'open-webui')
622WEAVIATE_HTTP_HOST = os.getenv('WEAVIATE_HTTP_HOST', '')
623WEAVIATE_GRPC_HOST = os.getenv('WEAVIATE_GRPC_HOST', '')
624WEAVIATE_HTTP_PORT = int(os.getenv('WEAVIATE_HTTP_PORT', '8080'))
625WEAVIATE_GRPC_PORT = int(os.getenv('WEAVIATE_GRPC_PORT', '50051'))
626WEAVIATE_API_KEY = os.getenv('WEAVIATE_API_KEY')
627WEAVIATE_HTTP_SECURE = os.getenv('WEAVIATE_HTTP_SECURE', 'false').lower() == 'true'
628WEAVIATE_GRPC_SECURE = os.getenv('WEAVIATE_GRPC_SECURE', 'false').lower() == 'true'
629WEAVIATE_SKIP_INIT_CHECKS = os.getenv('WEAVIATE_SKIP_INIT_CHECKS', 'false').lower() == 'true'
631# OpenSearch
632OPENSEARCH_URI = os.getenv('OPENSEARCH_URI', 'https://localhost:9200')
633OPENSEARCH_SSL = os.getenv('OPENSEARCH_SSL', 'true').lower() == 'true'
634OPENSEARCH_CERT_VERIFY = os.getenv('OPENSEARCH_CERT_VERIFY', 'false').lower() == 'true'
635OPENSEARCH_USERNAME = os.getenv('OPENSEARCH_USERNAME', None)
636OPENSEARCH_PASSWORD = os.getenv('OPENSEARCH_PASSWORD', None)
638# ElasticSearch
639ELASTICSEARCH_URL = os.getenv('ELASTICSEARCH_URL', 'https://localhost:9200')
640ELASTICSEARCH_CA_CERTS = os.getenv('ELASTICSEARCH_CA_CERTS', None)
641ELASTICSEARCH_API_KEY = os.getenv('ELASTICSEARCH_API_KEY', None)
642ELASTICSEARCH_USERNAME = os.getenv('ELASTICSEARCH_USERNAME', None)
643ELASTICSEARCH_PASSWORD = os.getenv('ELASTICSEARCH_PASSWORD', None)
644ELASTICSEARCH_CLOUD_ID = os.getenv('ELASTICSEARCH_CLOUD_ID', None)
645SSL_ASSERT_FINGERPRINT = os.getenv('SSL_ASSERT_FINGERPRINT', None)
646ELASTICSEARCH_INDEX_PREFIX = os.getenv('ELASTICSEARCH_INDEX_PREFIX', 'open_webui_collections')
647# Pgvector
648PGVECTOR_DB_URL = os.getenv('PGVECTOR_DB_URL', DATABASE_URL)
649if not USE_SLIM and VECTOR_DB == 'pgvector' and not PGVECTOR_DB_URL.startswith('postgres'): 649 ↛ 650line 649 didn't jump to line 650 because the condition on line 649 was never true
650 raise ValueError(
651 'Pgvector requires setting PGVECTOR_DB_URL or using Postgres with vector extension as the primary database.'
652 )
653PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH = int(os.getenv('PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH', '1536'))
655PGVECTOR_USE_HALFVEC = os.getenv('PGVECTOR_USE_HALFVEC', 'false').lower() == 'true'
657if PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH > 2000 and not PGVECTOR_USE_HALFVEC: 657 ↛ 658line 657 didn't jump to line 658 because the condition on line 657 was never true
658 raise ValueError(
659 'PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH is set to '
660 f'{PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH}, which exceeds the 2000 dimension limit of the '
661 "'vector' type. Set PGVECTOR_USE_HALFVEC=true to enable the 'halfvec' "
662 'type required for high-dimensional embeddings.'
663 )
665PGVECTOR_CREATE_EXTENSION = os.getenv('PGVECTOR_CREATE_EXTENSION', 'true').lower() == 'true'
666PGVECTOR_PGCRYPTO = os.getenv('PGVECTOR_PGCRYPTO', 'false').lower() == 'true'
667PGVECTOR_PGCRYPTO_KEY = os.getenv('PGVECTOR_PGCRYPTO_KEY', None)
668if PGVECTOR_PGCRYPTO and not PGVECTOR_PGCRYPTO_KEY: 668 ↛ 669line 668 didn't jump to line 669 because the condition on line 668 was never true
669 raise ValueError('PGVECTOR_PGCRYPTO is enabled but PGVECTOR_PGCRYPTO_KEY is not set. Please provide a valid key.')
672PGVECTOR_POOL_SIZE = os.getenv('PGVECTOR_POOL_SIZE', None)
674if PGVECTOR_POOL_SIZE != None: 674 ↛ 675line 674 didn't jump to line 675 because the condition on line 674 was never true
675 try:
676 PGVECTOR_POOL_SIZE = int(PGVECTOR_POOL_SIZE)
677 except Exception:
678 PGVECTOR_POOL_SIZE = None
680PGVECTOR_POOL_MAX_OVERFLOW = os.getenv('PGVECTOR_POOL_MAX_OVERFLOW', 0)
682if PGVECTOR_POOL_MAX_OVERFLOW == '': 682 ↛ 683line 682 didn't jump to line 683 because the condition on line 682 was never true
683 PGVECTOR_POOL_MAX_OVERFLOW = 0
684else:
685 try:
686 PGVECTOR_POOL_MAX_OVERFLOW = int(PGVECTOR_POOL_MAX_OVERFLOW)
687 except Exception:
688 PGVECTOR_POOL_MAX_OVERFLOW = 0
690PGVECTOR_POOL_TIMEOUT = os.getenv('PGVECTOR_POOL_TIMEOUT', 30)
692if PGVECTOR_POOL_TIMEOUT == '': 692 ↛ 693line 692 didn't jump to line 693 because the condition on line 692 was never true
693 PGVECTOR_POOL_TIMEOUT = 30
694else:
695 try:
696 PGVECTOR_POOL_TIMEOUT = int(PGVECTOR_POOL_TIMEOUT)
697 except Exception:
698 PGVECTOR_POOL_TIMEOUT = 30
700PGVECTOR_POOL_RECYCLE = os.getenv('PGVECTOR_POOL_RECYCLE', 3600)
702if PGVECTOR_POOL_RECYCLE == '': 702 ↛ 703line 702 didn't jump to line 703 because the condition on line 702 was never true
703 PGVECTOR_POOL_RECYCLE = 3600
704else:
705 try:
706 PGVECTOR_POOL_RECYCLE = int(PGVECTOR_POOL_RECYCLE)
707 except Exception:
708 PGVECTOR_POOL_RECYCLE = 3600
710PGVECTOR_INDEX_METHOD = os.getenv('PGVECTOR_INDEX_METHOD', '').strip().lower()
711if PGVECTOR_INDEX_METHOD not in ('ivfflat', 'hnsw', ''): 711 ↛ 712line 711 didn't jump to line 712 because the condition on line 711 was never true
712 PGVECTOR_INDEX_METHOD = ''
714PGVECTOR_HNSW_M = os.getenv('PGVECTOR_HNSW_M', 16)
716if PGVECTOR_HNSW_M == '': 716 ↛ 717line 716 didn't jump to line 717 because the condition on line 716 was never true
717 PGVECTOR_HNSW_M = 16
718else:
719 try:
720 PGVECTOR_HNSW_M = int(PGVECTOR_HNSW_M)
721 except Exception:
722 PGVECTOR_HNSW_M = 16
724PGVECTOR_HNSW_EF_CONSTRUCTION = os.getenv('PGVECTOR_HNSW_EF_CONSTRUCTION', 64)
726if PGVECTOR_HNSW_EF_CONSTRUCTION == '': 726 ↛ 727line 726 didn't jump to line 727 because the condition on line 726 was never true
727 PGVECTOR_HNSW_EF_CONSTRUCTION = 64
728else:
729 try:
730 PGVECTOR_HNSW_EF_CONSTRUCTION = int(PGVECTOR_HNSW_EF_CONSTRUCTION)
731 except Exception:
732 PGVECTOR_HNSW_EF_CONSTRUCTION = 64
734PGVECTOR_IVFFLAT_LISTS = os.getenv('PGVECTOR_IVFFLAT_LISTS', 100)
736if PGVECTOR_IVFFLAT_LISTS == '': 736 ↛ 737line 736 didn't jump to line 737 because the condition on line 736 was never true
737 PGVECTOR_IVFFLAT_LISTS = 100
738else:
739 try:
740 PGVECTOR_IVFFLAT_LISTS = int(PGVECTOR_IVFFLAT_LISTS)
741 except Exception:
742 PGVECTOR_IVFFLAT_LISTS = 100
744PGVECTOR_ITERATIVE_SCAN = os.getenv('PGVECTOR_ITERATIVE_SCAN', 'relaxed_order').strip().lower()
745if PGVECTOR_ITERATIVE_SCAN not in ('off', 'relaxed_order', 'strict_order'): 745 ↛ 746line 745 didn't jump to line 746 because the condition on line 745 was never true
746 PGVECTOR_ITERATIVE_SCAN = 'relaxed_order'
748# openGauss
749OPENGAUSS_DB_URL = os.getenv('OPENGAUSS_DB_URL', DATABASE_URL)
751OPENGAUSS_INITIALIZE_MAX_VECTOR_LENGTH = int(os.getenv('OPENGAUSS_INITIALIZE_MAX_VECTOR_LENGTH', '1536'))
753OPENGAUSS_POOL_SIZE = os.getenv('OPENGAUSS_POOL_SIZE', None)
755if OPENGAUSS_POOL_SIZE != None: 755 ↛ 756line 755 didn't jump to line 756 because the condition on line 755 was never true
756 try:
757 OPENGAUSS_POOL_SIZE = int(OPENGAUSS_POOL_SIZE)
758 except Exception:
759 OPENGAUSS_POOL_SIZE = None
761OPENGAUSS_POOL_MAX_OVERFLOW = os.getenv('OPENGAUSS_POOL_MAX_OVERFLOW', 0)
763if OPENGAUSS_POOL_MAX_OVERFLOW == '': 763 ↛ 764line 763 didn't jump to line 764 because the condition on line 763 was never true
764 OPENGAUSS_POOL_MAX_OVERFLOW = 0
765else:
766 try:
767 OPENGAUSS_POOL_MAX_OVERFLOW = int(OPENGAUSS_POOL_MAX_OVERFLOW)
768 except Exception:
769 OPENGAUSS_POOL_MAX_OVERFLOW = 0
771OPENGAUSS_POOL_TIMEOUT = os.getenv('OPENGAUSS_POOL_TIMEOUT', 30)
773if OPENGAUSS_POOL_TIMEOUT == '': 773 ↛ 774line 773 didn't jump to line 774 because the condition on line 773 was never true
774 OPENGAUSS_POOL_TIMEOUT = 30
775else:
776 try:
777 OPENGAUSS_POOL_TIMEOUT = int(OPENGAUSS_POOL_TIMEOUT)
778 except Exception:
779 OPENGAUSS_POOL_TIMEOUT = 30
781OPENGAUSS_POOL_RECYCLE = os.getenv('OPENGAUSS_POOL_RECYCLE', 3600)
783if OPENGAUSS_POOL_RECYCLE == '': 783 ↛ 784line 783 didn't jump to line 784 because the condition on line 783 was never true
784 OPENGAUSS_POOL_RECYCLE = 3600
785else:
786 try:
787 OPENGAUSS_POOL_RECYCLE = int(OPENGAUSS_POOL_RECYCLE)
788 except Exception:
789 OPENGAUSS_POOL_RECYCLE = 3600
791# Pinecone
792PINECONE_API_KEY = os.getenv('PINECONE_API_KEY', None)
793PINECONE_ENVIRONMENT = os.getenv('PINECONE_ENVIRONMENT', None)
794PINECONE_INDEX_NAME = os.getenv('PINECONE_INDEX_NAME', 'open-webui-index')
795PINECONE_DIMENSION = int(os.getenv('PINECONE_DIMENSION', 1536)) # or 3072, 1024, 768
796PINECONE_METRIC = os.getenv('PINECONE_METRIC', 'cosine')
797PINECONE_CLOUD = os.getenv('PINECONE_CLOUD', 'aws') # or "gcp" or "azure"
799# ORACLE23AI (Oracle23ai Vector Search)
801ORACLE_DB_USE_WALLET = os.getenv('ORACLE_DB_USE_WALLET', 'false').lower() == 'true'
802ORACLE_DB_USER = os.getenv('ORACLE_DB_USER', None) #
803ORACLE_DB_PASSWORD = os.getenv('ORACLE_DB_PASSWORD', None) #
804ORACLE_DB_DSN = os.getenv('ORACLE_DB_DSN', None) #
805ORACLE_WALLET_DIR = os.getenv('ORACLE_WALLET_DIR', None)
806ORACLE_WALLET_PASSWORD = os.getenv('ORACLE_WALLET_PASSWORD', None)
807ORACLE_VECTOR_LENGTH = os.getenv('ORACLE_VECTOR_LENGTH', 768)
809ORACLE_DB_POOL_MIN = int(os.getenv('ORACLE_DB_POOL_MIN', 2))
810ORACLE_DB_POOL_MAX = int(os.getenv('ORACLE_DB_POOL_MAX', 10))
811ORACLE_DB_POOL_INCREMENT = int(os.getenv('ORACLE_DB_POOL_INCREMENT', 1))
814if not USE_SLIM and VECTOR_DB == 'oracle23ai': 814 ↛ 815line 814 didn't jump to line 815 because the condition on line 814 was never true
815 if not ORACLE_DB_USER or not ORACLE_DB_PASSWORD or not ORACLE_DB_DSN:
816 raise ValueError('Oracle23ai requires setting ORACLE_DB_USER, ORACLE_DB_PASSWORD, and ORACLE_DB_DSN.')
817 if ORACLE_DB_USE_WALLET and (not ORACLE_WALLET_DIR or not ORACLE_WALLET_PASSWORD):
818 raise ValueError(
819 'Oracle23ai requires setting ORACLE_WALLET_DIR and ORACLE_WALLET_PASSWORD when using wallet authentication.'
820 )
822log.info('VECTOR_DB: %s', VECTOR_DB)
824# S3 Vector
825S3_VECTOR_BUCKET_NAME = os.getenv('S3_VECTOR_BUCKET_NAME', None)
826S3_VECTOR_REGION = os.getenv('S3_VECTOR_REGION', None)
828# Valkey Vector Store
829VALKEY_URL = os.getenv('VALKEY_URL', '')
830VALKEY_COLLECTION_PREFIX = os.getenv('VALKEY_COLLECTION_PREFIX', 'open_webui')
831VALKEY_INDEX_TYPE = os.getenv('VALKEY_INDEX_TYPE', 'HNSW').upper()
832VALKEY_DISTANCE_METRIC = os.getenv('VALKEY_DISTANCE_METRIC', 'COSINE').upper()
833VALKEY_HNSW_M = int(os.getenv('VALKEY_HNSW_M', '16'))
834VALKEY_HNSW_EF_CONSTRUCTION = int(os.getenv('VALKEY_HNSW_EF_CONSTRUCTION', '200'))
835VALKEY_HNSW_EF_RUNTIME = int(os.getenv('VALKEY_HNSW_EF_RUNTIME', '10'))
837####################################
838# Information Retrieval (RAG)
839####################################
842# If configured, Google Drive will be available as an upload option.
843ENABLE_GOOGLE_DRIVE_INTEGRATION = os.getenv('ENABLE_GOOGLE_DRIVE_INTEGRATION', 'False').lower() == 'true'
845GOOGLE_DRIVE_CLIENT_ID = os.getenv('GOOGLE_DRIVE_CLIENT_ID', '')
847GOOGLE_DRIVE_API_KEY = os.getenv('GOOGLE_DRIVE_API_KEY', '')
849ENABLE_ONEDRIVE_INTEGRATION = os.getenv('ENABLE_ONEDRIVE_INTEGRATION', 'False').lower() == 'true'
852ONEDRIVE_CLIENT_ID = os.getenv('ONEDRIVE_CLIENT_ID', '')
853ONEDRIVE_CLIENT_ID_PERSONAL = os.getenv('ONEDRIVE_CLIENT_ID_PERSONAL', ONEDRIVE_CLIENT_ID)
854ONEDRIVE_CLIENT_ID_BUSINESS = os.getenv('ONEDRIVE_CLIENT_ID_BUSINESS', ONEDRIVE_CLIENT_ID)
856ENABLE_ONEDRIVE_PERSONAL = os.getenv('ENABLE_ONEDRIVE_PERSONAL', 'True').lower() == 'true' and bool(
857 ONEDRIVE_CLIENT_ID_PERSONAL
858)
859ENABLE_ONEDRIVE_BUSINESS = os.getenv('ENABLE_ONEDRIVE_BUSINESS', 'True').lower() == 'true' and bool(
860 ONEDRIVE_CLIENT_ID_BUSINESS
861)
863ONEDRIVE_SHAREPOINT_URL = os.getenv('ONEDRIVE_SHAREPOINT_URL', '')
865ONEDRIVE_SHAREPOINT_TENANT_ID = os.getenv('ONEDRIVE_SHAREPOINT_TENANT_ID', '')
867# RAG Content Extraction
868CONTENT_EXTRACTION_ENGINE = os.getenv('CONTENT_EXTRACTION_ENGINE', '').lower()
870content_extraction_supported_media_mime_types = os.getenv('CONTENT_EXTRACTION_SUPPORTED_MEDIA_MIME_TYPES')
871CONTENT_EXTRACTION_SUPPORTED_MEDIA_MIME_TYPES = (
872 [mime_type.strip() for mime_type in content_extraction_supported_media_mime_types.split(',') if mime_type.strip()]
873 if content_extraction_supported_media_mime_types is not None
874 else None
875)
877DATALAB_MARKER_API_KEY = os.getenv('DATALAB_MARKER_API_KEY', '')
879DATALAB_MARKER_API_BASE_URL = os.getenv('DATALAB_MARKER_API_BASE_URL', '')
881DATALAB_MARKER_ADDITIONAL_CONFIG = os.getenv('DATALAB_MARKER_ADDITIONAL_CONFIG', '')
883DATALAB_MARKER_USE_LLM = os.getenv('DATALAB_MARKER_USE_LLM', 'false').lower() == 'true'
885DATALAB_MARKER_SKIP_CACHE = os.getenv('DATALAB_MARKER_SKIP_CACHE', 'false').lower() == 'true'
887DATALAB_MARKER_FORCE_OCR = os.getenv('DATALAB_MARKER_FORCE_OCR', 'false').lower() == 'true'
889DATALAB_MARKER_PAGINATE = os.getenv('DATALAB_MARKER_PAGINATE', 'false').lower() == 'true'
891DATALAB_MARKER_STRIP_EXISTING_OCR = os.getenv('DATALAB_MARKER_STRIP_EXISTING_OCR', 'false').lower() == 'true'
893DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = (
894 os.getenv('DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', 'false').lower() == 'true'
895)
897DATALAB_MARKER_FORMAT_LINES = os.getenv('DATALAB_MARKER_FORMAT_LINES', 'false').lower() == 'true'
899DATALAB_MARKER_OUTPUT_FORMAT = os.getenv('DATALAB_MARKER_OUTPUT_FORMAT', 'markdown')
901MINERU_API_MODE = os.getenv('MINERU_API_MODE', 'local')
903MINERU_API_URL = os.getenv('MINERU_API_URL', 'http://localhost:8000')
905MINERU_API_TIMEOUT = os.getenv('MINERU_API_TIMEOUT', '300')
907MINERU_API_KEY = os.getenv('MINERU_API_KEY', '')
909mineru_params = os.getenv('MINERU_PARAMS', '')
910try:
911 mineru_params = JSONCodec.loads(mineru_params)
912except JSONCodec.JSONDecodeError:
913 mineru_params = {}
915MINERU_PARAMS = mineru_params
917MINERU_FILE_EXTENSIONS = [ext.strip() for ext in os.getenv('MINERU_FILE_EXTENSIONS', 'pdf').split(',') if ext.strip()]
919EXTERNAL_DOCUMENT_LOADER_URL = os.getenv('EXTERNAL_DOCUMENT_LOADER_URL', '')
921EXTERNAL_DOCUMENT_LOADER_API_KEY = os.getenv('EXTERNAL_DOCUMENT_LOADER_API_KEY', '')
923external_document_loader_headers = os.getenv('EXTERNAL_DOCUMENT_LOADER_HEADERS', '')
924try:
925 external_document_loader_headers = JSONCodec.loads(external_document_loader_headers)
926except JSONCodec.JSONDecodeError:
927 external_document_loader_headers = {}
928if not isinstance(external_document_loader_headers, dict): 928 ↛ 929line 928 didn't jump to line 929 because the condition on line 928 was never true
929 external_document_loader_headers = {}
931EXTERNAL_DOCUMENT_LOADER_HEADERS = external_document_loader_headers
933TIKA_SERVER_URL = os.getenv('TIKA_SERVER_URL', 'http://tika:9998')
935TIKA_SERVER_VERSION = os.getenv('TIKA_SERVER_VERSION', '3')
937DOCLING_SERVER_URL = os.getenv('DOCLING_SERVER_URL', 'http://docling:5001')
939DOCLING_API_KEY = os.getenv('DOCLING_API_KEY', '')
941docling_params = os.getenv('DOCLING_PARAMS', '')
942try:
943 docling_params = JSONCodec.loads(docling_params)
944except JSONCodec.JSONDecodeError:
945 docling_params = {}
947DOCLING_PARAMS = docling_params
949DOCUMENT_INTELLIGENCE_ENDPOINT = os.getenv('DOCUMENT_INTELLIGENCE_ENDPOINT', '')
951DOCUMENT_INTELLIGENCE_KEY = os.getenv('DOCUMENT_INTELLIGENCE_KEY', '')
953DOCUMENT_INTELLIGENCE_MODEL = os.getenv('DOCUMENT_INTELLIGENCE_MODEL', 'prebuilt-layout')
955MISTRAL_OCR_API_BASE_URL = os.getenv('MISTRAL_OCR_API_BASE_URL', 'https://api.mistral.ai/v1')
957MISTRAL_OCR_API_KEY = os.getenv('MISTRAL_OCR_API_KEY', '')
959MISTRAL_OCR_USE_BASE64 = os.getenv('MISTRAL_OCR_USE_BASE64', 'False').lower() == 'true'
961PADDLEOCR_VL_BASE_URL = os.getenv('PADDLEOCR_VL_BASE_URL', 'http://localhost:8080')
963PADDLEOCR_VL_TOKEN = os.getenv('PADDLEOCR_VL_TOKEN', '')
965BYPASS_EMBEDDING_AND_RETRIEVAL = os.getenv('BYPASS_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true'
968RAG_TOP_K = int(os.getenv('RAG_TOP_K', '3'))
969RAG_TOP_K_RERANKER = int(os.getenv('RAG_TOP_K_RERANKER', '3'))
970RAG_RELEVANCE_THRESHOLD = float(os.getenv('RAG_RELEVANCE_THRESHOLD', '0.0'))
971RAG_HYBRID_BM25_WEIGHT = float(os.getenv('RAG_HYBRID_BM25_WEIGHT', '0.5'))
973ENABLE_RAG_HYBRID_SEARCH = os.getenv('ENABLE_RAG_HYBRID_SEARCH', '').lower() == 'true'
975ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = (
976 os.getenv('ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', 'False').lower() == 'true'
977)
979RAG_FULL_CONTEXT = os.getenv('RAG_FULL_CONTEXT', 'False').lower() == 'true'
981RAG_FILE_MAX_COUNT = int(os.getenv('RAG_FILE_MAX_COUNT')) if os.getenv('RAG_FILE_MAX_COUNT') else None
983RAG_FILE_MAX_SIZE = int(os.getenv('RAG_FILE_MAX_SIZE')) if os.getenv('RAG_FILE_MAX_SIZE') else None
985ENABLE_KNOWLEDGE_FILE_RETENTION = os.getenv('ENABLE_KNOWLEDGE_FILE_RETENTION', 'False').lower() == 'true'
987RAG_FILE_CONTENT_SEARCH_MAX_CHARS = int(os.getenv('RAG_FILE_CONTENT_SEARCH_MAX_CHARS', str(64 * 1024 * 1024)))
989FILE_IMAGE_COMPRESSION_WIDTH = (
990 int(os.getenv('FILE_IMAGE_COMPRESSION_WIDTH')) if os.getenv('FILE_IMAGE_COMPRESSION_WIDTH') else None
991)
993FILE_IMAGE_COMPRESSION_HEIGHT = (
994 int(os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT')) if os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT') else None
995)
998RAG_ALLOWED_FILE_EXTENSIONS = [
999 ext.strip() for ext in os.getenv('RAG_ALLOWED_FILE_EXTENSIONS', '').split(',') if ext.strip()
1000]
1002RAG_EMBEDDING_ENGINE = os.getenv('RAG_EMBEDDING_ENGINE', '')
1004PDF_EXTRACT_IMAGES = os.getenv('PDF_EXTRACT_IMAGES', 'False').lower() == 'true'
1006PDF_LOADER_MODE = os.getenv('PDF_LOADER_MODE', 'page')
1008RAG_EMBEDDING_MODEL = os.getenv('RAG_EMBEDDING_MODEL', 'sentence-transformers/all-MiniLM-L6-v2')
1009log.info('Embedding model set: %s', RAG_EMBEDDING_MODEL)
1011RAG_TOKENIZER_MODEL = os.getenv('RAG_TOKENIZER_MODEL', '')
1013RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
1014 not OFFLINE_MODE and os.getenv('RAG_EMBEDDING_MODEL_AUTO_UPDATE', 'True').lower() == 'true'
1015)
1017RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = os.getenv('RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE', 'True').lower() == 'true'
1019RAG_EMBEDDING_BATCH_SIZE = int(
1020 os.getenv('RAG_EMBEDDING_BATCH_SIZE') or os.getenv('RAG_EMBEDDING_OPENAI_BATCH_SIZE', '1')
1021)
1023ENABLE_ASYNC_EMBEDDING = os.getenv('ENABLE_ASYNC_EMBEDDING', 'True').lower() == 'true'
1025RAG_EMBEDDING_CONCURRENT_REQUESTS = int(os.getenv('RAG_EMBEDDING_CONCURRENT_REQUESTS', '0'))
1027RAG_EMBEDDING_QUERY_PREFIX = os.getenv('RAG_EMBEDDING_QUERY_PREFIX', None)
1029RAG_EMBEDDING_CONTENT_PREFIX = os.getenv('RAG_EMBEDDING_CONTENT_PREFIX', None)
1031RAG_EMBEDDING_PREFIX_FIELD_NAME = os.getenv('RAG_EMBEDDING_PREFIX_FIELD_NAME', None)
1033RAG_RERANKING_ENGINE = os.getenv('RAG_RERANKING_ENGINE', '')
1035RAG_RERANKING_MODEL = os.getenv('RAG_RERANKING_MODEL', '')
1036if RAG_RERANKING_MODEL != '': 1036 ↛ 1037line 1036 didn't jump to line 1037 because the condition on line 1036 was never true
1037 log.info('Reranking model set: %s', RAG_RERANKING_MODEL)
1040RAG_RERANKING_MODEL_AUTO_UPDATE = (
1041 not OFFLINE_MODE and os.getenv('RAG_RERANKING_MODEL_AUTO_UPDATE', 'True').lower() == 'true'
1042)
1044RAG_RERANKING_MODEL_TRUST_REMOTE_CODE = os.getenv('RAG_RERANKING_MODEL_TRUST_REMOTE_CODE', 'True').lower() == 'true'
1046RAG_RERANKING_BATCH_SIZE = int(os.getenv('RAG_RERANKING_BATCH_SIZE', '32'))
1048RAG_EXTERNAL_RERANKER_URL = os.getenv('RAG_EXTERNAL_RERANKER_URL', '')
1050RAG_EXTERNAL_RERANKER_API_KEY = os.getenv('RAG_EXTERNAL_RERANKER_API_KEY', '')
1052RAG_EXTERNAL_RERANKER_TIMEOUT = os.getenv('RAG_EXTERNAL_RERANKER_TIMEOUT', '')
1055RAG_TEXT_SPLITTER = os.getenv('RAG_TEXT_SPLITTER', '')
1057ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER = os.getenv('ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER', 'True').lower() == 'true'
1060TIKTOKEN_CACHE_DIR = os.getenv('TIKTOKEN_CACHE_DIR', f'{CACHE_DIR}/tiktoken')
1061TIKTOKEN_ENCODING_NAME = os.getenv('TIKTOKEN_ENCODING_NAME', 'cl100k_base')
1064CHUNK_SIZE = int(os.getenv('CHUNK_SIZE', '1000'))
1066CHUNK_MIN_SIZE_TARGET = int(os.getenv('CHUNK_MIN_SIZE_TARGET', '0'))
1068CHUNK_OVERLAP = int(os.getenv('CHUNK_OVERLAP', '100'))
1070DEFAULT_RAG_TEMPLATE = """### Task:
1071Respond to the user query using the provided context, incorporating inline citations in the format [id] **only when the <source> tag includes an explicit id attribute** (e.g., <source id="1">).
1073### Guidelines:
1074- If you don't know the answer, clearly state that.
1075- If uncertain, ask the user for clarification.
1076- Respond in the same language as the user's query.
1077- If the context is unreadable or of poor quality, inform the user and provide the best possible answer.
1078- If the answer isn't present in the context but you possess the knowledge, explain this to the user and provide the answer using your own understanding.
1079- **Only include inline citations using [id] (e.g., [1], [2]) when the <source> tag includes an id attribute.**
1080- Do not cite if the <source> tag does not contain an id attribute.
1081- Do not use XML tags in your response.
1082- Ensure citations are concise and directly related to the information provided.
1084### Example of Citation:
1085If the user asks about a specific topic and the information is found in a source with a provided id attribute, the response should include the citation like in the following example:
1086* "According to the study, the proposed method increases efficiency by 20% [1]."
1088### Output:
1089Provide a clear and direct response to the user's query, including inline citations in the format [id] only when the <source> tag with id attribute is present in the context.
1091<context>
1092{{CONTEXT}}
1093</context>
1094"""
1096RAG_TEMPLATE = os.getenv('RAG_TEMPLATE', DEFAULT_RAG_TEMPLATE)
1098RAG_OPENAI_API_BASE_URL = os.getenv('RAG_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
1099RAG_OPENAI_API_KEY = os.getenv('RAG_OPENAI_API_KEY', OPENAI_API_KEY)
1101RAG_AZURE_OPENAI_BASE_URL = os.getenv('RAG_AZURE_OPENAI_BASE_URL', '')
1102RAG_AZURE_OPENAI_API_KEY = os.getenv('RAG_AZURE_OPENAI_API_KEY', '')
1103RAG_AZURE_OPENAI_API_VERSION = os.getenv('RAG_AZURE_OPENAI_API_VERSION', '')
1105RAG_OLLAMA_BASE_URL = os.getenv('RAG_OLLAMA_BASE_URL', OLLAMA_BASE_URL)
1107RAG_OLLAMA_API_KEY = os.getenv('RAG_OLLAMA_API_KEY', '')
1110ENABLE_LOCAL_WEB_FETCH = (
1111 os.getenv(
1112 'ENABLE_LOCAL_WEB_FETCH',
1113 os.getenv('ENABLE_RAG_LOCAL_WEB_FETCH', 'False'),
1114 ).lower()
1115 == 'true'
1116)
1117# Deprecated compatibility alias; use ENABLE_LOCAL_WEB_FETCH for new deployments.
1118ENABLE_RAG_LOCAL_WEB_FETCH = ENABLE_LOCAL_WEB_FETCH
1121# Operators extend this through WEB_FETCH_FILTER_LIST.
1122DEFAULT_WEB_FETCH_FILTER_LIST = [
1123 '!169.254.169.254',
1124 '!fd00:ec2::254',
1125 '!metadata.google.internal',
1126 '!metadata.azure.com',
1127 '!100.100.100.200',
1128 '!168.63.129.16', # Azure platform channel, reachable from every Azure VM
1129 '!192.88.99.0/24', # 6to4 relay anycast, deprecated by RFC 7526
1130 '!224.0.0.0/4', # IPv4 multicast
1131 '!::ffff:0:0:0/96', # IPv4-translated (SIIT, RFC 2765), never routed
1132 '!64:ff9b:1::/48', # NAT64 local-use prefix, RFC 8215, not a public destination
1133 '!100:0:0:1::/64', # dummy prefix, RFC 9780
1134 '!2001:1::1', # PCP anycast, RFC 7723, answered by the local network's own edge device
1135 '!2001:1::2', # TURN anycast, RFC 8155, likewise
1136 '!2001:20::/28', # ORCHIDv2, RFC 7343, never routed
1137 '!2001:30::/28', # DRIP, RFC 9374, never routed
1138 '!5f00::/16', # SRv6 SIDs, RFC 9602, internal to one segment routing domain
1139 '!fec0::/10', # IPv6 site-local, deprecated by RFC 3879
1140 '!ff00::/8', # IPv6 multicast
1141]
1143web_fetch_filter_list = os.getenv('WEB_FETCH_FILTER_LIST', '')
1144if web_fetch_filter_list == '': 1144 ↛ 1147line 1144 didn't jump to line 1147 because the condition on line 1144 was always true
1145 web_fetch_filter_list = []
1146else:
1147 web_fetch_filter_list = [item.strip() for item in web_fetch_filter_list.split(',') if item.strip()]
1149WEB_FETCH_FILTER_LIST = list(set(DEFAULT_WEB_FETCH_FILTER_LIST + web_fetch_filter_list))
1152YOUTUBE_LOADER_LANGUAGE = os.getenv('YOUTUBE_LOADER_LANGUAGE', 'en').split(',')
1154YOUTUBE_LOADER_PROXY_URL = os.getenv('YOUTUBE_LOADER_PROXY_URL', '')
1157####################################
1158# Web Search
1159####################################
1161ENABLE_WEB_SEARCH = os.getenv('ENABLE_WEB_SEARCH', 'False').lower() == 'true'
1163ENABLE_WEB_SEARCH_CONFIRMATION = os.getenv('ENABLE_WEB_SEARCH_CONFIRMATION', 'False').lower() == 'true'
1165WEB_SEARCH_CONFIRMATION_CONTENT = os.getenv(
1166 'WEB_SEARCH_CONFIRMATION_CONTENT',
1167 'Your query will be sent to the configured web search provider.',
1168)
1170WEB_SEARCH_ENGINE = os.getenv('WEB_SEARCH_ENGINE', '')
1172BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
1173 os.getenv('BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true'
1174)
1177BYPASS_WEB_SEARCH_WEB_LOADER = os.getenv('BYPASS_WEB_SEARCH_WEB_LOADER', 'False').lower() == 'true'
1179WEB_SEARCH_RESULT_COUNT = int(os.getenv('WEB_SEARCH_RESULT_COUNT', '3'))
1182try:
1183 web_search_domain_filter_list = JSONCodec.loads(os.getenv('WEB_SEARCH_DOMAIN_FILTER_LIST', '[]'))
1184except Exception as e:
1185 web_search_domain_filter_list = [
1186 # "wikipedia.com",
1187 # "wikimedia.org",
1188 # "wikidata.org",
1189 # "!stackoverflow.com",
1190 ]
1192# You can provide a list of your own websites to filter after performing a web search.
1193# This ensures the highest level of safety and reliability of the information sources.
1194WEB_SEARCH_DOMAIN_FILTER_LIST = web_search_domain_filter_list
1196WEB_SEARCH_CONCURRENT_REQUESTS = int(os.getenv('WEB_SEARCH_CONCURRENT_REQUESTS', '0'))
1198WEB_FETCH_MAX_CONTENT_LENGTH = (
1199 int(os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH')) if os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH') else None
1200)
1202WEB_LOADER_ENGINE = os.getenv('WEB_LOADER_ENGINE', '')
1205WEB_LOADER_CONCURRENT_REQUESTS = int(os.getenv('WEB_LOADER_CONCURRENT_REQUESTS', '10'))
1207WEB_LOADER_TIMEOUT = os.getenv('WEB_LOADER_TIMEOUT', '')
1210ENABLE_WEB_LOADER_SSL_VERIFICATION = os.getenv('ENABLE_WEB_LOADER_SSL_VERIFICATION', 'True').lower() == 'true'
1212WEB_SEARCH_TRUST_ENV = os.getenv('WEB_SEARCH_TRUST_ENV', 'True').lower() == 'true'
1215OLLAMA_CLOUD_WEB_SEARCH_API_KEY = os.getenv('OLLAMA_CLOUD_API_KEY', '')
1217SEARXNG_QUERY_URL = os.getenv('SEARXNG_QUERY_URL', '')
1218OPENSERP_BASE_URL = os.getenv('OPENSERP_BASE_URL', 'http://localhost:7000')
1220SEARXNG_LANGUAGE = os.getenv('SEARXNG_LANGUAGE', 'all')
1222YACY_QUERY_URL = os.getenv('YACY_QUERY_URL', '')
1224YACY_USERNAME = os.getenv('YACY_USERNAME', '')
1226YACY_PASSWORD = os.getenv('YACY_PASSWORD', '')
1228GOOGLE_PSE_API_KEY = os.getenv('GOOGLE_PSE_API_KEY', '')
1230GOOGLE_PSE_ENGINE_ID = os.getenv('GOOGLE_PSE_ENGINE_ID', '')
1232BRAVE_SEARCH_API_KEY = os.getenv('BRAVE_SEARCH_API_KEY', '')
1234BRAVE_SEARCH_CONTEXT_TOKENS = int(os.getenv('BRAVE_SEARCH_CONTEXT_TOKENS', '8192'))
1236KAGI_SEARCH_API_KEY = os.getenv('KAGI_SEARCH_API_KEY', '')
1238MOJEEK_SEARCH_API_KEY = os.getenv('MOJEEK_SEARCH_API_KEY', '')
1240BOCHA_SEARCH_API_KEY = os.getenv('BOCHA_SEARCH_API_KEY', '')
1242SERPSTACK_API_KEY = os.getenv('SERPSTACK_API_KEY', '')
1244SERPSTACK_HTTPS = os.getenv('SERPSTACK_HTTPS', 'True').lower() == 'true'
1246SERPER_API_KEY = os.getenv('SERPER_API_KEY', '')
1248SERPLY_API_KEY = os.getenv('SERPLY_API_KEY', '')
1250SERPHOUSE_API_KEY = os.getenv('SERPHOUSE_API_KEY', '')
1252SERPHOUSE_DOMAIN = os.getenv('SERPHOUSE_DOMAIN', 'google.com')
1254DDGS_BACKEND = os.getenv('DDGS_BACKEND', 'auto')
1256JINA_API_KEY = os.getenv('JINA_API_KEY', '')
1258JINA_API_BASE_URL = os.getenv('JINA_API_BASE_URL', '')
1260SEARCHAPI_API_KEY = os.getenv('SEARCHAPI_API_KEY', '')
1262SEARCHAPI_ENGINE = os.getenv('SEARCHAPI_ENGINE', '')
1264SERPAPI_API_KEY = os.getenv('SERPAPI_API_KEY', '')
1266SERPAPI_ENGINE = os.getenv('SERPAPI_ENGINE', '')
1268BING_SEARCH_V7_ENDPOINT = os.getenv('BING_SEARCH_V7_ENDPOINT', 'https://api.bing.microsoft.com/v7.0/search')
1270BING_SEARCH_V7_SUBSCRIPTION_KEY = os.getenv('BING_SEARCH_V7_SUBSCRIPTION_KEY', '')
1272AZURE_AI_SEARCH_API_KEY = os.getenv('AZURE_AI_SEARCH_API_KEY', '')
1274AZURE_AI_SEARCH_ENDPOINT = os.getenv('AZURE_AI_SEARCH_ENDPOINT', '')
1276AZURE_AI_SEARCH_INDEX_NAME = os.getenv('AZURE_AI_SEARCH_INDEX_NAME', '')
1278EXA_API_KEY = os.getenv('EXA_API_KEY', '')
1279EXA_MAX_CONTENT_LENGTH = int(os.environ['EXA_MAX_CONTENT_LENGTH']) if os.getenv('EXA_MAX_CONTENT_LENGTH') else None
1280if EXA_MAX_CONTENT_LENGTH is not None and EXA_MAX_CONTENT_LENGTH <= 0: 1280 ↛ 1281line 1280 didn't jump to line 1281 because the condition on line 1280 was never true
1281 raise ValueError('EXA_MAX_CONTENT_LENGTH must be a positive integer or unset')
1283PERPLEXITY_API_KEY = os.getenv('PERPLEXITY_API_KEY', '')
1285PERPLEXITY_MODEL = os.getenv('PERPLEXITY_MODEL', 'sonar')
1287PERPLEXITY_SEARCH_CONTEXT_USAGE = os.getenv('PERPLEXITY_SEARCH_CONTEXT_USAGE', 'medium')
1289PERPLEXITY_SEARCH_API_URL = os.getenv('PERPLEXITY_SEARCH_API_URL', 'https://api.perplexity.ai/search')
1291MICROSOFT_WEB_IQ_API_BASE_URL = os.getenv('MICROSOFT_WEB_IQ_API_BASE_URL', 'https://api.microsoft.ai/v3')
1293MICROSOFT_WEB_IQ_API_KEY = os.getenv('MICROSOFT_WEB_IQ_API_KEY', '')
1295MICROSOFT_WEB_IQ_LANGUAGE = os.getenv('MICROSOFT_WEB_IQ_LANGUAGE', 'en')
1297SOUGOU_API_SID = os.getenv('SOUGOU_API_SID', '')
1299SOUGOU_API_SK = os.getenv('SOUGOU_API_SK', '')
1301TAVILY_API_KEY = os.getenv('TAVILY_API_KEY', '')
1303TAVILY_EXTRACT_DEPTH = os.getenv('TAVILY_EXTRACT_DEPTH', 'basic')
1305STAAN_API_KEY = os.getenv('STAAN_API_KEY', '')
1307STAAN_MARKET = os.getenv('STAAN_MARKET', 'en-us')
1309STAAN_MAX_SNIPPETS = int(os.getenv('STAAN_MAX_SNIPPETS', '0'))
1311PLAYWRIGHT_WS_URL = os.getenv('PLAYWRIGHT_WS_URL', '')
1313PLAYWRIGHT_TIMEOUT = int(os.getenv('PLAYWRIGHT_TIMEOUT', '10000'))
1315FIRECRAWL_API_KEY = os.getenv('FIRECRAWL_API_KEY', '')
1317FIRECRAWL_API_BASE_URL = os.getenv('FIRECRAWL_API_BASE_URL', 'https://api.firecrawl.dev')
1319FIRECRAWL_TIMEOUT = os.getenv('FIRECRAWL_TIMEOUT', '')
1321EXTERNAL_WEB_SEARCH_URL = os.getenv('EXTERNAL_WEB_SEARCH_URL', '')
1323EXTERNAL_WEB_SEARCH_API_KEY = os.getenv('EXTERNAL_WEB_SEARCH_API_KEY', '')
1325EXTERNAL_WEB_LOADER_URL = os.getenv('EXTERNAL_WEB_LOADER_URL', '')
1327EXTERNAL_WEB_LOADER_API_KEY = os.getenv('EXTERNAL_WEB_LOADER_API_KEY', '')
1329YANDEX_WEB_SEARCH_URL = os.getenv('YANDEX_WEB_SEARCH_URL', '')
1331YANDEX_WEB_SEARCH_API_KEY = os.getenv('YANDEX_WEB_SEARCH_API_KEY', '')
1333YANDEX_WEB_SEARCH_CONFIG = os.getenv('YANDEX_WEB_SEARCH_CONFIG', '')
1335YOUCOM_API_KEY = os.getenv('YOUCOM_API_KEY', os.getenv('YDC_API_KEY', ''))
1337LINKUP_API_KEY = os.getenv('LINKUP_API_KEY', '')
1339linkup_search_params = os.getenv('LINKUP_SEARCH_PARAMS', '')
1340try:
1341 linkup_search_params = JSONCodec.loads(linkup_search_params)
1342except JSONCodec.JSONDecodeError:
1343 linkup_search_params = {}
1345LINKUP_SEARCH_PARAMS = linkup_search_params
1347####################################
1348# Images
1349####################################
1351ENABLE_IMAGE_GENERATION = os.getenv('ENABLE_IMAGE_GENERATION', '').lower() == 'true'
1353IMAGE_GENERATION_ENGINE = os.getenv('IMAGE_GENERATION_ENGINE', 'openai')
1355IMAGE_GENERATION_MODEL = os.getenv('IMAGE_GENERATION_MODEL', '')
1357# Regex pattern for models that support IMAGE_SIZE = "auto".
1358IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv('IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN', '^gpt-image')
1360# Regex pattern for models that return URLs instead of base64 data.
1361IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv('IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN', '^gpt-image')
1363IMAGE_SIZE = os.getenv('IMAGE_SIZE', '512x512')
1365IMAGE_STEPS = int(os.getenv('IMAGE_STEPS', 50))
1367ENABLE_IMAGE_PROMPT_GENERATION = os.getenv('ENABLE_IMAGE_PROMPT_GENERATION', 'true').lower() == 'true'
1369AUTOMATIC1111_BASE_URL = os.getenv('AUTOMATIC1111_BASE_URL', '')
1370AUTOMATIC1111_API_AUTH = os.getenv('AUTOMATIC1111_API_AUTH', '')
1372automatic1111_params = os.getenv('AUTOMATIC1111_PARAMS', '')
1373try:
1374 automatic1111_params = JSONCodec.loads(automatic1111_params)
1375except JSONCodec.JSONDecodeError:
1376 automatic1111_params = {}
1378AUTOMATIC1111_PARAMS = automatic1111_params
1380COMFYUI_BASE_URL = os.getenv('COMFYUI_BASE_URL', '')
1382COMFYUI_API_KEY = os.getenv('COMFYUI_API_KEY', '')
1384COMFYUI_DEFAULT_WORKFLOW = """
1385{
1386 "3": {
1387 "inputs": {
1388 "seed": 0,
1389 "steps": 20,
1390 "cfg": 8,
1391 "sampler_name": "euler",
1392 "scheduler": "normal",
1393 "denoise": 1,
1394 "model": [
1395 "4",
1396 0
1397 ],
1398 "positive": [
1399 "6",
1400 0
1401 ],
1402 "negative": [
1403 "7",
1404 0
1405 ],
1406 "latent_image": [
1407 "5",
1408 0
1409 ]
1410 },
1411 "class_type": "KSampler",
1412 "_meta": {
1413 "title": "KSampler"
1414 }
1415 },
1416 "4": {
1417 "inputs": {
1418 "ckpt_name": "model.safetensors"
1419 },
1420 "class_type": "CheckpointLoaderSimple",
1421 "_meta": {
1422 "title": "Load Checkpoint"
1423 }
1424 },
1425 "5": {
1426 "inputs": {
1427 "width": 512,
1428 "height": 512,
1429 "batch_size": 1
1430 },
1431 "class_type": "EmptyLatentImage",
1432 "_meta": {
1433 "title": "Empty Latent Image"
1434 }
1435 },
1436 "6": {
1437 "inputs": {
1438 "text": "Prompt",
1439 "clip": [
1440 "4",
1441 1
1442 ]
1443 },
1444 "class_type": "CLIPTextEncode",
1445 "_meta": {
1446 "title": "CLIP Text Encode (Prompt)"
1447 }
1448 },
1449 "7": {
1450 "inputs": {
1451 "text": "",
1452 "clip": [
1453 "4",
1454 1
1455 ]
1456 },
1457 "class_type": "CLIPTextEncode",
1458 "_meta": {
1459 "title": "CLIP Text Encode (Prompt)"
1460 }
1461 },
1462 "8": {
1463 "inputs": {
1464 "samples": [
1465 "3",
1466 0
1467 ],
1468 "vae": [
1469 "4",
1470 2
1471 ]
1472 },
1473 "class_type": "VAEDecode",
1474 "_meta": {
1475 "title": "VAE Decode"
1476 }
1477 },
1478 "9": {
1479 "inputs": {
1480 "filename_prefix": "ComfyUI",
1481 "images": [
1482 "8",
1483 0
1484 ]
1485 },
1486 "class_type": "SaveImage",
1487 "_meta": {
1488 "title": "Save Image"
1489 }
1490 }
1491}
1492"""
1495COMFYUI_WORKFLOW = os.getenv('COMFYUI_WORKFLOW', COMFYUI_DEFAULT_WORKFLOW)
1497comfyui_workflow_nodes = os.getenv('COMFYUI_WORKFLOW_NODES', '')
1498try:
1499 comfyui_workflow_nodes = JSONCodec.loads(comfyui_workflow_nodes)
1500except JSONCodec.JSONDecodeError:
1501 comfyui_workflow_nodes = []
1503COMFYUI_WORKFLOW_NODES = comfyui_workflow_nodes
1505IMAGES_OPENAI_API_BASE_URL = os.getenv('IMAGES_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
1506IMAGES_OPENAI_API_VERSION = os.getenv('IMAGES_OPENAI_API_VERSION', '')
1508IMAGES_OPENAI_API_KEY = os.getenv('IMAGES_OPENAI_API_KEY', OPENAI_API_KEY)
1510images_openai_params = os.getenv('IMAGES_OPENAI_PARAMS', '')
1511try:
1512 images_openai_params = JSONCodec.loads(images_openai_params)
1513except JSONCodec.JSONDecodeError:
1514 images_openai_params = {}
1517IMAGES_OPENAI_API_PARAMS = images_openai_params
1520IMAGES_GEMINI_API_BASE_URL = os.getenv('IMAGES_GEMINI_API_BASE_URL', GEMINI_API_BASE_URL)
1521IMAGES_GEMINI_API_KEY = os.getenv('IMAGES_GEMINI_API_KEY', GEMINI_API_KEY)
1523IMAGES_GEMINI_ENDPOINT_METHOD = os.getenv('IMAGES_GEMINI_ENDPOINT_METHOD', '')
1525ENABLE_IMAGE_EDIT = os.getenv('ENABLE_IMAGE_EDIT', '').lower() == 'true'
1527IMAGE_EDIT_ENGINE = os.getenv('IMAGE_EDIT_ENGINE', 'openai')
1529IMAGE_EDIT_MODEL = os.getenv('IMAGE_EDIT_MODEL', '')
1531IMAGE_EDIT_SIZE = os.getenv('IMAGE_EDIT_SIZE', '')
1533ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION = os.getenv('ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION', 'true').lower() == 'true'
1535IMAGES_EDIT_OPENAI_API_BASE_URL = os.getenv('IMAGES_EDIT_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
1536IMAGES_EDIT_OPENAI_API_VERSION = os.getenv('IMAGES_EDIT_OPENAI_API_VERSION', '')
1538IMAGES_EDIT_OPENAI_API_KEY = os.getenv('IMAGES_EDIT_OPENAI_API_KEY', OPENAI_API_KEY)
1540IMAGES_EDIT_GEMINI_API_BASE_URL = os.getenv('IMAGES_EDIT_GEMINI_API_BASE_URL', GEMINI_API_BASE_URL)
1541IMAGES_EDIT_GEMINI_API_KEY = os.getenv('IMAGES_EDIT_GEMINI_API_KEY', GEMINI_API_KEY)
1544IMAGES_EDIT_COMFYUI_BASE_URL = os.getenv('IMAGES_EDIT_COMFYUI_BASE_URL', '')
1545IMAGES_EDIT_COMFYUI_API_KEY = os.getenv('IMAGES_EDIT_COMFYUI_API_KEY', '')
1547IMAGES_EDIT_COMFYUI_WORKFLOW = os.getenv('IMAGES_EDIT_COMFYUI_WORKFLOW', '')
1549images_edit_comfyui_workflow_nodes = os.getenv('IMAGES_EDIT_COMFYUI_WORKFLOW_NODES', '')
1550try:
1551 images_edit_comfyui_workflow_nodes = JSONCodec.loads(images_edit_comfyui_workflow_nodes)
1552except JSONCodec.JSONDecodeError:
1553 images_edit_comfyui_workflow_nodes = []
1555IMAGES_EDIT_COMFYUI_WORKFLOW_NODES = images_edit_comfyui_workflow_nodes
1557####################################
1558# Audio
1559####################################
1561# Transcription
1562WHISPER_MODEL = os.getenv('WHISPER_MODEL', 'base')
1564WHISPER_COMPUTE_TYPE = os.getenv('WHISPER_COMPUTE_TYPE', 'int8')
1565WHISPER_MODEL_DIR = os.getenv('WHISPER_MODEL_DIR', f'{CACHE_DIR}/whisper/models')
1566WHISPER_MODEL_AUTO_UPDATE = not OFFLINE_MODE and os.getenv('WHISPER_MODEL_AUTO_UPDATE', '').lower() == 'true'
1568WHISPER_VAD_FILTER = os.getenv('WHISPER_VAD_FILTER', 'False').lower() == 'true'
1570WHISPER_MULTILINGUAL = os.getenv('WHISPER_MULTILINGUAL', 'False').lower() == 'true'
1572WHISPER_LANGUAGE = os.getenv('WHISPER_LANGUAGE', '').lower() or None
1574# Add Deepgram configuration
1575DEEPGRAM_API_KEY = os.getenv('DEEPGRAM_API_KEY', '')
1577# ElevenLabs configuration
1578ELEVENLABS_API_BASE_URL = os.getenv('ELEVENLABS_API_BASE_URL', 'https://api.elevenlabs.io')
1580AUDIO_STT_OPENAI_API_BASE_URL = os.getenv('AUDIO_STT_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
1582AUDIO_STT_OPENAI_API_KEY = os.getenv('AUDIO_STT_OPENAI_API_KEY', OPENAI_API_KEY)
1584AUDIO_STT_OPENAI_API_REQUEST_FORMAT = os.getenv('AUDIO_STT_OPENAI_API_REQUEST_FORMAT', 'multipart')
1586AUDIO_STT_ENGINE = os.getenv('AUDIO_STT_ENGINE', '')
1588AUDIO_STT_MODEL = os.getenv('AUDIO_STT_MODEL', '')
1590AUDIO_STT_SUPPORTED_CONTENT_TYPES = [
1591 content_type.strip()
1592 for content_type in os.getenv('AUDIO_STT_SUPPORTED_CONTENT_TYPES', '').split(',')
1593 if content_type.strip()
1594]
1596AUDIO_STT_ALLOWED_EXTENSIONS = [
1597 ext.strip()
1598 for ext in os.getenv(
1599 'AUDIO_STT_ALLOWED_EXTENSIONS',
1600 'mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg',
1601 ).split(',')
1602 if ext.strip()
1603]
1605AUDIO_STT_AZURE_API_KEY = os.getenv('AUDIO_STT_AZURE_API_KEY', '')
1607AUDIO_STT_AZURE_REGION = os.getenv('AUDIO_STT_AZURE_REGION', '')
1609AUDIO_STT_AZURE_LOCALES = os.getenv('AUDIO_STT_AZURE_LOCALES', '')
1611AUDIO_STT_AZURE_BASE_URL = os.getenv('AUDIO_STT_AZURE_BASE_URL', '')
1613AUDIO_STT_AZURE_MAX_SPEAKERS = os.getenv('AUDIO_STT_AZURE_MAX_SPEAKERS', '')
1615AUDIO_STT_MISTRAL_API_KEY = os.getenv('AUDIO_STT_MISTRAL_API_KEY', '')
1617AUDIO_STT_MISTRAL_API_BASE_URL = os.getenv('AUDIO_STT_MISTRAL_API_BASE_URL', 'https://api.mistral.ai/v1')
1619AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS = os.getenv('AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS', 'false').lower() == 'true'
1621AUDIO_TTS_OPENAI_API_BASE_URL = os.getenv('AUDIO_TTS_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
1622AUDIO_TTS_OPENAI_API_KEY = os.getenv('AUDIO_TTS_OPENAI_API_KEY', OPENAI_API_KEY)
1624audio_tts_openai_params = os.getenv('AUDIO_TTS_OPENAI_PARAMS', '')
1625try:
1626 audio_tts_openai_params = JSONCodec.loads(audio_tts_openai_params)
1627except JSONCodec.JSONDecodeError:
1628 audio_tts_openai_params = {}
1630AUDIO_TTS_OPENAI_PARAMS = audio_tts_openai_params
1633AUDIO_TTS_API_KEY = os.getenv('AUDIO_TTS_API_KEY', '')
1635AUDIO_TTS_ENGINE = os.getenv('AUDIO_TTS_ENGINE', '')
1638AUDIO_TTS_MODEL = os.getenv('AUDIO_TTS_MODEL', 'tts-1')
1640AUDIO_TTS_VOICE = os.getenv('AUDIO_TTS_VOICE', 'alloy')
1642AUDIO_TTS_SPLIT_ON = os.getenv('AUDIO_TTS_SPLIT_ON', 'punctuation')
1644AUDIO_TTS_AZURE_SPEECH_REGION = os.getenv('AUDIO_TTS_AZURE_SPEECH_REGION', '')
1646AUDIO_TTS_AZURE_SPEECH_BASE_URL = os.getenv('AUDIO_TTS_AZURE_SPEECH_BASE_URL', '')
1648AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = os.getenv(
1649 'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', 'audio-24khz-160kbitrate-mono-mp3'
1650)
1652AUDIO_TTS_MISTRAL_API_KEY = os.getenv('AUDIO_TTS_MISTRAL_API_KEY', '')
1654AUDIO_TTS_MISTRAL_API_BASE_URL = os.getenv('AUDIO_TTS_MISTRAL_API_BASE_URL', 'https://api.mistral.ai/v1')
1656####################################
1657# WEBUI
1658####################################
1661WEBUI_URL = os.getenv('WEBUI_URL', '')
1664ENABLE_SIGNUP = False if not WEBUI_AUTH else os.getenv('ENABLE_SIGNUP', 'True').lower() == 'true'
1666ENABLE_LOGIN_FORM = os.getenv('ENABLE_LOGIN_FORM', 'True').lower() == 'true'
1668ENABLE_PASSWORD_CHANGE_FORM = os.getenv('ENABLE_PASSWORD_CHANGE_FORM', 'True').lower() == 'true'
1670ENABLE_PASSWORD_AUTH = os.getenv('ENABLE_PASSWORD_AUTH', 'True').lower() == 'true'
1672DEFAULT_LOCALE = os.getenv('DEFAULT_LOCALE', '')
1674DEFAULT_MODELS = os.getenv('DEFAULT_MODELS', None)
1676DEFAULT_PINNED_MODELS = os.getenv('DEFAULT_PINNED_MODELS', None)
1678# None uses the frontend's localized defaults; an empty list disables suggestions.
1679try:
1680 DEFAULT_PROMPT_SUGGESTIONS = JSONCodec.loads(os.getenv('DEFAULT_PROMPT_SUGGESTIONS', 'null'))
1681except Exception as e:
1682 log.exception(f'Error loading DEFAULT_PROMPT_SUGGESTIONS: {e}')
1683 DEFAULT_PROMPT_SUGGESTIONS = None
1685DEFAULT_PROMPT_SUGGESTIONS_I18N = {}
1687try:
1688 model_order_list = JSONCodec.loads(os.getenv('MODEL_ORDER_LIST', '[]'))
1689except Exception as e:
1690 log.exception(f'Error loading MODEL_ORDER_LIST: {e}')
1691 model_order_list = []
1693MODEL_ORDER_LIST = model_order_list
1695try:
1696 default_model_metadata = JSONCodec.loads(os.getenv('DEFAULT_MODEL_METADATA', '{}'))
1697except Exception as e:
1698 log.exception(f'Error loading DEFAULT_MODEL_METADATA: {e}')
1699 default_model_metadata = {}
1701DEFAULT_MODEL_METADATA = default_model_metadata
1703try:
1704 default_model_params = JSONCodec.loads(os.getenv('DEFAULT_MODEL_PARAMS', '{}'))
1705except Exception as e:
1706 log.exception(f'Error loading DEFAULT_MODEL_PARAMS: {e}')
1707 default_model_params = {}
1709DEFAULT_MODEL_PARAMS = default_model_params
1712try:
1713 default_interface_settings = JSONCodec.loads(os.getenv('DEFAULT_INTERFACE_SETTINGS', '{}'))
1714except Exception as e:
1715 log.exception(f'Error loading DEFAULT_INTERFACE_SETTINGS: {e}')
1716 default_interface_settings = {}
1718DEFAULT_INTERFACE_SETTINGS = default_interface_settings if isinstance(default_interface_settings, dict) else {}
1720DEFAULT_USER_ROLE = os.getenv('DEFAULT_USER_ROLE', 'pending')
1722DEFAULT_GROUP_ID = os.getenv('DEFAULT_GROUP_ID', '')
1724PENDING_USER_OVERLAY_TITLE = os.getenv('PENDING_USER_OVERLAY_TITLE', '')
1726PENDING_USER_OVERLAY_CONTENT = os.getenv('PENDING_USER_OVERLAY_CONTENT', '')
1729RESPONSE_WATERMARK = os.getenv('RESPONSE_WATERMARK', '')
1731IFRAME_CSP = os.getenv('IFRAME_CSP', '')
1733USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS = (
1734 os.getenv('USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS', 'False').lower() == 'true'
1735)
1737USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS = (
1738 os.getenv('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS', 'False').lower() == 'true'
1739)
1741USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS = (
1742 os.getenv('USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS', 'False').lower() == 'true'
1743)
1745USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS = (
1746 os.getenv('USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS', 'False').lower() == 'true'
1747)
1749USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS = (
1750 os.getenv('USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS', 'False').lower() == 'true'
1751)
1753USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT = (
1754 os.getenv('USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT', 'False').lower() == 'true'
1755)
1757USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT = (
1758 os.getenv('USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT', 'False').lower() == 'true'
1759)
1761USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT = (
1762 os.getenv('USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT', 'False').lower() == 'true'
1763)
1765USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT = (
1766 os.getenv('USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT', 'False').lower() == 'true'
1767)
1769USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT = (
1770 os.getenv('USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT', 'False').lower() == 'true'
1771)
1773USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT = (
1774 os.getenv('USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT', 'False').lower() == 'true'
1775)
1777USER_PERMISSIONS_WORKSPACE_SKILLS_IMPORT = (
1778 os.getenv('USER_PERMISSIONS_WORKSPACE_SKILLS_IMPORT', 'False').lower() == 'true'
1779)
1781USER_PERMISSIONS_WORKSPACE_SKILLS_EXPORT = (
1782 os.getenv('USER_PERMISSIONS_WORKSPACE_SKILLS_EXPORT', 'False').lower() == 'true'
1783)
1786USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING = (
1787 os.getenv('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING', 'False').lower() == 'true'
1788)
1790USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING = (
1791 os.getenv('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1792)
1794USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING = (
1795 os.getenv('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING', 'False').lower() == 'true'
1796)
1798USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING = (
1799 os.getenv('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1800)
1802USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING = (
1803 os.getenv('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING', 'False').lower() == 'true'
1804)
1806USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING = (
1807 os.getenv('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1808)
1811USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING = (
1812 os.getenv('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING', 'False').lower() == 'true'
1813)
1815USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING = (
1816 os.getenv('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1817)
1819USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING = (
1820 os.getenv('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING', 'False').lower() == 'true'
1821)
1823USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING = (
1824 os.getenv('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1825)
1828USER_PERMISSIONS_NOTES_ALLOW_SHARING = os.getenv('USER_PERMISSIONS_NOTES_ALLOW_SHARING', 'False').lower() == 'true'
1830USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = (
1831 os.getenv('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1832)
1834USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true'
1837USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = (
1838 os.getenv('USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1839)
1841USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS = (
1842 os.getenv('USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS', 'True').lower() == 'true'
1843)
1844USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_GROUPS = (
1845 os.getenv('USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_GROUPS', 'True').lower() == 'true'
1846)
1849USER_PERMISSIONS_CHAT_CONTROLS = os.getenv('USER_PERMISSIONS_CHAT_CONTROLS', 'True').lower() == 'true'
1851USER_PERMISSIONS_CHAT_VALVES = os.getenv('USER_PERMISSIONS_CHAT_VALVES', 'True').lower() == 'true'
1853USER_PERMISSIONS_CHAT_SYSTEM_PROMPT = os.getenv('USER_PERMISSIONS_CHAT_SYSTEM_PROMPT', 'True').lower() == 'true'
1855USER_PERMISSIONS_CHAT_PARAMS = os.getenv('USER_PERMISSIONS_CHAT_PARAMS', 'True').lower() == 'true'
1857USER_PERMISSIONS_CHAT_FILE_UPLOAD = os.getenv('USER_PERMISSIONS_CHAT_FILE_UPLOAD', 'True').lower() == 'true'
1859USER_PERMISSIONS_CHAT_WEB_UPLOAD = os.getenv('USER_PERMISSIONS_CHAT_WEB_UPLOAD', 'True').lower() == 'true'
1861USER_PERMISSIONS_CHAT_DELETE = os.getenv('USER_PERMISSIONS_CHAT_DELETE', 'True').lower() == 'true'
1863USER_PERMISSIONS_CHAT_DELETE_MESSAGE = os.getenv('USER_PERMISSIONS_CHAT_DELETE_MESSAGE', 'True').lower() == 'true'
1865USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE = os.getenv('USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE', 'True').lower() == 'true'
1867USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE = (
1868 os.getenv('USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE', 'True').lower() == 'true'
1869)
1871USER_PERMISSIONS_CHAT_RATE_RESPONSE = os.getenv('USER_PERMISSIONS_CHAT_RATE_RESPONSE', 'True').lower() == 'true'
1873USER_PERMISSIONS_CHAT_EDIT = os.getenv('USER_PERMISSIONS_CHAT_EDIT', 'True').lower() == 'true'
1875USER_PERMISSIONS_CHAT_SHARE = os.getenv('USER_PERMISSIONS_CHAT_SHARE', 'True').lower() == 'true'
1877USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING = (
1878 os.getenv('USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
1879)
1881USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING = (
1882 os.getenv('USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING', 'False').lower() == 'true'
1883)
1885USER_PERMISSIONS_CHAT_EXPORT = os.getenv('USER_PERMISSIONS_CHAT_EXPORT', 'True').lower() == 'true'
1887USER_PERMISSIONS_CHAT_IMPORT = os.getenv('USER_PERMISSIONS_CHAT_IMPORT', 'True').lower() == 'true'
1889USER_PERMISSIONS_CHAT_STT = os.getenv('USER_PERMISSIONS_CHAT_STT', 'True').lower() == 'true'
1891USER_PERMISSIONS_CHAT_TTS = os.getenv('USER_PERMISSIONS_CHAT_TTS', 'True').lower() == 'true'
1893USER_PERMISSIONS_CHAT_CALL = os.getenv('USER_PERMISSIONS_CHAT_CALL', 'True').lower() == 'true'
1895USER_PERMISSIONS_CHAT_MULTIPLE_MODELS = os.getenv('USER_PERMISSIONS_CHAT_MULTIPLE_MODELS', 'True').lower() == 'true'
1897USER_PERMISSIONS_CHAT_TEMPORARY = os.getenv('USER_PERMISSIONS_CHAT_TEMPORARY', 'True').lower() == 'true'
1899USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = (
1900 os.getenv('USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED', 'False').lower() == 'true'
1901)
1904USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = (
1905 os.getenv('USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS', 'False').lower() == 'true'
1906)
1908USER_PERMISSIONS_FEATURES_WEB_SEARCH = os.getenv('USER_PERMISSIONS_FEATURES_WEB_SEARCH', 'True').lower() == 'true'
1910USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = (
1911 os.getenv('USER_PERMISSIONS_FEATURES_IMAGE_GENERATION', 'True').lower() == 'true'
1912)
1914USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = (
1915 os.getenv('USER_PERMISSIONS_FEATURES_CODE_INTERPRETER', 'True').lower() == 'true'
1916)
1918USER_PERMISSIONS_FEATURES_FOLDERS = os.getenv('USER_PERMISSIONS_FEATURES_FOLDERS', 'True').lower() == 'true'
1920USER_PERMISSIONS_FEATURES_NOTES = os.getenv('USER_PERMISSIONS_FEATURES_NOTES', 'True').lower() == 'true'
1922USER_PERMISSIONS_FEATURES_CHANNELS = os.getenv('USER_PERMISSIONS_FEATURES_CHANNELS', 'True').lower() == 'true'
1924USER_PERMISSIONS_FEATURES_API_KEYS = os.getenv('USER_PERMISSIONS_FEATURES_API_KEYS', 'False').lower() == 'true'
1926USER_PERMISSIONS_FEATURES_MEMORIES = os.getenv('USER_PERMISSIONS_FEATURES_MEMORIES', 'True').lower() == 'true'
1928USER_PERMISSIONS_FEATURES_AUTOMATIONS = os.getenv('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true'
1930USER_PERMISSIONS_FEATURES_CALENDAR = os.getenv('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true'
1932USER_PERMISSIONS_FEATURES_USER_WEBHOOKS = (
1933 os.getenv('USER_PERMISSIONS_FEATURES_USER_WEBHOOKS', 'False').lower() == 'true'
1934)
1937USER_PERMISSIONS_SETTINGS_INTERFACE = os.getenv('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true'
1940DEFAULT_USER_PERMISSIONS = {
1941 'workspace': {
1942 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS,
1943 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS,
1944 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS,
1945 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS,
1946 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS,
1947 'models_import': USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT,
1948 'models_export': USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT,
1949 'prompts_import': USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT,
1950 'prompts_export': USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT,
1951 'tools_import': USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT,
1952 'tools_export': USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT,
1953 'skills_import': USER_PERMISSIONS_WORKSPACE_SKILLS_IMPORT,
1954 'skills_export': USER_PERMISSIONS_WORKSPACE_SKILLS_EXPORT,
1955 },
1956 'sharing': {
1957 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING,
1958 'public_models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING,
1959 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING,
1960 'public_knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING,
1961 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING,
1962 'public_prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING,
1963 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING,
1964 'public_tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING,
1965 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING,
1966 'public_skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING,
1967 'notes': USER_PERMISSIONS_NOTES_ALLOW_SHARING,
1968 'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING,
1969 'folders': USER_PERMISSIONS_FOLDERS_ALLOW_SHARING,
1970 'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING,
1971 'open_chats': USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING,
1972 'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING,
1973 },
1974 'access_grants': {
1975 'allow_users': USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS,
1976 'allow_groups': USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_GROUPS,
1977 },
1978 'chat': {
1979 'controls': USER_PERMISSIONS_CHAT_CONTROLS,
1980 'valves': USER_PERMISSIONS_CHAT_VALVES,
1981 'system_prompt': USER_PERMISSIONS_CHAT_SYSTEM_PROMPT,
1982 'params': USER_PERMISSIONS_CHAT_PARAMS,
1983 'file_upload': USER_PERMISSIONS_CHAT_FILE_UPLOAD,
1984 'web_upload': USER_PERMISSIONS_CHAT_WEB_UPLOAD,
1985 'delete': USER_PERMISSIONS_CHAT_DELETE,
1986 'delete_message': USER_PERMISSIONS_CHAT_DELETE_MESSAGE,
1987 'continue_response': USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE,
1988 'regenerate_response': USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE,
1989 'rate_response': USER_PERMISSIONS_CHAT_RATE_RESPONSE,
1990 'edit': USER_PERMISSIONS_CHAT_EDIT,
1991 'share': USER_PERMISSIONS_CHAT_SHARE,
1992 'export': USER_PERMISSIONS_CHAT_EXPORT,
1993 'import': USER_PERMISSIONS_CHAT_IMPORT,
1994 'stt': USER_PERMISSIONS_CHAT_STT,
1995 'tts': USER_PERMISSIONS_CHAT_TTS,
1996 'call': USER_PERMISSIONS_CHAT_CALL,
1997 'multiple_models': USER_PERMISSIONS_CHAT_MULTIPLE_MODELS,
1998 'temporary': USER_PERMISSIONS_CHAT_TEMPORARY,
1999 'temporary_enforced': USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
2000 },
2001 'features': {
2002 # General features
2003 'api_keys': USER_PERMISSIONS_FEATURES_API_KEYS,
2004 'notes': USER_PERMISSIONS_FEATURES_NOTES,
2005 'folders': USER_PERMISSIONS_FEATURES_FOLDERS,
2006 'channels': USER_PERMISSIONS_FEATURES_CHANNELS,
2007 'direct_tool_servers': USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
2008 # Chat features
2009 'web_search': USER_PERMISSIONS_FEATURES_WEB_SEARCH,
2010 'image_generation': USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
2011 'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
2012 'memories': USER_PERMISSIONS_FEATURES_MEMORIES,
2013 'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS,
2014 'calendar': USER_PERMISSIONS_FEATURES_CALENDAR,
2015 'webhooks': USER_PERMISSIONS_FEATURES_USER_WEBHOOKS,
2016 },
2017 'settings': {
2018 'interface': USER_PERMISSIONS_SETTINGS_INTERFACE,
2019 },
2020}
2022USER_PERMISSIONS = DEFAULT_USER_PERMISSIONS
2024ENABLE_FOLDERS = os.getenv('ENABLE_FOLDERS', 'True').lower() == 'true'
2026FOLDER_MAX_FILE_COUNT = os.getenv('FOLDER_MAX_FILE_COUNT', '')
2028ENABLE_CHANNELS = os.getenv('ENABLE_CHANNELS', 'False').lower() == 'true'
2030CHANNEL_MODEL_RESPONSE_MODE = os.getenv('CHANNEL_MODEL_RESPONSE_MODE', 'thread')
2032ENABLE_CALENDAR = os.getenv('ENABLE_CALENDAR', 'True').lower() == 'true'
2034ENABLE_AUTOMATIONS = os.getenv('ENABLE_AUTOMATIONS', 'True').lower() == 'true'
2036ENABLE_SUBAGENTS = os.getenv('ENABLE_SUBAGENTS', 'False').lower() == 'true'
2037SUBAGENTS_BACKGROUND_ENABLED = os.getenv('SUBAGENTS_BACKGROUND_ENABLED', 'False').lower() == 'true'
2038SUBAGENTS_MAX_CONCURRENT = int(os.getenv('SUBAGENTS_MAX_CONCURRENT', '20'))
2039SUBAGENTS_MAX_ASYNC = int(os.getenv('SUBAGENTS_MAX_ASYNC', '20'))
2040SUBAGENTS_MAX_ITERATIONS = int(os.getenv('SUBAGENTS_MAX_ITERATIONS', '30'))
2041SUBAGENTS_MAX_OUTPUT = int(os.getenv('SUBAGENTS_MAX_OUTPUT', '30000'))
2042SUBAGENTS_SYSTEM_PROMPT = os.getenv('SUBAGENTS_SYSTEM_PROMPT', '')
2044AUTOMATION_MAX_COUNT = os.getenv('AUTOMATION_MAX_COUNT', '')
2046AUTOMATION_MIN_INTERVAL = os.getenv('AUTOMATION_MIN_INTERVAL', '')
2048AUTOMATION_AUTH_TOKEN_EXPIRES_IN = os.getenv('AUTOMATION_AUTH_TOKEN_EXPIRES_IN', '1h')
2050ENABLE_NOTES = os.getenv('ENABLE_NOTES', 'True').lower() == 'true'
2052ENABLE_USER_STATUS = os.getenv('ENABLE_USER_STATUS', 'True').lower() == 'true'
2054ENABLE_EVALUATION_ARENA_MODELS = os.getenv('ENABLE_EVALUATION_ARENA_MODELS', 'True').lower() == 'true'
2055try:
2056 evaluation_arena_models = JSONCodec.loads(os.getenv('EVALUATION_ARENA_MODELS', '[]'))
2057 if not isinstance(evaluation_arena_models, list) or not all( 2057 ↛ 2060line 2057 didn't jump to line 2060 because the condition on line 2057 was never true
2058 isinstance(model, dict) for model in evaluation_arena_models
2059 ):
2060 raise ValueError('EVALUATION_ARENA_MODELS must be a JSON list of objects')
2061except Exception as e:
2062 log.exception(f'Error loading EVALUATION_ARENA_MODELS: {e}')
2063 evaluation_arena_models = []
2065EVALUATION_ARENA_MODELS = evaluation_arena_models
2067DEFAULT_ARENA_MODEL = {
2068 'id': 'arena-model',
2069 'name': 'Arena Model',
2070 'meta': {
2071 # LICENSE covers this Open WebUI fallback logo.
2072 # Do not alter, remove, obscure, or replace it except as LICENSE permits:
2073 # https://docs.openwebui.com/license.
2074 'profile_image_url': '/favicon.png',
2075 'description': 'Submit your questions to anonymous AI chatbots and vote on the best response.',
2076 'model_ids': None,
2077 },
2078}
2080WEBHOOK_URL = os.getenv('WEBHOOK_URL', '')
2082ENABLE_ADMIN_EXPORT = os.getenv('ENABLE_ADMIN_EXPORT', 'True').lower() == 'true'
2084ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS = os.getenv('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True').lower() == 'true'
2086BYPASS_ADMIN_ACCESS_CONTROL = (
2087 os.getenv(
2088 'BYPASS_ADMIN_ACCESS_CONTROL',
2089 os.getenv('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True'),
2090 ).lower()
2091 == 'true'
2092)
2094ENABLE_ADMIN_ANALYTICS = os.getenv('ENABLE_ADMIN_ANALYTICS', 'True').lower() == 'true'
2096ENABLE_COMMUNITY_SHARING = os.getenv('ENABLE_COMMUNITY_SHARING', 'True').lower() == 'true'
2098ENABLE_MESSAGE_RATING = os.getenv('ENABLE_MESSAGE_RATING', 'True').lower() == 'true'
2100ENABLE_USER_WEBHOOKS = os.getenv('ENABLE_USER_WEBHOOKS', 'False').lower() == 'true'
2102# FastAPI / AnyIO settings
2103THREAD_POOL_SIZE = os.getenv('THREAD_POOL_SIZE', None)
2104THREAD_POOL_THREAD_NAME_PREFIX = os.getenv('THREAD_POOL_THREAD_NAME_PREFIX', '')
2106if THREAD_POOL_SIZE is not None and isinstance(THREAD_POOL_SIZE, str): 2106 ↛ 2107line 2106 didn't jump to line 2107 because the condition on line 2106 was never true
2107 try:
2108 THREAD_POOL_SIZE = int(THREAD_POOL_SIZE)
2109 except ValueError:
2110 log.warning(f'THREAD_POOL_SIZE is not a valid integer: {THREAD_POOL_SIZE}. Defaulting to None.')
2111 THREAD_POOL_SIZE = None
2114def validate_cors_origin(origin):
2115 parsed_url = urlparse(origin)
2117 # Check if the scheme is either http or https, or a custom scheme
2118 schemes = ['http', 'https'] + CORS_ALLOW_CUSTOM_SCHEME
2119 if parsed_url.scheme not in schemes:
2120 raise ValueError(
2121 f"Invalid scheme in CORS_ALLOW_ORIGIN: '{origin}'. Only 'http' and 'https' and CORS_ALLOW_CUSTOM_SCHEME are allowed."
2122 )
2124 # Ensure that the netloc (domain + port) is present, indicating it's a valid URL
2125 if not parsed_url.netloc:
2126 raise ValueError(f"Invalid URL structure in CORS_ALLOW_ORIGIN: '{origin}'.")
2129# For production, you should only need one host as
2130# fastapi serves the svelte-kit built frontend and backend from the same host and port.
2131# To test CORS_ALLOW_ORIGIN locally, you can set something like
2132# CORS_ALLOW_ORIGIN=http://localhost:5173;http://localhost:8080
2133# in your .env file depending on your frontend port, 5173 in this case.
2134CORS_ALLOW_ORIGIN = os.getenv('CORS_ALLOW_ORIGIN', '*').split(';')
2136# Allows custom URL schemes (e.g., app://) to be used as origins for CORS.
2137# Useful for local development or desktop clients with schemes like app:// or other custom protocols.
2138# Provide a semicolon-separated list of allowed schemes in the environment variable CORS_ALLOW_CUSTOM_SCHEMES.
2139CORS_ALLOW_CUSTOM_SCHEME = os.getenv('CORS_ALLOW_CUSTOM_SCHEME', '').split(';')
2141if CORS_ALLOW_ORIGIN == ['*']: 2141 ↛ 2146line 2141 didn't jump to line 2146 because the condition on line 2141 was always true
2142 log.warning("\n\nWARNING: CORS_ALLOW_ORIGIN IS SET TO '*' - NOT RECOMMENDED FOR PRODUCTION DEPLOYMENTS.\n")
2143else:
2144 # You have to pick between a single wildcard or a list of origins.
2145 # Doing both will result in CORS errors in the browser.
2146 for origin in CORS_ALLOW_ORIGIN:
2147 validate_cors_origin(origin)
2150class BannerModel(BaseModel):
2151 i18n: dict[str, dict[str, str]] | None = None
2152 id: str
2153 type: str
2154 title: str | None = None
2155 content: str
2156 dismissible: bool
2157 timestamp: int
2160try:
2161 banners = JSONCodec.loads(os.getenv('WEBUI_BANNERS', '[]'))
2162 banners = [BannerModel(**banner) for banner in banners]
2163except Exception as e:
2164 log.exception(f'Error loading WEBUI_BANNERS: {e}')
2165 banners = []
2167WEBUI_BANNERS = banners
2170SHOW_ADMIN_DETAILS = os.getenv('SHOW_ADMIN_DETAILS', 'true').lower() == 'true'
2172ADMIN_EMAIL = os.getenv('ADMIN_EMAIL', None)
2175####################################
2176# TASKS
2177####################################
2180TASK_MODEL = os.getenv('TASK_MODEL', '')
2182TASK_MODEL_EXTERNAL = os.getenv('TASK_MODEL_EXTERNAL', '')
2184try:
2185 task_model_params = JSONCodec.loads(os.getenv('TASK_MODEL_PARAMS', '{}'))
2186except Exception as e:
2187 log.exception(f'Error loading TASK_MODEL_PARAMS: {e}')
2188 task_model_params = {}
2190TASK_MODEL_PARAMS = task_model_params
2192CONTEXT_COMPACTION_MODEL = os.getenv('CONTEXT_COMPACTION_MODEL', '')
2194ENABLE_CONTEXT_COMPACTION = os.getenv('ENABLE_CONTEXT_COMPACTION', 'False').lower() == 'true'
2196ENABLE_TOOL_PERMISSIONS = os.getenv('ENABLE_TOOL_PERMISSIONS', 'False').lower() == 'true'
2198CONTEXT_COMPACTION_TOKEN_THRESHOLD = int(os.getenv('CONTEXT_COMPACTION_TOKEN_THRESHOLD', '80000'))
2200_CONTEXT_COMPACTION_TOKEN_CAP = os.getenv('CONTEXT_COMPACTION_TOKEN_CAP')
2201CONTEXT_COMPACTION_TOKEN_CAP = int(_CONTEXT_COMPACTION_TOKEN_CAP) if _CONTEXT_COMPACTION_TOKEN_CAP else None
2203CONTEXT_COMPACTION_RETENTION_PERCENTAGE = min(
2204 50, max(10, int(os.getenv('CONTEXT_COMPACTION_RETENTION_PERCENTAGE', '40')))
2205)
2207CONTEXT_COMPACTION_PROMPT_TEMPLATE = os.getenv('CONTEXT_COMPACTION_PROMPT_TEMPLATE', '')
2209TITLE_GENERATION_PROMPT_TEMPLATE = os.getenv('TITLE_GENERATION_PROMPT_TEMPLATE', '')
2211DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task:
2212Generate a concise title summarizing the chat history.
2213### Guidelines:
2214- The title should clearly represent the main theme or subject of the conversation.
2215- Keep it short: 2-4 words is best.
2216- Do not use emojis, quotation marks, or special formatting.
2217- Write the title in the chat's primary language; default to English if multilingual.
2218- Prioritize accuracy over creativity.
2219- Your entire response must consist solely of the JSON object, without any introductory or concluding text.
2220- The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text.
2221- Ensure no conversational text, affirmations, or explanations precede or follow the raw JSON output, as this will cause direct parsing failure.
2222### Output:
2223JSON format: { "title": "your concise title here" }
2224### Examples:
2225- { "title": "Stock Trends" },
2226- { "title": "Chocolate Chip Cookies" },
2227- { "title": "Music Streaming" },
2228- { "title": "Remote Work" }
2229### Chat History:
2230<chat_history>
2231{{MESSAGES:END:2}}
2232</chat_history>"""
2234TAGS_GENERATION_PROMPT_TEMPLATE = os.getenv('TAGS_GENERATION_PROMPT_TEMPLATE', '')
2236DEFAULT_TAGS_GENERATION_PROMPT_TEMPLATE = """### Task:
2237Generate 1-3 broad tags categorizing the main themes of the chat history, along with 1-3 more specific subtopic tags.
2239### Guidelines:
2240- Start with high-level domains (e.g. Science, Technology, Philosophy, Arts, Politics, Business, Health, Sports, Entertainment, Education)
2241- Consider including relevant subfields/subdomains if they are strongly represented throughout the conversation
2242- If content is too short (less than 3 messages) or too diverse, use only ["General"]
2243- Use the chat's primary language; default to English if multilingual
2244- Prioritize accuracy over specificity
2246### Output:
2247JSON format: { "tags": ["tag1", "tag2", "tag3"] }
2249### Chat History:
2250<chat_history>
2251{{MESSAGES:END:6}}
2252</chat_history>"""
2254IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = os.getenv('IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', '')
2256DEFAULT_IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = """### Task:
2257Generate a detailed prompt for am image generation task based on the given language and context. Describe the image as if you were explaining it to someone who cannot see it. Include relevant details, colors, shapes, and any other important elements.
2259### Guidelines:
2260- Be descriptive and detailed, focusing on the most important aspects of the image.
2261- Avoid making assumptions or adding information not present in the image.
2262- Use the chat's primary language; default to English if multilingual.
2263- If the image is too complex, focus on the most prominent elements.
2265### Output:
2266Strictly return in JSON format:
2267{
2268 "prompt": "Your detailed description here."
2269}
2271### Chat History:
2272<chat_history>
2273{{MESSAGES:END:6}}
2274</chat_history>"""
2277FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = os.getenv('FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', '')
2279DEFAULT_FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = """### Task:
2280Suggest 3-5 relevant follow-up questions or prompts that the user might naturally ask next in this conversation as a **user**, based on the chat history, to help continue or deepen the discussion.
2281### Guidelines:
2282- Write all follow-up questions from the user’s point of view, directed to the assistant.
2283- Make questions concise, clear, and directly related to the discussed topic(s).
2284- Only suggest follow-ups that make sense given the chat content and do not repeat what was already covered.
2285- If the conversation is very short or not specific, suggest more general (but relevant) follow-ups the user might ask.
2286- Use the conversation's primary language; default to English if multilingual.
2287- Response must be a JSON object with a "follow_ups" key containing an array of strings, no extra text or formatting.
2288### Output:
2289JSON format: { "follow_ups": ["Question 1?", "Question 2?", "Question 3?"] }
2290### Chat History:
2291<chat_history>
2292{{MESSAGES:END:6}}
2293</chat_history>"""
2295ENABLE_FOLLOW_UP_GENERATION = os.getenv('ENABLE_FOLLOW_UP_GENERATION', 'True').lower() == 'true'
2297ENABLE_TAGS_GENERATION = os.getenv('ENABLE_TAGS_GENERATION', 'True').lower() == 'true'
2299ENABLE_TITLE_GENERATION = os.getenv('ENABLE_TITLE_GENERATION', 'True').lower() == 'true'
2302ENABLE_SEARCH_QUERY_GENERATION = os.getenv('ENABLE_SEARCH_QUERY_GENERATION', 'True').lower() == 'true'
2304ENABLE_RETRIEVAL_QUERY_GENERATION = os.getenv('ENABLE_RETRIEVAL_QUERY_GENERATION', 'True').lower() == 'true'
2307QUERY_GENERATION_PROMPT_TEMPLATE = os.getenv('QUERY_GENERATION_PROMPT_TEMPLATE', '')
2309DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE = """### Task:
2310Analyze the chat history to determine the necessity of generating search queries, in the given language. By default, **prioritize generating 1-3 broad and relevant search queries** unless it is absolutely certain that no additional information is required. The aim is to retrieve comprehensive, updated, and valuable information even with minimal uncertainty. If no search is unequivocally needed, return an empty list.
2312### Guidelines:
2313- Respond **EXCLUSIVELY** with a JSON object. Any form of extra commentary, explanation, or additional text is strictly prohibited.
2314- When generating search queries, respond in the format: { "queries": ["query1", "query2"] }, ensuring each query is distinct, concise, and relevant to the topic.
2315- If and only if it is entirely certain that no useful results can be retrieved by a search, return: { "queries": [] }.
2316- Err on the side of suggesting search queries if there is **any chance** they might provide useful or updated information.
2317- Be concise and focused on composing high-quality search queries, avoiding unnecessary elaboration, commentary, or assumptions.
2318- Today's date is: {{CURRENT_DATE}}.
2319- Always prioritize providing actionable and broad queries that maximize informational coverage.
2321### Output:
2322Strictly return in JSON format:
2323{
2324 "queries": ["query1", "query2"]
2325}
2327### Chat History:
2328<chat_history>
2329{{MESSAGES:END:6}}
2330</chat_history>
2331"""
2333ENABLE_AUTOCOMPLETE_GENERATION = os.getenv('ENABLE_AUTOCOMPLETE_GENERATION', 'False').lower() == 'true'
2335AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = int(os.getenv('AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', '-1'))
2337AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = os.getenv('AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', '')
2340DEFAULT_AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = """### Task:
2341You are an autocompletion system. Continue the text in `<text>` based on the **completion type** in `<type>` and the given language.
2343### **Instructions**:
23441. Analyze `<text>` for context and meaning.
23452. Use `<type>` to guide your output:
2346 - **General**: Provide a natural, concise continuation.
2347 - **Search Query**: Complete as if generating a realistic search query.
23483. Start as if you are directly continuing `<text>`. Do **not** repeat, paraphrase, or respond as a model. Simply complete the text.
23494. Ensure the continuation:
2350 - Flows naturally from `<text>`.
2351 - Avoids repetition, overexplaining, or unrelated ideas.
23525. If unsure, return: `{ "text": "" }`.
2354### **Output Rules**:
2355- Respond only in JSON format: `{ "text": "<your_completion>" }`.
2357### **Examples**:
2358#### Example 1:
2359Input:
2360<type>General</type>
2361<text>The sun was setting over the horizon, painting the sky</text>
2362Output:
2363{ "text": "with vibrant shades of orange and pink." }
2365#### Example 2:
2366Input:
2367<type>Search Query</type>
2368<text>Top-rated restaurants in</text>
2369Output:
2370{ "text": "New York City for Italian cuisine." }
2372---
2373### Context:
2374<chat_history>
2375{{MESSAGES:END:6}}
2376</chat_history>
2377<type>{{TYPE}}</type>
2378<text>{{PROMPT}}</text>
2379#### Output:
2380"""
2383VOICE_MODE_PROMPT_TEMPLATE = os.getenv('VOICE_MODE_PROMPT_TEMPLATE', '')
2385ENABLE_VOICE_MODE_PROMPT = os.getenv('ENABLE_VOICE_MODE_PROMPT', 'True').lower() == 'true'
2387DEFAULT_VOICE_MODE_PROMPT_TEMPLATE = """You are a friendly, concise voice assistant.
2389Everything you say will be spoken aloud.
2390Keep responses short, clear, and natural.
2392STYLE:
2393- Use simple words and short sentences.
2394- Sound warm and conversational.
2395- Avoid long explanations, lists, or complex phrasing.
2397BEHAVIOR:
2398- Give the quickest helpful answer first.
2399- Offer extra detail only if needed.
2400- Ask for clarification only when necessary.
2402VOICE OPTIMIZATION:
2403- Break information into small, easy-to-hear chunks.
2404- Avoid dense wording or anything that sounds like reading text.
2406ERROR HANDLING:
2407- If unsure, say so briefly and offer options.
2408- If something is unsafe or impossible, decline kindly and suggest a safe alternative.
2410Stay consistent, helpful, and easy to listen to."""
2412TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = os.getenv('TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', '')
2415DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}
2417Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines:
2419- Return only the JSON object, without any additional text or explanation.
2421- If no tools match the query, return an empty array:
2422 {
2423 "tool_calls": []
2424 }
2426- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include:
2427 - "name": The tool's name.
2428 - "parameters": A dictionary of required parameters and their corresponding values.
2430The format for the JSON response is strictly:
2431{
2432 "tool_calls": [
2433 {"name": "toolName1", "parameters": {"key1": "value1"}},
2434 {"name": "toolName2", "parameters": {"key2": "value2"}}
2435 ]
2436}"""
2439DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
2441Message: ```{{prompt}}```"""
2443DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE = """You have been provided with a set of responses from various models to the latest user query: "{{prompt}}"
2445Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.
2447Responses from models: {{responses}}"""
2450####################################
2451# Auth
2452####################################
2454ENABLE_API_KEYS = os.getenv('ENABLE_API_KEYS', 'False').lower() == 'true'
2456ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = (
2457 os.getenv(
2458 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS',
2459 os.getenv('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'),
2460 ).lower()
2461 == 'true'
2462)
2464API_KEYS_ALLOWED_ENDPOINTS = os.getenv('API_KEYS_ALLOWED_ENDPOINTS', os.getenv('API_KEY_ALLOWED_ENDPOINTS', ''))
2466JWT_EXPIRES_IN = os.getenv('JWT_EXPIRES_IN', '4w')
2468if JWT_EXPIRES_IN == '-1': 2468 ↛ 2469line 2468 didn't jump to line 2469 because the condition on line 2468 was never true
2469 log.warning(
2470 "⚠️ SECURITY WARNING: JWT_EXPIRES_IN is set to '-1'\n"
2471 ' See: https://docs.openwebui.com/reference/env-configuration\n'
2472 )
2474####################################
2475# OAuth config
2476####################################
2478# Master switch for OAuth/OIDC sign-in. Defaults to enabled so existing
2479# deployments that already have a provider configured keep working; admins can
2480# turn it off to disable OAuth login without clearing their provider settings.
2481ENABLE_OAUTH = os.getenv('ENABLE_OAUTH', 'True').lower() == 'true'
2483ENABLE_OAUTH_SIGNUP = os.getenv('ENABLE_OAUTH_SIGNUP', 'False').lower() == 'true'
2485OAUTH_AUTO_REDIRECT = os.getenv('OAUTH_AUTO_REDIRECT', 'False').lower() == 'true'
2487OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE = os.getenv('OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', 'False').lower() == 'true'
2490OAUTH_MERGE_ACCOUNTS_BY_EMAIL = os.getenv('OAUTH_MERGE_ACCOUNTS_BY_EMAIL', 'False').lower() == 'true'
2492OAUTH_PROVIDERS = {}
2494GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID', '')
2496GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET', '')
2499GOOGLE_OAUTH_SCOPE = os.getenv('GOOGLE_OAUTH_SCOPE', 'openid email profile')
2501GOOGLE_REDIRECT_URI = os.getenv('GOOGLE_REDIRECT_URI', '')
2503GOOGLE_OAUTH_AUTHORIZE_PARAMS = {}
2504_google_oauth_authorize_params = os.getenv('GOOGLE_OAUTH_AUTHORIZE_PARAMS', '')
2505if _google_oauth_authorize_params: 2505 ↛ 2506line 2505 didn't jump to line 2506 because the condition on line 2505 was never true
2506 try:
2507 _parsed = JSONCodec.loads(_google_oauth_authorize_params)
2508 if isinstance(_parsed, dict):
2509 GOOGLE_OAUTH_AUTHORIZE_PARAMS = _parsed
2510 else:
2511 log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring')
2512 except (JSONCodec.JSONDecodeError, TypeError):
2513 log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring')
2515MICROSOFT_CLIENT_ID = os.getenv('MICROSOFT_CLIENT_ID', '')
2517MICROSOFT_CLIENT_SECRET = os.getenv('MICROSOFT_CLIENT_SECRET', '')
2519MICROSOFT_CLIENT_TENANT_ID = os.getenv('MICROSOFT_CLIENT_TENANT_ID', '')
2521MICROSOFT_CLIENT_LOGIN_BASE_URL = os.getenv('MICROSOFT_CLIENT_LOGIN_BASE_URL', 'https://login.microsoftonline.com')
2523MICROSOFT_CLIENT_PICTURE_URL = os.getenv(
2524 'MICROSOFT_CLIENT_PICTURE_URL',
2525 'https://graph.microsoft.com/v1.0/me/photo/$value',
2526)
2529MICROSOFT_OAUTH_SCOPE = os.getenv('MICROSOFT_OAUTH_SCOPE', 'openid email profile')
2531MICROSOFT_REDIRECT_URI = os.getenv('MICROSOFT_REDIRECT_URI', '')
2533GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID', '')
2535GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET', '')
2537GITHUB_CLIENT_SCOPE = os.getenv('GITHUB_CLIENT_SCOPE', 'user:email')
2539GITHUB_CLIENT_REDIRECT_URI = os.getenv('GITHUB_CLIENT_REDIRECT_URI', '')
2541OAUTH_CLIENT_ID = os.getenv('OAUTH_CLIENT_ID', '')
2543OAUTH_CLIENT_SECRET = os.getenv('OAUTH_CLIENT_SECRET', '')
2545OPENID_PROVIDER_URL = os.getenv('OPENID_PROVIDER_URL', '')
2547OPENID_END_SESSION_ENDPOINT = os.getenv('OPENID_END_SESSION_ENDPOINT', '')
2549OPENID_REDIRECT_URI = os.getenv('OPENID_REDIRECT_URI', '')
2551OAUTH_SCOPES = os.getenv('OAUTH_SCOPES', 'openid email profile')
2553OAUTH_TIMEOUT = os.getenv('OAUTH_TIMEOUT', '')
2555OAUTH_TOKEN_ENDPOINT_AUTH_METHOD = os.getenv('OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', None)
2557OAUTH_CODE_CHALLENGE_METHOD = os.getenv('OAUTH_CODE_CHALLENGE_METHOD', None)
2559OAUTH_PROVIDER_NAME = os.getenv('OAUTH_PROVIDER_NAME', 'SSO')
2561OAUTH_SUB_CLAIM = os.getenv('OAUTH_SUB_CLAIM', None)
2563OAUTH_USERNAME_CLAIM = os.getenv('OAUTH_USERNAME_CLAIM', 'name')
2566OAUTH_PICTURE_CLAIM = os.getenv('OAUTH_PICTURE_CLAIM', 'picture')
2568OAUTH_EMAIL_CLAIM = os.getenv('OAUTH_EMAIL_CLAIM', 'email')
2570OAUTH_GROUPS_CLAIM = os.getenv('OAUTH_GROUPS_CLAIM', os.getenv('OAUTH_GROUP_CLAIM', 'groups'))
2572FEISHU_CLIENT_ID = os.getenv('FEISHU_CLIENT_ID', '')
2574FEISHU_CLIENT_SECRET = os.getenv('FEISHU_CLIENT_SECRET', '')
2576FEISHU_OAUTH_SCOPE = os.getenv('FEISHU_OAUTH_SCOPE', 'contact:user.base:readonly')
2578FEISHU_REDIRECT_URI = os.getenv('FEISHU_REDIRECT_URI', '')
2580ENABLE_OAUTH_ROLE_MANAGEMENT = os.getenv('ENABLE_OAUTH_ROLE_MANAGEMENT', 'False').lower() == 'true'
2582ENABLE_OAUTH_GROUP_MANAGEMENT = os.getenv('ENABLE_OAUTH_GROUP_MANAGEMENT', 'False').lower() == 'true'
2584ENABLE_OAUTH_GROUP_CREATION = os.getenv('ENABLE_OAUTH_GROUP_CREATION', 'False').lower() == 'true'
2587oauth_group_default_share = os.getenv('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower()
2588OAUTH_GROUP_DEFAULT_SHARE = 'members' if oauth_group_default_share == 'members' else oauth_group_default_share == 'true'
2591OAUTH_BLOCKED_GROUPS = os.getenv('OAUTH_BLOCKED_GROUPS', '[]')
2593OAUTH_GROUPS_SEPARATOR = os.getenv('OAUTH_GROUPS_SEPARATOR', ';')
2595OAUTH_ROLES_CLAIM = os.getenv('OAUTH_ROLES_CLAIM', 'roles')
2597OAUTH_ROLES_SEPARATOR = os.getenv('OAUTH_ROLES_SEPARATOR', ',')
2599OAUTH_ALLOWED_ROLES = [
2600 role.strip()
2601 for role in os.getenv('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split(OAUTH_ROLES_SEPARATOR)
2602 if role
2603]
2605OAUTH_ADMIN_ROLES = [
2606 role.strip() for role in os.getenv('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role
2607]
2609OAUTH_ALLOWED_DOMAINS = [domain.strip() for domain in os.getenv('OAUTH_ALLOWED_DOMAINS', '*').split(',')]
2611OAUTH_UPDATE_PICTURE_ON_LOGIN = os.getenv('OAUTH_UPDATE_PICTURE_ON_LOGIN', 'False').lower() == 'true'
2613OAUTH_UPDATE_NAME_ON_LOGIN = os.getenv('OAUTH_UPDATE_NAME_ON_LOGIN', 'False').lower() == 'true'
2615OAUTH_UPDATE_EMAIL_ON_LOGIN = os.getenv('OAUTH_UPDATE_EMAIL_ON_LOGIN', 'False').lower() == 'true'
2617OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID = (
2618 os.getenv('OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID', 'False').lower() == 'true'
2619)
2621OAUTH_AUDIENCE = os.getenv('OAUTH_AUDIENCE', '')
2623OAUTH_AUTHORIZE_PARAMS = {}
2624_oauth_authorize_params = os.getenv('OAUTH_AUTHORIZE_PARAMS', '')
2625if _oauth_authorize_params: 2625 ↛ 2626line 2625 didn't jump to line 2626 because the condition on line 2625 was never true
2626 try:
2627 _parsed = JSONCodec.loads(_oauth_authorize_params)
2628 if isinstance(_parsed, dict):
2629 OAUTH_AUTHORIZE_PARAMS = _parsed
2630 else:
2631 log.warning('OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring')
2632 except (JSONCodec.JSONDecodeError, TypeError):
2633 log.warning('OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring')
2636def oauth_client_kwargs(scope: str, **kwargs):
2637 client_kwargs = {
2638 'scope': scope,
2639 **kwargs,
2640 **({'timeout': int(OAUTH_TIMEOUT)} if OAUTH_TIMEOUT else {}),
2641 }
2643 if OAUTH_CODE_CHALLENGE_METHOD == 'S256':
2644 client_kwargs['code_challenge_method'] = 'S256'
2645 elif OAUTH_CODE_CHALLENGE_METHOD:
2646 raise Exception(
2647 'Code challenge methods other than "%s" not supported. Given: "%s"' % ('S256', OAUTH_CODE_CHALLENGE_METHOD)
2648 )
2650 return client_kwargs
2653def load_oauth_providers():
2654 OAUTH_PROVIDERS.clear()
2655 if GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET: 2655 ↛ 2657line 2655 didn't jump to line 2657 because the condition on line 2655 was never true
2657 def google_oauth_register(oauth: OAuth):
2658 client = oauth.register(
2659 name='google',
2660 client_id=GOOGLE_CLIENT_ID,
2661 client_secret=GOOGLE_CLIENT_SECRET,
2662 server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
2663 client_kwargs=oauth_client_kwargs(GOOGLE_OAUTH_SCOPE),
2664 redirect_uri=GOOGLE_REDIRECT_URI,
2665 **({'authorize_params': GOOGLE_OAUTH_AUTHORIZE_PARAMS} if GOOGLE_OAUTH_AUTHORIZE_PARAMS else {}),
2666 )
2667 return client
2669 OAUTH_PROVIDERS['google'] = {
2670 'register': google_oauth_register,
2671 }
2673 if MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET and MICROSOFT_CLIENT_TENANT_ID: 2673 ↛ 2675line 2673 didn't jump to line 2675 because the condition on line 2673 was never true
2675 def microsoft_oauth_register(oauth: OAuth):
2676 client = oauth.register(
2677 name='microsoft',
2678 client_id=MICROSOFT_CLIENT_ID,
2679 client_secret=MICROSOFT_CLIENT_SECRET,
2680 server_metadata_url=f'{MICROSOFT_CLIENT_LOGIN_BASE_URL}/{MICROSOFT_CLIENT_TENANT_ID}/v2.0/.well-known/openid-configuration?appid={MICROSOFT_CLIENT_ID}',
2681 client_kwargs=oauth_client_kwargs(MICROSOFT_OAUTH_SCOPE),
2682 redirect_uri=MICROSOFT_REDIRECT_URI,
2683 )
2684 return client
2686 OAUTH_PROVIDERS['microsoft'] = {
2687 'picture_url': MICROSOFT_CLIENT_PICTURE_URL,
2688 'register': microsoft_oauth_register,
2689 }
2691 if GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET: 2691 ↛ 2693line 2691 didn't jump to line 2693 because the condition on line 2691 was never true
2693 def github_oauth_register(oauth: OAuth):
2694 client = oauth.register(
2695 name='github',
2696 client_id=GITHUB_CLIENT_ID,
2697 client_secret=GITHUB_CLIENT_SECRET,
2698 access_token_url='https://github.com/login/oauth/access_token',
2699 authorize_url='https://github.com/login/oauth/authorize',
2700 api_base_url='https://api.github.com',
2701 userinfo_endpoint='https://api.github.com/user',
2702 client_kwargs=oauth_client_kwargs(GITHUB_CLIENT_SCOPE),
2703 redirect_uri=GITHUB_CLIENT_REDIRECT_URI,
2704 )
2705 return client
2707 OAUTH_PROVIDERS['github'] = {
2708 'register': github_oauth_register,
2709 'sub_claim': 'id',
2710 }
2712 if OAUTH_CLIENT_ID and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD) and OPENID_PROVIDER_URL: 2712 ↛ 2714line 2712 didn't jump to line 2714 because the condition on line 2712 was never true
2714 def oidc_oauth_register(oauth: OAuth):
2715 client = oauth.register(
2716 name='oidc',
2717 client_id=OAUTH_CLIENT_ID,
2718 client_secret=OAUTH_CLIENT_SECRET,
2719 server_metadata_url=OPENID_PROVIDER_URL,
2720 client_kwargs=oauth_client_kwargs(
2721 OAUTH_SCOPES,
2722 **(
2723 {'token_endpoint_auth_method': OAUTH_TOKEN_ENDPOINT_AUTH_METHOD}
2724 if OAUTH_TOKEN_ENDPOINT_AUTH_METHOD
2725 else {}
2726 ),
2727 ),
2728 redirect_uri=OPENID_REDIRECT_URI,
2729 )
2730 return client
2732 OAUTH_PROVIDERS['oidc'] = {
2733 'name': OAUTH_PROVIDER_NAME,
2734 'register': oidc_oauth_register,
2735 }
2737 if FEISHU_CLIENT_ID and FEISHU_CLIENT_SECRET: 2737 ↛ 2739line 2737 didn't jump to line 2739 because the condition on line 2737 was never true
2739 def feishu_oauth_register(oauth: OAuth):
2740 client = oauth.register(
2741 name='feishu',
2742 client_id=FEISHU_CLIENT_ID,
2743 client_secret=FEISHU_CLIENT_SECRET,
2744 access_token_url='https://open.feishu.cn/open-apis/authen/v2/oauth/token',
2745 authorize_url='https://accounts.feishu.cn/open-apis/authen/v1/authorize',
2746 api_base_url='https://open.feishu.cn/open-apis',
2747 userinfo_endpoint='https://open.feishu.cn/open-apis/authen/v1/user_info',
2748 client_kwargs={
2749 'scope': FEISHU_OAUTH_SCOPE,
2750 **({'timeout': int(OAUTH_TIMEOUT)} if OAUTH_TIMEOUT else {}),
2751 },
2752 redirect_uri=FEISHU_REDIRECT_URI,
2753 )
2754 return client
2756 OAUTH_PROVIDERS['feishu'] = {
2757 'register': feishu_oauth_register,
2758 'sub_claim': 'user_id',
2759 }
2761 configured_providers = []
2762 if GOOGLE_CLIENT_ID: 2762 ↛ 2763line 2762 didn't jump to line 2763 because the condition on line 2762 was never true
2763 configured_providers.append('Google')
2764 if MICROSOFT_CLIENT_ID: 2764 ↛ 2765line 2764 didn't jump to line 2765 because the condition on line 2764 was never true
2765 configured_providers.append('Microsoft')
2766 if GITHUB_CLIENT_ID: 2766 ↛ 2767line 2766 didn't jump to line 2767 because the condition on line 2766 was never true
2767 configured_providers.append('GitHub')
2768 if FEISHU_CLIENT_ID: 2768 ↛ 2769line 2768 didn't jump to line 2769 because the condition on line 2768 was never true
2769 configured_providers.append('Feishu')
2771 if configured_providers and not OPENID_PROVIDER_URL and not OPENID_END_SESSION_ENDPOINT: 2771 ↛ 2772line 2771 didn't jump to line 2772 because the condition on line 2771 was never true
2772 provider_list = ', '.join(configured_providers)
2773 log.warning(
2774 f'⚠️ OAuth providers configured ({provider_list}) but OPENID_PROVIDER_URL not set - logout will not work!'
2775 )
2776 log.warning(
2777 f"Set OPENID_PROVIDER_URL to your OAuth provider's OpenID Connect discovery endpoint,"
2778 f' or set OPENID_END_SESSION_ENDPOINT to a custom logout URL to fix logout functionality.'
2779 )
2782load_oauth_providers()
2784####################################
2785# LDAP
2786####################################
2788ENABLE_LDAP = os.getenv('ENABLE_LDAP', 'false').lower() == 'true'
2790LDAP_SERVER_LABEL = os.getenv('LDAP_SERVER_LABEL', 'LDAP Server')
2792LDAP_SERVER_HOST = os.getenv('LDAP_SERVER_HOST', 'localhost')
2794LDAP_SERVER_PORT = int(os.getenv('LDAP_SERVER_PORT', '389'))
2796LDAP_ATTRIBUTE_FOR_MAIL = os.getenv('LDAP_ATTRIBUTE_FOR_MAIL', 'mail')
2798LDAP_ATTRIBUTE_FOR_USERNAME = os.getenv('LDAP_ATTRIBUTE_FOR_USERNAME', 'uid')
2800LDAP_APP_DN = os.getenv('LDAP_APP_DN', '')
2802LDAP_APP_PASSWORD = os.getenv('LDAP_APP_PASSWORD', '')
2804LDAP_SEARCH_BASE = os.getenv('LDAP_SEARCH_BASE', '')
2806LDAP_SEARCH_FILTERS = os.getenv('LDAP_SEARCH_FILTER', os.getenv('LDAP_SEARCH_FILTERS', ''))
2808LDAP_USE_TLS = os.getenv('LDAP_USE_TLS', 'True').lower() == 'true'
2810LDAP_CA_CERT_FILE = os.getenv('LDAP_CA_CERT_FILE', '')
2812LDAP_VALIDATE_CERT = os.getenv('LDAP_VALIDATE_CERT', 'True').lower() == 'true'
2814LDAP_CIPHERS = os.getenv('LDAP_CIPHERS', 'ALL')
2816ENABLE_LDAP_GROUP_MANAGEMENT = os.getenv('ENABLE_LDAP_GROUP_MANAGEMENT', 'False').lower() == 'true'
2818ENABLE_LDAP_GROUP_CREATION = os.getenv('ENABLE_LDAP_GROUP_CREATION', 'False').lower() == 'true'
2820LDAP_ATTRIBUTE_FOR_GROUPS = os.getenv('LDAP_ATTRIBUTE_FOR_GROUPS', 'memberOf')
2822DEFAULT_CONFIG = {
2823 'direct.enable': ENABLE_DIRECT_CONNECTIONS,
2824 'direct.integrations.enable': ENABLE_DIRECT_INTEGRATIONS,
2825 'ollama.enable': ENABLE_OLLAMA_API,
2826 'ollama.base_urls': OLLAMA_BASE_URLS,
2827 'ollama.api_configs': OLLAMA_API_CONFIGS,
2828 'openai.enable': ENABLE_OPENAI_API,
2829 'openai.api_keys': OPENAI_API_KEYS,
2830 'openai.api_base_urls': OPENAI_API_BASE_URLS,
2831 'openai.api_configs': OPENAI_API_CONFIGS,
2832 'models.base_models_cache': ENABLE_BASE_MODELS_CACHE,
2833 'tool_server.connections': TOOL_SERVER_CONNECTIONS,
2834 'oauth.client.timeout': OAUTH_CLIENT_TIMEOUT,
2835 'terminal_server.connections': TERMINAL_SERVER_CONNECTIONS,
2836 'code_execution.enable': ENABLE_CODE_EXECUTION,
2837 'code_execution.engine': CODE_EXECUTION_ENGINE,
2838 'code_execution.jupyter.url': CODE_EXECUTION_JUPYTER_URL,
2839 'code_execution.jupyter.auth': CODE_EXECUTION_JUPYTER_AUTH,
2840 'code_execution.jupyter.auth_token': CODE_EXECUTION_JUPYTER_AUTH_TOKEN,
2841 'code_execution.jupyter.auth_password': CODE_EXECUTION_JUPYTER_AUTH_PASSWORD,
2842 'code_execution.jupyter.timeout': CODE_EXECUTION_JUPYTER_TIMEOUT,
2843 'code_interpreter.enable': ENABLE_CODE_INTERPRETER,
2844 'memories.enable': ENABLE_MEMORIES,
2845 'memories.system_context.enable': ENABLE_MEMORY_SYSTEM_CONTEXT,
2846 'memories.background_review.enable': ENABLE_MEMORY_BACKGROUND_REVIEW,
2847 'memories.review_interval_turns': MEMORIES_REVIEW_INTERVAL_TURNS,
2848 'memories.user_char_limit': MEMORIES_USER_CHAR_LIMIT,
2849 'memories.context_char_limit': MEMORIES_CONTEXT_CHAR_LIMIT,
2850 'code_interpreter.engine': CODE_INTERPRETER_ENGINE,
2851 'code_interpreter.prompt_template': CODE_INTERPRETER_PROMPT_TEMPLATE,
2852 'code_interpreter.jupyter.url': CODE_INTERPRETER_JUPYTER_URL,
2853 'code_interpreter.jupyter.auth': CODE_INTERPRETER_JUPYTER_AUTH,
2854 'code_interpreter.jupyter.auth_token': CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
2855 'code_interpreter.jupyter.auth_password': CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
2856 'code_interpreter.jupyter.timeout': CODE_INTERPRETER_JUPYTER_TIMEOUT,
2857 'google_drive.enable': ENABLE_GOOGLE_DRIVE_INTEGRATION,
2858 'google_drive.client_id': GOOGLE_DRIVE_CLIENT_ID,
2859 'google_drive.api_key': GOOGLE_DRIVE_API_KEY,
2860 'onedrive.enable': ENABLE_ONEDRIVE_INTEGRATION,
2861 'onedrive.sharepoint_url': ONEDRIVE_SHAREPOINT_URL,
2862 'onedrive.sharepoint_tenant_id': ONEDRIVE_SHAREPOINT_TENANT_ID,
2863 'rag.content_extraction_engine': CONTENT_EXTRACTION_ENGINE,
2864 'rag.content_extraction.supported_media_mime_types': CONTENT_EXTRACTION_SUPPORTED_MEDIA_MIME_TYPES,
2865 'rag.datalab_marker_api_key': DATALAB_MARKER_API_KEY,
2866 'rag.datalab_marker_api_base_url': DATALAB_MARKER_API_BASE_URL,
2867 'rag.datalab_marker_additional_config': DATALAB_MARKER_ADDITIONAL_CONFIG,
2868 'rag.datalab_marker_use_llm': DATALAB_MARKER_USE_LLM,
2869 'rag.datalab_marker_skip_cache': DATALAB_MARKER_SKIP_CACHE,
2870 'rag.datalab_marker_force_ocr': DATALAB_MARKER_FORCE_OCR,
2871 'rag.datalab_marker_paginate': DATALAB_MARKER_PAGINATE,
2872 'rag.datalab_marker_strip_existing_ocr': DATALAB_MARKER_STRIP_EXISTING_OCR,
2873 'rag.datalab_marker_disable_image_extraction': DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION,
2874 'rag.datalab_marker_format_lines': DATALAB_MARKER_FORMAT_LINES,
2875 'rag.datalab_marker_output_format': DATALAB_MARKER_OUTPUT_FORMAT,
2876 'rag.mineru_api_mode': MINERU_API_MODE,
2877 'rag.mineru_api_url': MINERU_API_URL,
2878 'rag.mineru_api_timeout': MINERU_API_TIMEOUT,
2879 'rag.mineru_api_key': MINERU_API_KEY,
2880 'rag.mineru_params': MINERU_PARAMS,
2881 'rag.mineru_file_extensions': MINERU_FILE_EXTENSIONS,
2882 'rag.external_document_loader_url': EXTERNAL_DOCUMENT_LOADER_URL,
2883 'rag.external_document_loader_api_key': EXTERNAL_DOCUMENT_LOADER_API_KEY,
2884 'rag.external_document_loader_headers': EXTERNAL_DOCUMENT_LOADER_HEADERS,
2885 'rag.tika_server_url': TIKA_SERVER_URL,
2886 'rag.tika_server_version': TIKA_SERVER_VERSION,
2887 'rag.docling_server_url': DOCLING_SERVER_URL,
2888 'rag.docling_api_key': DOCLING_API_KEY,
2889 'rag.docling_params': DOCLING_PARAMS,
2890 'rag.document_intelligence_endpoint': DOCUMENT_INTELLIGENCE_ENDPOINT,
2891 'rag.document_intelligence_key': DOCUMENT_INTELLIGENCE_KEY,
2892 'rag.document_intelligence_model': DOCUMENT_INTELLIGENCE_MODEL,
2893 'rag.mistral_ocr_api_base_url': MISTRAL_OCR_API_BASE_URL,
2894 'rag.mistral_ocr_api_key': MISTRAL_OCR_API_KEY,
2895 'rag.mistral_ocr_use_base64': MISTRAL_OCR_USE_BASE64,
2896 'rag.paddleocr_vl_base_url': PADDLEOCR_VL_BASE_URL,
2897 'rag.paddleocr_vl_token': PADDLEOCR_VL_TOKEN,
2898 'rag.bypass_embedding_and_retrieval': BYPASS_EMBEDDING_AND_RETRIEVAL,
2899 'rag.top_k': RAG_TOP_K,
2900 'rag.top_k_reranker': RAG_TOP_K_RERANKER,
2901 'rag.relevance_threshold': RAG_RELEVANCE_THRESHOLD,
2902 'rag.hybrid_bm25_weight': RAG_HYBRID_BM25_WEIGHT,
2903 'rag.enable_hybrid_search': ENABLE_RAG_HYBRID_SEARCH,
2904 'rag.enable_hybrid_search_enriched_texts': ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS,
2905 'rag.full_context': RAG_FULL_CONTEXT,
2906 'rag.file.max_count': RAG_FILE_MAX_COUNT,
2907 'rag.file.max_size': RAG_FILE_MAX_SIZE,
2908 'file.image_compression_width': FILE_IMAGE_COMPRESSION_WIDTH,
2909 'file.image_compression_height': FILE_IMAGE_COMPRESSION_HEIGHT,
2910 'rag.file.allowed_extensions': RAG_ALLOWED_FILE_EXTENSIONS,
2911 'rag.embedding_engine': RAG_EMBEDDING_ENGINE,
2912 'rag.pdf_extract_images': PDF_EXTRACT_IMAGES,
2913 'rag.pdf_loader_mode': PDF_LOADER_MODE,
2914 'rag.embedding_model': RAG_EMBEDDING_MODEL,
2915 'rag.tokenizer_model': RAG_TOKENIZER_MODEL,
2916 'rag.embedding_batch_size': RAG_EMBEDDING_BATCH_SIZE,
2917 'rag.enable_async_embedding': ENABLE_ASYNC_EMBEDDING,
2918 'rag.embedding_concurrent_requests': RAG_EMBEDDING_CONCURRENT_REQUESTS,
2919 'rag.reranking_engine': RAG_RERANKING_ENGINE,
2920 'rag.reranking_model': RAG_RERANKING_MODEL,
2921 'rag.reranking_batch_size': RAG_RERANKING_BATCH_SIZE,
2922 'rag.external_reranker_url': RAG_EXTERNAL_RERANKER_URL,
2923 'rag.external_reranker_api_key': RAG_EXTERNAL_RERANKER_API_KEY,
2924 'rag.external_reranker_timeout': RAG_EXTERNAL_RERANKER_TIMEOUT,
2925 'rag.text_splitter': RAG_TEXT_SPLITTER,
2926 'rag.enable_markdown_header_text_splitter': ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER,
2927 'rag.tiktoken_encoding_name': TIKTOKEN_ENCODING_NAME,
2928 'rag.chunk_size': CHUNK_SIZE,
2929 'rag.chunk_min_size_target': CHUNK_MIN_SIZE_TARGET,
2930 'rag.chunk_overlap': CHUNK_OVERLAP,
2931 'rag.template': RAG_TEMPLATE,
2932 'rag.openai.api_base_url': RAG_OPENAI_API_BASE_URL,
2933 'rag.openai.api_key': RAG_OPENAI_API_KEY,
2934 'rag.azure_openai.base_url': RAG_AZURE_OPENAI_BASE_URL,
2935 'rag.azure_openai.api_key': RAG_AZURE_OPENAI_API_KEY,
2936 'rag.azure_openai.api_version': RAG_AZURE_OPENAI_API_VERSION,
2937 'rag.ollama.base_url': RAG_OLLAMA_BASE_URL,
2938 'rag.ollama.api_key': RAG_OLLAMA_API_KEY,
2939 'rag.youtube_loader_language': YOUTUBE_LOADER_LANGUAGE,
2940 'rag.youtube_loader_proxy_url': YOUTUBE_LOADER_PROXY_URL,
2941 'web.search.enable': ENABLE_WEB_SEARCH,
2942 'web.search.confirmation.enable': ENABLE_WEB_SEARCH_CONFIRMATION,
2943 'web.search.confirmation.content': WEB_SEARCH_CONFIRMATION_CONTENT,
2944 'web.search.engine': WEB_SEARCH_ENGINE,
2945 'web.search.bypass_embedding_and_retrieval': BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
2946 'web.search.bypass_web_loader': BYPASS_WEB_SEARCH_WEB_LOADER,
2947 'web.search.result_count': WEB_SEARCH_RESULT_COUNT,
2948 'web.search.domain.filter_list': WEB_SEARCH_DOMAIN_FILTER_LIST,
2949 'web.search.concurrent_requests': WEB_SEARCH_CONCURRENT_REQUESTS,
2950 'web.fetch.max_content_length': WEB_FETCH_MAX_CONTENT_LENGTH,
2951 'web.loader.engine': WEB_LOADER_ENGINE,
2952 'web.loader.concurrent_requests': WEB_LOADER_CONCURRENT_REQUESTS,
2953 'web.loader.timeout': WEB_LOADER_TIMEOUT,
2954 'web.loader.ssl_verification': ENABLE_WEB_LOADER_SSL_VERIFICATION,
2955 'web.search.trust_env': WEB_SEARCH_TRUST_ENV,
2956 'web.search.ollama_cloud_api_key': OLLAMA_CLOUD_WEB_SEARCH_API_KEY,
2957 'web.search.searxng_query_url': SEARXNG_QUERY_URL,
2958 'web.search.openserp_base_url': OPENSERP_BASE_URL,
2959 'web.search.searxng_language': SEARXNG_LANGUAGE,
2960 'web.search.yacy_query_url': YACY_QUERY_URL,
2961 'web.search.yacy_username': YACY_USERNAME,
2962 'web.search.yacy_password': YACY_PASSWORD,
2963 'web.search.google_pse_api_key': GOOGLE_PSE_API_KEY,
2964 'web.search.google_pse_engine_id': GOOGLE_PSE_ENGINE_ID,
2965 'web.search.brave_search_api_key': BRAVE_SEARCH_API_KEY,
2966 'web.search.brave_search_context_tokens': BRAVE_SEARCH_CONTEXT_TOKENS,
2967 'web.search.kagi_search_api_key': KAGI_SEARCH_API_KEY,
2968 'web.search.mojeek_search_api_key': MOJEEK_SEARCH_API_KEY,
2969 'web.search.bocha_search_api_key': BOCHA_SEARCH_API_KEY,
2970 'web.search.serpstack_api_key': SERPSTACK_API_KEY,
2971 'web.search.serpstack_https': SERPSTACK_HTTPS,
2972 'web.search.serper_api_key': SERPER_API_KEY,
2973 'web.search.serply_api_key': SERPLY_API_KEY,
2974 'web.search.serphouse_api_key': SERPHOUSE_API_KEY,
2975 'web.search.serphouse_domain': SERPHOUSE_DOMAIN,
2976 'web.search.ddgs_backend': DDGS_BACKEND,
2977 'web.search.jina_api_key': JINA_API_KEY,
2978 'web.search.jina_api_base_url': JINA_API_BASE_URL,
2979 'web.search.searchapi_api_key': SEARCHAPI_API_KEY,
2980 'web.search.searchapi_engine': SEARCHAPI_ENGINE,
2981 'web.search.serpapi_api_key': SERPAPI_API_KEY,
2982 'web.search.serpapi_engine': SERPAPI_ENGINE,
2983 'web.search.bing_search_v7_endpoint': BING_SEARCH_V7_ENDPOINT,
2984 'web.search.bing_search_v7_subscription_key': BING_SEARCH_V7_SUBSCRIPTION_KEY,
2985 'web.search.azure_ai_search_api_key': AZURE_AI_SEARCH_API_KEY,
2986 'web.search.azure_ai_search_endpoint': AZURE_AI_SEARCH_ENDPOINT,
2987 'web.search.azure_ai_search_index_name': AZURE_AI_SEARCH_INDEX_NAME,
2988 'web.search.exa_api_key': EXA_API_KEY,
2989 'web.search.exa_max_content_length': EXA_MAX_CONTENT_LENGTH,
2990 'web.search.perplexity_api_key': PERPLEXITY_API_KEY,
2991 'web.search.perplexity_model': PERPLEXITY_MODEL,
2992 'web.search.perplexity_search_context_usage': PERPLEXITY_SEARCH_CONTEXT_USAGE,
2993 'web.search.perplexity_search_api_url': PERPLEXITY_SEARCH_API_URL,
2994 'web.search.microsoft_web_iq_api_base_url': MICROSOFT_WEB_IQ_API_BASE_URL,
2995 'web.search.microsoft_web_iq_api_key': MICROSOFT_WEB_IQ_API_KEY,
2996 'web.search.microsoft_web_iq_language': MICROSOFT_WEB_IQ_LANGUAGE,
2997 'web.search.sougou_api_sid': SOUGOU_API_SID,
2998 'web.search.sougou_api_sk': SOUGOU_API_SK,
2999 'web.search.tavily_api_key': TAVILY_API_KEY,
3000 'web.search.tavily_extract_depth': TAVILY_EXTRACT_DEPTH,
3001 'web.search.staan_api_key': STAAN_API_KEY,
3002 'web.search.staan_market': STAAN_MARKET,
3003 'web.search.staan_max_snippets': STAAN_MAX_SNIPPETS,
3004 'web.loader.playwright_ws_url': PLAYWRIGHT_WS_URL,
3005 'web.loader.playwright_timeout': PLAYWRIGHT_TIMEOUT,
3006 'web.loader.firecrawl_api_key': FIRECRAWL_API_KEY,
3007 'web.loader.firecrawl_api_url': FIRECRAWL_API_BASE_URL,
3008 'web.loader.firecrawl_timeout': FIRECRAWL_TIMEOUT,
3009 'web.search.external_web_search_url': EXTERNAL_WEB_SEARCH_URL,
3010 'web.search.external_web_search_api_key': EXTERNAL_WEB_SEARCH_API_KEY,
3011 'web.loader.external_web_loader_url': EXTERNAL_WEB_LOADER_URL,
3012 'web.loader.external_web_loader_api_key': EXTERNAL_WEB_LOADER_API_KEY,
3013 'web.search.yandex_web_search_url': YANDEX_WEB_SEARCH_URL,
3014 'web.search.yandex_web_search_api_key': YANDEX_WEB_SEARCH_API_KEY,
3015 'web.search.yandex_web_search_config': YANDEX_WEB_SEARCH_CONFIG,
3016 'web.search.youcom_api_key': YOUCOM_API_KEY,
3017 'web.search.linkup_api_key': LINKUP_API_KEY,
3018 'web.search.linkup_search_params': LINKUP_SEARCH_PARAMS,
3019 'image_generation.enable': ENABLE_IMAGE_GENERATION,
3020 'image_generation.engine': IMAGE_GENERATION_ENGINE,
3021 'image_generation.model': IMAGE_GENERATION_MODEL,
3022 'image_generation.size': IMAGE_SIZE,
3023 'image_generation.steps': IMAGE_STEPS,
3024 'image_generation.prompt.enable': ENABLE_IMAGE_PROMPT_GENERATION,
3025 'image_generation.automatic1111.base_url': AUTOMATIC1111_BASE_URL,
3026 'image_generation.automatic1111.api_auth': AUTOMATIC1111_API_AUTH,
3027 'image_generation.automatic1111.api_params': AUTOMATIC1111_PARAMS,
3028 'image_generation.comfyui.base_url': COMFYUI_BASE_URL,
3029 'image_generation.comfyui.api_key': COMFYUI_API_KEY,
3030 'image_generation.comfyui.workflow': COMFYUI_WORKFLOW,
3031 'image_generation.comfyui.nodes': COMFYUI_WORKFLOW_NODES,
3032 'image_generation.openai.api_base_url': IMAGES_OPENAI_API_BASE_URL,
3033 'image_generation.openai.api_version': IMAGES_OPENAI_API_VERSION,
3034 'image_generation.openai.api_key': IMAGES_OPENAI_API_KEY,
3035 'image_generation.openai.params': IMAGES_OPENAI_API_PARAMS,
3036 'image_generation.gemini.api_base_url': IMAGES_GEMINI_API_BASE_URL,
3037 'image_generation.gemini.api_key': IMAGES_GEMINI_API_KEY,
3038 'image_generation.gemini.endpoint_method': IMAGES_GEMINI_ENDPOINT_METHOD,
3039 'images.edit.enable': ENABLE_IMAGE_EDIT,
3040 'images.edit.engine': IMAGE_EDIT_ENGINE,
3041 'images.edit.model': IMAGE_EDIT_MODEL,
3042 'images.edit.size': IMAGE_EDIT_SIZE,
3043 'images.edit.openai.api_base_url': IMAGES_EDIT_OPENAI_API_BASE_URL,
3044 'images.edit.openai.api_version': IMAGES_EDIT_OPENAI_API_VERSION,
3045 'images.edit.openai.api_key': IMAGES_EDIT_OPENAI_API_KEY,
3046 'images.edit.gemini.api_base_url': IMAGES_EDIT_GEMINI_API_BASE_URL,
3047 'images.edit.gemini.api_key': IMAGES_EDIT_GEMINI_API_KEY,
3048 'images.edit.comfyui.base_url': IMAGES_EDIT_COMFYUI_BASE_URL,
3049 'images.edit.comfyui.api_key': IMAGES_EDIT_COMFYUI_API_KEY,
3050 'images.edit.comfyui.workflow': IMAGES_EDIT_COMFYUI_WORKFLOW,
3051 'images.edit.comfyui.nodes': IMAGES_EDIT_COMFYUI_WORKFLOW_NODES,
3052 'audio.stt.whisper_model': WHISPER_MODEL,
3053 'audio.stt.deepgram.api_key': DEEPGRAM_API_KEY,
3054 'audio.stt.openai.api_base_url': AUDIO_STT_OPENAI_API_BASE_URL,
3055 'audio.stt.openai.api_key': AUDIO_STT_OPENAI_API_KEY,
3056 'audio.stt.openai.api_request_format': AUDIO_STT_OPENAI_API_REQUEST_FORMAT,
3057 'audio.stt.engine': AUDIO_STT_ENGINE,
3058 'audio.stt.model': AUDIO_STT_MODEL,
3059 'audio.stt.supported_content_types': AUDIO_STT_SUPPORTED_CONTENT_TYPES,
3060 'audio.stt.allowed_extensions': AUDIO_STT_ALLOWED_EXTENSIONS,
3061 'audio.stt.azure.api_key': AUDIO_STT_AZURE_API_KEY,
3062 'audio.stt.azure.region': AUDIO_STT_AZURE_REGION,
3063 'audio.stt.azure.locales': AUDIO_STT_AZURE_LOCALES,
3064 'audio.stt.azure.base_url': AUDIO_STT_AZURE_BASE_URL,
3065 'audio.stt.azure.max_speakers': AUDIO_STT_AZURE_MAX_SPEAKERS,
3066 'audio.stt.mistral.api_key': AUDIO_STT_MISTRAL_API_KEY,
3067 'audio.stt.mistral.api_base_url': AUDIO_STT_MISTRAL_API_BASE_URL,
3068 'audio.stt.mistral.use_chat_completions': AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS,
3069 'audio.tts.openai.api_base_url': AUDIO_TTS_OPENAI_API_BASE_URL,
3070 'audio.tts.openai.api_key': AUDIO_TTS_OPENAI_API_KEY,
3071 'audio.tts.openai.params': AUDIO_TTS_OPENAI_PARAMS,
3072 'audio.tts.api_key': AUDIO_TTS_API_KEY,
3073 'audio.tts.engine': AUDIO_TTS_ENGINE,
3074 'audio.tts.model': AUDIO_TTS_MODEL,
3075 'audio.tts.voice': AUDIO_TTS_VOICE,
3076 'audio.tts.split_on': AUDIO_TTS_SPLIT_ON,
3077 'audio.tts.azure.speech_region': AUDIO_TTS_AZURE_SPEECH_REGION,
3078 'audio.tts.azure.speech_base_url': AUDIO_TTS_AZURE_SPEECH_BASE_URL,
3079 'audio.tts.azure.speech_output_format': AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
3080 'audio.tts.mistral.api_key': AUDIO_TTS_MISTRAL_API_KEY,
3081 'audio.tts.mistral.api_base_url': AUDIO_TTS_MISTRAL_API_BASE_URL,
3082 'webui.url': WEBUI_URL,
3083 'ui.enable_signup': ENABLE_SIGNUP,
3084 'ui.enable_login_form': ENABLE_LOGIN_FORM,
3085 'ui.enable_password_change_form': ENABLE_PASSWORD_CHANGE_FORM,
3086 'ui.default_locale': DEFAULT_LOCALE,
3087 'ui.default_models': DEFAULT_MODELS,
3088 'ui.default_pinned_models': DEFAULT_PINNED_MODELS,
3089 'ui.default_interface_settings': DEFAULT_INTERFACE_SETTINGS,
3090 'ui.i18n': {},
3091 'ui.prompt_suggestions': DEFAULT_PROMPT_SUGGESTIONS,
3092 'ui.prompt_suggestions_i18n': DEFAULT_PROMPT_SUGGESTIONS_I18N,
3093 'ui.model_order_list': MODEL_ORDER_LIST,
3094 'models.default_metadata': DEFAULT_MODEL_METADATA,
3095 'models.default_params': DEFAULT_MODEL_PARAMS,
3096 'ui.default_user_role': DEFAULT_USER_ROLE,
3097 'ui.default_group_id': DEFAULT_GROUP_ID,
3098 'ui.pending_user_overlay_title': PENDING_USER_OVERLAY_TITLE,
3099 'ui.pending_user_overlay_content': PENDING_USER_OVERLAY_CONTENT,
3100 'ui.watermark': RESPONSE_WATERMARK,
3101 'user.permissions': USER_PERMISSIONS,
3102 'folders.enable': ENABLE_FOLDERS,
3103 'folders.max_file_count': FOLDER_MAX_FILE_COUNT,
3104 'channels.enable': ENABLE_CHANNELS,
3105 'channels.model_response_mode': CHANNEL_MODEL_RESPONSE_MODE,
3106 'calendar.enable': ENABLE_CALENDAR,
3107 'automations.enable': ENABLE_AUTOMATIONS,
3108 'subagents.enable': ENABLE_SUBAGENTS,
3109 'subagents.background_enabled': SUBAGENTS_BACKGROUND_ENABLED,
3110 'subagents.max_concurrent': SUBAGENTS_MAX_CONCURRENT,
3111 'subagents.max_async': SUBAGENTS_MAX_ASYNC,
3112 'subagents.max_iterations': SUBAGENTS_MAX_ITERATIONS,
3113 'subagents.max_output': SUBAGENTS_MAX_OUTPUT,
3114 'subagents.system_prompt': SUBAGENTS_SYSTEM_PROMPT,
3115 'automations.max_count': AUTOMATION_MAX_COUNT,
3116 'automations.min_interval': AUTOMATION_MIN_INTERVAL,
3117 'automations.auth_token_expires_in': AUTOMATION_AUTH_TOKEN_EXPIRES_IN,
3118 'notes.enable': ENABLE_NOTES,
3119 'users.enable_status': ENABLE_USER_STATUS,
3120 'evaluation.arena.enable': ENABLE_EVALUATION_ARENA_MODELS,
3121 'evaluation.arena.models': EVALUATION_ARENA_MODELS,
3122 'webhook_url': WEBHOOK_URL,
3123 'ui.enable_community_sharing': ENABLE_COMMUNITY_SHARING,
3124 'ui.enable_message_rating': ENABLE_MESSAGE_RATING,
3125 'ui.enable_user_webhooks': ENABLE_USER_WEBHOOKS,
3126 'ui.banners': WEBUI_BANNERS,
3127 'auth.admin.show': SHOW_ADMIN_DETAILS,
3128 'auth.admin.email': ADMIN_EMAIL,
3129 'task.model.default': TASK_MODEL,
3130 'task.model.external': TASK_MODEL_EXTERNAL,
3131 'task.model.params': TASK_MODEL_PARAMS,
3132 'chat.context_compaction.model': CONTEXT_COMPACTION_MODEL,
3133 'chat.context_compaction.enable': ENABLE_CONTEXT_COMPACTION,
3134 'chat.context_compaction.token_threshold': CONTEXT_COMPACTION_TOKEN_THRESHOLD,
3135 'chat.context_compaction.token_cap': CONTEXT_COMPACTION_TOKEN_CAP,
3136 'chat.context_compaction.retention_percentage': CONTEXT_COMPACTION_RETENTION_PERCENTAGE,
3137 'chat.context_compaction.prompt_template': CONTEXT_COMPACTION_PROMPT_TEMPLATE,
3138 'chat.tool_permissions.enable': ENABLE_TOOL_PERMISSIONS,
3139 'task.title.prompt_template': TITLE_GENERATION_PROMPT_TEMPLATE,
3140 'task.tags.prompt_template': TAGS_GENERATION_PROMPT_TEMPLATE,
3141 'task.image.prompt_template': IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE,
3142 'task.follow_up.prompt_template': FOLLOW_UP_GENERATION_PROMPT_TEMPLATE,
3143 'task.follow_up.enable': ENABLE_FOLLOW_UP_GENERATION,
3144 'task.tags.enable': ENABLE_TAGS_GENERATION,
3145 'task.title.enable': ENABLE_TITLE_GENERATION,
3146 'task.query.search.enable': ENABLE_SEARCH_QUERY_GENERATION,
3147 'task.query.retrieval.enable': ENABLE_RETRIEVAL_QUERY_GENERATION,
3148 'task.query.prompt_template': QUERY_GENERATION_PROMPT_TEMPLATE,
3149 'task.autocomplete.enable': ENABLE_AUTOCOMPLETE_GENERATION,
3150 'task.autocomplete.input_max_length': AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH,
3151 'task.autocomplete.prompt_template': AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE,
3152 'task.voice.prompt_template': VOICE_MODE_PROMPT_TEMPLATE,
3153 'task.voice.prompt.enable': ENABLE_VOICE_MODE_PROMPT,
3154 'task.tools.prompt_template': TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
3155 'auth.enable_api_keys': ENABLE_API_KEYS,
3156 'auth.api_key.endpoint_restrictions': ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS,
3157 'auth.api_key.allowed_endpoints': API_KEYS_ALLOWED_ENDPOINTS,
3158 'auth.jwt_expiry': JWT_EXPIRES_IN,
3159 'oauth.enable': ENABLE_OAUTH,
3160 'oauth.enable_signup': ENABLE_OAUTH_SIGNUP,
3161 'oauth.auto_redirect': OAUTH_AUTO_REDIRECT,
3162 'oauth.refresh_token.include_scope': OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE,
3163 'oauth.merge_accounts_by_email': OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
3164 'oauth.google.client_id': GOOGLE_CLIENT_ID,
3165 'oauth.google.client_secret': GOOGLE_CLIENT_SECRET,
3166 'oauth.google.scope': GOOGLE_OAUTH_SCOPE,
3167 'oauth.google.redirect_uri': GOOGLE_REDIRECT_URI,
3168 'oauth.microsoft.client_id': MICROSOFT_CLIENT_ID,
3169 'oauth.microsoft.client_secret': MICROSOFT_CLIENT_SECRET,
3170 'oauth.microsoft.tenant_id': MICROSOFT_CLIENT_TENANT_ID,
3171 'oauth.microsoft.login_base_url': MICROSOFT_CLIENT_LOGIN_BASE_URL,
3172 'oauth.microsoft.picture_url': MICROSOFT_CLIENT_PICTURE_URL,
3173 'oauth.microsoft.scope': MICROSOFT_OAUTH_SCOPE,
3174 'oauth.microsoft.redirect_uri': MICROSOFT_REDIRECT_URI,
3175 'oauth.github.client_id': GITHUB_CLIENT_ID,
3176 'oauth.github.client_secret': GITHUB_CLIENT_SECRET,
3177 'oauth.github.scope': GITHUB_CLIENT_SCOPE,
3178 'oauth.github.redirect_uri': GITHUB_CLIENT_REDIRECT_URI,
3179 'oauth.client_id': OAUTH_CLIENT_ID,
3180 'oauth.client_secret': OAUTH_CLIENT_SECRET,
3181 'oauth.provider_url': OPENID_PROVIDER_URL,
3182 'oauth.end_session_endpoint': OPENID_END_SESSION_ENDPOINT,
3183 'oauth.redirect_uri': OPENID_REDIRECT_URI,
3184 'oauth.scopes': OAUTH_SCOPES,
3185 'oauth.timeout': OAUTH_TIMEOUT,
3186 'oauth.token_endpoint_auth_method': OAUTH_TOKEN_ENDPOINT_AUTH_METHOD,
3187 'oauth.code_challenge_method': OAUTH_CODE_CHALLENGE_METHOD,
3188 'oauth.provider_name': OAUTH_PROVIDER_NAME,
3189 'oauth.sub_claim': OAUTH_SUB_CLAIM,
3190 'oauth.username_claim': OAUTH_USERNAME_CLAIM,
3191 'oauth.picture_claim': OAUTH_PICTURE_CLAIM,
3192 'oauth.email_claim': OAUTH_EMAIL_CLAIM,
3193 'oauth.group_claim': OAUTH_GROUPS_CLAIM,
3194 'oauth.feishu.client_id': FEISHU_CLIENT_ID,
3195 'oauth.feishu.client_secret': FEISHU_CLIENT_SECRET,
3196 'oauth.feishu.scope': FEISHU_OAUTH_SCOPE,
3197 'oauth.feishu.redirect_uri': FEISHU_REDIRECT_URI,
3198 'oauth.enable_role_mapping': ENABLE_OAUTH_ROLE_MANAGEMENT,
3199 'oauth.enable_group_mapping': ENABLE_OAUTH_GROUP_MANAGEMENT,
3200 'oauth.enable_group_creation': ENABLE_OAUTH_GROUP_CREATION,
3201 'oauth.group_default_share': OAUTH_GROUP_DEFAULT_SHARE,
3202 'oauth.blocked_groups': OAUTH_BLOCKED_GROUPS,
3203 'oauth.roles_claim': OAUTH_ROLES_CLAIM,
3204 'oauth.allowed_roles': OAUTH_ALLOWED_ROLES,
3205 'oauth.admin_roles': OAUTH_ADMIN_ROLES,
3206 'oauth.allowed_domains': OAUTH_ALLOWED_DOMAINS,
3207 'oauth.update_picture_on_login': OAUTH_UPDATE_PICTURE_ON_LOGIN,
3208 'oauth.update_name_on_login': OAUTH_UPDATE_NAME_ON_LOGIN,
3209 'oauth.update_email_on_login': OAUTH_UPDATE_EMAIL_ON_LOGIN,
3210 'oauth.audience': OAUTH_AUDIENCE,
3211 'ldap.enable': ENABLE_LDAP,
3212 'ldap.server.label': LDAP_SERVER_LABEL,
3213 'ldap.server.host': LDAP_SERVER_HOST,
3214 'ldap.server.port': LDAP_SERVER_PORT,
3215 'ldap.server.attribute_for_mail': LDAP_ATTRIBUTE_FOR_MAIL,
3216 'ldap.server.attribute_for_username': LDAP_ATTRIBUTE_FOR_USERNAME,
3217 'ldap.server.app_dn': LDAP_APP_DN,
3218 'ldap.server.app_password': LDAP_APP_PASSWORD,
3219 'ldap.server.users_dn': LDAP_SEARCH_BASE,
3220 'ldap.server.search_filter': LDAP_SEARCH_FILTERS,
3221 'ldap.server.use_tls': LDAP_USE_TLS,
3222 'ldap.server.ca_cert_file': LDAP_CA_CERT_FILE,
3223 'ldap.server.validate_cert': LDAP_VALIDATE_CERT,
3224 'ldap.server.ciphers': LDAP_CIPHERS,
3225 'ldap.group.enable_management': ENABLE_LDAP_GROUP_MANAGEMENT,
3226 'ldap.group.enable_creation': ENABLE_LDAP_GROUP_CREATION,
3227 'ldap.server.attribute_for_groups': LDAP_ATTRIBUTE_FOR_GROUPS,
3228}
3231ENABLE_PERSISTENT_CONFIG = os.getenv('ENABLE_PERSISTENT_CONFIG', 'True').lower() == 'true'
3232ENABLE_OAUTH_PERSISTENT_CONFIG = os.getenv('ENABLE_OAUTH_PERSISTENT_CONFIG', 'False').lower() == 'true'
3234Config.configure(
3235 defaults=DEFAULT_CONFIG,
3236 enable_persistent=ENABLE_PERSISTENT_CONFIG,
3237 enable_oauth_persistent=ENABLE_OAUTH_PERSISTENT_CONFIG,
3238)