refac: modernize type annotations (PEP 604 / PEP 585)

This commit is contained in:
Timothy Jaeryang Baek committed 2026-05-12 17:10:15 +09:00
1 parent a59c967d7e
commit 6d0295588e
197 files changed
+3265 -3488

No files matched your search

+8 -8
View File
@@ -1,17 +1,17 @@
from typing import Optional
from datetime import datetime, timedelta
from collections import defaultdict
import logging
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Optional
from open_webui.models.chat_messages import ChatMessages, ChatMessageModel
from fastapi import APIRouter, Depends, Query
from open_webui.internal.db import get_async_session
from open_webui.models.chat_messages import ChatMessageModel, ChatMessages
from open_webui.models.chats import Chats
from open_webui.models.feedbacks import Feedbacks
from open_webui.models.groups import Groups
from open_webui.models.users import Users
from open_webui.models.feedbacks import Feedbacks
from open_webui.utils.auth import get_admin_user
from open_webui.internal.db import get_async_session
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+23 -27
View File
@@ -1,24 +1,22 @@
import asyncio
import io
import base64
import hashlib
import html
import io
import json
import logging
import mimetypes
import os
import uuid
import html
import base64
from pydub import AudioSegment
from pydub.silence import split_on_silence
from concurrent.futures import ThreadPoolExecutor
from fnmatch import fnmatch
from typing import Optional
from fnmatch import fnmatch
import aiohttp
import aiofiles
import aiohttp
import requests
import mimetypes
from fastapi import (
APIRouter,
Depends,
FastAPI,
File,
@@ -27,38 +25,36 @@ from fastapi import (
Request,
UploadFile,
status,
APIRouter,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from open_webui.utils.misc import strict_match_mime_type
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission
from open_webui.utils.headers import include_user_info_headers
from open_webui.config import (
WHISPER_MODEL_AUTO_UPDATE,
WHISPER_COMPUTE_TYPE,
WHISPER_MODEL_DIR,
WHISPER_VAD_FILTER,
CACHE_DIR,
WHISPER_LANGUAGE,
WHISPER_MULTILINGUAL,
ELEVENLABS_API_BASE_URL,
WHISPER_COMPUTE_TYPE,
WHISPER_LANGUAGE,
WHISPER_MODEL_AUTO_UPDATE,
WHISPER_MODEL_DIR,
WHISPER_MULTILINGUAL,
WHISPER_VAD_FILTER,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
ENV,
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
BYPASS_PYDUB_PREPROCESSING,
DEVICE_TYPE,
ENABLE_FORWARD_USER_INFO_HEADERS,
ENV,
)
from open_webui.utils.access_control import has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import include_user_info_headers
from open_webui.utils.misc import strict_match_mime_type
from pydantic import BaseModel
from pydub import AudioSegment
from pydub.silence import split_on_silence
router = APIRouter()
@@ -364,8 +360,8 @@ async def update_audio_config(request: Request, form_data: AudioConfigUpdateForm
def load_speech_pipeline(request):
from transformers import pipeline
from datasets import load_dataset
from transformers import pipeline
if request.app.state.speech_synthesiser is None:
request.app.state.speech_synthesiser = pipeline('text-to-speech', 'microsoft/speecht5_tts')
@@ -592,8 +588,8 @@ async def speech(request: Request, user=Depends(get_verified_user)):
log.exception(e)
raise HTTPException(status_code=400, detail='Invalid JSON payload')
import torch
import soundfile as sf
import torch
load_speech_pipeline(request)
+72 -78
View File
@@ -1,92 +1,86 @@
from __future__ import annotations
import asyncio
import re
import uuid
import time
import datetime
import logging
from aiohttp import ClientSession
import re
import time
import urllib
import uuid
from ssl import CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS
from typing import List, Optional
from aiohttp import ClientSession
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse, RedirectResponse, Response
from ldap3 import NONE, Connection, Server, Tls
from ldap3.utils.conv import escape_filter_chars
from open_webui.config import (
ENABLE_LDAP,
ENABLE_OAUTH_SIGNUP,
ENABLE_PASSWORD_AUTH,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
OAUTH_PROVIDERS,
OPENID_END_SESSION_ENDPOINT,
OPENID_PROVIDER_URL,
)
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from open_webui.env import (
AIOHTTP_CLIENT_SESSION_SSL,
ENABLE_INITIAL_ADMIN_SIGNUP,
ENABLE_OAUTH_TOKEN_EXCHANGE,
WEBUI_AUTH,
WEBUI_AUTH_COOKIE_SAME_SITE,
WEBUI_AUTH_COOKIE_SECURE,
WEBUI_AUTH_SIGNOUT_REDIRECT_URL,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_GROUPS_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
WEBUI_AUTH_TRUSTED_ROLE_HEADER,
)
from open_webui.internal.db import get_async_session
from open_webui.models.auths import (
AddUserForm,
ApiKey,
Auths,
Token,
LdapForm,
SigninForm,
SigninResponse,
SignupForm,
Token,
UpdatePasswordForm,
)
from open_webui.models.users import (
UserModel,
UserProfileImageResponse,
Users,
UpdateProfileForm,
UserStatus,
)
from open_webui.models.groups import Groups
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from open_webui.env import (
WEBUI_AUTH,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
WEBUI_AUTH_TRUSTED_GROUPS_HEADER,
WEBUI_AUTH_TRUSTED_ROLE_HEADER,
WEBUI_AUTH_COOKIE_SAME_SITE,
WEBUI_AUTH_COOKIE_SECURE,
WEBUI_AUTH_SIGNOUT_REDIRECT_URL,
ENABLE_INITIAL_ADMIN_SIGNUP,
ENABLE_OAUTH_TOKEN_EXCHANGE,
AIOHTTP_CLIENT_SESSION_SSL,
from open_webui.models.users import (
UpdateProfileForm,
UserModel,
UserProfileImageResponse,
Users,
UserStatus,
)
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import RedirectResponse, Response, JSONResponse
from open_webui.config import (
OPENID_PROVIDER_URL,
OPENID_END_SESSION_ENDPOINT,
ENABLE_OAUTH_SIGNUP,
ENABLE_LDAP,
ENABLE_PASSWORD_AUTH,
OAUTH_PROVIDERS,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
)
from open_webui.utils.oauth import auth_manager_config
from pydantic import BaseModel
from open_webui.utils.misc import parse_duration, validate_email_format
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.utils.auth import (
validate_password,
verify_password,
decode_token,
invalidate_token,
create_api_key,
create_token,
decode_token,
get_admin_user,
get_verified_user,
get_current_user,
get_password_hash,
get_http_authorization_cred,
get_password_hash,
get_verified_user,
invalidate_token,
validate_password,
verify_password,
)
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.webhook import post_webhook
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.utils.groups import apply_default_group_assignment
from open_webui.utils.redis import get_redis_client
from open_webui.utils.misc import parse_duration, validate_email_format
from open_webui.utils.oauth import auth_manager_config
from open_webui.utils.rate_limit import RateLimiter
from typing import Optional, List
from ssl import CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS
from ldap3 import Server, Connection, NONE, Tls
from ldap3.utils.conv import escape_filter_chars
from open_webui.utils.redis import get_redis_client
from open_webui.utils.webhook import post_webhook
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
router = APIRouter()
@@ -155,14 +149,14 @@ async def create_session_response(
class SessionUserResponse(Token, UserProfileImageResponse):
expires_at: Optional[int] = None
permissions: Optional[dict] = None
expires_at: int | None = None
permissions: dict | None = None
class SessionUserInfoResponse(SessionUserResponse, UserStatus):
bio: Optional[str] = None
gender: Optional[str] = None
date_of_birth: Optional[datetime.date] = None
bio: str | None = None
gender: str | None = None
date_of_birth: datetime.date | None = None
@router.get('/', response_model=SessionUserInfoResponse)
@@ -1031,7 +1025,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
class AdminConfig(BaseModel):
SHOW_ADMIN_DETAILS: bool
ADMIN_EMAIL: Optional[str] = None
ADMIN_EMAIL: str | None = None
WEBUI_URL: str
ENABLE_SIGNUP: bool
ENABLE_API_KEYS: bool
@@ -1043,9 +1037,9 @@ class AdminConfig(BaseModel):
ENABLE_COMMUNITY_SHARING: bool
ENABLE_MESSAGE_RATING: bool
ENABLE_FOLDERS: bool
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
AUTOMATION_MAX_COUNT: Optional[int | str] = None
AUTOMATION_MIN_INTERVAL: Optional[int | str] = None
FOLDER_MAX_FILE_COUNT: int | str | None = None
AUTOMATION_MAX_COUNT: int | str | None = None
AUTOMATION_MIN_INTERVAL: int | str | None = None
ENABLE_AUTOMATIONS: bool
ENABLE_CHANNELS: bool
ENABLE_CALENDAR: bool
@@ -1053,9 +1047,9 @@ class AdminConfig(BaseModel):
ENABLE_NOTES: bool
ENABLE_USER_WEBHOOKS: bool
ENABLE_USER_STATUS: bool
PENDING_USER_OVERLAY_TITLE: Optional[str] = None
PENDING_USER_OVERLAY_CONTENT: Optional[str] = None
RESPONSE_WATERMARK: Optional[str] = None
PENDING_USER_OVERLAY_TITLE: str | None = None
PENDING_USER_OVERLAY_CONTENT: str | None = None
RESPONSE_WATERMARK: str | None = None
@router.post('/admin/config')
@@ -1140,7 +1134,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep
class LdapServerConfig(BaseModel):
label: str
host: str
port: Optional[int] = None
port: int | None = None
attribute_for_mail: str = 'mail'
attribute_for_username: str = 'uid'
app_dn: str
@@ -1148,9 +1142,9 @@ class LdapServerConfig(BaseModel):
search_base: str
search_filters: str = ''
use_tls: bool = True
certificate_path: Optional[str] = None
certificate_path: str | None = None
validate_cert: bool = True
ciphers: Optional[str] = 'ALL'
ciphers: str | None = 'ALL'
@router.get('/admin/config/ldap/server', response_model=LdapServerConfig)
@@ -1223,7 +1217,7 @@ async def get_ldap_config(request: Request, user=Depends(get_admin_user)):
class LdapConfigForm(BaseModel):
enable_ldap: Optional[bool] = None
enable_ldap: bool | None = None
@router.post('/admin/config/ldap')
+15 -16
View File
@@ -1,30 +1,29 @@
import asyncio
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.automations import (
Automations,
AutomationRuns,
AutomationForm,
AutomationListResponse,
AutomationModel,
AutomationResponse,
AutomationRunModel,
AutomationListResponse,
AutomationRuns,
Automations,
)
from open_webui.utils.automations import (
validate_rrule,
next_run_ns,
next_n_runs_ns,
execute_automation,
rrule_interval_seconds,
)
from open_webui.utils.auth import get_verified_user, get_admin_user
from open_webui.utils.access_control import has_permission
from open_webui.internal.db import get_async_session
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.automations import (
execute_automation,
next_n_runs_ns,
next_run_ns,
rrule_interval_seconds,
validate_rrule,
)
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+12 -13
View File
@@ -3,28 +3,27 @@ import time
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.constants import ERROR_MESSAGES
from open_webui.models.access_grants import AccessGrants
from open_webui.models.calendar import (
Calendars,
CalendarEvents,
CalendarEventAttendees,
CalendarForm,
CalendarUpdateForm,
CalendarEventForm,
CalendarEventUpdateForm,
CalendarModel,
CalendarEventModel,
CalendarEventUserResponse,
CalendarEventListResponse,
CalendarEventModel,
CalendarEvents,
CalendarEventUpdateForm,
CalendarEventUserResponse,
CalendarForm,
CalendarModel,
Calendars,
CalendarUpdateForm,
RSVPForm,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.users import UserModel
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.utils.calendar import expand_recurring_event
from open_webui.constants import ERROR_MESSAGES
log = logging.getLogger(__name__)
@@ -188,7 +187,7 @@ async def get_events(
cal_id_list is None or SCHEDULED_TASKS_CALENDAR_ID in cal_id_list
):
try:
from open_webui.models.automations import Automations, AutomationRuns
from open_webui.models.automations import AutomationRuns, Automations
# Future runs: expand RRULEs for active automations only
active_automations = await Automations.get_active_by_user(user.id)
+37 -48
View File
@@ -1,69 +1,58 @@
import json
import logging
import base64
import io
import json
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
from fastapi.responses import Response, StreamingResponse, FileResponse
from pydantic import BaseModel
from pydantic import field_validator
from open_webui.socket.main import (
emit_to_users,
enter_room_for_users,
sio,
get_user_ids_from_room,
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, status
from fastapi.responses import FileResponse, Response, StreamingResponse
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import STATIC_DIR
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant, has_public_write_access_grant
from open_webui.models.channels import (
ChannelForm,
ChannelModel,
ChannelResponse,
Channels,
ChannelWebhookForm,
ChannelWebhookModel,
CreateChannelForm,
)
from open_webui.models.groups import Groups
from open_webui.models.messages import (
MessageForm,
MessageModel,
MessageResponse,
Messages,
MessageWithReactionsResponse,
)
from open_webui.models.users import (
UserIdNameResponse,
UserIdNameStatusResponse,
UserListResponse,
UserModelResponse,
Users,
UserModel,
UserModelResponse,
UserNameResponse,
Users,
)
from open_webui.models.groups import Groups
from open_webui.models.channels import (
Channels,
ChannelModel,
ChannelForm,
ChannelResponse,
CreateChannelForm,
ChannelWebhookModel,
ChannelWebhookForm,
from open_webui.socket.main import (
emit_to_users,
enter_room_for_users,
get_user_ids_from_room,
sio,
)
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant, has_public_write_access_grant
from open_webui.models.messages import (
Messages,
MessageModel,
MessageResponse,
MessageWithReactionsResponse,
MessageForm,
)
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.channels import extract_mentions, replace_mentions
from open_webui.utils.files import get_image_base64_from_file_id
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import STATIC_DIR
from open_webui.utils.models import (
get_all_models,
get_filtered_models,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.utils.webhook import post_webhook
from open_webui.utils.channels import extract_mentions, replace_mentions
from open_webui.internal.db import get_async_session
from pydantic import BaseModel, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -980,9 +969,9 @@ async def model_response_handler(request, channel, message, user, db=None):
# Resolve model config (same helpers automations use)
from open_webui.utils.automations import (
_resolve_model_tool_ids,
_resolve_model_features,
_resolve_model_filter_ids,
_resolve_model_tool_ids,
)
tool_ids = _resolve_model_tool_ids(request.app, model_id)
+73 -75
View File
@@ -1,43 +1,41 @@
from __future__ import annotations
import asyncio
import json
import logging
from typing import Optional
from uuid import uuid4
from sqlalchemy.ext.asyncio import AsyncSession
import asyncio
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import StreamingResponse
from open_webui.utils.misc import get_message_list
from open_webui.utils.middleware import serialize_output
from open_webui.socket.main import get_event_emitter
from open_webui.models.chats import (
ChatForm,
ChatImportForm,
ChatUsageStatsListResponse,
ChatsImportForm,
ChatResponse,
Chats,
ChatTitleIdResponse,
ChatStatsExport,
AggregateChatStats,
ChatBody,
ChatHistoryStats,
MessageStats,
)
from open_webui.models.shared_chats import SharedChats, SharedChatResponse
from open_webui.models.access_grants import AccessGrants
from open_webui.models.tags import TagModel, Tags
from open_webui.models.folders import Folders
from open_webui.internal.db import get_async_session
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.chats import (
AggregateChatStats,
ChatBody,
ChatForm,
ChatHistoryStats,
ChatImportForm,
ChatResponse,
Chats,
ChatsImportForm,
ChatStatsExport,
ChatTitleIdResponse,
ChatUsageStatsListResponse,
MessageStats,
)
from open_webui.models.folders import Folders
from open_webui.models.shared_chats import SharedChatResponse, SharedChats
from open_webui.models.tags import TagModel, Tags
from open_webui.socket.main import get_event_emitter
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.utils.middleware import serialize_output
from open_webui.utils.misc import get_message_list
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -54,9 +52,9 @@ router = APIRouter()
@router.get('/list', response_model=list[ChatTitleIdResponse])
async def get_session_user_chat_list(
user=Depends(get_verified_user),
page: Optional[int] = None,
include_pinned: Optional[bool] = False,
include_folders: Optional[bool] = False,
page: int | None = None,
include_pinned: bool | None = False,
include_folders: bool | None = False,
db: AsyncSession = Depends(get_async_session),
):
try:
@@ -92,8 +90,8 @@ async def get_session_user_chat_list(
@router.get('/stats/usage', response_model=ChatUsageStatsListResponse)
async def get_session_user_chat_usage_stats(
items_per_page: Optional[int] = 50,
page: Optional[int] = 1,
items_per_page: int | None = 50,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -210,7 +208,7 @@ class ChatStatsExportList(BaseModel):
page: int
def _process_chat_for_export(chat) -> Optional[ChatStatsExport]:
def _process_chat_for_export(chat) -> ChatStatsExport | None:
try:
def get_message_content_length(message):
@@ -395,8 +393,8 @@ async def generate_chat_stats_jsonl_generator(user_id, filter):
@router.get('/stats/export', response_model=ChatStatsExportList)
async def export_chat_stats(
request: Request,
updated_at: Optional[int] = None,
page: Optional[int] = 1,
updated_at: int | None = None,
page: int | None = 1,
stream: bool = False,
user=Depends(get_verified_user),
):
@@ -438,7 +436,7 @@ async def export_chat_stats(
############################
@router.get('/stats/export/{chat_id}', response_model=Optional[ChatStatsExport])
@router.get('/stats/export/{chat_id}', response_model=ChatStatsExport | None)
async def export_single_chat_stats(
request: Request,
chat_id: str,
@@ -516,10 +514,10 @@ async def delete_all_user_chats(
@router.get('/list/user/{user_id}', response_model=list[ChatTitleIdResponse])
async def get_user_chat_list_by_user_id(
user_id: str,
page: Optional[int] = None,
query: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: int | None = None,
query: str | None = None,
order_by: str | None = None,
direction: str | None = None,
user=Depends(get_admin_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -553,7 +551,7 @@ async def get_user_chat_list_by_user_id(
############################
@router.post('/new', response_model=Optional[ChatResponse])
@router.post('/new', response_model=ChatResponse | None)
async def create_new_chat(
form_data: ChatForm,
user=Depends(get_verified_user),
@@ -594,7 +592,7 @@ async def import_chats(
@router.get('/search', response_model=list[ChatTitleIdResponse])
async def search_user_chats(
text: str,
page: Optional[int] = None,
page: int | None = None,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -644,7 +642,7 @@ async def get_chats_by_folder_id(
@router.get('/folder/{folder_id}/list')
async def get_chat_list_by_folder_id(
folder_id: str,
page: Optional[int] = 1,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -766,10 +764,10 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: AsyncSessio
@router.get('/archived', response_model=list[ChatTitleIdResponse])
async def get_archived_session_user_chat_list(
page: Optional[int] = None,
query: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: int | None = None,
query: str | None = None,
order_by: str | None = None,
direction: str | None = None,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -823,10 +821,10 @@ async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession
@router.get('/shared', response_model=list[SharedChatResponse])
async def get_shared_session_user_chat_list(
page: Optional[int] = None,
query: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: int | None = None,
query: str | None = None,
order_by: str | None = None,
direction: str | None = None,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -858,7 +856,7 @@ async def get_shared_session_user_chat_list(
############################
@router.get('/share/{share_id}', response_model=Optional[ChatResponse])
@router.get('/share/{share_id}', response_model=ChatResponse | None)
async def get_shared_chat_by_id(
share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -904,8 +902,8 @@ class TagForm(BaseModel):
class TagFilterForm(TagForm):
skip: Optional[int] = 0
limit: Optional[int] = 50
skip: int | None = 0
limit: int | None = 50
@router.post('/tags', response_model=list[ChatTitleIdResponse])
@@ -928,7 +926,7 @@ async def get_user_chat_list_by_tag_name(
############################
@router.get('/{id}', response_model=Optional[ChatResponse])
@router.get('/{id}', response_model=ChatResponse | None)
async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
@@ -958,7 +956,7 @@ async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSess
############################
@router.post('/{id}', response_model=Optional[ChatResponse])
@router.post('/{id}', response_model=ChatResponse | None)
async def update_chat_by_id(
id: str,
form_data: ChatForm,
@@ -992,7 +990,7 @@ class MessageForm(BaseModel):
content: str
@router.post('/{id}/messages/{message_id}', response_model=Optional[ChatResponse])
@router.post('/{id}/messages/{message_id}', response_model=ChatResponse | None)
async def update_chat_message_by_id(
id: str,
message_id: str,
@@ -1054,7 +1052,7 @@ class EventForm(BaseModel):
data: dict
@router.post('/{id}/messages/{message_id}/event', response_model=Optional[bool])
@router.post('/{id}/messages/{message_id}/event', response_model=bool | None)
async def send_chat_message_event_by_id(
id: str,
message_id: str,
@@ -1142,7 +1140,7 @@ async def delete_chat_by_id(
############################
@router.get('/{id}/pinned', response_model=Optional[bool])
@router.get('/{id}/pinned', response_model=bool | None)
async def get_pinned_status_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -1158,7 +1156,7 @@ async def get_pinned_status_by_id(
############################
@router.post('/{id}/pin', response_model=Optional[ChatResponse])
@router.post('/{id}/pin', response_model=ChatResponse | None)
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
@@ -1174,10 +1172,10 @@ async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSess
class CloneForm(BaseModel):
title: Optional[str] = None
title: str | None = None
@router.post('/{id}/clone', response_model=Optional[ChatResponse])
@router.post('/{id}/clone', response_model=ChatResponse | None)
async def clone_chat_by_id(
form_data: CloneForm,
id: str,
@@ -1225,7 +1223,7 @@ async def clone_chat_by_id(
############################
@router.post('/{id}/clone/shared', response_model=Optional[ChatResponse])
@router.post('/{id}/clone/shared', response_model=ChatResponse | None)
async def clone_shared_chat_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -1294,7 +1292,7 @@ async def clone_shared_chat_by_id(
############################
@router.post('/{id}/archive', response_model=Optional[ChatResponse])
@router.post('/{id}/archive', response_model=ChatResponse | None)
async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
@@ -1318,7 +1316,7 @@ async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: Async
############################
@router.post('/{id}/share', response_model=Optional[ChatResponse])
@router.post('/{id}/share', response_model=ChatResponse | None)
async def share_chat_by_id(
request: Request,
id: str,
@@ -1372,7 +1370,7 @@ async def share_chat_by_id(
############################
@router.delete('/{id}/share', response_model=Optional[bool])
@router.delete('/{id}/share', response_model=bool | None)
async def delete_shared_chat_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -1404,7 +1402,7 @@ class ChatAccessGrantsForm(BaseModel):
access_grants: list[dict]
@router.post('/shared/{id}/access/update', response_model=Optional[ChatResponse])
@router.post('/shared/{id}/access/update', response_model=ChatResponse | None)
async def update_shared_chat_access_by_id(
request: Request,
id: str,
@@ -1474,10 +1472,10 @@ async def get_shared_chat_access_by_id(
class ChatFolderIdForm(BaseModel):
folder_id: Optional[str] = None
folder_id: str | None = None
@router.post('/{id}/folder', response_model=Optional[ChatResponse])
@router.post('/{id}/folder', response_model=ChatResponse | None)
async def update_chat_folder_id_by_id(
id: str,
form_data: ChatFolderIdForm,
@@ -1571,7 +1569,7 @@ async def delete_tag_by_id_and_tag_name(
############################
@router.delete('/{id}/tags/all', response_model=Optional[bool])
@router.delete('/{id}/tags/all', response_model=bool | None)
async def delete_all_tags_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
+56 -59
View File
@@ -1,37 +1,34 @@
import logging
import copy
from fastapi import APIRouter, Depends, Request, HTTPException
from pydantic import BaseModel, ConfigDict
import aiohttp
from __future__ import annotations
import copy
import logging
from typing import Optional
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request
from mcp.shared.auth import OAuthMetadata
from open_webui.config import BannerModel, async_save_config, get_config, save_config
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import get_custom_headers
from open_webui.config import get_config, save_config, async_save_config
from open_webui.config import BannerModel
from open_webui.utils.tools import (
get_tool_server_data,
get_tool_server_url,
set_tool_servers,
set_terminal_servers,
)
from open_webui.utils.mcp.client import MCPClient
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.utils.oauth import (
OAuthClientInformationFull,
decrypt_data,
encrypt_data,
get_discovery_urls,
get_oauth_client_info_with_dynamic_client_registration,
get_oauth_client_info_with_static_credentials,
encrypt_data,
decrypt_data,
resolve_oauth_client_info,
OAuthClientInformationFull,
)
from mcp.shared.auth import OAuthMetadata
from open_webui.utils.tools import (
get_tool_server_data,
get_tool_server_url,
set_terminal_servers,
set_tool_servers,
)
from pydantic import BaseModel, ConfigDict
router = APIRouter()
@@ -102,16 +99,16 @@ async def set_connections_config(
class OAuthClientRegistrationForm(BaseModel):
url: str
client_id: str
client_name: Optional[str] = None
client_secret: Optional[str] = None
oauth_server_url: Optional[str] = None
client_name: str | None = None
client_secret: str | None = None
oauth_server_url: str | None = None
@router.post('/oauth/clients/register')
async def register_oauth_client(
request: Request,
form_data: OAuthClientRegistrationForm,
type: Optional[str] = None,
type: str | None = None,
user=Depends(get_admin_user),
):
try:
@@ -154,12 +151,12 @@ async def register_oauth_client(
class ToolServerConnection(BaseModel):
url: str
path: str
type: Optional[str] = 'openapi' # openapi, mcp
auth_type: Optional[str]
headers: Optional[dict | str] = None
key: Optional[str]
config: Optional[dict]
info: Optional[dict] = None
type: str | None = 'openapi' # openapi, mcp
auth_type: str | None
headers: dict | str | None = None
key: str | None
config: dict | None
info: dict | None = None
model_config = ConfigDict(extra='allow')
@@ -225,23 +222,23 @@ async def set_tool_servers_config(
class TerminalServerConnection(BaseModel):
id: Optional[str] = ''
name: Optional[str] = ''
id: str | None = ''
name: str | None = ''
enabled: Optional[bool] = True
enabled: bool | None = True
url: str
path: Optional[str] = '/openapi.json'
path: str | None = '/openapi.json'
key: Optional[str] = ''
auth_type: Optional[str] = 'bearer'
key: str | None = ''
auth_type: str | None = 'bearer'
config: Optional[dict] = None
config: dict | None = None
# Orchestrator policy fields
server_type: Optional[str] = None # "orchestrator", "terminal"
policy_id: Optional[str] = None
policy: Optional[dict] = None # cached policy data
server_type: str | None = None # "orchestrator", "terminal"
policy_id: str | None = None
policy: dict | None = None # cached policy data
model_config = ConfigDict(extra='allow')
@@ -325,8 +322,8 @@ async def verify_terminal_server_connection(
class TerminalServerPolicyForm(BaseModel):
url: str
key: Optional[str] = ''
auth_type: Optional[str] = 'bearer'
key: str | None = ''
auth_type: str | None = 'bearer'
policy_id: str
policy_data: dict
@@ -504,19 +501,19 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn
class CodeInterpreterConfigForm(BaseModel):
ENABLE_CODE_EXECUTION: bool
CODE_EXECUTION_ENGINE: str
CODE_EXECUTION_JUPYTER_URL: Optional[str]
CODE_EXECUTION_JUPYTER_AUTH: Optional[str]
CODE_EXECUTION_JUPYTER_AUTH_TOKEN: Optional[str]
CODE_EXECUTION_JUPYTER_AUTH_PASSWORD: Optional[str]
CODE_EXECUTION_JUPYTER_TIMEOUT: Optional[int]
CODE_EXECUTION_JUPYTER_URL: str | None
CODE_EXECUTION_JUPYTER_AUTH: str | None
CODE_EXECUTION_JUPYTER_AUTH_TOKEN: str | None
CODE_EXECUTION_JUPYTER_AUTH_PASSWORD: str | None
CODE_EXECUTION_JUPYTER_TIMEOUT: int | None
ENABLE_CODE_INTERPRETER: bool
CODE_INTERPRETER_ENGINE: str
CODE_INTERPRETER_PROMPT_TEMPLATE: Optional[str]
CODE_INTERPRETER_JUPYTER_URL: Optional[str]
CODE_INTERPRETER_JUPYTER_AUTH: Optional[str]
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN: Optional[str]
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD: Optional[str]
CODE_INTERPRETER_JUPYTER_TIMEOUT: Optional[int]
CODE_INTERPRETER_PROMPT_TEMPLATE: str | None
CODE_INTERPRETER_JUPYTER_URL: str | None
CODE_INTERPRETER_JUPYTER_AUTH: str | None
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN: str | None
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD: str | None
CODE_INTERPRETER_JUPYTER_TIMEOUT: int | None
@router.get('/code_execution', response_model=CodeInterpreterConfigForm)
@@ -588,11 +585,11 @@ async def set_code_execution_config(
# SetDefaultModels
############################
class ModelsConfigForm(BaseModel):
DEFAULT_MODELS: Optional[str]
DEFAULT_PINNED_MODELS: Optional[str]
MODEL_ORDER_LIST: Optional[list[str]]
DEFAULT_MODEL_METADATA: Optional[dict] = None
DEFAULT_MODEL_PARAMS: Optional[dict] = None
DEFAULT_MODELS: str | None
DEFAULT_PINNED_MODELS: str | None
MODEL_ORDER_LIST: list[str | None]
DEFAULT_MODEL_METADATA: dict | None = None
DEFAULT_MODEL_PARAMS: dict | None = None
@router.get('/models/defaults')
+10 -11
View File
@@ -1,26 +1,25 @@
from typing import Optional
import logging
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel
from typing import Optional
from open_webui.models.users import Users, UserModel
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.concurrency import run_in_threadpool
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.feedbacks import (
FeedbackForm,
FeedbackIdResponse,
FeedbackListResponse,
FeedbackModel,
FeedbackResponse,
FeedbackForm,
Feedbacks,
FeedbackUserResponse,
FeedbackListResponse,
LeaderboardFeedbackData,
ModelHistoryEntry,
ModelHistoryResponse,
Feedbacks,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.models.users import UserModel, Users
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.internal.db import get_async_session
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+13 -21
View File
@@ -1,34 +1,31 @@
import asyncio
import json
import logging
import os
import uuid
import json
from pathlib import Path
from typing import Optional
from urllib.parse import quote
import asyncio
from fastapi import (
BackgroundTasks,
APIRouter,
BackgroundTasks,
Depends,
File,
Form,
HTTPException,
Query,
Request,
UploadFile,
status,
Query,
)
from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session, get_async_db_context
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STORAGE_LOCAL_CACHE, STORAGE_PROVIDER, UPLOAD_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.internal.db import get_async_db_context, get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.channels import Channels
from open_webui.models.users import Users
from open_webui.models.chats import Chats
from open_webui.models.files import (
FileForm,
FileListResponse,
@@ -36,22 +33,17 @@ from open_webui.models.files import (
FileModelResponse,
Files,
)
from open_webui.models.chats import Chats
from open_webui.models.knowledge import Knowledges
from open_webui.models.groups import Groups
from open_webui.models.access_grants import AccessGrants
from open_webui.routers.retrieval import ProcessFileForm, process_file
from open_webui.models.knowledge import Knowledges
from open_webui.models.users import Users
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.routers.audio import transcribe
from open_webui.routers.retrieval import ProcessFileForm, process_file
from open_webui.storage.provider import Storage
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STORAGE_LOCAL_CACHE, STORAGE_PROVIDER, UPLOAD_DIR
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.misc import strict_match_mime_type
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+14 -21
View File
@@ -1,36 +1,29 @@
import logging
import mimetypes
import os
import shutil
import uuid
from pathlib import Path
from typing import Optional
from pydantic import BaseModel
import mimetypes
from open_webui.models.folders import (
FolderForm,
FolderUpdateForm,
FolderModel,
FolderNameIdResponse,
Folders,
)
from open_webui.models.chats import Chats
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status
from fastapi.responses import FileResponse, StreamingResponse
from open_webui.config import UPLOAD_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status, Request
from fastapi.responses import FileResponse, StreamingResponse
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.models.chats import Chats
from open_webui.models.folders import (
FolderForm,
FolderModel,
FolderNameIdResponse,
Folders,
FolderUpdateForm,
)
from open_webui.utils.access_control import has_permission
from open_webui.utils.access_control.files import get_accessible_folder_files
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+23 -22
View File
@@ -1,32 +1,33 @@
import os
import re
from __future__ import annotations
import logging
import aiohttp
import os
import re
from pathlib import Path
from typing import Optional
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.config import CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
from open_webui.internal.db import get_async_session
from open_webui.models.functions import (
FunctionForm,
FunctionModel,
FunctionResponse,
Functions,
FunctionUserResponse,
FunctionWithValvesModel,
Functions,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.plugin import (
get_function_module_from_cache,
load_function_module_by_id,
replace_imports,
get_function_module_from_cache,
resolve_valves_schema_options,
)
from open_webui.config import CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel, HttpUrl
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -91,7 +92,7 @@ def github_url_to_raw_url(url: str) -> str:
return url
@router.post('/load/url', response_model=Optional[dict])
@router.post('/load/url', response_model=dict | None)
async def load_function_from_url(request: Request, form_data: LoadUrlForm, user=Depends(get_admin_user)):
# NOTE: This is NOT a SSRF vulnerability:
# This endpoint is admin-only (see get_admin_user), meant for *trusted* internal use,
@@ -179,7 +180,7 @@ async def sync_functions(
############################
@router.post('/create', response_model=Optional[FunctionResponse])
@router.post('/create', response_model=FunctionResponse | None)
async def create_new_function(
request: Request,
form_data: FunctionForm,
@@ -240,7 +241,7 @@ async def create_new_function(
############################
@router.get('/id/{id}', response_model=Optional[FunctionModel])
@router.get('/id/{id}', response_model=FunctionModel | None)
async def get_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
@@ -258,7 +259,7 @@ async def get_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSes
############################
@router.post('/id/{id}/toggle', response_model=Optional[FunctionModel])
@router.post('/id/{id}/toggle', response_model=FunctionModel | None)
async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
@@ -283,7 +284,7 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: Async
############################
@router.post('/id/{id}/toggle/global', response_model=Optional[FunctionModel])
@router.post('/id/{id}/toggle/global', response_model=FunctionModel | None)
async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
@@ -308,7 +309,7 @@ async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: AsyncSe
############################
@router.post('/id/{id}/update', response_model=Optional[FunctionModel])
@router.post('/id/{id}/update', response_model=FunctionModel | None)
async def update_function_by_id(
request: Request,
id: str,
@@ -374,7 +375,7 @@ async def delete_function_by_id(
############################
@router.get('/id/{id}/valves', response_model=Optional[dict])
@router.get('/id/{id}/valves', response_model=dict | None)
async def get_function_valves_by_id(
id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)
):
@@ -400,7 +401,7 @@ async def get_function_valves_by_id(
############################
@router.get('/id/{id}/valves/spec', response_model=Optional[dict])
@router.get('/id/{id}/valves/spec', response_model=dict | None)
async def get_function_valves_spec_by_id(
request: Request,
id: str,
@@ -430,7 +431,7 @@ async def get_function_valves_spec_by_id(
############################
@router.post('/id/{id}/valves/update', response_model=Optional[dict])
@router.post('/id/{id}/valves/update', response_model=dict | None)
async def update_function_valves_by_id(
request: Request,
id: str,
@@ -476,7 +477,7 @@ async def update_function_valves_by_id(
############################
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
@router.get('/id/{id}/valves/user', response_model=dict | None)
async def get_function_user_valves_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -497,7 +498,7 @@ async def get_function_user_valves_by_id(
)
@router.get('/id/{id}/valves/user/spec', response_model=Optional[dict])
@router.get('/id/{id}/valves/user/spec', response_model=dict | None)
async def get_function_user_valves_spec_by_id(
request: Request,
id: str,
@@ -522,7 +523,7 @@ async def get_function_user_valves_spec_by_id(
)
@router.post('/id/{id}/valves/user/update', response_model=Optional[dict])
@router.post('/id/{id}/valves/user/update', response_model=dict | None)
async def update_function_user_valves_by_id(
request: Request,
id: str,
+12 -15
View File
@@ -1,26 +1,23 @@
import logging
import os
from pathlib import Path
from typing import Optional
import logging
from open_webui.models.users import Users, UserInfoResponse
from open_webui.models.groups import (
Groups,
GroupForm,
GroupInfoResponse,
GroupUpdateForm,
GroupResponse,
UserIdsForm,
)
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.config import CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.models.groups import (
GroupForm,
GroupInfoResponse,
GroupResponse,
Groups,
GroupUpdateForm,
UserIdsForm,
)
from open_webui.models.users import UserInfoResponse, Users
from open_webui.utils.auth import get_admin_user, get_verified_user
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+31 -32
View File
@@ -1,46 +1,45 @@
from __future__ import annotations
import asyncio
import base64
import uuid
import io
import json
import logging
import mimetypes
import re
import uuid
from pathlib import Path
from typing import Optional
from urllib.parse import quote
import aiohttp
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
from fastapi.responses import FileResponse
from open_webui.config import (
CACHE_DIR,
IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN,
IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.retrieval.web.utils import validate_url
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_ALLOW_REDIRECTS, ENABLE_FORWARD_USER_INFO_HEADERS
from open_webui.utils.session_pool import get_session
from open_webui.models.chats import Chats
from open_webui.routers.files import upload_file_handler, get_file_content_by_id
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission
from open_webui.utils.headers import include_user_info_headers
from open_webui.env import AIOHTTP_CLIENT_ALLOW_REDIRECTS, AIOHTTP_CLIENT_SESSION_SSL, ENABLE_FORWARD_USER_INFO_HEADERS
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.models.chats import Chats
from open_webui.retrieval.web.utils import validate_url
from open_webui.routers.files import get_file_content_by_id, upload_file_handler
from open_webui.utils.access_control import has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import include_user_info_headers
from open_webui.utils.images.comfyui import (
ComfyUICreateImageForm,
ComfyUIEditImageForm,
ComfyUIWorkflow,
comfyui_upload_image,
comfyui_create_image,
comfyui_edit_image,
comfyui_upload_image,
)
from open_webui.utils.session_pool import get_session
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -121,17 +120,17 @@ class ImagesConfig(BaseModel):
IMAGE_GENERATION_ENGINE: str
IMAGE_GENERATION_MODEL: str
IMAGE_SIZE: Optional[str]
IMAGE_STEPS: Optional[int]
IMAGE_SIZE: str | None
IMAGE_STEPS: int | None
IMAGES_OPENAI_API_BASE_URL: str
IMAGES_OPENAI_API_KEY: str
IMAGES_OPENAI_API_VERSION: str
IMAGES_OPENAI_API_PARAMS: Optional[dict | str]
IMAGES_OPENAI_API_PARAMS: dict | str | None
AUTOMATIC1111_BASE_URL: str
AUTOMATIC1111_API_AUTH: Optional[dict | str]
AUTOMATIC1111_PARAMS: Optional[dict | str]
AUTOMATIC1111_API_AUTH: dict | str | None
AUTOMATIC1111_PARAMS: dict | str | None
COMFYUI_BASE_URL: str
COMFYUI_API_KEY: str
@@ -145,7 +144,7 @@ class ImagesConfig(BaseModel):
ENABLE_IMAGE_EDIT: bool
IMAGE_EDIT_ENGINE: str
IMAGE_EDIT_MODEL: str
IMAGE_EDIT_SIZE: Optional[str]
IMAGE_EDIT_SIZE: str | None
IMAGES_EDIT_OPENAI_API_BASE_URL: str
IMAGES_EDIT_OPENAI_API_KEY: str
@@ -428,12 +427,12 @@ async def get_models(request: Request, user=Depends(get_verified_user)):
class CreateImageForm(BaseModel):
model: Optional[str] = None
model: str | None = None
prompt: str
size: Optional[str] = None
size: str | None = None
n: int = 1
steps: Optional[int] = None
negative_prompt: Optional[str] = None
steps: int | None = None
negative_prompt: str | None = None
GenerateImageForm = CreateImageForm # Alias for backward compatibility
@@ -528,7 +527,7 @@ async def generate_images(request: Request, form_data: CreateImageForm, user=Dep
async def image_generations(
request: Request,
form_data: CreateImageForm,
metadata: Optional[dict] = None,
metadata: dict | None = None,
user=None,
):
# if IMAGE_SIZE = 'auto', default WidthxHeight to the 512x512 default
@@ -776,18 +775,18 @@ async def image_generations(
class EditImageForm(BaseModel):
image: str | list[str] # base64-encoded image(s) or URL(s)
prompt: str
model: Optional[str] = None
size: Optional[str] = None
n: Optional[int] = None
negative_prompt: Optional[str] = None
background: Optional[str] = None
model: str | None = None
size: str | None = None
n: int | None = None
negative_prompt: str | None = None
background: str | None = None
@router.post('/edit')
async def image_edits(
request: Request,
form_data: EditImageForm,
metadata: Optional[dict] = None,
metadata: dict | None = None,
user=Depends(get_verified_user),
):
size = None
+41 -43
View File
@@ -1,42 +1,40 @@
from typing import List, Optional
from pydantic import BaseModel
from fastapi import APIRouter, Depends, HTTPException, status, Request, Query
from fastapi.responses import StreamingResponse
from __future__ import annotations
import logging
import io
import logging
import zipfile
from typing import List, Optional
from urllib.parse import quote
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.responses import StreamingResponse
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.files import FileMetadataResponse, FileModel, Files
from open_webui.models.groups import Groups
from open_webui.models.knowledge import (
KnowledgeFileListResponse,
Knowledges,
KnowledgeForm,
KnowledgeResponse,
Knowledges,
KnowledgeUserResponse,
)
from open_webui.models.files import Files, FileModel, FileMetadataResponse
from open_webui.models.models import ModelForm, Models
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.routers.retrieval import (
process_file,
ProcessFileForm,
process_files_batch,
BatchProcessFilesForm,
ProcessFileForm,
process_file,
process_files_batch,
)
from open_webui.storage.provider import Storage
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_verified_user, get_admin_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.access_control.files import has_access_to_file
from open_webui.models.access_grants import AccessGrants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.models.models import Models, ModelForm
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -100,7 +98,7 @@ async def remove_knowledge_base_metadata_embedding(knowledge_base_id: str) -> bo
class KnowledgeAccessResponse(KnowledgeUserResponse):
write_access: Optional[bool] = False
write_access: bool | None = False
class KnowledgeAccessListResponse(BaseModel):
@@ -110,7 +108,7 @@ class KnowledgeAccessListResponse(BaseModel):
@router.get('/', response_model=KnowledgeAccessListResponse)
async def get_knowledge_bases(
page: Optional[int] = 1,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -159,9 +157,9 @@ async def get_knowledge_bases(
@router.get('/search', response_model=KnowledgeAccessListResponse)
async def search_knowledge_bases(
query: Optional[str] = None,
view_option: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
view_option: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -215,8 +213,8 @@ async def search_knowledge_bases(
@router.get('/search/files', response_model=KnowledgeFileListResponse)
async def search_knowledge_files(
query: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -242,7 +240,7 @@ async def search_knowledge_files(
############################
@router.post('/create', response_model=Optional[KnowledgeResponse])
@router.post('/create', response_model=KnowledgeResponse | None)
async def create_new_knowledge(
request: Request,
form_data: KnowledgeForm,
@@ -380,11 +378,11 @@ async def reindex_knowledge_base_metadata_embeddings(
class KnowledgeFilesResponse(KnowledgeResponse):
files: Optional[list[FileMetadataResponse]] = None
write_access: Optional[bool] = False
files: list[FileMetadataResponse | None] = None
write_access: bool | None = False
@router.get('/{id}', response_model=Optional[KnowledgeFilesResponse])
@router.get('/{id}', response_model=KnowledgeFilesResponse | None)
async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
@@ -431,7 +429,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Asyn
############################
@router.post('/{id}/update', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/update', response_model=KnowledgeFilesResponse | None)
async def update_knowledge_by_id(
request: Request,
id: str,
@@ -501,7 +499,7 @@ class KnowledgeAccessGrantsForm(BaseModel):
access_grants: list[dict]
@router.post('/{id}/access/update', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/access/update', response_model=KnowledgeFilesResponse | None)
async def update_knowledge_access_by_id(
request: Request,
id: str,
@@ -556,11 +554,11 @@ async def update_knowledge_access_by_id(
@router.get('/{id}/files', response_model=KnowledgeFileListResponse)
async def get_knowledge_files_by_id(
id: str,
query: Optional[str] = None,
view_option: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
view_option: str | None = None,
order_by: str | None = None,
direction: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -614,7 +612,7 @@ class KnowledgeFileIdForm(BaseModel):
file_id: str
@router.post('/{id}/file/add', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/file/add', response_model=KnowledgeFilesResponse | None)
async def add_file_to_knowledge_by_id(
request: Request,
id: str,
@@ -695,7 +693,7 @@ async def add_file_to_knowledge_by_id(
)
@router.post('/{id}/file/update', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/file/update', response_model=KnowledgeFilesResponse | None)
async def update_file_from_knowledge_by_id(
request: Request,
id: str,
@@ -774,7 +772,7 @@ async def update_file_from_knowledge_by_id(
############################
@router.post('/{id}/file/remove', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/file/remove', response_model=KnowledgeFilesResponse | None)
async def remove_file_from_knowledge_by_id(
id: str,
form_data: KnowledgeFileIdForm,
@@ -936,7 +934,7 @@ async def delete_knowledge_by_id(
############################
@router.post('/{id}/reset', response_model=Optional[KnowledgeResponse])
@router.post('/{id}/reset', response_model=KnowledgeResponse | None)
async def reset_knowledge_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -978,7 +976,7 @@ async def reset_knowledge_by_id(
############################
@router.post('/{id}/files/batch/add', response_model=Optional[KnowledgeFilesResponse])
@router.post('/{id}/files/batch/add', response_model=KnowledgeFilesResponse | None)
async def add_files_to_knowledge_batch(
request: Request,
id: str,
+13 -12
View File
@@ -1,17 +1,18 @@
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
import logging
from __future__ import annotations
import asyncio
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.memories import Memories, MemoryModel
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.utils.auth import get_verified_user
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.access_control import has_permission
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_verified_user
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -56,10 +57,10 @@ class AddMemoryForm(BaseModel):
class MemoryUpdateModel(BaseModel):
content: Optional[str] = None
content: str | None = None
@router.post('/add', response_model=Optional[MemoryModel])
@router.post('/add', response_model=MemoryModel | None)
async def add_memory(
request: Request,
form_data: AddMemoryForm,
@@ -107,7 +108,7 @@ async def add_memory(
class QueryMemoryForm(BaseModel):
content: str
k: Optional[int] = 1
k: int | None = 1
@router.post('/query')
@@ -275,7 +276,7 @@ async def delete_memory_by_user_id(
############################
@router.post('/{memory_id}/update', response_model=Optional[MemoryModel])
@router.post('/{memory_id}/update', response_model=MemoryModel | None)
async def update_memory_by_id(
memory_id: str,
request: Request,
+36 -37
View File
@@ -1,28 +1,14 @@
from typing import Optional
import io
import base64
import json
from __future__ import annotations
import asyncio
import base64
import io
import json
import logging
import posixpath
from typing import Optional
from urllib.parse import unquote
from open_webui.models.groups import Groups
from open_webui.models.models import (
ModelForm,
ModelMeta,
ModelModel,
ModelParams,
ModelResponse,
ModelListResponse,
ModelAccessListResponse,
ModelAccessResponse,
Models,
)
from open_webui.models.access_grants import AccessGrants
from pydantic import BaseModel
from open_webui.constants import ERROR_MESSAGES
from fastapi import (
APIRouter,
Depends,
@@ -32,13 +18,26 @@ from fastapi import (
status,
)
from fastapi.responses import RedirectResponse, StreamingResponse
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.models import (
ModelAccessListResponse,
ModelAccessResponse,
ModelForm,
ModelListResponse,
ModelMeta,
ModelModel,
ModelParams,
ModelResponse,
Models,
)
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -46,7 +45,7 @@ log = logging.getLogger(__name__)
router = APIRouter()
def _safe_static_redirect_path(url: str) -> Optional[str]:
def _safe_static_redirect_path(url: str) -> str | None:
"""
If url is a same-origin static asset path, return a normalized path safe for
RedirectResponse Location. Otherwise None (caller should fall back to default).
@@ -90,12 +89,12 @@ PAGE_ITEM_COUNT = 30
@router.get('/list', response_model=ModelAccessListResponse) # do NOT use "/" as path, conflicts with main.py
async def get_models(
query: Optional[str] = None,
view_option: Optional[str] = None,
tag: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
view_option: str | None = None,
tag: str | None = None,
order_by: str | None = None,
direction: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -192,7 +191,7 @@ async def get_model_tags(user=Depends(get_verified_user), db: AsyncSession = Dep
############################
@router.post('/create', response_model=Optional[ModelModel])
@router.post('/create', response_model=ModelModel | None)
async def create_new_model(
request: Request,
form_data: ModelForm,
@@ -409,7 +408,7 @@ class ModelIdForm(BaseModel):
# Note: We're not using the typical url path param here, but instead using a query parameter to allow '/' in the id
@router.get('/model', response_model=Optional[ModelAccessResponse])
@router.get('/model', response_model=ModelAccessResponse | None)
async def get_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
model = await Models.get_model_by_id(id, db=db)
if model:
@@ -537,7 +536,7 @@ async def get_model_profile_image(
############################
@router.post('/model/toggle', response_model=Optional[ModelResponse])
@router.post('/model/toggle', response_model=ModelResponse | None)
async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
model = await Models.get_model_by_id(id, db=db)
if model:
@@ -578,7 +577,7 @@ async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: Async
############################
@router.post('/model/update', response_model=Optional[ModelModel])
@router.post('/model/update', response_model=ModelModel | None)
async def update_model_by_id(
request: Request,
form_data: ModelForm,
@@ -627,11 +626,11 @@ async def update_model_by_id(
class ModelAccessGrantsForm(BaseModel):
id: str
name: Optional[str] = None
name: str | None = None
access_grants: list[dict]
@router.post('/model/access/update', response_model=Optional[ModelModel])
@router.post('/model/access/update', response_model=ModelModel | None)
async def update_model_access_by_id(
request: Request,
form_data: ModelAccessGrantsForm,
+16 -22
View File
@@ -2,39 +2,33 @@ import json
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
from pydantic import BaseModel
from open_webui.socket.main import sio
from open_webui.models.groups import Groups
from open_webui.models.users import Users, UserResponse
from open_webui.models.notes import (
NoteListResponse,
Notes,
NoteModel,
NoteForm,
NoteUserResponse,
)
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, status
from open_webui.config import (
BYPASS_ADMIN_ACCESS_CONTROL,
ENABLE_ADMIN_CHAT_ACCESS,
ENABLE_ADMIN_EXPORT,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.notes import (
NoteForm,
NoteListResponse,
NoteModel,
Notes,
NoteUserResponse,
)
from open_webui.models.users import UserResponse, Users
from open_webui.socket.main import sio
from open_webui.utils.access_control import (
filter_allowed_access_grants,
has_permission,
has_public_read_access_grant,
has_public_write_access_grant,
filter_allowed_access_grants,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.internal.db import get_async_session
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+84 -93
View File
@@ -1,7 +1,8 @@
from __future__ import annotations
# TODO: Implement a more intelligent load balancing mechanism for distributing requests among multiple backend instances.
# Current implementation uses a simple round-robin approach (random.choice). Consider incorporating algorithms like weighted round-robin,
# least connections, or least response time for better resource utilization and performance optimization.
import asyncio
import json
import logging
@@ -10,70 +11,60 @@ import random
import re
import time
from datetime import datetime
from typing import Optional, Union
from urllib.parse import urlparse
import aiohttp
from aiocache import cached
from open_webui.utils.headers import include_user_info_headers
from open_webui.models.chats import Chats
from open_webui.models.users import UserModel
from open_webui.env import (
ENABLE_FORWARD_USER_INFO_HEADERS,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
)
from fastapi import (
APIRouter,
Depends,
FastAPI,
File,
HTTPException,
Request,
UploadFile,
APIRouter,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict, validator
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.config import (
UPLOAD_DIR,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
BYPASS_MODEL_ACCESS_CONTROL,
ENABLE_FORWARD_USER_INFO_HEADERS,
ENV,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
)
from open_webui.internal.db import get_async_session
from open_webui.models.models import Models
from open_webui.models.access_grants import AccessGrants
from open_webui.models.chats import Chats
from open_webui.models.groups import Groups
from open_webui.models.models import Models
from open_webui.models.users import UserModel
from open_webui.utils.access_control import check_model_access
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import include_user_info_headers
from open_webui.utils.misc import (
calculate_sha256,
)
from open_webui.utils.session_pool import (
cleanup_response,
get_session,
stream_wrapper,
)
from open_webui.utils.payload import (
apply_model_params_to_body_ollama,
apply_model_params_to_body_openai,
apply_system_prompt_to_body,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.config import (
UPLOAD_DIR,
from open_webui.utils.session_pool import (
cleanup_response,
get_session,
stream_wrapper,
)
from open_webui.env import (
ENV,
MODELS_CACHE_TTL,
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
BYPASS_MODEL_ACCESS_CONTROL,
)
from open_webui.constants import ERROR_MESSAGES
from pydantic import BaseModel, ConfigDict, validator
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -126,12 +117,12 @@ async def send_request(
url: str,
method: str = 'POST',
*,
payload: Optional[Union[str, bytes]] = None,
key: Optional[str] = None,
payload: Union[str, bytes | None] = None,
key: str | None = None,
user: UserModel = None,
stream: bool = False,
content_type: Optional[str] = None,
metadata: Optional[dict] = None,
content_type: str | None = None,
metadata: dict | None = None,
):
r = None
streaming = False
@@ -225,7 +216,7 @@ async def get_status():
class ConnectionVerificationForm(BaseModel):
url: str
key: Optional[str] = None
key: str | None = None
@router.post('/verify')
@@ -279,7 +270,7 @@ async def get_config(request: Request, user=Depends(get_admin_user)):
class OllamaConfigForm(BaseModel):
ENABLE_OLLAMA_API: Optional[bool] = None
ENABLE_OLLAMA_API: bool | None = None
OLLAMA_BASE_URLS: list[str]
OLLAMA_API_CONFIGS: dict
@@ -437,7 +428,7 @@ async def get_filtered_models(models, user, db=None):
@router.get('/api/tags')
@router.get('/api/tags/{url_idx}')
async def get_ollama_tags(request: Request, url_idx: Optional[int] = None, user=Depends(get_verified_user)):
async def get_ollama_tags(request: Request, url_idx: int | None = None, user=Depends(get_verified_user)):
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
@@ -514,7 +505,7 @@ async def get_ollama_loaded_models(request: Request, user=Depends(get_admin_user
@router.get('/api/version')
@router.get('/api/version/{url_idx}')
async def get_ollama_versions(request: Request, url_idx: Optional[int] = None):
async def get_ollama_versions(request: Request, url_idx: int | None = None):
if request.app.state.config.ENABLE_OLLAMA_API:
if url_idx is None:
# returns lowest version
@@ -560,7 +551,7 @@ async def get_ollama_versions(request: Request, url_idx: Optional[int] = None):
class ModelNameForm(BaseModel):
model: Optional[str] = None
model: str | None = None
model_config = ConfigDict(
extra='allow',
)
@@ -654,8 +645,8 @@ async def pull_model(
class PushModelForm(BaseModel):
model: str
insecure: Optional[bool] = None
stream: Optional[bool] = None
insecure: bool | None = None
stream: bool | None = None
@router.delete('/api/push')
@@ -663,7 +654,7 @@ class PushModelForm(BaseModel):
async def push_model(
request: Request,
form_data: PushModelForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -694,9 +685,9 @@ async def push_model(
class CreateModelForm(BaseModel):
model: Optional[str] = None
stream: Optional[bool] = None
path: Optional[str] = None
model: str | None = None
stream: bool | None = None
path: str | None = None
model_config = ConfigDict(extra='allow')
@@ -734,7 +725,7 @@ class CopyModelForm(BaseModel):
async def copy_model(
request: Request,
form_data: CopyModelForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -769,7 +760,7 @@ async def copy_model(
async def delete_model(
request: Request,
form_data: ModelNameForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_admin_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -843,9 +834,9 @@ async def show_model_info(request: Request, form_data: ModelNameForm, user=Depen
class GenerateEmbedForm(BaseModel):
model: str
input: list[str] | str
truncate: Optional[bool] = None
options: Optional[dict] = None
keep_alive: Optional[Union[int, str]] = None
truncate: bool | None = None
options: dict | None = None
keep_alive: Union[int, str | None] = None
model_config = ConfigDict(
extra='allow',
@@ -857,7 +848,7 @@ class GenerateEmbedForm(BaseModel):
async def embed(
request: Request,
form_data: GenerateEmbedForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -907,8 +898,8 @@ async def embed(
class GenerateEmbeddingsForm(BaseModel):
model: str
prompt: str
options: Optional[dict] = None
keep_alive: Optional[Union[int, str]] = None
options: dict | None = None
keep_alive: Union[int, str | None] = None
@router.post('/api/embeddings')
@@ -916,7 +907,7 @@ class GenerateEmbeddingsForm(BaseModel):
async def embeddings(
request: Request,
form_data: GenerateEmbeddingsForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -965,17 +956,17 @@ async def embeddings(
class GenerateCompletionForm(BaseModel):
model: str
prompt: Optional[str] = None
suffix: Optional[str] = None
images: Optional[list[str]] = None
format: Optional[Union[dict, str]] = None
options: Optional[dict] = None
system: Optional[str] = None
template: Optional[str] = None
context: Optional[list[int]] = None
stream: Optional[bool] = True
raw: Optional[bool] = None
keep_alive: Optional[Union[int, str]] = None
prompt: str | None = None
suffix: str | None = None
images: list[str | None] = None
format: Union[dict, str | None] = None
options: dict | None = None
system: str | None = None
template: str | None = None
context: list[int | None] = None
stream: bool | None = True
raw: bool | None = None
keep_alive: Union[int, str | None] = None
@router.post('/api/generate')
@@ -983,7 +974,7 @@ class GenerateCompletionForm(BaseModel):
async def generate_completion(
request: Request,
form_data: GenerateCompletionForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
if not request.app.state.config.ENABLE_OLLAMA_API:
@@ -1027,9 +1018,9 @@ async def generate_completion(
class ChatMessage(BaseModel):
role: str
content: Optional[str] = None
tool_calls: Optional[list[dict]] = None
images: Optional[list[str]] = None
content: str | None = None
tool_calls: list[dict | None] = None
images: list[str | None] = None
model_config = ConfigDict(extra='allow')
@@ -1046,18 +1037,18 @@ class ChatMessage(BaseModel):
class GenerateChatCompletionForm(BaseModel):
model: str
messages: list[ChatMessage]
format: Optional[Union[dict, str]] = None
options: Optional[dict] = None
template: Optional[str] = None
stream: Optional[bool] = True
keep_alive: Optional[Union[int, str]] = None
tools: Optional[list[dict]] = None
format: Union[dict, str | None] = None
options: dict | None = None
template: str | None = None
stream: bool | None = True
keep_alive: Union[int, str | None] = None
tools: list[dict | None] = None
model_config = ConfigDict(
extra='allow',
)
async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] = None):
async def get_ollama_url(request: Request, model: str, url_idx: int | None = None):
if url_idx is None:
models = request.app.state.OLLAMA_MODELS
if model not in models:
@@ -1075,7 +1066,7 @@ async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] =
async def generate_chat_completion(
request: Request,
form_data: dict,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
bypass_system_prompt: bool = False,
):
@@ -1162,7 +1153,7 @@ class OpenAIChatMessageContent(BaseModel):
class OpenAIChatMessage(BaseModel):
role: str
content: Union[Optional[str], list[OpenAIChatMessageContent]]
content: Union[str | None, list[OpenAIChatMessageContent]]
model_config = ConfigDict(extra='allow')
@@ -1186,7 +1177,7 @@ class OpenAICompletionForm(BaseModel):
async def generate_openai_completion(
request: Request,
form_data: dict,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
@@ -1248,7 +1239,7 @@ async def generate_openai_completion(
async def generate_openai_chat_completion(
request: Request,
form_data: dict,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
@@ -1313,7 +1304,7 @@ async def generate_openai_chat_completion(
async def generate_anthropic_messages(
request: Request,
form_data: dict,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
"""
@@ -1371,7 +1362,7 @@ class ResponsesForm(BaseModel):
async def generate_responses(
request: Request,
form_data: ResponsesForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
):
"""
@@ -1422,7 +1413,7 @@ async def generate_responses(
@router.get('/v1/models/{url_idx}')
async def get_openai_models(
request: Request,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -1565,7 +1556,7 @@ async def download_file_stream(ollama_url, file_url, file_path, file_name, chunk
async def download_model(
request: Request,
form_data: UrlForm,
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_admin_user),
):
allowed_hosts = ['https://huggingface.co/', 'https://github.com/']
@@ -1598,7 +1589,7 @@ async def download_model(
async def upload_model(
request: Request,
file: UploadFile = File(...),
url_idx: Optional[int] = None,
url_idx: int | None = None,
user=Depends(get_admin_user),
):
if url_idx is None:
+40 -50
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import asyncio
import hashlib
import json
@@ -8,62 +10,52 @@ from urllib.parse import quote, urlparse
import aiohttp
from aiocache import cached
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from fastapi import Depends, HTTPException, Request, APIRouter, status
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import (
FileResponse,
StreamingResponse,
JSONResponse,
PlainTextResponse,
StreamingResponse,
)
from pydantic import BaseModel, ConfigDict
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
from open_webui.models.models import Models
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.utils.access_control import has_connection_access, check_model_access
from open_webui.config import (
CACHE_DIR,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
MODELS_CACHE_TTL,
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
ENABLE_FORWARD_USER_INFO_HEADERS,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
BYPASS_MODEL_ACCESS_CONTROL,
ENABLE_FORWARD_USER_INFO_HEADERS,
ENABLE_OPENAI_API_PASSTHROUGH,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
)
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.models import Models
from open_webui.models.users import UserModel
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_system_prompt_to_body,
)
from open_webui.utils.access_control import check_model_access, has_connection_access
from open_webui.utils.anthropic import get_anthropic_models, is_anthropic_url
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import get_custom_headers, include_user_info_headers
from open_webui.utils.misc import (
convert_logit_bias_input_to_json,
stream_chunks_handler,
)
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_system_prompt_to_body,
)
from open_webui.utils.session_pool import (
cleanup_response,
get_session,
stream_wrapper,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import include_user_info_headers, get_custom_headers
from open_webui.utils.anthropic import is_anthropic_url, get_anthropic_models
from pydantic import BaseModel, ConfigDict
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -160,7 +152,7 @@ async def get_headers_and_cookies(
url,
key=None,
config=None,
metadata: Optional[dict] = None,
metadata: dict | None = None,
user: UserModel = None,
):
cookies = {}
@@ -256,7 +248,7 @@ async def get_config(request: Request, user=Depends(get_admin_user)):
class OpenAIConfigForm(BaseModel):
ENABLE_OPENAI_API: Optional[bool] = None
ENABLE_OPENAI_API: bool | None = None
OPENAI_API_BASE_URLS: list[str]
OPENAI_API_KEYS: list[str]
OPENAI_API_CONFIGS: dict
@@ -493,7 +485,6 @@ async def get_filtered_models(models, user, db=None):
return filtered_models
@cached(
ttl=MODELS_CACHE_TTL,
key=lambda _, user: f'openai_all_models_{user.id}' if user else 'openai_all_models',
@@ -571,14 +562,13 @@ async def get_all_models(request: Request, user: UserModel) -> dict[str, list]:
models = get_merged_models(map(extract_data, responses))
log.debug(f'models: {models}')
request.app.state.OPENAI_MODELS = models
return {'data': list(models.values())}
@router.get('/models')
@router.get('/models/{url_idx}')
async def get_models(request: Request, url_idx: Optional[int] = None, user=Depends(get_verified_user)):
async def get_models(request: Request, url_idx: int | None = None, user=Depends(get_verified_user)):
if not request.app.state.config.ENABLE_OPENAI_API:
raise HTTPException(status_code=503, detail='OpenAI API is disabled')
@@ -670,7 +660,7 @@ class ConnectionVerificationForm(BaseModel):
url: str
key: str
config: Optional[dict] = None
config: dict | None = None
@router.post('/verify')
@@ -1361,20 +1351,20 @@ class ResponsesForm(BaseModel):
model_config = ConfigDict(extra='allow')
model: str
input: Optional[list | str] = None
instructions: Optional[str] = None
stream: Optional[bool] = None
temperature: Optional[float] = None
max_output_tokens: Optional[int] = None
top_p: Optional[float] = None
tools: Optional[list] = None
tool_choice: Optional[str | dict] = None
text: Optional[dict] = None
truncation: Optional[str] = None
metadata: Optional[dict] = None
store: Optional[bool] = None
reasoning: Optional[dict] = None
previous_response_id: Optional[str] = None
input: list | str | None = None
instructions: str | None = None
stream: bool | None = None
temperature: float | None = None
max_output_tokens: int | None = None
top_p: float | None = None
tools: list | None = None
tool_choice: str | dict | None = None
text: dict | None = None
truncation: str | None = None
metadata: dict | None = None
store: bool | None = None
reasoning: dict | None = None
previous_response_id: str | None = None
@router.post('/responses')
+10 -13
View File
@@ -1,4 +1,11 @@
import logging
import os
import shutil
from typing import Optional
import aiohttp
from fastapi import (
APIRouter,
Depends,
FastAPI,
File,
@@ -7,24 +14,14 @@ from fastapi import (
Request,
UploadFile,
status,
APIRouter,
)
import aiohttp
import os
import logging
import shutil
from pydantic import BaseModel
from starlette.responses import FileResponse
from typing import Optional
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.config import CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.routers.openai import get_all_models_responses
from open_webui.utils.auth import get_admin_user
from pydantic import BaseModel
from starlette.responses import FileResponse
log = logging.getLogger(__name__)
+32 -30
View File
@@ -1,14 +1,11 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Request
from __future__ import annotations
from open_webui.models.prompts import (
PromptForm,
PromptUserResponse,
PromptAccessResponse,
PromptAccessListResponse,
PromptModel,
Prompts,
)
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.prompt_history import (
@@ -16,13 +13,18 @@ from open_webui.models.prompt_history import (
PromptHistoryModel,
PromptHistoryResponse,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.models.prompts import (
PromptAccessListResponse,
PromptAccessResponse,
PromptForm,
PromptModel,
Prompts,
PromptUserResponse,
)
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
class PromptVersionUpdateForm(BaseModel):
@@ -32,7 +34,7 @@ class PromptVersionUpdateForm(BaseModel):
class PromptMetadataForm(BaseModel):
name: str
command: str
tags: Optional[list[str]] = None
tags: list[str | None] = None
router = APIRouter()
@@ -66,12 +68,12 @@ async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = De
@router.get('/list', response_model=PromptAccessListResponse)
async def get_prompt_list(
query: Optional[str] = None,
view_option: Optional[str] = None,
tag: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
view_option: str | None = None,
tag: str | None = None,
order_by: str | None = None,
direction: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -136,7 +138,7 @@ async def get_prompt_list(
############################
@router.post('/create', response_model=Optional[PromptModel])
@router.post('/create', response_model=PromptModel | None)
async def create_new_prompt(
request: Request,
form_data: PromptForm,
@@ -191,7 +193,7 @@ async def create_new_prompt(
############################
@router.get('/command/{command}', response_model=Optional[PromptAccessResponse])
@router.get('/command/{command}', response_model=PromptAccessResponse | None)
async def get_prompt_by_command(
command: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -235,7 +237,7 @@ async def get_prompt_by_command(
############################
@router.get('/id/{prompt_id}', response_model=Optional[PromptAccessResponse])
@router.get('/id/{prompt_id}', response_model=PromptAccessResponse | None)
async def get_prompt_by_id(
prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -279,7 +281,7 @@ async def get_prompt_by_id(
############################
@router.post('/id/{prompt_id}/update', response_model=Optional[PromptModel])
@router.post('/id/{prompt_id}/update', response_model=PromptModel | None)
async def update_prompt_by_id(
request: Request,
prompt_id: str,
@@ -345,7 +347,7 @@ async def update_prompt_by_id(
############################
@router.post('/id/{prompt_id}/update/meta', response_model=Optional[PromptModel])
@router.post('/id/{prompt_id}/update/meta', response_model=PromptModel | None)
async def update_prompt_metadata(
prompt_id: str,
form_data: PromptMetadataForm,
@@ -398,7 +400,7 @@ async def update_prompt_metadata(
)
@router.post('/id/{prompt_id}/update/version', response_model=Optional[PromptModel])
@router.post('/id/{prompt_id}/update/version', response_model=PromptModel | None)
async def set_prompt_version(
prompt_id: str,
form_data: PromptVersionUpdateForm,
@@ -447,7 +449,7 @@ class PromptAccessGrantsForm(BaseModel):
access_grants: list[dict]
@router.post('/id/{prompt_id}/access/update', response_model=Optional[PromptModel])
@router.post('/id/{prompt_id}/access/update', response_model=PromptModel | None)
async def update_prompt_access_by_id(
request: Request,
prompt_id: str,
@@ -496,7 +498,7 @@ async def update_prompt_access_by_id(
############################
@router.post('/id/{prompt_id}/toggle', response_model=Optional[PromptModel])
@router.post('/id/{prompt_id}/toggle', response_model=PromptModel | None)
async def toggle_prompt_active(
prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
+235 -243
View File
@@ -1,130 +1,122 @@
from __future__ import annotations
import asyncio
import json
import logging
import mimetypes
import os
import shutil
import asyncio
import re
import shutil
import uuid
from datetime import datetime
from pathlib import Path
from typing import Iterator, List, Optional, Sequence, Union
from typing import Iterator, Optional, Sequence, Union
import tiktoken
from fastapi import (
APIRouter,
Depends,
FastAPI,
Query,
File,
Form,
HTTPException,
UploadFile,
Query,
Request,
UploadFile,
status,
APIRouter,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel
import tiktoken
from fastapi.middleware.cors import CORSMiddleware
from langchain_core.documents import Document
from langchain_text_splitters import (
MarkdownHeaderTextSplitter,
RecursiveCharacterTextSplitter,
TokenTextSplitter,
MarkdownHeaderTextSplitter,
)
from langchain_core.documents import Document
from open_webui.models.files import FileModel, FileUpdateForm, Files
from open_webui.utils.access_control.files import has_access_to_file
from open_webui.models.knowledge import Knowledges
from open_webui.storage.provider import Storage
from open_webui.internal.db import get_async_db, get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
# Document loaders
from open_webui.retrieval.loaders.youtube import YoutubeLoader
# Web search engines
from open_webui.retrieval.web.main import SearchResult
from open_webui.retrieval.web.utils import get_web_loader
from open_webui.retrieval.web.ollama import search_ollama_cloud
from open_webui.retrieval.web.perplexity_search import search_perplexity_search
from open_webui.retrieval.web.brave import search_brave
from open_webui.retrieval.web.brave_llm_context import search_brave_llm_context
from open_webui.retrieval.web.kagi import search_kagi
from open_webui.retrieval.web.mojeek import search_mojeek
from open_webui.retrieval.web.bocha import search_bocha
from open_webui.retrieval.web.duckduckgo import search_duckduckgo
from open_webui.retrieval.web.google_pse import search_google_pse
from open_webui.retrieval.web.jina_search import search_jina
from open_webui.retrieval.web.searchapi import search_searchapi
from open_webui.retrieval.web.serpapi import search_serpapi
from open_webui.retrieval.web.searxng import search_searxng
from open_webui.retrieval.web.yacy import search_yacy
from open_webui.retrieval.web.serper import search_serper
from open_webui.retrieval.web.serply import search_serply
from open_webui.retrieval.web.serpstack import search_serpstack
from open_webui.retrieval.web.tavily import search_tavily
from open_webui.retrieval.web.bing import search_bing
from open_webui.retrieval.web.azure import search_azure
from open_webui.retrieval.web.exa import search_exa
from open_webui.retrieval.web.perplexity import search_perplexity
from open_webui.retrieval.web.sougou import search_sougou
from open_webui.retrieval.web.firecrawl import search_firecrawl
from open_webui.retrieval.web.external import search_external
from open_webui.retrieval.web.yandex import search_yandex
from open_webui.retrieval.web.ydc import search_youcom
from open_webui.retrieval.utils import (
build_loader_from_config,
filter_accessible_collections,
get_content_from_url,
get_embedding_function,
get_reranking_function,
get_model_path,
query_collection,
query_collection_with_hybrid_search,
query_doc,
query_doc_with_hybrid_search,
)
from open_webui.retrieval.vector.utils import filter_metadata
from open_webui.utils.misc import (
calculate_sha256_string,
sanitize_text_for_db,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission
from open_webui.config import (
DEFAULT_LOCALE,
ENV,
RAG_EMBEDDING_CONTENT_PREFIX,
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
RAG_EMBEDDING_QUERY_PREFIX,
RAG_RERANKING_MODEL_AUTO_UPDATE,
RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
UPLOAD_DIR,
DEFAULT_LOCALE,
RAG_EMBEDDING_CONTENT_PREFIX,
RAG_EMBEDDING_QUERY_PREFIX,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
DEVICE_TYPE,
DOCKER,
RAG_EMBEDDING_TIMEOUT,
SENTENCE_TRANSFORMERS_BACKEND,
SENTENCE_TRANSFORMERS_MODEL_KWARGS,
SENTENCE_TRANSFORMERS_CROSS_ENCODER_BACKEND,
SENTENCE_TRANSFORMERS_CROSS_ENCODER_MODEL_KWARGS,
SENTENCE_TRANSFORMERS_CROSS_ENCODER_SIGMOID_ACTIVATION_FUNCTION,
SENTENCE_TRANSFORMERS_MODEL_KWARGS,
)
from open_webui.internal.db import get_async_db, get_async_session
from open_webui.models.files import FileModel, Files, FileUpdateForm
from open_webui.models.knowledge import Knowledges
from open_webui.constants import ERROR_MESSAGES
# Document loaders
from open_webui.retrieval.loaders.youtube import YoutubeLoader
from open_webui.retrieval.utils import (
build_loader_from_config,
filter_accessible_collections,
get_content_from_url,
get_embedding_function,
get_model_path,
get_reranking_function,
query_collection,
query_collection_with_hybrid_search,
query_doc,
query_doc_with_hybrid_search,
)
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
from open_webui.retrieval.vector.utils import filter_metadata
from open_webui.retrieval.web.azure import search_azure
from open_webui.retrieval.web.bing import search_bing
from open_webui.retrieval.web.bocha import search_bocha
from open_webui.retrieval.web.brave import search_brave
from open_webui.retrieval.web.brave_llm_context import search_brave_llm_context
from open_webui.retrieval.web.duckduckgo import search_duckduckgo
from open_webui.retrieval.web.exa import search_exa
from open_webui.retrieval.web.external import search_external
from open_webui.retrieval.web.firecrawl import search_firecrawl
from open_webui.retrieval.web.google_pse import search_google_pse
from open_webui.retrieval.web.jina_search import search_jina
from open_webui.retrieval.web.kagi import search_kagi
# Web search engines
from open_webui.retrieval.web.main import SearchResult
from open_webui.retrieval.web.mojeek import search_mojeek
from open_webui.retrieval.web.ollama import search_ollama_cloud
from open_webui.retrieval.web.perplexity import search_perplexity
from open_webui.retrieval.web.perplexity_search import search_perplexity_search
from open_webui.retrieval.web.searchapi import search_searchapi
from open_webui.retrieval.web.searxng import search_searxng
from open_webui.retrieval.web.serpapi import search_serpapi
from open_webui.retrieval.web.serper import search_serper
from open_webui.retrieval.web.serply import search_serply
from open_webui.retrieval.web.serpstack import search_serpstack
from open_webui.retrieval.web.sougou import search_sougou
from open_webui.retrieval.web.tavily import search_tavily
from open_webui.retrieval.web.utils import get_web_loader
from open_webui.retrieval.web.yacy import search_yacy
from open_webui.retrieval.web.yandex import search_yandex
from open_webui.retrieval.web.ydc import search_youcom
from open_webui.storage.provider import Storage
from open_webui.utils.access_control import has_permission
from open_webui.utils.access_control.files import has_access_to_file
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.misc import (
calculate_sha256_string,
sanitize_text_for_db,
)
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -162,7 +154,7 @@ def get_ef(
def get_rf(
engine: str = '',
reranking_model: Optional[str] = None,
reranking_model: str | None = None,
external_reranker_url: str = '',
external_reranker_api_key: str = '',
external_reranker_timeout: str = '',
@@ -249,7 +241,7 @@ router = APIRouter()
class CollectionNameForm(BaseModel):
collection_name: Optional[str] = None
collection_name: str | None = None
class ProcessUrlForm(CollectionNameForm):
@@ -257,7 +249,7 @@ class ProcessUrlForm(CollectionNameForm):
class SearchForm(BaseModel):
queries: List[str]
queries: list[str]
@router.get('/embedding')
@@ -302,14 +294,14 @@ class AzureOpenAIConfigForm(BaseModel):
class EmbeddingModelUpdateForm(BaseModel):
openai_config: Optional[OpenAIConfigForm] = None
ollama_config: Optional[OllamaConfigForm] = None
azure_openai_config: Optional[AzureOpenAIConfigForm] = None
openai_config: OpenAIConfigForm | None = None
ollama_config: OllamaConfigForm | None = None
azure_openai_config: AzureOpenAIConfigForm | None = None
RAG_EMBEDDING_ENGINE: str
RAG_EMBEDDING_MODEL: str
RAG_EMBEDDING_BATCH_SIZE: Optional[int] = 1
ENABLE_ASYNC_EMBEDDING: Optional[bool] = True
RAG_EMBEDDING_CONCURRENT_REQUESTS: Optional[int] = 0
RAG_EMBEDDING_BATCH_SIZE: int | None = 1
ENABLE_ASYNC_EMBEDDING: bool | None = True
RAG_EMBEDDING_CONCURRENT_REQUESTS: int | None = 0
def unload_embedding_model(request: Request):
@@ -566,153 +558,153 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
class WebConfig(BaseModel):
ENABLE_WEB_SEARCH: Optional[bool] = None
WEB_SEARCH_ENGINE: Optional[str] = None
WEB_SEARCH_TRUST_ENV: Optional[bool] = None
WEB_SEARCH_RESULT_COUNT: Optional[int] = None
WEB_SEARCH_CONCURRENT_REQUESTS: Optional[int] = None
WEB_SEARCH_DOMAIN_FILTER_LIST: Optional[List[str]] = []
WEB_FETCH_MAX_CONTENT_LENGTH: Optional[int] = None
WEB_LOADER_CONCURRENT_REQUESTS: Optional[int] = None
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL: Optional[bool] = None
BYPASS_WEB_SEARCH_WEB_LOADER: Optional[bool] = None
OLLAMA_CLOUD_WEB_SEARCH_API_KEY: Optional[str] = None
SEARXNG_QUERY_URL: Optional[str] = None
SEARXNG_LANGUAGE: Optional[str] = None
YACY_QUERY_URL: Optional[str] = None
YACY_USERNAME: Optional[str] = None
YACY_PASSWORD: Optional[str] = None
GOOGLE_PSE_API_KEY: Optional[str] = None
GOOGLE_PSE_ENGINE_ID: Optional[str] = None
BRAVE_SEARCH_API_KEY: Optional[str] = None
BRAVE_SEARCH_CONTEXT_TOKENS: Optional[int] = None
KAGI_SEARCH_API_KEY: Optional[str] = None
MOJEEK_SEARCH_API_KEY: Optional[str] = None
BOCHA_SEARCH_API_KEY: Optional[str] = None
SERPSTACK_API_KEY: Optional[str] = None
SERPSTACK_HTTPS: Optional[bool] = None
SERPER_API_KEY: Optional[str] = None
SERPLY_API_KEY: Optional[str] = None
DDGS_BACKEND: Optional[str] = None
TAVILY_API_KEY: Optional[str] = None
SEARCHAPI_API_KEY: Optional[str] = None
SEARCHAPI_ENGINE: Optional[str] = None
SERPAPI_API_KEY: Optional[str] = None
SERPAPI_ENGINE: Optional[str] = None
JINA_API_KEY: Optional[str] = None
JINA_API_BASE_URL: Optional[str] = None
BING_SEARCH_V7_ENDPOINT: Optional[str] = None
BING_SEARCH_V7_SUBSCRIPTION_KEY: Optional[str] = None
EXA_API_KEY: Optional[str] = None
PERPLEXITY_API_KEY: Optional[str] = None
PERPLEXITY_MODEL: Optional[str] = None
PERPLEXITY_SEARCH_CONTEXT_USAGE: Optional[str] = None
PERPLEXITY_SEARCH_API_URL: Optional[str] = None
SOUGOU_API_SID: Optional[str] = None
SOUGOU_API_SK: Optional[str] = None
WEB_LOADER_ENGINE: Optional[str] = None
WEB_LOADER_TIMEOUT: Optional[str] = None
ENABLE_WEB_LOADER_SSL_VERIFICATION: Optional[bool] = None
PLAYWRIGHT_WS_URL: Optional[str] = None
PLAYWRIGHT_TIMEOUT: Optional[int] = None
FIRECRAWL_API_KEY: Optional[str] = None
FIRECRAWL_API_BASE_URL: Optional[str] = None
FIRECRAWL_TIMEOUT: Optional[str] = None
TAVILY_EXTRACT_DEPTH: Optional[str] = None
EXTERNAL_WEB_SEARCH_URL: Optional[str] = None
EXTERNAL_WEB_SEARCH_API_KEY: Optional[str] = None
EXTERNAL_WEB_LOADER_URL: Optional[str] = None
EXTERNAL_WEB_LOADER_API_KEY: Optional[str] = None
YOUTUBE_LOADER_LANGUAGE: Optional[List[str]] = None
YOUTUBE_LOADER_PROXY_URL: Optional[str] = None
YOUTUBE_LOADER_TRANSLATION: Optional[str] = None
YANDEX_WEB_SEARCH_URL: Optional[str] = None
YANDEX_WEB_SEARCH_API_KEY: Optional[str] = None
YANDEX_WEB_SEARCH_CONFIG: Optional[str] = None
YOUCOM_API_KEY: Optional[str] = None
ENABLE_WEB_SEARCH: bool | None = None
WEB_SEARCH_ENGINE: str | None = None
WEB_SEARCH_TRUST_ENV: bool | None = None
WEB_SEARCH_RESULT_COUNT: int | None = None
WEB_SEARCH_CONCURRENT_REQUESTS: int | None = None
WEB_SEARCH_DOMAIN_FILTER_LIST: list[str | None] = []
WEB_FETCH_MAX_CONTENT_LENGTH: int | None = None
WEB_LOADER_CONCURRENT_REQUESTS: int | None = None
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL: bool | None = None
BYPASS_WEB_SEARCH_WEB_LOADER: bool | None = None
OLLAMA_CLOUD_WEB_SEARCH_API_KEY: str | None = None
SEARXNG_QUERY_URL: str | None = None
SEARXNG_LANGUAGE: str | None = None
YACY_QUERY_URL: str | None = None
YACY_USERNAME: str | None = None
YACY_PASSWORD: str | None = None
GOOGLE_PSE_API_KEY: str | None = None
GOOGLE_PSE_ENGINE_ID: str | None = None
BRAVE_SEARCH_API_KEY: str | None = None
BRAVE_SEARCH_CONTEXT_TOKENS: int | None = None
KAGI_SEARCH_API_KEY: str | None = None
MOJEEK_SEARCH_API_KEY: str | None = None
BOCHA_SEARCH_API_KEY: str | None = None
SERPSTACK_API_KEY: str | None = None
SERPSTACK_HTTPS: bool | None = None
SERPER_API_KEY: str | None = None
SERPLY_API_KEY: str | None = None
DDGS_BACKEND: str | None = None
TAVILY_API_KEY: str | None = None
SEARCHAPI_API_KEY: str | None = None
SEARCHAPI_ENGINE: str | None = None
SERPAPI_API_KEY: str | None = None
SERPAPI_ENGINE: str | None = None
JINA_API_KEY: str | None = None
JINA_API_BASE_URL: str | None = None
BING_SEARCH_V7_ENDPOINT: str | None = None
BING_SEARCH_V7_SUBSCRIPTION_KEY: str | None = None
EXA_API_KEY: str | None = None
PERPLEXITY_API_KEY: str | None = None
PERPLEXITY_MODEL: str | None = None
PERPLEXITY_SEARCH_CONTEXT_USAGE: str | None = None
PERPLEXITY_SEARCH_API_URL: str | None = None
SOUGOU_API_SID: str | None = None
SOUGOU_API_SK: str | None = None
WEB_LOADER_ENGINE: str | None = None
WEB_LOADER_TIMEOUT: str | None = None
ENABLE_WEB_LOADER_SSL_VERIFICATION: bool | None = None
PLAYWRIGHT_WS_URL: str | None = None
PLAYWRIGHT_TIMEOUT: int | None = None
FIRECRAWL_API_KEY: str | None = None
FIRECRAWL_API_BASE_URL: str | None = None
FIRECRAWL_TIMEOUT: str | None = None
TAVILY_EXTRACT_DEPTH: str | None = None
EXTERNAL_WEB_SEARCH_URL: str | None = None
EXTERNAL_WEB_SEARCH_API_KEY: str | None = None
EXTERNAL_WEB_LOADER_URL: str | None = None
EXTERNAL_WEB_LOADER_API_KEY: str | None = None
YOUTUBE_LOADER_LANGUAGE: list[str | None] = None
YOUTUBE_LOADER_PROXY_URL: str | None = None
YOUTUBE_LOADER_TRANSLATION: str | None = None
YANDEX_WEB_SEARCH_URL: str | None = None
YANDEX_WEB_SEARCH_API_KEY: str | None = None
YANDEX_WEB_SEARCH_CONFIG: str | None = None
YOUCOM_API_KEY: str | None = None
class ConfigForm(BaseModel):
# RAG settings
RAG_TEMPLATE: Optional[str] = None
TOP_K: Optional[int] = None
BYPASS_EMBEDDING_AND_RETRIEVAL: Optional[bool] = None
RAG_FULL_CONTEXT: Optional[bool] = None
RAG_TEMPLATE: str | None = None
TOP_K: int | None = None
BYPASS_EMBEDDING_AND_RETRIEVAL: bool | None = None
RAG_FULL_CONTEXT: bool | None = None
# Hybrid search settings
ENABLE_RAG_HYBRID_SEARCH: Optional[bool] = None
ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS: Optional[bool] = None
TOP_K_RERANKER: Optional[int] = None
RELEVANCE_THRESHOLD: Optional[float] = None
HYBRID_BM25_WEIGHT: Optional[float] = None
ENABLE_RAG_HYBRID_SEARCH: bool | None = None
ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS: bool | None = None
TOP_K_RERANKER: int | None = None
RELEVANCE_THRESHOLD: float | None = None
HYBRID_BM25_WEIGHT: float | None = None
# Content extraction settings
CONTENT_EXTRACTION_ENGINE: Optional[str] = None
PDF_EXTRACT_IMAGES: Optional[bool] = None
PDF_LOADER_MODE: Optional[str] = None
CONTENT_EXTRACTION_ENGINE: str | None = None
PDF_EXTRACT_IMAGES: bool | None = None
PDF_LOADER_MODE: str | None = None
DATALAB_MARKER_API_KEY: Optional[str] = None
DATALAB_MARKER_API_BASE_URL: Optional[str] = None
DATALAB_MARKER_ADDITIONAL_CONFIG: Optional[str] = None
DATALAB_MARKER_SKIP_CACHE: Optional[bool] = None
DATALAB_MARKER_FORCE_OCR: Optional[bool] = None
DATALAB_MARKER_PAGINATE: Optional[bool] = None
DATALAB_MARKER_STRIP_EXISTING_OCR: Optional[bool] = None
DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION: Optional[bool] = None
DATALAB_MARKER_FORMAT_LINES: Optional[bool] = None
DATALAB_MARKER_USE_LLM: Optional[bool] = None
DATALAB_MARKER_OUTPUT_FORMAT: Optional[str] = None
DATALAB_MARKER_API_KEY: str | None = None
DATALAB_MARKER_API_BASE_URL: str | None = None
DATALAB_MARKER_ADDITIONAL_CONFIG: str | None = None
DATALAB_MARKER_SKIP_CACHE: bool | None = None
DATALAB_MARKER_FORCE_OCR: bool | None = None
DATALAB_MARKER_PAGINATE: bool | None = None
DATALAB_MARKER_STRIP_EXISTING_OCR: bool | None = None
DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION: bool | None = None
DATALAB_MARKER_FORMAT_LINES: bool | None = None
DATALAB_MARKER_USE_LLM: bool | None = None
DATALAB_MARKER_OUTPUT_FORMAT: str | None = None
EXTERNAL_DOCUMENT_LOADER_URL: Optional[str] = None
EXTERNAL_DOCUMENT_LOADER_API_KEY: Optional[str] = None
EXTERNAL_DOCUMENT_LOADER_URL: str | None = None
EXTERNAL_DOCUMENT_LOADER_API_KEY: str | None = None
TIKA_SERVER_URL: Optional[str] = None
DOCLING_SERVER_URL: Optional[str] = None
DOCLING_API_KEY: Optional[str] = None
DOCLING_PARAMS: Optional[dict] = None
DOCUMENT_INTELLIGENCE_ENDPOINT: Optional[str] = None
DOCUMENT_INTELLIGENCE_KEY: Optional[str] = None
DOCUMENT_INTELLIGENCE_MODEL: Optional[str] = None
MISTRAL_OCR_API_BASE_URL: Optional[str] = None
MISTRAL_OCR_API_KEY: Optional[str] = None
PADDLEOCR_VL_BASE_URL: Optional[str] = None
PADDLEOCR_VL_TOKEN: Optional[str] = None
TIKA_SERVER_URL: str | None = None
DOCLING_SERVER_URL: str | None = None
DOCLING_API_KEY: str | None = None
DOCLING_PARAMS: dict | None = None
DOCUMENT_INTELLIGENCE_ENDPOINT: str | None = None
DOCUMENT_INTELLIGENCE_KEY: str | None = None
DOCUMENT_INTELLIGENCE_MODEL: str | None = None
MISTRAL_OCR_API_BASE_URL: str | None = None
MISTRAL_OCR_API_KEY: str | None = None
PADDLEOCR_VL_BASE_URL: str | None = None
PADDLEOCR_VL_TOKEN: str | None = None
# MinerU settings
MINERU_API_MODE: Optional[str] = None
MINERU_API_URL: Optional[str] = None
MINERU_API_KEY: Optional[str] = None
MINERU_API_TIMEOUT: Optional[str] = None
MINERU_PARAMS: Optional[dict] = None
MINERU_API_MODE: str | None = None
MINERU_API_URL: str | None = None
MINERU_API_KEY: str | None = None
MINERU_API_TIMEOUT: str | None = None
MINERU_PARAMS: dict | None = None
# Reranking settings
RAG_RERANKING_MODEL: Optional[str] = None
RAG_RERANKING_ENGINE: Optional[str] = None
RAG_RERANKING_BATCH_SIZE: Optional[int] = None
RAG_EXTERNAL_RERANKER_URL: Optional[str] = None
RAG_EXTERNAL_RERANKER_API_KEY: Optional[str] = None
RAG_EXTERNAL_RERANKER_TIMEOUT: Optional[str] = None
RAG_RERANKING_MODEL: str | None = None
RAG_RERANKING_ENGINE: str | None = None
RAG_RERANKING_BATCH_SIZE: int | None = None
RAG_EXTERNAL_RERANKER_URL: str | None = None
RAG_EXTERNAL_RERANKER_API_KEY: str | None = None
RAG_EXTERNAL_RERANKER_TIMEOUT: str | None = None
# Chunking settings
TEXT_SPLITTER: Optional[str] = None
ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER: Optional[bool] = None
CHUNK_SIZE: Optional[int] = None
CHUNK_MIN_SIZE_TARGET: Optional[int] = None
CHUNK_OVERLAP: Optional[int] = None
TEXT_SPLITTER: str | None = None
ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER: bool | None = None
CHUNK_SIZE: int | None = None
CHUNK_MIN_SIZE_TARGET: int | None = None
CHUNK_OVERLAP: int | None = None
# File upload settings
FILE_MAX_SIZE: Optional[Union[int, str]] = None
FILE_MAX_COUNT: Optional[Union[int, str]] = None
FILE_IMAGE_COMPRESSION_WIDTH: Optional[Union[int, str]] = None
FILE_IMAGE_COMPRESSION_HEIGHT: Optional[Union[int, str]] = None
ALLOWED_FILE_EXTENSIONS: Optional[List[str]] = None
FILE_MAX_SIZE: Union[int, str | None] = None
FILE_MAX_COUNT: Union[int, str | None] = None
FILE_IMAGE_COMPRESSION_WIDTH: Union[int, str | None] = None
FILE_IMAGE_COMPRESSION_HEIGHT: Union[int, str | None] = None
ALLOWED_FILE_EXTENSIONS: list[str | None] = None
# Integration settings
ENABLE_GOOGLE_DRIVE_INTEGRATION: Optional[bool] = None
ENABLE_ONEDRIVE_INTEGRATION: Optional[bool] = None
ENABLE_GOOGLE_DRIVE_INTEGRATION: bool | None = None
ENABLE_ONEDRIVE_INTEGRATION: bool | None = None
# Web search settings
web: Optional[WebConfig] = None
web: WebConfig | None = None
@router.post('/config/update')
@@ -1341,7 +1333,7 @@ def save_docs_to_vector_db(
request: Request,
docs,
collection_name,
metadata: Optional[dict] = None,
metadata: dict | None = None,
overwrite: bool = False,
split: bool = True,
add: bool = False,
@@ -1539,8 +1531,8 @@ def save_docs_to_vector_db(
class ProcessFileForm(BaseModel):
file_id: str
content: Optional[str] = None
collection_name: Optional[str] = None
content: str | None = None
collection_name: str | None = None
@router.post('/process/file')
@@ -1767,7 +1759,7 @@ async def process_file(
class ProcessTextForm(BaseModel):
name: str
content: str
collection_name: Optional[str] = None
collection_name: str | None = None
@router.post('/process/text')
@@ -2357,10 +2349,10 @@ async def _validate_collection_access(collection_names: list[str], user, access_
class QueryDocForm(BaseModel):
collection_name: str
query: str
k: Optional[int] = None
k_reranker: Optional[int] = None
r: Optional[float] = None
hybrid: Optional[bool] = None
k: int | None = None
k_reranker: int | None = None
r: float | None = None
hybrid: bool | None = None
@router.post('/query/doc')
@@ -2423,12 +2415,12 @@ async def query_doc_handler(
class QueryCollectionsForm(BaseModel):
collection_names: list[str]
query: str
k: Optional[int] = None
k_reranker: Optional[int] = None
r: Optional[float] = None
hybrid: Optional[bool] = None
hybrid_bm25_weight: Optional[float] = None
enable_enriched_texts: Optional[bool] = None
k: int | None = None
k_reranker: int | None = None
r: float | None = None
hybrid: bool | None = None
hybrid_bm25_weight: float | None = None
enable_enriched_texts: bool | None = None
@router.post('/query/collection')
@@ -2581,24 +2573,24 @@ async def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
if ENV == 'dev':
@router.get('/ef/{text}')
async def get_embeddings(request: Request, text: Optional[str] = 'Hello World!'):
async def get_embeddings(request: Request, text: str | None = 'Hello World!'):
return {'result': await request.app.state.EMBEDDING_FUNCTION(text, prefix=RAG_EMBEDDING_QUERY_PREFIX)}
class BatchProcessFilesForm(BaseModel):
files: List[FileModel]
files: list[FileModel]
collection_name: str
class BatchProcessFilesResult(BaseModel):
file_id: str
status: str
error: Optional[str] = None
error: str | None = None
class BatchProcessFilesResponse(BaseModel):
results: List[BatchProcessFilesResult]
errors: List[BatchProcessFilesResult]
results: list[BatchProcessFilesResult]
errors: list[BatchProcessFilesResult]
@router.post('/process/files/batch')
@@ -2622,12 +2614,12 @@ async def process_files_batch(
if collection_name:
await _validate_collection_access([collection_name], user, access_type='write')
file_results: List[BatchProcessFilesResult] = []
file_errors: List[BatchProcessFilesResult] = []
file_updates: List[FileUpdateForm] = []
file_results: list[BatchProcessFilesResult] = []
file_errors: list[BatchProcessFilesResult] = []
file_updates: list[FileUpdateForm] = []
# Prepare all documents first
all_docs: List[Document] = []
all_docs: list[Document] = []
for file in form_data.files:
try:
@@ -2653,7 +2645,7 @@ async def process_files_batch(
continue
text_content = file.data.get('content', '')
docs: List[Document] = [
docs: list[Document] = [
Document(
page_content=text_content.replace('<br/>', '\n'),
metadata={
+11 -15
View File
@@ -7,31 +7,27 @@ NOTE: This is an experimental implementation and may not fully comply with SCIM
import hmac
import logging
import uuid
import time
from typing import Optional, List, Dict, Any
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Query, Header, status
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, ConfigDict
from open_webui.models.users import Users, UserModel
from open_webui.models.groups import Groups, GroupModel
from open_webui.config import OAUTH_PROVIDERS
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SCIM_AUTH_PROVIDER
from open_webui.internal.db import get_async_session
from open_webui.models.groups import GroupModel, Groups
from open_webui.models.users import UserModel, Users
from open_webui.utils.auth import (
decode_token,
get_admin_user,
get_current_user,
decode_token,
get_verified_user,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.config import OAUTH_PROVIDERS
from open_webui.env import SCIM_AUTH_PROVIDER
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
log = logging.getLogger(__name__)
+10 -13
View File
@@ -1,28 +1,25 @@
import logging
from typing import Optional
from open_webui.models.groups import Groups
from pydantic import BaseModel
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.skills import (
SkillAccessListResponse,
SkillAccessResponse,
SkillForm,
SkillModel,
SkillResponse,
SkillUserResponse,
SkillAccessResponse,
SkillAccessListResponse,
Skills,
SkillUserResponse,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
+24 -28
View File
@@ -1,40 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException, Response, status, Request
from fastapi.responses import JSONResponse, RedirectResponse
from pydantic import BaseModel
from typing import Optional
import logging
import re
from typing import Optional
from open_webui.utils.chat import generate_chat_completion
from open_webui.utils.task import (
title_generation_template,
follow_up_generation_template,
query_generation_template,
image_prompt_generation_template,
autocomplete_generation_template,
tags_generation_template,
emoji_generation_template,
moa_response_generation_template,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.constants import ERROR_MESSAGES, TASKS
from open_webui.routers.pipelines import process_pipeline_inlet_filter
from open_webui.utils.task import get_task_model_id
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.responses import JSONResponse, RedirectResponse
from open_webui.config import (
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE,
DEFAULT_FOLLOW_UP_GENERATION_PROMPT_TEMPLATE,
DEFAULT_TAGS_GENERATION_PROMPT_TEMPLATE,
DEFAULT_IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE,
DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE,
DEFAULT_AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE,
DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE,
DEFAULT_FOLLOW_UP_GENERATION_PROMPT_TEMPLATE,
DEFAULT_IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE,
DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE,
DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE,
DEFAULT_TAGS_GENERATION_PROMPT_TEMPLATE,
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE,
DEFAULT_VOICE_MODE_PROMPT_TEMPLATE,
)
from open_webui.constants import ERROR_MESSAGES, TASKS
from open_webui.routers.pipelines import process_pipeline_inlet_filter
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.chat import generate_chat_completion
from open_webui.utils.task import (
autocomplete_generation_template,
emoji_generation_template,
follow_up_generation_template,
get_task_model_id,
image_prompt_generation_template,
moa_response_generation_template,
query_generation_template,
tags_generation_template,
title_generation_template,
)
from pydantic import BaseModel
log = logging.getLogger(__name__)
+5 -5
View File
@@ -12,14 +12,13 @@ from urllib.parse import unquote
import aiohttp
from fastapi import APIRouter, Depends, Request, Response, WebSocket
from fastapi.responses import JSONResponse, StreamingResponse
from starlette.background import BackgroundTask
from open_webui.utils.auth import get_verified_user
from open_webui.utils.access_control import has_connection_access
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.config import TERMINAL_PROXY_HEADERS
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.models.groups import Groups
from open_webui.models.users import Users
from open_webui.utils.access_control import has_connection_access
from open_webui.utils.auth import get_verified_user
from starlette.background import BackgroundTask
log = logging.getLogger(__name__)
@@ -199,6 +198,7 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
"""
import asyncio
import json
from open_webui.utils.auth import decode_token
# First-message authentication
+33 -34
View File
@@ -1,44 +1,43 @@
from __future__ import annotations
import logging
import re
import time
from pathlib import Path
from typing import Optional
import time
import re
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
from open_webui.models.groups import Groups
from pydantic import BaseModel, HttpUrl
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.groups import Groups
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.tools import (
ToolAccessResponse,
ToolForm,
ToolModel,
ToolResponse,
ToolUserResponse,
ToolAccessResponse,
Tools,
ToolUserResponse,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.utils.access_control import (
filter_allowed_access_grants,
has_access,
has_permission,
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.plugin import (
get_tool_module_from_cache,
load_tool_module_by_id,
replace_imports,
get_tool_module_from_cache,
resolve_valves_schema_options,
)
from open_webui.utils.tools import get_tool_specs
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import (
has_permission,
has_access,
filter_allowed_access_grants,
)
from open_webui.utils.tools import get_tool_servers
from open_webui.config import CACHE_DIR, BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.tools import get_tool_servers, get_tool_specs
from pydantic import BaseModel, HttpUrl
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -248,7 +247,7 @@ def github_url_to_raw_url(url: str) -> str:
return url
@router.post('/load/url', response_model=Optional[dict])
@router.post('/load/url', response_model=dict | None)
async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depends(get_admin_user)):
# NOTE: This is NOT a SSRF vulnerability:
# This endpoint is admin-only (see get_admin_user), meant for *trusted* internal use,
@@ -323,7 +322,7 @@ async def export_tools(
############################
@router.post('/create', response_model=Optional[ToolResponse])
@router.post('/create', response_model=ToolResponse | None)
async def create_new_tools(
request: Request,
form_data: ToolForm,
@@ -401,7 +400,7 @@ async def create_new_tools(
############################
@router.get('/id/{id}', response_model=Optional[ToolAccessResponse])
@router.get('/id/{id}', response_model=ToolAccessResponse | None)
async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
tools = await Tools.get_tool_by_id(id, db=db)
@@ -448,7 +447,7 @@ async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: AsyncSes
############################
@router.post('/id/{id}/update', response_model=Optional[ToolModel])
@router.post('/id/{id}/update', response_model=ToolModel | None)
async def update_tools_by_id(
request: Request,
id: str,
@@ -541,7 +540,7 @@ class ToolAccessGrantsForm(BaseModel):
access_grants: list[dict]
@router.post('/id/{id}/access/update', response_model=Optional[ToolModel])
@router.post('/id/{id}/access/update', response_model=ToolModel | None)
async def update_tool_access_by_id(
request: Request,
id: str,
@@ -634,7 +633,7 @@ async def delete_tools_by_id(
############################
@router.get('/id/{id}/valves', response_model=Optional[dict])
@router.get('/id/{id}/valves', response_model=dict | None)
async def get_tools_valves_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -676,7 +675,7 @@ async def get_tools_valves_by_id(
############################
@router.get('/id/{id}/valves/spec', response_model=Optional[dict])
@router.get('/id/{id}/valves/spec', response_model=dict | None)
async def get_tools_valves_spec_by_id(
request: Request,
id: str,
@@ -726,7 +725,7 @@ async def get_tools_valves_spec_by_id(
############################
@router.post('/id/{id}/valves/update', response_model=Optional[dict])
@router.post('/id/{id}/valves/update', response_model=dict | None)
async def update_tools_valves_by_id(
request: Request,
id: str,
@@ -789,7 +788,7 @@ async def update_tools_valves_by_id(
############################
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
@router.get('/id/{id}/valves/user', response_model=dict | None)
async def get_tools_user_valves_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -826,7 +825,7 @@ async def get_tools_user_valves_by_id(
)
@router.get('/id/{id}/valves/user/spec', response_model=Optional[dict])
@router.get('/id/{id}/valves/user/spec', response_model=dict | None)
async def get_tools_user_valves_spec_by_id(
request: Request,
id: str,
@@ -871,7 +870,7 @@ async def get_tools_user_valves_spec_by_id(
return None
@router.post('/id/{id}/valves/user/update', response_model=Optional[dict])
@router.post('/id/{id}/valves/user/update', response_model=dict | None)
async def update_tools_user_valves_by_id(
request: Request,
id: str,
+38 -44
View File
@@ -1,46 +1,40 @@
import logging
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from __future__ import annotations
import base64
import io
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import Response, StreamingResponse, FileResponse
from pydantic import BaseModel, ConfigDict
from open_webui.models.auths import Auths
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.groups import Groups
from open_webui.models.users import (
UserModel,
UserGroupIdsModel,
UserGroupIdsListResponse,
UserInfoResponse,
UserInfoListResponse,
UserRoleUpdateForm,
UserStatus,
Users,
UserSettings,
UserUpdateForm,
)
from fastapi.responses import FileResponse, Response, StreamingResponse
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING, PROFILE_IMAGE_ALLOWED_MIME_TYPES, STATIC_DIR
from open_webui.internal.db import get_async_session
from open_webui.models.auths import Auths
from open_webui.models.groups import Groups
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.users import (
UserGroupIdsListResponse,
UserGroupIdsModel,
UserInfoListResponse,
UserInfoResponse,
UserModel,
UserRoleUpdateForm,
Users,
UserSettings,
UserStatus,
UserUpdateForm,
)
from open_webui.socket.main import disconnect_user_sessions
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.utils.auth import (
get_admin_user,
get_password_hash,
get_verified_user,
validate_password,
)
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.socket.main import disconnect_user_sessions
from pydantic import BaseModel, ConfigDict
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -59,10 +53,10 @@ PAGE_ITEM_COUNT = 30
@router.get('/', response_model=UserGroupIdsListResponse)
async def get_users(
query: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
order_by: str | None = None,
direction: str | None = None,
page: int | None = 1,
user=Depends(get_admin_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -114,10 +108,10 @@ async def get_all_users(
@router.get('/search', response_model=UserInfoListResponse)
async def search_users(
query: Optional[str] = None,
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
query: str | None = None,
order_by: str | None = None,
direction: str | None = None,
page: int | None = 1,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
@@ -275,7 +269,7 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi
############################
@router.get('/user/settings', response_model=Optional[UserSettings])
@router.get('/user/settings', response_model=UserSettings | None)
async def get_user_settings_by_session_user(
user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -372,7 +366,7 @@ async def update_user_status_by_session_user(
############################
@router.get('/user/info', response_model=Optional[dict])
@router.get('/user/info', response_model=dict | None)
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
# user already fetched by get_verified_user — no need to refetch
return user.info
@@ -383,7 +377,7 @@ async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Asy
############################
@router.post('/user/info/update', response_model=Optional[dict])
@router.post('/user/info/update', response_model=dict | None)
async def update_user_info_by_session_user(
form_data: dict, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
@@ -408,8 +402,8 @@ async def update_user_info_by_session_user(
class UserActiveResponse(UserStatus):
name: str
profile_image_url: Optional[str] = None
groups: Optional[list] = []
profile_image_url: str | None = None
groups: list | None = []
is_active: bool
model_config = ConfigDict(extra='allow')
@@ -536,7 +530,7 @@ async def get_user_active_status_by_id(
############################
@router.post('/{user_id}/update', response_model=Optional[UserModel])
@router.post('/{user_id}/update', response_model=UserModel | None)
async def update_user_by_id(
user_id: str,
form_data: UserUpdateForm,
+11 -11
View File
@@ -1,19 +1,19 @@
import black
import logging
import markdown
from __future__ import annotations
from open_webui.models.chats import ChatTitleMessagesForm
import logging
import black
import markdown
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from open_webui.config import DATA_DIR, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from pydantic import BaseModel
from starlette.responses import FileResponse
from open_webui.utils.misc import get_gravatar_url
from open_webui.utils.pdf_generator import PDFGenerator
from open_webui.models.chats import ChatTitleMessagesForm
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.code_interpreter import execute_code_jupyter
from open_webui.utils.misc import get_gravatar_url
from open_webui.utils.pdf_generator import PDFGenerator
from pydantic import BaseModel
from starlette.responses import FileResponse
log = logging.getLogger(__name__)