diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 88801fa00f..4b5e6c237b 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -735,7 +735,7 @@ async def create_new_chat( subject_id=chat.id, data={'title': chat.title, 'folder_id': chat.folder_id}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) except Exception as e: log.exception(e) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()) @@ -820,7 +820,17 @@ async def search_user_chats( search_text = chat_search_content_text(text) chat_list = [] for chat in await Chats.get_chats_by_user_id_and_search_text(user.id, text, skip=skip, limit=limit, db=db): - chat_list.append(ChatTitleIdResponse(**chat.model_dump(), snippet=chat_search_snippet(chat.chat, search_text))) + # Explicit fields: model_dump() would deep-copy the entire chat blob per row + chat_list.append( + ChatTitleIdResponse( + id=chat.id, + title=chat.title, + updated_at=chat.updated_at, + created_at=chat.created_at, + last_read_at=chat.last_read_at, + snippet=chat_search_snippet(chat.chat, search_text), + ) + ) # Delete tag if no chat is found words = text.strip().split(' ') @@ -849,7 +859,7 @@ async def get_chats_by_folder_id( folder_ids.extend([folder.id for folder in children_folders]) return [ - ChatResponse(**chat.model_dump()) + ChatResponse.model_validate(chat, from_attributes=True) for chat in await Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id, db=db) ] @@ -915,7 +925,7 @@ async def generate_chat_export_ndjson(user_id: str): for chat in result.items: try: - yield ChatResponse(**chat.model_dump()).model_dump_json() + '\n' + yield ChatResponse.model_validate(chat, from_attributes=True).model_dump_json() + '\n' except Exception as e: log.exception(f'Error serializing chat {chat.id}: {e}') @@ -940,7 +950,7 @@ async def get_user_chats(user=Depends(get_verified_user)): @router.get('/all/archived', response_model=list[ChatResponse]) async def get_user_archived_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): - return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_archived_chats_by_user_id(user.id, db=db)] + return [ChatResponse.model_validate(chat, from_attributes=True) for chat in await Chats.get_archived_chats_by_user_id(user.id, db=db)] ############################ @@ -967,7 +977,7 @@ async def get_all_user_tags(user=Depends(get_verified_user), db: AsyncSession = async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): if not ENABLE_ADMIN_EXPORT: raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) - return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_chats(db=db)] + return [ChatResponse.model_validate(chat, from_attributes=True) for chat in await Chats.get_chats(db=db)] ############################ @@ -1133,7 +1143,7 @@ async def get_shared_chat_by_id( ): chat = await Chats.get_chat_by_share_id(share_id, db=db) if chat: - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -1145,7 +1155,7 @@ async def get_shared_chat_by_id( if user is not None and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS: chat = await Chats.get_chat_by_id(share_id, db=db) if chat: - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) @@ -1270,7 +1280,7 @@ async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSess chat = candidate if chat: - data = ChatResponse(**chat.model_dump()).model_dump() + data = ChatResponse.model_validate(chat, from_attributes=True).model_dump() data['context_usage'] = await get_chat_context_usage(chat) return data @@ -1316,7 +1326,7 @@ async def update_chat_by_id( subject_id=id, data={'title': chat.title}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -1390,7 +1400,7 @@ async def update_chat_message_by_id( subject_id=message_id, data={'chat_id': id, 'content_preview': form_data.content[:300]}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) @router.delete('/{id}/messages/{message_id}', response_model=ChatResponse | None) @@ -1429,7 +1439,7 @@ async def delete_chat_message_by_id( subject_id=message_id, data={'chat_id': id}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) ############################ @@ -1685,7 +1695,7 @@ async def fork_chat_by_id( subject_id=fork.id, data={'original_chat_id': id, 'forked_from_message_id': source_message_id}, ) - return ChatResponse(**fork.model_dump()) + return ChatResponse.model_validate(fork, from_attributes=True) @router.post('/{id}/clone', response_model=ChatResponse | None) @@ -1732,7 +1742,7 @@ async def clone_chat_by_id( subject_id=chat.id, data={'original_chat_id': id}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -1809,7 +1819,7 @@ async def clone_shared_chat_by_id( if chats: chat = chats[0] - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -1850,7 +1860,7 @@ async def archive_chat_by_id( subject_id=id, subject_type='chat', ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) else: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()) @@ -1884,7 +1894,7 @@ async def share_chat_by_id( subject_id=id, data={'share_id': chat.share_id, 'updated': True}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) # Create a new share shared = await SharedChats.create(id, user.id, db=db) @@ -1902,7 +1912,7 @@ async def share_chat_by_id( subject_id=id, data={'share_id': shared.id}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) # --- Delete Shared Chat --- @@ -1972,7 +1982,7 @@ async def update_shared_chat_access_by_id( await AccessGrants.set_access_grants('shared_chat', id, form_data.access_grants, db=db) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) ############################ @@ -2047,7 +2057,7 @@ async def update_chat_folder_id_by_id( subject_id=id, data={'folder_id': form_data.folder_id}, ) - return ChatResponse(**chat.model_dump()) + return ChatResponse.model_validate(chat, from_attributes=True) else: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())