From 3e2a8020efcd6141d7b101268085d87c21855865 Mon Sep 17 00:00:00 2001 From: Kyou0203 Date: Thu, 16 Jul 2026 23:18:24 +0800 Subject: [PATCH] fix(tools): make RAG file extension detection case-insensitive DataTypes.from_content() compared path.endswith(ext) against a lowercase-only map, so .PDF/.CSV/.DOCX were misclassified as text (or website for URLs). Lowercase the path before matching. Closes #6399 --- .../src/crewai_tools/rag/data_types.py | 6 +- .../tools/rag/test_data_types_from_content.py | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 lib/crewai-tools/tests/tools/rag/test_data_types_from_content.py diff --git a/lib/crewai-tools/src/crewai_tools/rag/data_types.py b/lib/crewai-tools/src/crewai_tools/rag/data_types.py index 0fcf07e121..057f234838 100644 --- a/lib/crewai-tools/src/crewai_tools/rag/data_types.py +++ b/lib/crewai-tools/src/crewai_tools/rag/data_types.py @@ -113,6 +113,9 @@ def from_content(content: str | Path | None = None) -> DataType: pass def get_file_type(path: str) -> DataType | None: + # Case-insensitive extension match: Windows scanners/exports often + # produce uppercase extensions (.PDF, .CSV, .DOCX) that must not fall + # through to the plain-text loader. mapping = { ".pdf": DataType.PDF_FILE, ".csv": DataType.CSV, @@ -123,8 +126,9 @@ def get_file_type(path: str) -> DataType | None: ".xml": DataType.XML, ".txt": DataType.TEXT_FILE, } + lowered = path.lower() for ext, dtype in mapping.items(): - if path.endswith(ext): + if lowered.endswith(ext): return dtype return None diff --git a/lib/crewai-tools/tests/tools/rag/test_data_types_from_content.py b/lib/crewai-tools/tests/tools/rag/test_data_types_from_content.py new file mode 100644 index 0000000000..ae2fe9cd59 --- /dev/null +++ b/lib/crewai-tools/tests/tools/rag/test_data_types_from_content.py @@ -0,0 +1,69 @@ +"""Tests for DataTypes.from_content extension auto-detection.""" + +from pathlib import Path + +import pytest + +from crewai_tools.rag.data_types import DataType, DataTypes + + +class TestFromContentCaseInsensitiveExtensions: + """Regression tests for #6399: uppercase/mixed-case extensions.""" + + @pytest.mark.parametrize( + ("path", "expected"), + [ + ("report.pdf", DataType.PDF_FILE), + ("report.PDF", DataType.PDF_FILE), + ("report.Pdf", DataType.PDF_FILE), + ("data.csv", DataType.CSV), + ("data.CSV", DataType.CSV), + ("doc.docx", DataType.DOCX), + ("doc.DOCX", DataType.DOCX), + ("notes.md", DataType.MDX), + ("notes.MD", DataType.MDX), + ("notes.mdx", DataType.MDX), + ("notes.MDX", DataType.MDX), + ("payload.json", DataType.JSON), + ("payload.JSON", DataType.JSON), + ("tree.xml", DataType.XML), + ("tree.XML", DataType.XML), + ("readme.txt", DataType.TEXT_FILE), + ("readme.TXT", DataType.TEXT_FILE), + ], + ) + def test_extension_detection_is_case_insensitive( + self, path: str, expected: DataType, tmp_path: Path + ) -> None: + """Local file paths with mixed-case extensions map to the right DataType.""" + file_path = tmp_path / path + file_path.write_bytes(b"dummy") + assert DataTypes.from_content(str(file_path)) == expected + + @pytest.mark.parametrize( + ("url", "expected"), + [ + ("https://example.com/file.pdf", DataType.PDF_FILE), + ("https://example.com/file.PDF", DataType.PDF_FILE), + ("https://example.com/data.CSV", DataType.CSV), + ("https://example.com/doc.DOCX", DataType.DOCX), + ("https://example.com/payload.JSON", DataType.JSON), + ], + ) + def test_url_extension_detection_is_case_insensitive( + self, url: str, expected: DataType + ) -> None: + """URL path extensions must also be matched case-insensitively.""" + assert DataTypes.from_content(url) == expected + + def test_url_without_known_extension_stays_website(self) -> None: + """URLs without a known file extension still classify as website.""" + assert ( + DataTypes.from_content("https://example.com/docs/page") == DataType.WEBSITE + ) + + def test_path_object_with_uppercase_extension(self, tmp_path: Path) -> None: + """Path objects with uppercase suffixes are handled correctly.""" + file_path = tmp_path / "Report.PDF" + file_path.write_bytes(b"%PDF") + assert DataTypes.from_content(file_path) == DataType.PDF_FILE