diff --git a/backend/open_webui/retrieval/vector/utils.py b/backend/open_webui/retrieval/vector/utils.py index a597390b92..ef40e088f1 100644 --- a/backend/open_webui/retrieval/vector/utils.py +++ b/backend/open_webui/retrieval/vector/utils.py @@ -13,16 +13,42 @@ def filter_metadata(metadata: dict[str, any]) -> dict[str, any]: def process_metadata( metadata: dict[str, any], ) -> dict[str, any]: - for key, value in metadata.items(): - # Remove large fields - if key in KEYS_TO_EXCLUDE: - del metadata[key] + """ + Process metadata for vector storage. - # Convert non-serializable fields to strings - if ( - isinstance(value, datetime) - or isinstance(value, list) - or isinstance(value, dict) - ): + - Removes large excluded fields + - Converts non-JSON-serializable types (datetime, dict) to strings + - Preserves lists/arrays (Pinecone supports array filtering with $in) + """ + keys_to_delete = [] + + for key, value in metadata.items(): + # Mark large fields for deletion + if key in KEYS_TO_EXCLUDE: + keys_to_delete.append(key) + continue + + # Convert datetime to ISO string + if isinstance(value, datetime): + metadata[key] = value.isoformat() + # Convert dict to string (nested objects not supported in most vector DBs) + elif isinstance(value, dict): metadata[key] = str(value) + # KEEP lists as-is - Pinecone supports arrays for $in filtering + # e.g., filter: {"labels": {"$in": ["SENT"]}} + elif isinstance(value, list): + # Ensure list items are serializable (strings, numbers, booleans) + metadata[key] = [ + ( + str(item) + if not isinstance(item, (str, int, float, bool, type(None))) + else item + ) + for item in value + ] + + # Delete excluded keys (can't modify dict while iterating) + for key in keys_to_delete: + del metadata[key] + return metadata diff --git a/backend/open_webui/utils/gmail_auto_sync.py b/backend/open_webui/utils/gmail_auto_sync.py index 7f07bbcd38..268d3107dc 100644 --- a/backend/open_webui/utils/gmail_auto_sync.py +++ b/backend/open_webui/utils/gmail_auto_sync.py @@ -1238,10 +1238,20 @@ async def _sync_user_periodic(user_id: str) -> bool: logger.debug(f"⏭️ No Google OAuth session for user {user_id}") return False - # Use OAuth token from session - # Note: Token refresh happens automatically on next OAuth login - # If token is expired, Gmail API will fail and we'll retry on next cycle - oauth_token = oauth_session.token + # Get refreshed OAuth token using OAuth manager (auto-refreshes if expired) + # This is critical for long-running syncs where token may expire mid-process + try: + from open_webui.main import app + + oauth_token = await app.state.oauth_manager.get_oauth_token( + user_id=user_id, + session_id=oauth_session.id, + ) + except Exception as e: + logger.warning( + f"⚠️ Failed to get/refresh OAuth token for user {user_id}: {e}" + ) + oauth_token = oauth_session.token # Fallback to stored token # Validation: Check OAuth token exists and has access_token if not oauth_token or not oauth_token.get("access_token"):