From c3394288bb3e40dd57c05e693203be8be8146cc3 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 29 Jun 2026 09:05:54 +0200 Subject: [PATCH] fix: repair Mistral OCR async upload (aiohttp.streams.FilePayload removed) (#25779) _upload_file_async built its multipart body with `aiohttp.streams.FilePayload(...)`, which no longer exists in aiohttp (payload classes live in aiohttp.payload, and there is no FilePayload). The reference sits inside a lazy closure, so import succeeds and only the async Mistral OCR file-upload path blows up at runtime with AttributeError on every call. Mirror the working sync path: open the file in a context manager and let MultipartWriter.append(file_obj, {...}) build a streaming BufferedReaderPayload, with the POST issued inside the open() block so the handle stays valid for the whole upload. Preserves the streaming / memory-efficiency intent. Found via the dependency-contract test suite (unit/deps/test_aiohttp.py), which pins aiohttp.streams.FilePayload as absent. --- .../open_webui/retrieval/loaders/mistral.py | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/backend/open_webui/retrieval/loaders/mistral.py b/backend/open_webui/retrieval/loaders/mistral.py index 465ea5d91e..5e30160c47 100644 --- a/backend/open_webui/retrieval/loaders/mistral.py +++ b/backend/open_webui/retrieval/loaders/mistral.py @@ -261,33 +261,32 @@ class MistralLoader: url = f'{self.base_url}/files' async def upload_request(): - # Create multipart writer for streaming upload - writer = aiohttp.MultipartWriter('form-data') + # Open inside the request so the handle stays valid for the whole + # streamed POST and is closed right after. + with open(self.file_path, 'rb') as f: + writer = aiohttp.MultipartWriter('form-data') - # Add purpose field - purpose_part = writer.append('ocr') - purpose_part.set_content_disposition('form-data', name='purpose') + # Add purpose field + purpose_part = writer.append('ocr') + purpose_part.set_content_disposition('form-data', name='purpose') - # Add file part with streaming - file_part = writer.append_payload( - aiohttp.streams.FilePayload( - self.file_path, - filename=self.file_name, - content_type='application/pdf', - ) - ) - file_part.set_content_disposition('form-data', name='file', filename=self.file_name) + # Stream the file. aiohttp builds a payload from the file object; + # the previous aiohttp.streams.FilePayload was removed upstream + # (payloads live in aiohttp.payload and there is no FilePayload), + # so this path raised AttributeError on every async OCR upload. + file_part = writer.append(f, {'Content-Type': 'application/pdf'}) + file_part.set_content_disposition('form-data', name='file', filename=self.file_name) - self._debug_log(f'Uploading file: {self.file_name} ({self.file_size:,} bytes)') + self._debug_log(f'Uploading file: {self.file_name} ({self.file_size:,} bytes)') - async with session.post( - url, - data=writer, - headers=self.headers, - timeout=aiohttp.ClientTimeout(total=self.upload_timeout), - ssl=AIOHTTP_CLIENT_SESSION_SSL, - ) as response: - return await self._handle_response_async(response) + async with session.post( + url, + data=writer, + headers=self.headers, + timeout=aiohttp.ClientTimeout(total=self.upload_timeout), + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as response: + return await self._handle_response_async(response) response_data = await self._retry_request_async(upload_request)