This commit is contained in:
Timothy Jaeryang Baek
2026-04-13 14:08:58 -05:00
parent 45f45f5bba
commit d0188f3fe1
13 changed files with 70 additions and 56 deletions
+13
View File
@@ -91,6 +91,19 @@ class ERROR_MESSAGES(str, Enum):
INVALID_PASSWORD = lambda err='': err if err else 'The password does not meet the required validation criteria.'
AUTOMATION_LIMIT_EXCEEDED = lambda size='': f'Automation limit reached ({size})'
AUTOMATION_TOO_FREQUENT = (
lambda interval='': f'Schedule too frequent. Minimum interval is {interval} seconds.'
)
AUTOMATION_INVALID_RRULE = lambda err='': f'Invalid RRULE: {err}'
AUTOMATION_NO_FUTURE_RUNS = 'RRULE has no future occurrences'
FEATURE_DISABLED = lambda name='': f'{name} is disabled'
INPUT_TOO_LONG = lambda size='': f'Input prompt exceeds maximum length of {size}'
SERVER_CONNECTION_ERROR = 'Open WebUI: Server Connection Error'
REQUIRED_FIELD_EMPTY = lambda name='': f'Required field {name} is empty'
OAUTH_NOT_CONFIGURED = lambda name='': f"Provider '{name}' is not configured"
class TASKS(str, Enum):
def __str__(self) -> str:
+3 -3
View File
@@ -1131,7 +1131,7 @@ async def update_ldap_server(request: Request, form_data: LdapServerConfig, user
for key in required_fields:
value = getattr(form_data, key)
if not value:
raise HTTPException(400, detail=f'Required field {key} is empty')
raise HTTPException(400, detail=ERROR_MESSAGES.REQUIRED_FIELD_EMPTY(key))
request.app.state.config.LDAP_SERVER_LABEL = form_data.label
request.app.state.config.LDAP_SERVER_HOST = form_data.host
@@ -1260,7 +1260,7 @@ async def token_exchange(
if provider not in OAUTH_PROVIDERS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Provider '{provider}' is not configured",
detail=ERROR_MESSAGES.OAUTH_NOT_CONFIGURED(provider),
)
# Get the OAuth client for this provider
oauth_manager = request.app.state.oauth_manager
@@ -1268,7 +1268,7 @@ async def token_exchange(
if not client:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"OAuth client for '{provider}' not found",
detail=ERROR_MESSAGES.OAUTH_NOT_CONFIGURED(provider),
)
# Validate the token by calling the userinfo endpoint
+2 -2
View File
@@ -74,7 +74,7 @@ async def check_automation_limits(request, user, rrule_str: str, db, is_create:
if max_count > 0 and await Automations.count_by_user(user.id, db=db) >= max_count:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f'Automation limit reached ({max_count})',
detail=ERROR_MESSAGES.AUTOMATION_LIMIT_EXCEEDED(max_count),
)
# Min interval (create + update)
@@ -86,7 +86,7 @@ async def check_automation_limits(request, user, rrule_str: str, db, is_create:
if interval is not None and interval < min_interval:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'Schedule too frequent. Minimum interval is {min_interval} seconds.',
detail=ERROR_MESSAGES.AUTOMATION_TOO_FREQUENT(min_interval),
)
+3 -3
View File
@@ -140,7 +140,7 @@ async def check_channels_access(request: Request, user: Optional[UserModel] = No
if not request.app.state.config.ENABLE_CHANNELS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail='Channels are not enabled',
detail=ERROR_MESSAGES.FEATURE_DISABLED('Channels'),
)
if user:
@@ -1791,7 +1791,7 @@ async def post_webhook_message(
if not webhook:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid webhook URL',
detail=ERROR_MESSAGES.INVALID_URL,
)
channel = await Channels.get_channel_by_id(webhook.channel_id, db=db)
@@ -1809,7 +1809,7 @@ async def post_webhook_message(
if not message:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Failed to create message',
detail=ERROR_MESSAGES.DEFAULT('Failed to create message'),
)
# Update last_used_at
+1 -1
View File
@@ -128,7 +128,7 @@ async def load_function_from_url(request: Request, form_data: LoadUrlForm, user=
'content': data,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f'Error importing function: {e}')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.DEFAULT(e))
############################
+1 -1
View File
@@ -268,7 +268,7 @@ async def update_memory_by_id(
memory = await Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content)
if memory is None:
raise HTTPException(status_code=404, detail='Memory not found')
raise HTTPException(status_code=404, detail=ERROR_MESSAGES.NOT_FOUND)
if form_data.content is not None:
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
+18 -18
View File
@@ -157,7 +157,7 @@ async def send_request(
log.error(f'Failed to parse error response: {e}')
raise HTTPException(
status_code=r.status,
detail='Open WebUI: Server Connection Error',
detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR,
)
r.raise_for_status()
@@ -184,7 +184,7 @@ async def send_request(
except Exception as e:
raise HTTPException(
status_code=r.status if r else 500,
detail=f'Ollama: {e}' if str(e) else 'Open WebUI: Server Connection Error',
detail=f'Ollama: {e}' if str(e) else ERROR_MESSAGES.SERVER_CONNECTION_ERROR,
)
finally:
if not streaming:
@@ -251,7 +251,7 @@ async def verify_connection(form_data: ConnectionVerificationForm, user=Depends(
return data
except aiohttp.ClientError as e:
log.exception(f'Client error: {str(e)}')
raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR)
except Exception as e:
log.exception(f'Unexpected error: {e}')
error_detail = f'Unexpected error: {str(e)}'
@@ -428,7 +428,7 @@ async def get_filtered_models(models, user, db=None):
@router.get('/api/tags/{url_idx}')
async def get_ollama_tags(request: Request, url_idx: Optional[int] = None, user=Depends(get_verified_user)):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
models = []
@@ -621,7 +621,7 @@ async def pull_model(
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
form_data = form_data.model_dump(exclude_none=True)
form_data['model'] = form_data.get('model', form_data.get('name'))
@@ -656,7 +656,7 @@ async def push_model(
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
if url_idx is None:
await get_all_models(request, user=user)
@@ -699,7 +699,7 @@ async def create_model(
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
log.debug(f'form_data: {form_data}')
url = request.app.state.config.OLLAMA_BASE_URLS[url_idx]
@@ -727,7 +727,7 @@ async def copy_model(
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
if url_idx is None:
await get_all_models(request, user=user)
@@ -762,7 +762,7 @@ async def delete_model(
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
form_data = form_data.model_dump(exclude_none=True)
form_data['model'] = form_data.get('model', form_data.get('name'))
@@ -797,7 +797,7 @@ async def delete_model(
@router.post('/api/show')
async def show_model_info(request: Request, form_data: ModelNameForm, user=Depends(get_verified_user)):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
form_data = form_data.model_dump(exclude_none=True)
form_data['model'] = form_data.get('model', form_data.get('name'))
@@ -850,7 +850,7 @@ async def embed(
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
log.info(f'generate_ollama_batch_embeddings {form_data}')
@@ -909,7 +909,7 @@ async def embeddings(
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
log.info(f'generate_ollama_embeddings {form_data}')
@@ -976,7 +976,7 @@ async def generate_completion(
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
# Enforce per-model access control
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
@@ -1067,7 +1067,7 @@ async def generate_chat_completion(
bypass_system_prompt: bool = False,
):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
@@ -1313,7 +1313,7 @@ async def generate_anthropic_messages(
See https://docs.ollama.com/api/anthropic-compatibility
"""
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
payload = {**form_data}
model_id = payload.get('model', '')
@@ -1371,7 +1371,7 @@ async def generate_responses(
See https://ollama.com/blog/responses-api
"""
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
payload = form_data.model_dump()
model_id = form_data.model
@@ -1396,13 +1396,13 @@ async def generate_responses(
):
raise HTTPException(
status_code=403,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
else:
if user.role != 'admin':
raise HTTPException(
status_code=403,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
+7 -7
View File
@@ -691,7 +691,7 @@ async def verify_connection(
elif is_anthropic_url(url):
result = await get_anthropic_models(url, key)
if result is None:
raise HTTPException(status_code=500, detail='Failed to connect to Anthropic API')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR)
if 'error' in result:
raise HTTPException(status_code=500, detail=result['error'])
return result
@@ -718,10 +718,10 @@ async def verify_connection(
except aiohttp.ClientError as e:
# ClientError covers all aiohttp requests issues
log.exception(f'Client error: {str(e)}')
raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR)
except Exception as e:
log.exception(f'Unexpected error: {e}')
raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR)
def get_azure_allowed_params(api_version: str) -> set[str]:
@@ -1083,7 +1083,7 @@ async def generate_chat_completion(
else:
raise HTTPException(
status_code=404,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Get the API config for the model
@@ -1243,7 +1243,7 @@ async def generate_chat_completion(
raise HTTPException(
status_code=r.status if r else 500,
detail='Open WebUI: Server Connection Error',
detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR,
)
finally:
if not streaming:
@@ -1321,7 +1321,7 @@ async def embeddings(request: Request, form_data: dict, user):
log.exception(e)
raise HTTPException(
status_code=r.status if r else 500,
detail='Open WebUI: Server Connection Error',
detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR,
)
finally:
if not streaming:
@@ -1445,7 +1445,7 @@ async def responses(
log.exception(e)
raise HTTPException(
status_code=r.status if r else 500,
detail='Open WebUI: Server Connection Error',
detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR,
)
finally:
if not streaming:
+3 -3
View File
@@ -324,7 +324,7 @@ async def update_prompt_by_id(
if existing_prompt and existing_prompt.id != prompt.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Command '/{form_data.command}' is already in use by another prompt",
detail=ERROR_MESSAGES.COMMAND_TAKEN,
)
form_data.access_grants = await filter_allowed_access_grants(
@@ -389,7 +389,7 @@ async def update_prompt_metadata(
if existing_prompt and existing_prompt.id != prompt.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Command '/{form_data.command}' is already in use",
detail=ERROR_MESSAGES.COMMAND_TAKEN,
)
updated_prompt = await Prompts.update_prompt_metadata(
@@ -751,7 +751,7 @@ async def get_prompt_diff(
if not diff:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='One or both history entries not found',
detail=ERROR_MESSAGES.NOT_FOUND,
)
return diff
+13 -13
View File
@@ -18,7 +18,7 @@ from open_webui.utils.task import (
moa_response_generation_template,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.constants import TASKS
from open_webui.constants import ERROR_MESSAGES, TASKS
from open_webui.routers.pipelines import process_pipeline_inlet_filter
@@ -168,7 +168,7 @@ async def generate_title(request: Request, form_data: dict, user=Depends(get_ver
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -245,7 +245,7 @@ async def generate_follow_ups(request: Request, form_data: dict, user=Depends(ge
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -313,7 +313,7 @@ async def generate_chat_tags(request: Request, form_data: dict, user=Depends(get
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -375,7 +375,7 @@ async def generate_image_prompt(request: Request, form_data: dict, user=Depends(
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -431,13 +431,13 @@ async def generate_queries(request: Request, form_data: dict, user=Depends(get_v
if not request.app.state.config.ENABLE_SEARCH_QUERY_GENERATION:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'Search query generation is disabled',
detail=ERROR_MESSAGES.FEATURE_DISABLED('Search query generation'),
)
elif type == 'retrieval':
if not request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'Query generation is disabled',
detail=ERROR_MESSAGES.FEATURE_DISABLED('Query generation'),
)
if getattr(request.state, 'cached_queries', None):
@@ -455,7 +455,7 @@ async def generate_queries(request: Request, form_data: dict, user=Depends(get_v
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -508,7 +508,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend
if not request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'Autocompletion generation is disabled',
detail=ERROR_MESSAGES.FEATURE_DISABLED('Autocompletion generation'),
)
type = form_data.get('type')
@@ -519,7 +519,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend
if len(prompt) > request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'Input prompt exceeds maximum length of {request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}',
detail=ERROR_MESSAGES.INPUT_TOO_LONG(request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH),
)
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
@@ -533,7 +533,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -595,7 +595,7 @@ async def generate_emoji(request: Request, form_data: dict, user=Depends(get_ver
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
# Check if the user has a custom task model
@@ -661,7 +661,7 @@ async def generate_moa_response(request: Request, form_data: dict, user=Depends(
if model_id not in models:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Model not found',
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(),
)
template = DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE
+1 -1
View File
@@ -285,7 +285,7 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe
'content': data,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f'Error importing tool: {e}')
raise HTTPException(status_code=500, detail=ERROR_MESSAGES.DEFAULT(e))
############################
+2 -2
View File
@@ -45,7 +45,7 @@ async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_v
if not request.app.state.config.ENABLE_CODE_EXECUTION:
raise HTTPException(
status_code=403,
detail='Code execution is disabled',
detail=ERROR_MESSAGES.FEATURE_DISABLED('Code execution'),
)
if request.app.state.config.CODE_EXECUTION_ENGINE == 'jupyter':
@@ -69,7 +69,7 @@ async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_v
else:
raise HTTPException(
status_code=400,
detail='Code execution engine not supported',
detail=ERROR_MESSAGES.DEFAULT('Code execution engine not supported'),
)
+3 -2
View File
@@ -22,6 +22,7 @@ from dateutil.rrule import rrulestr
from fastapi import Request
from starlette.datastructures import Headers
from open_webui.constants import ERROR_MESSAGES
from open_webui.models.automations import Automations, AutomationRuns, AutomationModel
from open_webui.models.chats import ChatForm, Chats
from open_webui.models.users import Users
@@ -59,9 +60,9 @@ def validate_rrule(s: str) -> None:
try:
rule = _parse_rule(s)
except Exception as e:
raise ValueError(f'Invalid RRULE: {e}')
raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e))
if rule.after(datetime.now()) is None:
raise ValueError('RRULE has no future occurrences')
raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS)
def next_run_ns(s: str, tz: str = None) -> Optional[int]: