diff --git a/CHANGELOG.md b/CHANGELOG.md index 803de314d..e1dee8791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next +### Added + +- Experimental: `LiteParseLoader` — an alternative PDF/document loader backed by [LiteParse](https://github.com/run-llama/liteparse), a local Rust-based parser with optional OCR support. Install with `pip install "neo4j-graphrag[liteparse]"` and pass it as `file_loader` to `SimpleKGPipeline`. + ### Changed - Experimental: `GraphSchema` validation now rejects `KEY` and `EXISTENCE` constraints on the same node or relationship property (including composite KEY members), since KEY already implies mandatory presence. Legacy `PropertyType.required` migration no longer adds redundant EXISTENCE constraints for KEY-covered properties. The schema-from-text extraction prompt includes the same rule. diff --git a/examples/customize/build_graph/components/loaders/liteparse_loader.py b/examples/customize/build_graph/components/loaders/liteparse_loader.py new file mode 100644 index 000000000..e8a1859ad --- /dev/null +++ b/examples/customize/build_graph/components/loaders/liteparse_loader.py @@ -0,0 +1,26 @@ +"""Example: using LiteParseLoader as an alternative PDF loader. + +LiteParse (https://github.com/run-llama/liteparse) is a local, zero-cloud PDF +parser with optional OCR support. Install the optional extra before running: + + pip install "neo4j-graphrag[liteparse]" + +Usage:: + + from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline + from neo4j_graphrag.experimental.components.liteparse_loader import LiteParseLoader + + pipeline = SimpleKGPipeline( + llm=..., + driver=..., + embedder=..., + file_loader=LiteParseLoader(ocr_enabled=True), + from_file=True, + ) + await pipeline.run_async(file_path="document.pdf") +""" + +# LiteParseLoader lives in the main package — import it from there. +from neo4j_graphrag.experimental.components.liteparse_loader import LiteParseLoader + +__all__ = ["LiteParseLoader"] diff --git a/pyproject.toml b/pyproject.toml index ab3f47369..63b9bf01c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ examples = [ # only advertise/install the `nlp` extra on Python <3.14. nlp = ["spacy==3.8.7; python_version < '3.14'"] fuzzy-matching = ["rapidfuzz>=3.12.2,<4.0.0"] +liteparse = ["liteparse>=2.1.0,<3.0.0"] [dependency-groups] dev = [ @@ -115,6 +116,7 @@ exclude = ["docs"] module = [ "weaviate.*", "sentence_transformers.*", + "liteparse.*", "conftest", ] ignore_missing_imports = true diff --git a/src/neo4j_graphrag/experimental/components/data_loader.py b/src/neo4j_graphrag/experimental/components/data_loader.py index bcc5960bc..3d161b60c 100644 --- a/src/neo4j_graphrag/experimental/components/data_loader.py +++ b/src/neo4j_graphrag/experimental/components/data_loader.py @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Document loaders: base class, PDF, Markdown, and extension-based file dispatch.""" +"""Document loaders: base class, PDF, and Markdown.""" from __future__ import annotations diff --git a/src/neo4j_graphrag/experimental/components/liteparse_loader.py b/src/neo4j_graphrag/experimental/components/liteparse_loader.py new file mode 100644 index 000000000..483c1f498 --- /dev/null +++ b/src/neo4j_graphrag/experimental/components/liteparse_loader.py @@ -0,0 +1,134 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# # +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# # +# https://www.apache.org/licenses/LICENSE-2.0 +# # +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""LiteParse-backed document loader (optional extra: ``neo4j-graphrag[liteparse]``).""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any, Dict, Optional, Union + +import fsspec +from fsspec import AbstractFileSystem +from fsspec.implementations.local import LocalFileSystem + +from neo4j_graphrag.exceptions import PdfLoaderError +from neo4j_graphrag.experimental.components.data_loader import DataLoader, is_default_fs +from neo4j_graphrag.experimental.components.types import ( + DocumentInfo, + DocumentType, + LoadedDocument, +) + +try: + from liteparse import LiteParse as _LiteParse + + _LITEPARSE_AVAILABLE = True +except ImportError: + _LiteParse = None # type: ignore[assignment,misc] + _LITEPARSE_AVAILABLE = False + + +class LiteParseLoader(DataLoader): + """Loads and parses documents using LiteParse (local, no cloud dependency). + + LiteParse uses a compiled Rust core and optional Tesseract OCR to extract text + from PDFs, DOCX, XLSX, PPTX, and images. It runs fully offline — no API key + or network access required. + + Requires the ``liteparse`` optional extra:: + + pip install "neo4j-graphrag[liteparse]" + + Args: + ocr_enabled: Enable Tesseract OCR for scanned pages. + ocr_server_url: URL of a remote OCR server (optional). + ocr_language: Tesseract language code, e.g. ``"eng"``, ``"fra"``. + dpi: Rendering resolution for OCR (default 300). + target_pages: Page range string, e.g. ``"1-5,10,15-20"``. + password: Password for encrypted PDFs. + output_format: Output format — ``"text"`` (default, plain text) or + ``"markdown"`` (requires LiteParse >=2.1). Markdown output preserves + ``#``/``##`` section headers, making it suitable for use with + ``HierarchicalTextSplitter(header_strategy="markdown")``. + """ + + def __init__( + self, + ocr_enabled: bool = False, + ocr_server_url: Optional[str] = None, + ocr_language: Optional[str] = None, + dpi: Optional[int] = None, + target_pages: Optional[str] = None, + password: Optional[str] = None, + output_format: str = "text", + ) -> None: + optional = { + "ocr_server_url": ocr_server_url, + "ocr_language": ocr_language, + "dpi": dpi, + "target_pages": target_pages, + "password": password, + } + self._kwargs: Dict[str, Any] = { + "ocr_enabled": ocr_enabled, + "output_format": output_format, + **{k: v for k, v in optional.items() if v is not None}, + } + self._parser: Any = None # lazily initialised + + def _get_parser(self) -> Any: + if not _LITEPARSE_AVAILABLE: + raise ImportError( + "liteparse is required for LiteParseLoader. " + 'Install it with: pip install "neo4j-graphrag[liteparse]"' + ) + if self._parser is None: + self._parser = _LiteParse(**self._kwargs) + return self._parser + + def load_file(self, filepath: str, fs: AbstractFileSystem) -> str: + """Parse a document and return the full extracted text.""" + parser = self._get_parser() # ImportError propagates; not caught below + try: + if is_default_fs(fs): + result = parser.parse(filepath) + else: + with fs.open(filepath, "rb") as fp: + result = parser.parse(fp.read()) + return str(result.text) + except Exception as e: + raise PdfLoaderError(e) from e + + async def run( + self, + filepath: Union[str, Path], + metadata: Optional[Dict[str, str]] = None, + fs: Optional[Union[AbstractFileSystem, str]] = None, + ) -> LoadedDocument: + if not isinstance(filepath, str): + filepath = str(filepath) + if isinstance(fs, str): + fs = fsspec.filesystem(fs) + elif fs is None: + fs = LocalFileSystem() + text = self.load_file(filepath, fs) + return LoadedDocument( + text=text, + document_info=DocumentInfo( + path=filepath, + metadata=self.get_document_metadata(text, metadata), + document_type=DocumentType.PDF, + ), + ) diff --git a/tests/unit/experimental/components/test_liteparse_loader.py b/tests/unit/experimental/components/test_liteparse_loader.py new file mode 100644 index 000000000..e89eeffae --- /dev/null +++ b/tests/unit/experimental/components/test_liteparse_loader.py @@ -0,0 +1,167 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# # +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# # +# https://www.apache.org/licenses/LICENSE-2.0 +# # +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for LiteParseLoader — liteparse is mocked throughout so no install needed.""" + +import sys +from pathlib import Path +from types import ModuleType, SimpleNamespace +from typing import Generator +from unittest.mock import MagicMock, patch + +import pytest +from fsspec.implementations.local import LocalFileSystem + +from neo4j_graphrag.exceptions import PdfLoaderError +from neo4j_graphrag.experimental.components.liteparse_loader import LiteParseLoader +from neo4j_graphrag.experimental.components.types import DocumentType + +SAMPLE_PDF = str(Path(__file__).parent / "sample_data/lorem_ipsum.pdf") +SAMPLE_TEXT = "Lorem ipsum dolor sit amet." + + +def _make_liteparse_stub(parse_text: str = SAMPLE_TEXT) -> ModuleType: + result = SimpleNamespace(text=parse_text) + LiteParse = MagicMock(return_value=MagicMock(parse=MagicMock(return_value=result))) + stub = ModuleType("liteparse") + stub.LiteParse = LiteParse # type: ignore[attr-defined] + return stub + + +@pytest.fixture(autouse=True) +def inject_liteparse_stub() -> Generator[ModuleType, None, None]: + stub = _make_liteparse_stub() + with patch.dict(sys.modules, {"liteparse": stub}), patch( + "neo4j_graphrag.experimental.components.liteparse_loader._LiteParse", + stub.LiteParse, + ), patch( + "neo4j_graphrag.experimental.components.liteparse_loader._LITEPARSE_AVAILABLE", + True, + ): + yield stub + + +def test_load_file_local_fs_returns_text(inject_liteparse_stub: ModuleType) -> None: + loader = LiteParseLoader() + text = loader.load_file(SAMPLE_PDF, fs=LocalFileSystem()) + assert text == SAMPLE_TEXT + inject_liteparse_stub.LiteParse.return_value.parse.assert_called_once_with( + SAMPLE_PDF + ) + + +def test_load_file_non_local_fs_uses_bytes(inject_liteparse_stub: ModuleType) -> None: + fake_fs = MagicMock() + fake_bytes = b"%PDF fake" + fake_fs.open.return_value.__enter__ = MagicMock( + return_value=MagicMock(read=MagicMock(return_value=fake_bytes)) + ) + fake_fs.open.return_value.__exit__ = MagicMock(return_value=False) + + loader = LiteParseLoader() + with patch( + "neo4j_graphrag.experimental.components.liteparse_loader.is_default_fs", + return_value=False, + ): + text = loader.load_file(SAMPLE_PDF, fs=fake_fs) + + assert text == SAMPLE_TEXT + inject_liteparse_stub.LiteParse.return_value.parse.assert_called_once_with( + fake_bytes + ) + + +def test_load_file_wraps_parse_error_as_pdf_loader_error( + inject_liteparse_stub: ModuleType, +) -> None: + inject_liteparse_stub.LiteParse.return_value.parse.side_effect = RuntimeError( + "corrupt PDF" + ) + loader = LiteParseLoader() + with pytest.raises(PdfLoaderError): + loader.load_file(SAMPLE_PDF, fs=LocalFileSystem()) + + +def test_missing_liteparse_raises_import_error() -> None: + with patch( + "neo4j_graphrag.experimental.components.liteparse_loader._LITEPARSE_AVAILABLE", + False, + ): + loader = LiteParseLoader() + with pytest.raises(ImportError, match="pip install"): + loader.load_file(SAMPLE_PDF, fs=LocalFileSystem()) + + +@pytest.mark.asyncio +async def test_run_returns_loaded_document() -> None: + loader = LiteParseLoader() + doc = await loader.run(filepath=SAMPLE_PDF) + assert doc.text == SAMPLE_TEXT + assert doc.document_info.document_type == DocumentType.PDF + assert doc.document_info.path == SAMPLE_PDF + + +@pytest.mark.asyncio +async def test_run_with_path_object() -> None: + loader = LiteParseLoader() + doc = await loader.run(filepath=Path(SAMPLE_PDF)) + assert doc.document_info.path == SAMPLE_PDF + + +@pytest.mark.asyncio +async def test_run_passes_metadata() -> None: + loader = LiteParseLoader() + meta = {"source": "test", "lang": "en"} + doc = await loader.run(filepath=SAMPLE_PDF, metadata=meta) + assert doc.document_info.metadata == meta + + +@pytest.mark.asyncio +async def test_run_fs_string_resolves() -> None: + loader = LiteParseLoader() + doc = await loader.run(filepath=SAMPLE_PDF, fs="file") + assert doc.text == SAMPLE_TEXT + + +def test_constructor_kwargs_forwarded_to_liteparse( + inject_liteparse_stub: ModuleType, +) -> None: + loader = LiteParseLoader( + ocr_enabled=True, + ocr_language="fra", + dpi=300, + target_pages="1-3", + password="s3cr3t", + ) + loader.load_file(SAMPLE_PDF, fs=LocalFileSystem()) + inject_liteparse_stub.LiteParse.assert_called_once_with( + ocr_enabled=True, + output_format="text", + ocr_language="fra", + dpi=300, + target_pages="1-3", + password="s3cr3t", + ) + + +def test_output_format_markdown_forwarded_to_liteparse( + inject_liteparse_stub: ModuleType, +) -> None: + loader = LiteParseLoader(output_format="markdown") + loader.load_file(SAMPLE_PDF, fs=LocalFileSystem()) + inject_liteparse_stub.LiteParse.assert_called_once_with( + ocr_enabled=False, + output_format="markdown", + ) diff --git a/tests/unit/experimental/components/test_liteparse_loader_integration.py b/tests/unit/experimental/components/test_liteparse_loader_integration.py new file mode 100644 index 000000000..2854eff0b --- /dev/null +++ b/tests/unit/experimental/components/test_liteparse_loader_integration.py @@ -0,0 +1,80 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# # +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# # +# https://www.apache.org/licenses/LICENSE-2.0 +# # +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Integration tests for LiteParseLoader — require ``pip install neo4j-graphrag[liteparse]``. + +These tests call the real liteparse library against actual PDF files. +They are skipped automatically when liteparse is not installed. +""" + +from pathlib import Path + +import pytest +from fsspec.implementations.local import LocalFileSystem +from fsspec.implementations.memory import MemoryFileSystem + +from neo4j_graphrag.experimental.components.types import DocumentType + +pytest.importorskip( + "liteparse", reason="liteparse not installed — skipping integration tests" +) + +from neo4j_graphrag.experimental.components.liteparse_loader import LiteParseLoader # noqa: E402 + +SAMPLE_PDF = Path(__file__).parent / "sample_data/lorem_ipsum.pdf" + + +def test_real_parse_local_file() -> None: + """LiteParseLoader extracts text from the sample PDF via the local FS path.""" + loader = LiteParseLoader() + text = loader.load_file(str(SAMPLE_PDF), fs=LocalFileSystem()) + assert len(text) > 0 + assert "Lorem" in text + + +def test_real_parse_from_bytes() -> None: + """Loader works when bytes are read through a non-default FS (bytes branch).""" + pdf_bytes = SAMPLE_PDF.read_bytes() + mfs = MemoryFileSystem() + with mfs.open("/lorem.pdf", "wb") as fh: + fh.write(pdf_bytes) + + loader = LiteParseLoader() + text = loader.load_file("/lorem.pdf", fs=mfs) + assert "Lorem" in text + + +@pytest.mark.asyncio +async def test_real_run_returns_loaded_document() -> None: + loader = LiteParseLoader() + doc = await loader.run(filepath=SAMPLE_PDF) + assert doc.document_info.document_type == DocumentType.PDF + assert doc.document_info.path == str(SAMPLE_PDF) + assert "Lorem" in doc.text + + +@pytest.mark.asyncio +async def test_real_run_with_metadata() -> None: + loader = LiteParseLoader() + meta = {"source": "integration-test"} + doc = await loader.run(filepath=SAMPLE_PDF, metadata=meta) + assert doc.document_info.metadata == meta + + +@pytest.mark.asyncio +async def test_real_run_text_is_nonempty() -> None: + loader = LiteParseLoader() + doc = await loader.run(filepath=SAMPLE_PDF) + assert doc.text.strip() != "" diff --git a/uv.lock b/uv.lock index 6865845f0..42e562e76 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10.0, <3.15" resolution-markers = [ "python_full_version >= '3.14'", @@ -2250,6 +2250,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/0e/b756c7708143a63fca65a51ca07990fa647db2cc8fcd65177b9e96680255/librt-0.7.7-cp314-cp314t-win_arm64.whl", hash = "sha256:142c2cd91794b79fd0ce113bd658993b7ede0fe93057668c2f98a45ca00b7e91", size = 39724, upload-time = "2026-01-01T23:52:09.745Z" }, ] +[[package]] +name = "liteparse" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/de/ce77f6cab49e4c21691dc3607c71edd0592ecad2370a822d8b4cb8ca07ce/liteparse-2.0.3.tar.gz", hash = "sha256:a13b089f068b36fd563d5ac580f4ef4c691623653395dafc01f7d2bf9254b495", size = 114877, upload-time = "2026-05-28T17:34:02.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/2b/c375c383ba03cfe972ddf5c281f13a1501a8a9b11ec00789786b457b49dd/liteparse-2.0.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:dd31b18cf999fcf99f64529bec48e40ccd6201425d388d9ce9555d6fd6b8e503", size = 13035388, upload-time = "2026-05-28T17:33:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/18553f58e7cf9beee1fdfd44c7299ce85d06a5ac022b4b223a1e19f07b88/liteparse-2.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f988f78f4fd14df8652a0cc05f00d334965881730da4cdc365cbaa42acdb0343", size = 13153361, upload-time = "2026-05-28T17:33:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f9/22b60c3fc5c854037ee30d7c6159c4c2f662682ffb27f93f74955f002d1a/liteparse-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:62c024bcaaa70b1ffaa0b55863cee943bb4cbb742242dcce2df43158ce05e821", size = 11114480, upload-time = "2026-05-28T17:33:21.001Z" }, + { url = "https://files.pythonhosted.org/packages/dd/7a/fb45339a4b1d4b9bd2522beccc3cf0b77dc4c4a1b4f32f2af25daa0a5901/liteparse-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe70bf7a57a74823f25826af6b4f369e730ce8683cf2e10a6d9b622396939075", size = 11027830, upload-time = "2026-05-28T17:33:22.899Z" }, + { url = "https://files.pythonhosted.org/packages/e2/36/c0d74760f03d6b40a74e56997284d3bae3ee1aa90c4f4e6ead450cf3e197/liteparse-2.0.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1dd3816c7fadd20f77a7697e8511707033caadceb13d9452cf784e9b0626a2cc", size = 13036395, upload-time = "2026-05-28T17:33:24.686Z" }, + { url = "https://files.pythonhosted.org/packages/4d/62/d104418ca15df05a12980079cfe7854b8dc05e9e3b21ee43c898d1bb36fb/liteparse-2.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5067de89d3642543bcecf5c1ae5151ed13aaebefc073ffd688dae3acd133e2be", size = 13153096, upload-time = "2026-05-28T17:33:26.672Z" }, + { url = "https://files.pythonhosted.org/packages/a0/49/df3099f4193efc30faaefbd5b5987f33d5720468a07404278c2f8d7bfec0/liteparse-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:32cecb053b2ba0aeb6bb36b22199f8fc7659de28fad278521efa5c129fc1ab0c", size = 11114658, upload-time = "2026-05-28T17:33:28.47Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2e/c145dddedc7364880b7970183cd93bd3d8003fd26e8283eb26240959aca2/liteparse-2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6f92aea47bfb34dadfb3e14f55a94cbfbba625087d3a894ace1ca0bf70b029", size = 11024688, upload-time = "2026-05-28T17:33:30.463Z" }, + { url = "https://files.pythonhosted.org/packages/3d/52/3eb455aa68f54748cccf28ba55d11513abefd24e0613d33cfd6e0c55a239/liteparse-2.0.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e97524938546020669465f54a495a909a7e2b1dc6ca448fc61835e74bf985808", size = 13027651, upload-time = "2026-05-28T17:33:32.213Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3b/fba08905b4d78c37a9ab23f02cb1079ce1e9f3ac429ed4c5e86e12f37244/liteparse-2.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5c5bf58d697ff6f496c7ffa7fc5b31278dd9c56f8f44a52a3740c660461b870c", size = 13147630, upload-time = "2026-05-28T17:33:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/50/08/3a5d4364f7a19b85af6b1c1fbffccb6a0b65ce11d7c3c899ce191c41e223/liteparse-2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:59a9b4400ba8895c9c4a1652bede02279d014dea5f16fa4636b0825b6d2192f7", size = 11115874, upload-time = "2026-05-28T17:33:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/6d/dc/77fb2dc98ea0ec6c16b00a63a280f1f1c12919c5aa297a7724b885233b8d/liteparse-2.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7994d25f7be466926b8a32d033cad86691283830d18c9d371cb15603fb8aa09", size = 11023495, upload-time = "2026-05-28T17:33:38.106Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/c5a4098e881442bf795670a1311f3b2ec641984ca231a81cfc3c5ebd9724/liteparse-2.0.3-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1f3645df4bc1b63a8a96833ef7172bea39ad7d692253e8052597b3d097dc28f5", size = 13027576, upload-time = "2026-05-28T17:33:40.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/dd/77d801c27279bb4e5b4a89d9387d28abafe92e64236349adff5df391e5a8/liteparse-2.0.3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:06090184880085154011e8e8c4bc6403e293cfa1e5d72d26b86ccf4d43c85c2f", size = 13145429, upload-time = "2026-05-28T17:33:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/11/43/75799b5cd5362a4f73705393bc94a920d22995251c405a9e26b2dc2fa5b1/liteparse-2.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:8350ca6a8f13dd616ef7e2944364a1bb0837e2ec6d5eb169ae36d029d719ae84", size = 11115797, upload-time = "2026-05-28T17:33:44.332Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/af39a60580fa91525e3d7c655f829a97caf2b07981cdb7d86663b189e00c/liteparse-2.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe077508031700d85dc887d9162c9a30ead5b34a4c10637f078081a9a0d14c98", size = 11022879, upload-time = "2026-05-28T17:33:46.246Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/3ce288552db14d431ffa73f3bf36fd927c8c0f05188d27f4ed0c18b6114b/liteparse-2.0.3-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a7876e90dd45d2a4c074318503346408ea66eb28e2fd3fec213522964708f722", size = 13033326, upload-time = "2026-05-28T17:33:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/68/52/1d457a09c76ed6771bc8081ccc08d62d6ee6f28faffadfd2620f645f4b6b/liteparse-2.0.3-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:77c15b7d9da9f5ccf3486e93634d7a9e24b5dd20c153e33191fb873acfc93420", size = 13153316, upload-time = "2026-05-28T17:33:50.498Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/aed6f026c669f2f24318a87edfd9011ade7fd7c805324679be86116c385c/liteparse-2.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b5eadaf93738a481b679446e43653f3d69201daf0f585415913550e245b812c", size = 11114075, upload-time = "2026-05-28T17:33:52.373Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/39f70c3e97abf568a6976a41df07d86b46ba5bf3aa85e760cca6d7ecd158/liteparse-2.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:054f81a5f32badb93d0e124455b16224ca082599a762bc659223b95e13abe3f1", size = 13035905, upload-time = "2026-05-28T17:33:58.784Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cc/14a459967bbc97b1c5e63d8bc63ab0ebe52b8a2f3224a4966cbe45abd772/liteparse-2.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3e9e74e4610c5c4739422d6732548239d409cb19a76b5dabe988f3232e5ed0a6", size = 13156090, upload-time = "2026-05-28T17:34:00.864Z" }, +] + [[package]] name = "llama-cloud" version = "0.1.35" @@ -2974,7 +3003,7 @@ wheels = [ [[package]] name = "neo4j-graphrag" -version = "1.15.0" +version = "1.17.0" source = { editable = "." } dependencies = [ { name = "fsspec" }, @@ -3023,6 +3052,9 @@ google-genai = [ kg-creation-tools = [ { name = "neo4j-viz" }, ] +liteparse = [ + { name = "liteparse" }, +] mistralai = [ { name = "mistralai" }, ] @@ -3073,6 +3105,7 @@ requires-dist = [ { name = "langchain-huggingface", marker = "extra == 'examples'", specifier = ">=0.1.0,<2.0.0" }, { name = "langchain-openai", marker = "extra == 'examples'", specifier = ">=0.2.2,<2.0.0" }, { name = "langchain-text-splitters", marker = "extra == 'experimental'", specifier = ">=0.3.0,<2.0.0" }, + { name = "liteparse", marker = "extra == 'liteparse'", specifier = ">=2.0.0,<3.0.0" }, { name = "llama-index", marker = "extra == 'experimental'", specifier = ">=0.13.0,<0.14.0" }, { name = "mistralai", marker = "extra == 'mistralai'", specifier = ">=1.0.3,<2.0.0" }, { name = "neo4j", specifier = ">=5.17.0,<7.0.0" }, @@ -3097,7 +3130,7 @@ requires-dist = [ { name = "types-pyyaml", specifier = ">=6.0.12.20240917,<7.0.0" }, { name = "weaviate-client", marker = "extra == 'weaviate'", specifier = ">=4.6.1,<5.0.0" }, ] -provides-extras = ["weaviate", "pinecone", "google", "google-genai", "cohere", "anthropic", "bedrock", "ollama", "openai", "mistralai", "qdrant", "kg-creation-tools", "sentence-transformers", "experimental", "examples", "nlp", "fuzzy-matching"] +provides-extras = ["weaviate", "pinecone", "google", "google-genai", "cohere", "anthropic", "bedrock", "ollama", "openai", "mistralai", "qdrant", "kg-creation-tools", "sentence-transformers", "experimental", "examples", "nlp", "fuzzy-matching", "liteparse"] [package.metadata.requires-dev] dev = [