Skip to content

Commit 1364df0

Browse files
committed
refac
1 parent 352391f commit 1364df0

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

backend/open_webui/models/files.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from sqlalchemy.orm import Session
66
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
7+
from open_webui.utils.misc import sanitize_metadata
78
from pydantic import BaseModel, ConfigDict, model_validator
89
from sqlalchemy import BigInteger, Column, String, Text, JSON
910

@@ -127,9 +128,16 @@ def insert_new_file(
127128
self, user_id: str, form_data: FileForm, db: Optional[Session] = None
128129
) -> Optional[FileModel]:
129130
with get_db_context(db) as db:
131+
file_data = form_data.model_dump()
132+
133+
# Sanitize meta to remove non-JSON-serializable objects
134+
# (e.g. callable tool functions, MCP client instances from middleware)
135+
if file_data.get("meta"):
136+
file_data["meta"] = sanitize_metadata(file_data["meta"])
137+
130138
file = FileModel(
131139
**{
132-
**form_data.model_dump(),
140+
**file_data,
133141
"user_id": user_id,
134142
"created_at": int(time.time()),
135143
"updated_at": int(time.time()),

backend/open_webui/utils/misc.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,51 @@ def sanitize_data_for_db(obj):
566566
return obj
567567

568568

569+
def sanitize_metadata(metadata: dict) -> dict:
570+
"""
571+
Return a JSON-safe copy of a metadata dict for database storage.
572+
573+
The middleware metadata accumulates non-serializable Python objects
574+
(e.g. callable tool functions, MCP client instances) that cause
575+
PostgreSQL JSON inserts to fail. This helper strips those out while
576+
preserving the primitive data needed for file-to-chat linking.
577+
"""
578+
if not isinstance(metadata, dict):
579+
return metadata
580+
581+
def _sanitize(obj):
582+
if isinstance(obj, (str, int, float, bool, type(None))):
583+
return obj
584+
if isinstance(obj, dict):
585+
return {
586+
k: _sanitize(v)
587+
for k, v in obj.items()
588+
if not callable(v) and _is_serializable(v)
589+
}
590+
if isinstance(obj, list):
591+
return [_sanitize(v) for v in obj if not callable(v) and _is_serializable(v)]
592+
if callable(obj):
593+
return None
594+
# Last resort: try to see if it's serializable
595+
try:
596+
json.dumps(obj)
597+
return obj
598+
except (TypeError, ValueError):
599+
return None
600+
601+
def _is_serializable(obj):
602+
"""Quick check whether a value can survive JSON serialization."""
603+
if isinstance(obj, (str, int, float, bool, type(None), dict, list)):
604+
return True
605+
try:
606+
json.dumps(obj)
607+
return True
608+
except (TypeError, ValueError):
609+
return False
610+
611+
return _sanitize(metadata)
612+
613+
569614
def extract_folders_after_data_docs(path):
570615
# Convert the path to a Path object if it's not already
571616
path = Path(path)

0 commit comments

Comments
 (0)