diff --git a/api/core/rag/extractor/extract_processor.py b/api/core/rag/extractor/extract_processor.py index 0a008b5f31e..1d2f98075ef 100644 --- a/api/core/rag/extractor/extract_processor.py +++ b/api/core/rag/extractor/extract_processor.py @@ -147,9 +147,11 @@ class ExtractProcessor: upload_file.id, ) elif file_extension == ".pdf": - assert upload_file is not None extractor = PdfExtractor( - file_path, upload_file.tenant_id, upload_file.created_by, session=session + file_path, + upload_file.tenant_id if upload_file else None, + upload_file.created_by if upload_file else None, + session=session, ) elif file_extension in {".md", ".markdown", ".mdx"}: extractor = ( @@ -195,9 +197,11 @@ class ExtractProcessor: upload_file.id, ) elif file_extension == ".pdf": - assert upload_file is not None extractor = PdfExtractor( - file_path, upload_file.tenant_id, upload_file.created_by, session=session + file_path, + upload_file.tenant_id if upload_file else None, + upload_file.created_by if upload_file else None, + session=session, ) elif file_extension in {".md", ".markdown", ".mdx"}: extractor = MarkdownExtractor(file_path, autodetect_encoding=True) diff --git a/api/core/rag/extractor/pdf_extractor.py b/api/core/rag/extractor/pdf_extractor.py index 587d1220696..778514cd27d 100644 --- a/api/core/rag/extractor/pdf_extractor.py +++ b/api/core/rag/extractor/pdf_extractor.py @@ -55,8 +55,8 @@ class PdfExtractor(BaseExtractor): def __init__( self, file_path: str, - tenant_id: str, - user_id: str, + tenant_id: str | None = None, + user_id: str | None = None, file_cache_key: str | None = None, *, session: Session | None = None, @@ -127,6 +127,12 @@ class PdfExtractor(BaseExtractor): Returns: Markdown string containing links to the extracted images. """ + if not self._tenant_id or not self._user_id: + # No tenant context (e.g. file loaded from a URL rather than an + # uploaded file): extracted images cannot be persisted, so skip + # image extraction and return text only. + return "" + image_content = [] upload_files = [] base_url = dify_config.FILES_URL diff --git a/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py b/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py index 49c2cce919f..6b81f24fcb8 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py @@ -152,6 +152,21 @@ class TestExtractProcessorLoaders: assert text == content + def test_load_from_url_extracts_pdf_without_upload_file(self, monkeypatch: pytest.MonkeyPatch): + response = SimpleNamespace(headers={"Content-Type": "application/pdf"}, content=b"%PDF-1.1 body") + monkeypatch.setattr(processor_module.remote_fetcher, "make_request", lambda *args, **kwargs: response) + factory = _patch_all_extractors(monkeypatch) + apply_config_overrides(monkeypatch, ETL_TYPE="dify") + + text = ExtractProcessor.load_from_url("https://example.com/report", return_text=True) + + assert text == "extracted-by-PdfExtractor" + name, args, kwargs = factory.calls[-1] + assert name == "PdfExtractor" + # no upload_file for URL-loaded files: tenant/user context must be None + assert args[1] is None + assert args[2] is None + class TestExtractProcessorFileRouting: @pytest.fixture(autouse=True) diff --git a/api/tests/unit_tests/core/rag/extractor/test_pdf_extractor.py b/api/tests/unit_tests/core/rag/extractor/test_pdf_extractor.py index bc35f5fde03..5fab1844cbd 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_pdf_extractor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_pdf_extractor.py @@ -196,3 +196,15 @@ def test_extract_images_failures(mock_dependencies: _Dependencies): assert upload_file is not None assert f"![image](http://files.local/files/{upload_file.id}/file-preview)" in result assert mock_dependencies.storage.saves == [(upload_file.key, jpeg_bytes)] + + +def test_extract_images_skipped_without_tenant_context(): + """PDFs loaded from a URL have no tenant/user context; image extraction must be skipped.""" + mock_page = MagicMock() + + extractor = pe.PdfExtractor(file_path="test.pdf") + + result = extractor._extract_images(mock_page) + + assert result == "" + mock_page.get_objects.assert_not_called()