This commit is contained in:
Timothy Jaeryang Baek committed 2026-07-31 17:41:14 -04:00
1 parent 3becec6ccf
commit bb0f898b43
45 files changed
+552 -552

No files matched your search

+17 -18
View File
@@ -5,7 +5,6 @@ import base64
import hashlib
import html
import io
import json
import logging
import mimetypes
import os
@@ -27,13 +26,6 @@ from fastapi import (
status,
)
from fastapi.responses import FileResponse
from pydantic import BaseModel
# pydub needs stdlib audioop (gone in 3.13); keep requires-python capped < 3.13
from pydub import AudioSegment
from pydub.silence import split_on_silence
from pydub.utils import mediainfo
from open_webui.config import (
CACHE_DIR,
ELEVENLABS_API_BASE_URL,
@@ -60,8 +52,15 @@ from open_webui.models.config import Config
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.json_codec import JSONCodec
from open_webui.utils.misc import strict_match_mime_type
from open_webui.utils.session_pool import get_session
from pydantic import BaseModel
# pydub needs stdlib audioop (gone in 3.13); keep requires-python capped < 3.13
from pydub import AudioSegment
from pydub.silence import split_on_silence
from pydub.utils import mediainfo
log = logging.getLogger(__name__)
router = APIRouter()
@@ -357,7 +356,7 @@ async def _write_tts_cache(
async with aiofiles.open(file_path, 'wb') as f:
await f.write(audio)
async with aiofiles.open(body_path, 'w') as f:
await f.write(json.dumps(payload))
await f.write(JSONCodec.dumps(payload))
async def _tts_openai(request, payload, file_path, file_body_path, user):
@@ -395,7 +394,7 @@ async def _tts_openai(request, payload, file_path, file_body_path, user):
await f.write(audio_data)
async with aiofiles.open(file_body_path, 'w') as f:
await f.write(json.dumps(payload))
await f.write(JSONCodec.dumps(payload))
return FileResponse(file_path)
except Exception as exc:
@@ -503,7 +502,7 @@ async def _tts_transformers(request, payload, file_path, file_body_path, user):
# Audio file already written by sf.write; just persist the request metadata.
async with aiofiles.open(file_body_path, 'w') as f:
await f.write(json.dumps(payload))
await f.write(JSONCodec.dumps(payload))
return FileResponse(file_path)
@@ -591,7 +590,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
return FileResponse(file_path)
try:
payload = json.loads(body)
payload = JSONCodec.loads(body)
except Exception as exc:
log.exception(exc)
raise HTTPException(status_code=400, detail='Invalid JSON payload')
@@ -639,7 +638,7 @@ async def _transcribe_whisper(request, file_path, languages, file_dir, id):
data = {'text': transcript.strip()}
async with aiofiles.open(os.path.join(file_dir, f'{id}.json'), 'w') as f:
await f.write(json.dumps(data))
await f.write(JSONCodec.dumps(data))
log.debug(data)
return data
@@ -702,7 +701,7 @@ async def _transcribe_openai(request, file_path, filename, languages, file_dir,
data = await r.json()
async with aiofiles.open(os.path.join(file_dir, f'{id}.json'), 'w') as f:
await f.write(json.dumps(data))
await f.write(JSONCodec.dumps(data))
return data
except Exception as e:
log.exception(e)
@@ -762,7 +761,7 @@ async def _transcribe_deepgram(request, file_path, languages, file_dir, id):
data = {'text': transcript}
async with aiofiles.open(os.path.join(file_dir, f'{id}.json'), 'w') as f:
await f.write(json.dumps(data))
await f.write(JSONCodec.dumps(data))
return data
except Exception as e:
@@ -828,7 +827,7 @@ async def _transcribe_azure(request, file_path, filename, file_dir, id):
raise HTTPException(status_code=400, detail='Azure API key and region are required for Azure STT')
# Build the transcription definition payload
definition = json.dumps(
definition = JSONCodec.dumps(
{'locales': locale_str.split(','), 'diarization': {'maxSpeakers': max_speakers, 'enabled': True}}
if locale_str
else {}
@@ -868,7 +867,7 @@ async def _transcribe_azure(request, file_path, filename, file_dir, id):
data = {'text': transcript}
async with aiofiles.open(os.path.join(file_dir, f'{id}.json'), 'w') as f:
await f.write(json.dumps(data))
await f.write(JSONCodec.dumps(data))
log.debug(data)
return data
@@ -1046,7 +1045,7 @@ async def _transcribe_mistral(request, file_path, filename, metadata, file_dir,
data = {'text': transcript}
async with aiofiles.open(os.path.join(file_dir, f'{id}.json'), 'w') as f:
await f.write(json.dumps(data))
await f.write(JSONCodec.dumps(data))
log.debug(data)
return data
+6 -6
View File
@@ -1,7 +1,6 @@
import asyncio
import errno
import hashlib
import json
import logging
import os
import uuid
@@ -28,8 +27,8 @@ from open_webui.events import EVENTS, publish_event
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.config import Config
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.models.files import (
FileForm,
FileListResponse,
@@ -55,6 +54,7 @@ router = APIRouter()
from open_webui.utils.access_control.files import has_access_to_file
from open_webui.utils.json_codec import JSONCodec
############################
# Upload File
@@ -326,8 +326,8 @@ async def upload_file_handler(
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = JSONCodec.loads(metadata)
except JSONCodec.JSONDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT('Invalid metadata format'),
@@ -642,14 +642,14 @@ async def get_file_process_status(
if status == 'failed':
event['error'] = data.get('error')
yield f'data: {json.dumps(event)}\n\n'
yield f'data: {JSONCodec.dumps(event)}\n\n'
if status in ('completed', 'failed'):
break
else:
# Legacy
break
else:
yield f'data: {json.dumps({"status": "not_found"})}\n\n'
yield f'data: {JSONCodec.dumps({"status": "not_found"})}\n\n'
break
await asyncio.sleep(1)
+4 -4
View File
@@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
import base64
import io
import json
import logging
import mimetypes
import re
@@ -17,7 +16,6 @@ import aiofiles
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
from fastapi.responses import FileResponse
from PIL import Image, ImageOps
from open_webui.config import (
CACHE_DIR,
ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION,
@@ -43,7 +41,9 @@ from open_webui.utils.images.comfyui import (
comfyui_edit_image,
comfyui_upload_image,
)
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.session_pool import get_session
from PIL import Image, ImageOps
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
@@ -389,7 +389,7 @@ async def get_models(request: Request, user=Depends(get_verified_user)):
) as r:
info = await r.json()
workflow = json.loads(image_config.COMFYUI_WORKFLOW)
workflow = JSONCodec.loads(image_config.COMFYUI_WORKFLOW)
model_node_id = None
for node in image_config.COMFYUI_WORKFLOW_NODES:
@@ -1008,7 +1008,7 @@ async def image_edits(
form = aiohttp.FormData()
for key, value in data.items():
if isinstance(value, dict):
form.add_field(key, json.dumps(value))
form.add_field(key, JSONCodec.dumps(value))
else:
form.add_field(key, str(value))
for param_name, (filename, file_obj, content_type_val) in files:
+3 -3
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import asyncio
import io
import json
import logging
import time
import uuid
@@ -31,8 +30,8 @@ from open_webui.models.knowledge import (
KnowledgeUserResponse,
)
from open_webui.models.models import ModelForm, Models
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.retrieval.external import retrieve_external_knowledge, retrieve_external_knowledge_for_connection
from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT
from open_webui.routers.retrieval import (
BatchProcessFilesForm,
ProcessFileForm,
@@ -43,6 +42,7 @@ from open_webui.storage.provider import Storage
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.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.json_codec import JSONCodec
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
@@ -1277,7 +1277,7 @@ async def get_pending_knowledge_files(
for _ in range(MAX_POLL_DURATION // 3):
pending = await Files.get_pending_files_for_knowledge(knowledge_id)
data = [f.model_dump() for f in pending]
yield f'data: {json.dumps(data)}\n\n'
yield f'data: {JSONCodec.dumps(data)}\n\n'
if len(pending) == 0:
break
await asyncio.sleep(3)
+13 -15
View File
@@ -1,7 +1,6 @@
from __future__ import annotations
import asyncio
import json
import logging
import os
import random
@@ -16,12 +15,8 @@ import aiohttp
from aiocache import cached
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile
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.events import EVENTS, publish_event, publish_model_provider_request_failed
from open_webui.env import (
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
@@ -31,6 +26,7 @@ from open_webui.env import (
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
)
from open_webui.events import EVENTS, publish_event, publish_model_provider_request_failed
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.config import Config
@@ -40,15 +36,17 @@ 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 get_custom_headers, include_user_info_headers
from open_webui.utils.model_ids import strip_provider_model_prefix
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import calculate_sha256
from open_webui.utils.model_ids import strip_provider_model_prefix
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.session_pool import cleanup_response, get_client_timeout, get_session, stream_wrapper
from pydantic import BaseModel, ConfigDict, validator
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -619,7 +617,7 @@ async def unload_model(
try:
res = await send_request(
f'{url}/api/generate',
payload=json.dumps(payload),
payload=JSONCodec.dumps(payload),
key=key,
user=user,
)
@@ -657,7 +655,7 @@ async def pull_model(
# Admins may pull from any registry
return await send_request(
f'{url}/api/pull',
payload=json.dumps({**form_data, 'insecure': True}),
payload=JSONCodec.dumps({**form_data, 'insecure': True}),
key=get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {}))),
user=user,
stream=True,
@@ -812,7 +810,7 @@ async def delete_model(
await send_request(
f'{url}/api/delete',
'DELETE',
payload=json.dumps(payload),
payload=JSONCodec.dumps(payload),
key=key,
user=user,
)
@@ -854,7 +852,7 @@ async def show_model_info(
return await send_request(
f'{url}/api/show',
payload=json.dumps(payload),
payload=JSONCodec.dumps(payload),
key=key,
user=user,
)
@@ -1581,7 +1579,7 @@ async def download_file_stream(
) as blob_resp:
if blob_resp.ok:
await asyncio.to_thread(os.remove, file_path)
yield f'data: {json.dumps({"done": done, "blob": f"sha256:{hashed}", "name": file_name})}\n\n'
yield f'data: {JSONCodec.dumps({"done": done, "blob": f"sha256:{hashed}", "name": file_name})}\n\n'
else:
raise RuntimeError('Ollama: Could not create blob, Please try again.')
@@ -1651,7 +1649,7 @@ async def upload_model(
while chunk := await f.read(chunk_size):
bytes_read += len(chunk)
progress = round(bytes_read / total_size * 100, 2)
event = json.dumps({'progress': progress, 'total': total_size, 'completed': bytes_read})
event = JSONCodec.dumps({'progress': progress, 'total': total_size, 'completed': bytes_read})
yield f'data: {event}\n\n'
session = await get_session()
@@ -1688,13 +1686,13 @@ async def upload_model(
async with session.post(
f'{ollama_url}/api/create',
headers={'Content-Type': 'application/json'},
data=json.dumps(create_payload),
data=JSONCodec.dumps(create_payload),
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=get_client_timeout(),
) as create_resp:
if create_resp.ok:
log.info('API SUCCESS!')
event = json.dumps(
event = JSONCodec.dumps(
{'done': True, 'blob': f'sha256:{file_hash}', 'name': filename, 'model_created': model}
)
yield f'data: {event}\n\n'
@@ -1703,6 +1701,6 @@ async def upload_model(
raise Exception(f'Failed to create model in Ollama. {resp_text}')
except Exception as exc:
yield f'data: {json.dumps({"error": str(exc)})}\n\n'
yield f'data: {JSONCodec.dumps({"error": str(exc)})}\n\n'
return StreamingResponse(file_process_stream(), media_type='text/event-stream')
+14 -15
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import asyncio
import hashlib
import json
import logging
import re
from typing import Optional
@@ -23,7 +22,6 @@ from open_webui.config import (
CACHE_DIR,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.events import EVENTS, publish_event, publish_model_provider_request_failed
from open_webui.env import (
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
@@ -33,6 +31,7 @@ from open_webui.env import (
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
)
from open_webui.events import EVENTS, publish_event, publish_model_provider_request_failed
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.config import Config
@@ -44,11 +43,11 @@ 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.json_codec import JSONCodec
from open_webui.utils.model_ids import strip_provider_model_prefix
from open_webui.utils.misc import (
convert_logit_bias_input_to_json,
stream_chunks_handler,
)
from open_webui.utils.model_ids import strip_provider_model_prefix
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_system_prompt_to_body,
@@ -353,7 +352,7 @@ async def count_anthropic_tokens(request: Request, form_data: dict, user: UserMo
response = await session.request(
method='POST',
url=request_url,
data=json.dumps(payload),
data=JSONCodec.dumps(payload),
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
@@ -502,7 +501,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
await f.write(chunk)
async with aiofiles.open(file_body_path, 'w') as f:
await f.write(json.dumps(json.loads(body.decode('utf-8'))))
await f.write(JSONCodec.dumps(JSONCodec.loads(body.decode('utf-8'))))
# Return the saved file
return FileResponse(file_path)
@@ -1286,7 +1285,7 @@ async def generate_chat_completion(
logit_bias = convert_logit_bias_input_to_json(payload['logit_bias'])
if logit_bias:
payload['logit_bias'] = json.loads(logit_bias)
payload['logit_bias'] = JSONCodec.loads(logit_bias)
headers, cookies = await get_headers_and_cookies(request, url, key, api_config, metadata, user=user)
@@ -1338,7 +1337,7 @@ async def generate_chat_completion(
if not is_streaming_request:
payload.pop('stream_options', None)
payload = json.dumps(payload)
payload = JSONCodec.dumps(payload)
r = None
streaming = False
@@ -1370,7 +1369,7 @@ async def generate_chat_completion(
error_body[:1000],
)
try:
error_json = json.loads(error_body)
error_json = JSONCodec.loads(error_body)
await publish_model_provider_request_failed(
request,
actor=user,
@@ -1382,7 +1381,7 @@ async def generate_chat_completion(
upstream_error=error_json,
)
return JSONResponse(status_code=r.status, content=error_json)
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
await publish_model_provider_request_failed(
request,
actor=user,
@@ -1458,7 +1457,7 @@ async def embeddings(request: Request, form_data: dict, user):
"""
idx = 0
# Prepare payload/body
body = json.dumps(form_data)
body = JSONCodec.dumps(form_data)
# Find correct backend url/key based on model
model_id = form_data.get('model')
# Check if model is already in app state cache to avoid expensive get_all_models() call
@@ -1589,7 +1588,7 @@ async def responses(
# Enforce per-model access control
await check_model_access(user, await Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL)
body = json.dumps(payload)
body = JSONCodec.dumps(payload)
if model_id:
models = request.app.state.OPENAI_MODELS
@@ -1699,8 +1698,8 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
payload = None
if body:
try:
payload = json.loads(body)
except (json.JSONDecodeError, ValueError):
payload = JSONCodec.loads(body)
except (JSONCodec.JSONDecodeError, ValueError):
payload = None
is_streaming_request = bool(payload.get('stream', False)) if isinstance(payload, dict) else False
@@ -1738,9 +1737,9 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
api_version = api_config.get('api_version', '2023-03-15-preview')
headers['api-version'] = api_version
payload = json.loads(body)
payload = JSONCodec.loads(body)
url, payload = convert_to_azure_payload(url, payload, api_version)
body = json.dumps(payload).encode()
body = JSONCodec.dumps(payload).encode()
request_url = f'{url}/{path}?api-version={api_version}'
else:
+4 -4
View File
@@ -13,12 +13,13 @@ import aiohttp
from fastapi import APIRouter, Depends, Request, Response, WebSocket
from fastapi.responses import JSONResponse, StreamingResponse
from open_webui.config import TERMINAL_PROXY_HEADERS
from open_webui.events import EVENTS, publish_event
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.events import EVENTS, publish_event
from open_webui.models.config import Config
from open_webui.models.groups import Groups
from open_webui.utils.access_control import has_connection_access
from open_webui.utils.auth import get_verified_user
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.terminals import get_terminal_server_url
from open_webui.utils.tools import bearer_auth_header, normalize_bearer_token
from starlette.background import BackgroundTask
@@ -221,14 +222,13 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
with an appropriate error code.
"""
import asyncio
import json
from open_webui.utils.auth import get_verified_user_by_token
# First-message authentication
try:
raw = await asyncio.wait_for(ws.receive_text(), timeout=10.0)
payload = json.loads(raw)
payload = JSONCodec.loads(raw)
if payload.get('type') != 'auth':
await ws.close(code=4001, reason='Expected auth message')
return None
@@ -236,7 +236,7 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
if user is None:
await ws.close(code=4001, reason='Invalid token')
return None
except (asyncio.TimeoutError, json.JSONDecodeError):
except (asyncio.TimeoutError, JSONCodec.JSONDecodeError):
await ws.close(code=4001, reason='Auth timeout or invalid payload')
return None
except Exception: