This commit is contained in:
Timothy Jaeryang Baek
2026-07-31 17:41:14 -04:00
parent 3becec6ccf
commit bb0f898b43
45 changed files with 552 additions and 552 deletions
+26 -26
View File
@@ -1,4 +1,3 @@
import json
import logging
import aiohttp
@@ -9,6 +8,7 @@ from open_webui.env import (
)
from open_webui.models.users import UserModel
from open_webui.utils.headers import include_user_info_headers
from open_webui.utils.json_codec import JSONCodec
log = logging.getLogger(__name__)
@@ -235,7 +235,7 @@ def convert_anthropic_to_openai_payload(
'function': {
'name': block.get('name', ''),
'arguments': (
json.dumps(block.get('input', {}))
JSONCodec.dumps(block.get('input', {}))
if isinstance(block.get('input'), dict)
else str(block.get('input', '{}'))
),
@@ -531,8 +531,8 @@ def convert_openai_to_anthropic_response(
for tool_call in tool_calls:
function = tool_call.get('function', {})
try:
tool_input = json.loads(function.get('arguments', '{}'))
except (json.JSONDecodeError, TypeError):
tool_input = JSONCodec.loads(function.get('arguments', '{}'))
except (JSONCodec.JSONDecodeError, TypeError):
tool_input = {}
content.append(
{
@@ -643,7 +643,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'usage': {'input_tokens': input_tokens or 0, 'output_tokens': 0},
},
}
yield f'event: message_start\ndata: {json.dumps(message_start)}\n\n'.encode()
yield f'event: message_start\ndata: {JSONCodec.dumps(message_start)}\n\n'.encode()
try:
async for chunk in openai_stream_generator:
@@ -663,8 +663,8 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
continue
try:
data = json.loads(data_string)
except (json.JSONDecodeError, TypeError):
data = JSONCodec.loads(data_string)
except (JSONCodec.JSONDecodeError, TypeError):
continue
usage_data = data.get('usage')
@@ -730,7 +730,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'index': current_block_index,
'content_block': {'type': 'thinking', 'thinking': ''},
}
yield f'event: content_block_start\ndata: {json.dumps(block_start)}\n\n'.encode()
yield f'event: content_block_start\ndata: {JSONCodec.dumps(block_start)}\n\n'.encode()
thinking_block_open = True
block_delta = {
@@ -738,7 +738,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'index': current_block_index,
'delta': {'type': 'thinking_delta', 'thinking': reasoning_content},
}
yield f'event: content_block_delta\ndata: {json.dumps(block_delta)}\n\n'.encode()
yield f'event: content_block_delta\ndata: {JSONCodec.dumps(block_delta)}\n\n'.encode()
# --- Handle text content ---
# Anthropic expects text blocks before tool blocks, so skip
@@ -750,7 +750,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'type': 'content_block_stop',
'index': current_block_index,
}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
thinking_block_open = False
current_block_index += 1
@@ -760,7 +760,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'index': current_block_index,
'content_block': {'type': 'text', 'text': ''},
}
yield f'event: content_block_start\ndata: {json.dumps(block_start)}\n\n'.encode()
yield f'event: content_block_start\ndata: {JSONCodec.dumps(block_start)}\n\n'.encode()
text_block_open = True
block_delta = {
@@ -768,7 +768,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'index': current_block_index,
'delta': {'type': 'text_delta', 'text': content},
}
yield f'event: content_block_delta\ndata: {json.dumps(block_delta)}\n\n'.encode()
yield f'event: content_block_delta\ndata: {JSONCodec.dumps(block_delta)}\n\n'.encode()
# --- Handle tool calls ---
# Some providers put tool_calls on the final message object
@@ -784,7 +784,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'type': 'content_block_stop',
'index': current_block_index,
}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
thinking_block_open = False
current_block_index += 1
@@ -793,7 +793,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'type': 'content_block_stop',
'index': current_block_index,
}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
text_block_open = False
current_block_index += 1
@@ -856,7 +856,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'input': {},
},
}
yield f'event: content_block_start\ndata: {json.dumps(block_start)}\n\n'.encode()
yield f'event: content_block_start\ndata: {JSONCodec.dumps(block_start)}\n\n'.encode()
current_block_index += 1
# Buffer arguments and emit as input_json_delta
@@ -872,19 +872,19 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'partial_json': arguments_chunk,
},
}
yield f'event: content_block_delta\ndata: {json.dumps(block_delta)}\n\n'.encode()
yield f'event: content_block_delta\ndata: {JSONCodec.dumps(block_delta)}\n\n'.encode()
# Close the block once arguments form complete JSON
if tool['started'] and not tool['stopped']:
try:
json.loads(tool['arguments'])
JSONCodec.loads(tool['arguments'])
tool['stopped'] = True
block_stop = {
'type': 'content_block_stop',
'index': tool['block_index'],
}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
except (json.JSONDecodeError, ValueError):
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
except (JSONCodec.JSONDecodeError, ValueError):
pass
# --- Handle finish reason ---
@@ -902,7 +902,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
# Close any open thinking block
if thinking_block_open:
block_stop = {'type': 'content_block_stop', 'index': current_block_index}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
current_block_index += 1
# Flush any tools that buffered arguments but never emitted a block
@@ -921,7 +921,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'input': {},
},
}
yield f'event: content_block_start\ndata: {json.dumps(block_start)}\n\n'.encode()
yield f'event: content_block_start\ndata: {JSONCodec.dumps(block_start)}\n\n'.encode()
current_block_index += 1
if tool['arguments']:
@@ -933,18 +933,18 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
'partial_json': tool['arguments'],
},
}
yield f'event: content_block_delta\ndata: {json.dumps(block_delta)}\n\n'.encode()
yield f'event: content_block_delta\ndata: {JSONCodec.dumps(block_delta)}\n\n'.encode()
# Close any open text block
if text_block_open:
block_stop = {'type': 'content_block_stop', 'index': current_block_index}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
# Close any tool call blocks that are still open
for tool in tracked_tool_calls.values():
if tool['started'] and not tool['stopped']:
block_stop = {'type': 'content_block_stop', 'index': tool['block_index']}
yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode()
yield f'event: content_block_stop\ndata: {JSONCodec.dumps(block_stop)}\n\n'.encode()
# Emit message_delta with stop reason
usage = {'output_tokens': output_tokens}
@@ -969,7 +969,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
},
'usage': usage,
}
yield f'event: message_delta\ndata: {json.dumps(message_delta)}\n\n'.encode()
yield f'event: message_delta\ndata: {JSONCodec.dumps(message_delta)}\n\n'.encode()
# Emit message_stop
yield f'event: message_stop\ndata: {json.dumps({"type": "message_stop"})}\n\n'.encode()
yield f'event: message_stop\ndata: {JSONCodec.dumps({"type": "message_stop"})}\n\n'.encode()
+3 -3
View File
@@ -4,7 +4,6 @@ import asyncio
import base64
import hashlib
import hmac
import json
import logging
import os
import uuid
@@ -40,6 +39,7 @@ from open_webui.models.auths import Auths
from open_webui.models.config import Config
from open_webui.models.users import Users
from open_webui.utils.access_control import has_permission
from open_webui.utils.json_codec import JSONCodec
from pytz import UTC
log = logging.getLogger(__name__)
@@ -143,13 +143,13 @@ def get_license_data(app, key):
ln, lt = nt(lb)
aesgcm = AESGCM(kb)
p = json.loads(aesgcm.decrypt(ln, lt, None))
p = JSONCodec.loads(aesgcm.decrypt(ln, lt, None))
pk.verify(base64.b64decode(p['s']), p['p'].encode())
pb = base64.b64decode(p['p'])
pn, pt = nt(pb)
data = json.loads(aesgcm.decrypt(pn, pt, None).decode())
data = JSONCodec.loads(aesgcm.decrypt(pn, pt, None).decode())
exp = data.get('exp')
if exp:
+3 -3
View File
@@ -1,5 +1,4 @@
import asyncio
import json
import logging
import random
import sys
@@ -32,6 +31,7 @@ from open_webui.utils.filter import (
get_filter_functions,
process_filter_functions,
)
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.models import check_model_access, get_all_models
from open_webui.utils.payload import convert_payload_openai_to_ollama
from open_webui.utils.response import (
@@ -108,7 +108,7 @@ async def generate_direct_chat_completion(
if 'done' in data and data['done']:
break # Stop streaming when 'done' is received
yield f'data: {json.dumps(data)}\n\n'
yield f'data: {JSONCodec.dumps(data)}\n\n'
elif isinstance(data, str):
if 'data:' in data:
yield f'{data}\n\n'
@@ -249,7 +249,7 @@ async def generate_chat_completion(
if form_data.get('stream') == True:
async def stream_wrapper(stream):
yield f'data: {json.dumps({"selected_model_id": selected_model_id})}\n\n'
yield f'data: {JSONCodec.dumps({"selected_model_id": selected_model_id})}\n\n'
async for chunk in stream:
yield chunk
+5 -5
View File
@@ -1,9 +1,9 @@
from __future__ import annotations
import json
import re
from typing import Any
from open_webui.utils.json_codec import JSONCodec
CHAT_VARIABLE_KEY_RE = re.compile(r'^[a-z][a-z0-9_]*$')
CHAT_VARIABLE_ANY_RE = re.compile(r'{{\s*chat\.variables\.([^\s|}]+)(?:\s*\|\s*([^}]*))?\s*}}')
@@ -64,8 +64,8 @@ def parse_json_value(value: str) -> Any:
if re.match(r'^[\[{]', value):
try:
return json.loads(value)
except json.JSONDecodeError:
return JSONCodec.loads(value)
except JSONCodec.JSONDecodeError:
return value
return value
@@ -185,7 +185,7 @@ def validate_user_variables(variables: Any) -> dict[str, str]:
raise ChatVariablesError('User variables must be an object.')
try:
if len(json.dumps(variables)) > MAX_VARIABLES_JSON_LENGTH:
if len(JSONCodec.dumps(variables)) > MAX_VARIABLES_JSON_LENGTH:
raise ChatVariablesError('User variables are too large.')
except TypeError:
raise ChatVariablesError('User variables must be JSON serializable.')
@@ -214,7 +214,7 @@ def validate_chat_variables(
variables = normalize_chat_variables(variables)
try:
if len(json.dumps(variables)) > MAX_VARIABLES_JSON_LENGTH:
if len(JSONCodec.dumps(variables)) > MAX_VARIABLES_JSON_LENGTH:
raise ChatVariablesError('Chat variables are too large.')
except TypeError:
raise ChatVariablesError('Chat variables must be JSON serializable.')
+1 -2
View File
@@ -1,5 +1,4 @@
import asyncio
import json
import logging
import uuid
from typing import Optional
@@ -128,7 +127,7 @@ class JupyterCodeExecuter:
# send message
msg_id = uuid.uuid4().hex
await ws.send(
json.dumps(
JSONCodec.dumps(
{
'header': {
'msg_id': msg_id,
@@ -1,6 +1,5 @@
from __future__ import annotations
import json
import logging
from typing import Any
@@ -8,6 +7,7 @@ from fastapi.responses import JSONResponse
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.utils.chat_id import is_saved_chat_id
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import get_content_from_message, get_last_user_message, get_message_list
from open_webui.utils.task import (
get_task_model_id,
@@ -429,7 +429,7 @@ def _response_text(response: Any) -> str:
if isinstance(response, JSONResponse):
try:
response = json.loads(response.body.decode('utf-8', 'replace'))
response = JSONCodec.loads(response.body.decode('utf-8', 'replace'))
except Exception:
return ''
@@ -477,7 +477,7 @@ def _estimate_tokens(value: Any) -> int:
if not isinstance(value, str):
try:
value = json.dumps(value, ensure_ascii=False)
value = JSONCodec.dumps(value, ensure_ascii=False)
except Exception:
value = str(value)
+4 -4
View File
@@ -1,4 +1,3 @@
import json
import logging
import random
import urllib.parse
@@ -6,6 +5,7 @@ from typing import Optional
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.session_pool import get_session
from pydantic import BaseModel
@@ -76,7 +76,7 @@ async def _ws_get_images(ws, workflow, client_id, base_url, api_key):
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
message = json.loads(msg.data)
message = JSONCodec.loads(msg.data)
if message['type'] == 'executing':
data = message['data']
if data['node'] is None and data['prompt_id'] == prompt_id:
@@ -188,7 +188,7 @@ def _apply_workflow_nodes(workflow, nodes, model, payload):
async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key):
ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://')
workflow = json.loads(payload.workflow.workflow)
workflow = JSONCodec.loads(payload.workflow.workflow)
_apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload)
headers = {'Authorization': f'Bearer {api_key}'}
@@ -229,7 +229,7 @@ class ComfyUIEditImageForm(BaseModel):
async def comfyui_edit_image(model: str, payload: ComfyUIEditImageForm, client_id, base_url, api_key):
ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://')
workflow = json.loads(payload.workflow.workflow)
workflow = JSONCodec.loads(payload.workflow.workflow)
_apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload)
headers = {'Authorization': f'Bearer {api_key}'}
+2 -1
View File
@@ -19,6 +19,7 @@ from open_webui.env import (
LOG_FORMAT,
LOGURU_DIAGNOSE,
)
from open_webui.utils.json_codec import JSONCodec
if TYPE_CHECKING:
from loguru import Message, Record
@@ -34,7 +35,7 @@ def stdout_format(record: 'Record') -> str:
str: A formatted log string intended for stdout.
"""
if record['extra']:
record['extra']['extra_json'] = json.dumps(record['extra'])
record['extra']['extra_json'] = JSONCodec.dumps(record['extra'])
extra_format = ' - {extra[extra_json]}'
else:
extra_format = ''
+2 -3
View File
@@ -1,15 +1,14 @@
from __future__ import annotations
import asyncio
import json
import logging
import re
from typing import Any
from fastapi import HTTPException
from open_webui.models.config import Config
from open_webui.models.memories import Memories
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import add_or_update_system_message, get_content_from_message
log = logging.getLogger(__name__)
@@ -593,7 +592,7 @@ Conversation:
return []
try:
parsed = json.loads(content[start : end + 1])
parsed = JSONCodec.loads(content[start : end + 1])
except Exception:
return []
+16 -17
View File
@@ -40,6 +40,8 @@ from open_webui.env import (
GLOBAL_LOG_LEVEL,
RAG_SYSTEM_CONTEXT,
)
from open_webui.events import EVENTS, publish_event
from open_webui.models.access_grants import AccessGrants
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.models.folders import Folders
@@ -47,7 +49,6 @@ from open_webui.models.models import Models
from open_webui.models.notes import Notes
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.users import UserModel, Users
from open_webui.events import EVENTS, publish_event
from open_webui.retrieval.utils import get_sources_from_items
from open_webui.routers.images import (
CreateImageForm,
@@ -76,7 +77,6 @@ from open_webui.socket.main import (
get_event_emitter,
)
from open_webui.utils.access_control import has_connection_access, has_permission
from open_webui.models.access_grants import AccessGrants
from open_webui.utils.access_control.files import get_owner_accessible_folder_files
from open_webui.utils.access_control.folders import has_folder_access
from open_webui.utils.chat import generate_chat_completion
@@ -94,7 +94,6 @@ from open_webui.utils.filter import (
get_filter_functions,
process_filter_functions,
)
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.mcp.client import MCPClient
from open_webui.utils.memory import add_memory_context, review_memory_after_turn
@@ -242,7 +241,7 @@ def _split_tool_calls(
def split_json_objects(raw: str) -> list[str]:
if not isinstance(raw, str):
raw = '' if raw is None else json.dumps(raw)
raw = '' if raw is None else JSONCodec.dumps(raw)
decoder = json.JSONDecoder()
results = []
@@ -257,7 +256,7 @@ def _split_tool_calls(
_, end = decoder.raw_decode(raw, position)
results.append(raw[position:end].strip())
position = end
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
return [raw]
return results or [raw]
@@ -267,7 +266,7 @@ def _split_tool_calls(
function = tool_call.setdefault('function', {})
arguments = function.get('arguments')
if not isinstance(arguments, str):
arguments = '' if arguments is None else json.dumps(arguments)
arguments = '' if arguments is None else JSONCodec.dumps(arguments)
function['arguments'] = arguments
split_arguments = split_json_objects(arguments)
@@ -302,7 +301,7 @@ def get_citation_source_from_tool_result(
try:
try:
tool_result = JSONCodec.loads(tool_result)
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
pass # keep tool_result as-is (e.g. fetch_url returns plain text)
if isinstance(tool_result, dict) and 'error' in tool_result:
return []
@@ -1003,7 +1002,7 @@ async def process_tool_result(
if isinstance(text, str):
try:
text = JSONCodec.loads(text)
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
pass
tool_response.append(text)
elif item.get('type') in ['image', 'audio']:
@@ -1031,7 +1030,7 @@ async def process_tool_result(
if isinstance(text, str) and text:
try:
text = JSONCodec.loads(text)
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
pass
tool_response.append(text)
elif resource.get('blob'):
@@ -1106,7 +1105,7 @@ async def terminal_event_handler(
if isinstance(parsed, str):
try:
parsed = JSONCodec.loads(parsed)
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
pass
if isinstance(parsed, dict) and parsed.get('exists') is False:
return
@@ -1198,7 +1197,7 @@ async def chat_completion_tools_handler(
sources = []
specs = [tool['spec'] for tool in tools.values()]
tools_specs = json.dumps(specs, ensure_ascii=False)
tools_specs = JSONCodec.dumps(specs, ensure_ascii=False)
tools_prompt_template = task_config.get('task.tools.prompt_template')
if tools_prompt_template != '':
@@ -1969,7 +1968,7 @@ def apply_params_to_form_data(form_data, model):
try:
# Attempt to parse the string as JSON
custom_params[key] = JSONCodec.loads(value)
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
# If it fails, keep the original string
pass
@@ -3029,7 +3028,7 @@ def get_response_data(response):
if isinstance(response.body, bytes):
try:
response_data = JSONCodec.loads(response.body.decode('utf-8', 'replace'))
except json.JSONDecodeError:
except JSONCodec.JSONDecodeError:
response_data = {'error': {'detail': 'Invalid JSON response'}}
else:
response_data = response
@@ -4471,7 +4470,7 @@ async def streaming_chat_response_handler(response, ctx):
delta_tool_call['function']['arguments'] = (
''
if delta_arguments is None
else json.dumps(delta_arguments)
else JSONCodec.dumps(delta_arguments)
)
response_tool_calls.append(delta_tool_call)
else:
@@ -4486,7 +4485,7 @@ async def streaming_chat_response_handler(response, ctx):
if delta_arguments is not None:
if not isinstance(delta_arguments, str):
delta_arguments = json.dumps(delta_arguments)
delta_arguments = JSONCodec.dumps(delta_arguments)
current_response_tool_call.setdefault('function', {})
if not isinstance(
current_response_tool_call['function'].get('arguments'),
@@ -4874,7 +4873,7 @@ async def streaming_chat_response_handler(response, ctx):
'function': {
'name': item.get('name', ''),
'arguments': (
arguments if isinstance(arguments, str) else json.dumps(arguments)
arguments if isinstance(arguments, str) else JSONCodec.dumps(arguments)
),
},
}
@@ -4963,7 +4962,7 @@ async def streaming_chat_response_handler(response, ctx):
except Exception as e:
log.debug(e)
return None
tool_call.setdefault('function', {})['arguments'] = json.dumps(params)
tool_call.setdefault('function', {})['arguments'] = JSONCodec.dumps(params)
return params
async def execute_tool_call(tool_call):
+7 -7
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import collections.abc
import hashlib
import json
import logging
import re
import threading
@@ -15,6 +14,7 @@ from typing import Callable, Optional, Sequence, Union
import aiohttp
import mimeparse
from open_webui.env import CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE
from open_webui.utils.json_codec import JSONCodec
log = logging.getLogger(__name__)
SURROGATE_RE = re.compile('[\ud800-\udfff]')
@@ -355,7 +355,7 @@ def convert_output_to_messages(
arguments = item.get('arguments', '{}')
# Ensure arguments is always a JSON string
if not isinstance(arguments, str):
arguments = json.dumps(arguments)
arguments = JSONCodec.dumps(arguments)
pending_tool_calls.append(
{
'id': item.get('call_id', ''),
@@ -822,7 +822,7 @@ def sanitize_data_for_db(obj):
# json.dumps is implemented in C and much faster than a Python-level
# recursive walk over every leaf string.
try:
serialized = json.dumps(obj, ensure_ascii=False)
serialized = JSONCodec.dumps(obj, ensure_ascii=False)
if '\\u0000' not in serialized:
serialized.encode('utf-8')
return obj
@@ -854,7 +854,7 @@ def sanitize_metadata(metadata: dict) -> dict:
return None
# Last resort: try to see if it's serializable
try:
json.dumps(obj)
JSONCodec.dumps(obj)
return obj
except (TypeError, ValueError):
return None
@@ -864,7 +864,7 @@ def sanitize_metadata(metadata: dict) -> dict:
if isinstance(obj, (str, int, float, bool, type(None), dict, list)):
return True
try:
json.dumps(obj)
JSONCodec.dumps(obj)
return True
except (TypeError, ValueError):
return False
@@ -1018,7 +1018,7 @@ def convert_logit_bias_input_to_json(logit_bias_input) -> str | None:
return None
if isinstance(logit_bias_input, dict):
return json.dumps(logit_bias_input)
return JSONCodec.dumps(logit_bias_input)
logit_bias_pairs = logit_bias_input.split(',')
logit_bias_json = {}
@@ -1028,7 +1028,7 @@ def convert_logit_bias_input_to_json(logit_bias_input) -> str | None:
bias = int(bias.strip())
bias = 100 if bias > 100 else -100 if bias < -100 else bias
logit_bias_json[token] = bias
return json.dumps(logit_bias_json)
return JSONCodec.dumps(logit_bias_json)
def freeze(value):
+7 -7
View File
@@ -2,7 +2,6 @@ import asyncio
import base64
import fnmatch
import hashlib
import json
import logging
import re
import sys
@@ -35,8 +34,8 @@ from mcp.shared.auth import (
)
from open_webui.config import (
DEFAULT_USER_ROLE,
ENABLE_OAUTH_GROUP_CREATION,
ENABLE_OAUTH,
ENABLE_OAUTH_GROUP_CREATION,
ENABLE_OAUTH_GROUP_MANAGEMENT,
ENABLE_OAUTH_ROLE_MANAGEMENT,
ENABLE_OAUTH_SIGNUP,
@@ -67,7 +66,6 @@ from open_webui.config import (
WEBHOOK_URL,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.events import EVENTS, publish_event
from open_webui.env import (
AIOHTTP_CLIENT_ALLOW_REDIRECTS,
AIOHTTP_CLIENT_SESSION_SSL,
@@ -79,6 +77,7 @@ from open_webui.env import (
WEBUI_AUTH_COOKIE_SAME_SITE,
WEBUI_AUTH_COOKIE_SECURE,
)
from open_webui.events import EVENTS, publish_event
from open_webui.models.auths import Auths
from open_webui.models.config import Config
from open_webui.models.groups import GroupForm, GroupModel, Groups, GroupUpdateForm
@@ -114,6 +113,7 @@ class OAuthClientInformationFull(OAuthClientMetadata):
from open_webui.env import GLOBAL_LOG_LEVEL
from open_webui.utils.json_codec import JSONCodec
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)
@@ -265,7 +265,7 @@ except Exception as e:
def encrypt_data(data) -> str:
"""Encrypt data for storage"""
try:
data_json = json.dumps(data)
data_json = JSONCodec.dumps(data)
encrypted = FERNET.encrypt(data_json.encode()).decode()
return encrypted
except Exception as e:
@@ -277,7 +277,7 @@ def decrypt_data(data: str):
"""Decrypt data from storage"""
try:
decrypted = FERNET.decrypt(data.encode()).decode()
return json.loads(decrypted)
return JSONCodec.loads(decrypted)
except Exception as e:
log.error(f'Error decrypting data: {e}')
raise
@@ -968,7 +968,7 @@ class OAuthClientManager:
content_type = resp.headers.get('content-type', '')
if 'application/json' in content_type:
try:
payload = json.loads(response_text)
payload = JSONCodec.loads(response_text)
error = payload.get('error')
error_description = payload.get('error_description', '')
except Exception:
@@ -1556,7 +1556,7 @@ class OAuthManager:
oauth_claim = auth_config.OAUTH_GROUPS_CLAIM
try:
blocked_groups = json.loads(auth_config.OAUTH_BLOCKED_GROUPS)
blocked_groups = JSONCodec.loads(auth_config.OAUTH_BLOCKED_GROUPS)
except Exception as e:
log.exception(f'Error loading OAUTH_BLOCKED_GROUPS: {e}')
blocked_groups = []
+9 -9
View File
@@ -1,12 +1,12 @@
import json
from typing import Callable, Optional
from open_webui.utils.chat_variables import render_chat_variables, render_user_variables
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import (
add_or_update_system_message,
deep_update,
replace_system_message_content,
)
from open_webui.utils.chat_variables import render_chat_variables, render_user_variables
from open_webui.utils.task import prompt_template, prompt_variables_template
@@ -115,8 +115,8 @@ def apply_model_params_to_body_openai(params: dict, form_data: dict) -> dict:
if isinstance(value, str):
try:
# Attempt to parse the string as JSON
custom_params[key] = json.loads(value)
except json.JSONDecodeError:
custom_params[key] = JSONCodec.loads(value)
except JSONCodec.JSONDecodeError:
# If it fails, keep the original string
pass
@@ -149,8 +149,8 @@ def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
if isinstance(value, str):
try:
# Attempt to parse the string as JSON
custom_params[key] = json.loads(value)
except json.JSONDecodeError:
custom_params[key] = JSONCodec.loads(value)
except JSONCodec.JSONDecodeError:
# If it fails, keep the original string
pass
@@ -198,7 +198,7 @@ def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
Parses a JSON string into a dictionary, handling potential JSONDecodeError.
"""
try:
return json.loads(value)
return JSONCodec.loads(value)
except Exception as e:
return value
@@ -253,7 +253,7 @@ def convert_messages_openai_to_ollama(messages: list[dict]) -> list[dict]:
'id': tool_call.get('id', None),
'function': {
'name': tool_call.get('function', {}).get('name', ''),
'arguments': json.loads(tool_call.get('function', {}).get('arguments', {})),
'arguments': JSONCodec.loads(tool_call.get('function', {}).get('arguments', {})),
},
}
ollama_tool_calls.append(ollama_tool_call)
@@ -336,7 +336,7 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
Parses a JSON string into a dictionary, handling potential JSONDecodeError.
"""
try:
return json.loads(value)
return JSONCodec.loads(value)
except Exception as e:
return value
+1 -2
View File
@@ -1,4 +1,3 @@
import json
from numbers import Number
from uuid import uuid4
@@ -158,7 +157,7 @@ def convert_ollama_tool_call_to_openai(tool_calls: list) -> list:
'type': 'function',
'function': {
'name': function.get('name', ''),
'arguments': json.dumps(function.get('arguments', {})),
'arguments': JSONCodec.dumps(function.get('arguments', {})),
},
}
openai_tool_calls.append(openai_tool_call)
+2 -2
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import asyncio
import copy
import json
import time
from datetime import timedelta
from uuid import uuid4
@@ -16,6 +15,7 @@ from open_webui.models.config import Config
from open_webui.models.users import UserModel, Users
from open_webui.tasks import create_task, has_active_tasks
from open_webui.utils.auth import create_token
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import get_message_list
from sqlalchemy import select
from starlette.datastructures import Headers
@@ -647,7 +647,7 @@ async def delegate(
return f'Error: {exc}'
if background:
return json.dumps(
return JSONCodec.dumps(
{
'status': 'dispatched',
'delegation_id': delegation_id,
+4 -5
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio
import copy
import json
import logging
import re
import time
@@ -13,15 +12,15 @@ from typing import Literal
from uuid import uuid4
from fastapi import Request
from sqlalchemy import select
from starlette.datastructures import Headers
from open_webui.internal.db import get_async_db
from open_webui.models.chat_messages import ChatMessages
from open_webui.models.chats import Chat, ChatForm, Chats
from open_webui.models.users import UserModel, Users
from open_webui.tasks import has_active_tasks
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import get_message_list
from sqlalchemy import select
from starlette.datastructures import Headers
log = logging.getLogger(__name__)
@@ -157,7 +156,7 @@ async def create_timer(
if not chat:
return 'Error: failed to create timer.'
return json.dumps(
return JSONCodec.dumps(
{
'status': 'set',
'at': datetime.fromtimestamp(due_at / 1_000_000_000, timezone.utc).isoformat().replace('+00:00', 'Z'),
+10 -10
View File
@@ -4,7 +4,6 @@ import asyncio
import base64
import copy
import inspect
import json
import logging
import os
import re
@@ -46,7 +45,6 @@ from open_webui.models.config import Config
from open_webui.models.groups import Groups
from open_webui.models.tools import Tools
from open_webui.models.users import UserModel
from open_webui.utils.chat_id import is_saved_chat_id
from open_webui.tools.builtin import (
add_memory,
calculate_timestamp,
@@ -65,8 +63,8 @@ from open_webui.tools.builtin import (
grep_chat_files,
grep_knowledge_files,
kb_exec,
list_chat_files,
list_automations,
list_chat_files,
list_knowledge,
list_knowledge_bases,
list_memories,
@@ -103,7 +101,9 @@ from open_webui.tools.builtin import (
write_note,
)
from open_webui.utils.access_control import has_access, has_connection_access, has_permission
from open_webui.utils.chat_id import is_saved_chat_id
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.misc import is_string_allowed
from open_webui.utils.plugin import get_tool_contents_cache, get_tools_cache, load_tool_module_by_id
from open_webui.utils.terminals import get_terminal_server_url
@@ -1141,7 +1141,7 @@ async def set_tool_servers(request: Request):
try:
if request.app.state.redis is not None:
await request.app.state.redis.set(
f'{REDIS_KEY_PREFIX}:tool_servers', json.dumps(request.app.state.TOOL_SERVERS)
f'{REDIS_KEY_PREFIX}:tool_servers', JSONCodec.dumps(request.app.state.TOOL_SERVERS)
)
except Exception as e:
log.error(f'Error caching tool_servers to Redis: {e}')
@@ -1154,7 +1154,7 @@ async def get_tool_servers(request: Request):
tool_servers = []
if request.app.state.redis is not None:
try:
tool_servers = json.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:tool_servers'))
tool_servers = JSONCodec.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:tool_servers'))
request.app.state.TOOL_SERVERS = tool_servers
except Exception as e:
log.error(f'Error fetching tool_servers from Redis: {e}')
@@ -1286,7 +1286,7 @@ async def set_terminal_servers(request: Request):
if request.app.state.redis is not None:
await request.app.state.redis.set(
f'{REDIS_KEY_PREFIX}:terminal_servers', json.dumps(request.app.state.TERMINAL_SERVERS)
f'{REDIS_KEY_PREFIX}:terminal_servers', JSONCodec.dumps(request.app.state.TERMINAL_SERVERS)
)
return request.app.state.TERMINAL_SERVERS
@@ -1297,7 +1297,7 @@ async def get_terminal_servers(request: Request):
terminal_servers = []
if request.app.state.redis is not None:
try:
terminal_servers = json.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:terminal_servers'))
terminal_servers = JSONCodec.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:terminal_servers'))
request.app.state.TERMINAL_SERVERS = terminal_servers
except Exception as e:
log.error(f'Error fetching terminal_servers from Redis: {e}')
@@ -1434,8 +1434,8 @@ async def get_tool_server_data(url: str, headers: dict | None) -> dict[str, Any]
res = yaml.safe_load(text_content)
else:
try:
res = json.loads(text_content)
except json.JSONDecodeError:
res = JSONCodec.loads(text_content)
except JSONCodec.JSONDecodeError:
# Fall back to YAML for non-.yml URLs that aren't valid JSON
res = yaml.safe_load(text_content)
@@ -1491,7 +1491,7 @@ async def get_tool_servers_data(servers: list[dict[str, Any]]) -> list[dict[str,
# Use provided JSON spec
spec_json = None
try:
spec_json = json.loads(server.get('spec', ''))
spec_json = JSONCodec.loads(server.get('spec', ''))
except Exception as e:
log.error(f'Error parsing JSON spec for tool server {id}: {e}')
+4 -4
View File
@@ -1,11 +1,11 @@
import base64
import hashlib
import json
import logging
from functools import lru_cache
from cryptography.fernet import Fernet, InvalidToken
from open_webui.env import ENABLE_VALVE_ENCRYPTION, WEBUI_SECRET_KEY
from open_webui.utils.json_codec import JSONCodec
log = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ def _fernet() -> Fernet:
def encrypt_valves(valves: dict) -> dict | str:
if not ENABLE_VALVE_ENCRYPTION:
return valves
return _fernet().encrypt(json.dumps(valves).encode()).decode()
return _fernet().encrypt(JSONCodec.dumps(valves).encode()).decode()
def decrypt_valves(valves) -> dict:
@@ -33,8 +33,8 @@ def decrypt_valves(valves) -> dict:
return {}
try:
decrypted = json.loads(_fernet().decrypt(valves.encode()).decode())
except (InvalidToken, json.JSONDecodeError) as e:
decrypted = JSONCodec.loads(_fernet().decrypt(valves.encode()).decode())
except (InvalidToken, JSONCodec.JSONDecodeError) as e:
log.warning('Failed to decrypt valves: %s', type(e).__name__)
return {}
+2 -2
View File
@@ -1,5 +1,4 @@
import asyncio
import json
import logging
from open_webui.config import WEBUI_FAVICON_URL
@@ -9,6 +8,7 @@ from open_webui.env import (
VERSION,
)
from open_webui.retrieval.web.utils import get_ssrf_safe_session, validate_url
from open_webui.utils.json_codec import JSONCodec
log = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ async def post_webhook(name: str, url: str, message: str, event_data: dict, desc
if isinstance(user_data, dict):
user_dict = user_data
else:
user_dict = json.loads(user_data)
user_dict = JSONCodec.loads(user_data)
facts = [{'name': key, 'value': value} for key, value in user_dict.items()]
if event_data.get('event'):
facts.insert(0, {'name': 'event', 'value': event_data.get('event')})