From 1364df09136af2f30365a187ad4bf716435926e9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 8 Mar 2026 18:26:36 -0500 Subject: [PATCH] refac --- backend/open_webui/models/files.py | 10 ++++++- backend/open_webui/utils/misc.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index 59459cfb90..84dd43f5e8 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -4,6 +4,7 @@ from typing import Optional from sqlalchemy.orm import Session from open_webui.internal.db import Base, JSONField, get_db, get_db_context +from open_webui.utils.misc import sanitize_metadata from pydantic import BaseModel, ConfigDict, model_validator from sqlalchemy import BigInteger, Column, String, Text, JSON @@ -127,9 +128,16 @@ class FilesTable: self, user_id: str, form_data: FileForm, db: Optional[Session] = None ) -> Optional[FileModel]: with get_db_context(db) as db: + file_data = form_data.model_dump() + + # Sanitize meta to remove non-JSON-serializable objects + # (e.g. callable tool functions, MCP client instances from middleware) + if file_data.get("meta"): + file_data["meta"] = sanitize_metadata(file_data["meta"]) + file = FileModel( **{ - **form_data.model_dump(), + **file_data, "user_id": user_id, "created_at": int(time.time()), "updated_at": int(time.time()), diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index 35e19d70ce..bab1bb4bbf 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -566,6 +566,51 @@ def sanitize_data_for_db(obj): return obj +def sanitize_metadata(metadata: dict) -> dict: + """ + Return a JSON-safe copy of a metadata dict for database storage. + + The middleware metadata accumulates non-serializable Python objects + (e.g. callable tool functions, MCP client instances) that cause + PostgreSQL JSON inserts to fail. This helper strips those out while + preserving the primitive data needed for file-to-chat linking. + """ + if not isinstance(metadata, dict): + return metadata + + def _sanitize(obj): + if isinstance(obj, (str, int, float, bool, type(None))): + return obj + if isinstance(obj, dict): + return { + k: _sanitize(v) + for k, v in obj.items() + if not callable(v) and _is_serializable(v) + } + if isinstance(obj, list): + return [_sanitize(v) for v in obj if not callable(v) and _is_serializable(v)] + if callable(obj): + return None + # Last resort: try to see if it's serializable + try: + json.dumps(obj) + return obj + except (TypeError, ValueError): + return None + + def _is_serializable(obj): + """Quick check whether a value can survive JSON serialization.""" + if isinstance(obj, (str, int, float, bool, type(None), dict, list)): + return True + try: + json.dumps(obj) + return True + except (TypeError, ValueError): + return False + + return _sanitize(metadata) + + def extract_folders_after_data_docs(path): # Convert the path to a Path object if it's not already path = Path(path)