Package
lexical-graph
Problem statement
Adding support for new data sources (Confluence, Notion, Slack, Jira, Google Drive, etc.) currently requires writing a bespoke *_reader_provider.py for each one. This doesn't scale — LlamaIndex has 100+ reader packages, and each one follows the same pattern: import, instantiate with credentials, call load_data().
The real differences between readers are configuration (credentials, endpoints, filters), not code (import, instantiate, call, handle errors). We're writing the same boilerplate with different class names.
Proposed solution
A generic LlamaIndexPluginReaderProvider that wraps any LlamaIndex reader with just a config dict — no new Python file needed per reader.
from graphrag_toolkit.lexical_graph.indexing.load.readers.reader_provider_config import LlamaIndexPluginReaderConfig
from graphrag_toolkit.lexical_graph.indexing.load.readers.providers import LlamaIndexPluginReaderProvider
# Confluence — zero bespoke code
config = LlamaIndexPluginReaderConfig(
package="llama-index-readers-confluence",
module_path="llama_index.readers.confluence",
reader_class="ConfluenceReader",
init_args={"base_url": "https://mycompany.atlassian.net/wiki", "token": "..."},
load_args={"space_key": "ENG"},
timeout_seconds=60,
max_retries=2,
fail_on_error=False,
)
provider = LlamaIndexPluginReaderProvider(config)
docs = provider.read()
Same pattern for Notion, Slack, Jira, Google Drive, or any of the 100+ LlamaIndex readers.
Features
| Feature |
Description |
| Dynamic import |
Resolves any llama-index-readers-* package at runtime |
| Configurable timeout |
ThreadPoolExecutor-based timeout prevents hangs |
| Retry with backoff |
Exponential backoff on transient errors (429, 503, timeouts) |
| Auth error detection |
Recognizes 401/403 patterns — never retried, clear error message |
| Graceful degradation |
fail_on_error=False returns [] instead of crashing pipelines |
| input_source flexibility |
Auto-fallback if reader doesn't accept the kwarg |
| Document validation |
Filters non-Document items, handles generators |
| Metadata enrichment |
Optional metadata_fn to tag all documents |
| Module path resolution |
Derives llama_index.readers.X from package name automatically |
Error handling guarantees
- Missing package →
ReaderImportError with pip install instructions
- Class not found →
ReaderImportError listing available classes
- Bad constructor args →
ValueError with signature hint
- Auth failure (401/403) →
ReaderAuthError, never retried
- Transient error (429/503) → retried with exponential backoff
- Timeout →
ReaderTimeoutError, retried if attempts remain
- All retries exhausted →
RuntimeError or [] based on config
Test coverage
30 unit tests covering:
- Happy path (4 tests)
- Import errors (3)
- Config validation (2)
- Auth failures (7 parametrized)
- Timeout enforcement (2)
- Retry with backoff (4)
- Empty results (2)
- Partial failures / generators (2)
- Module path resolution (2)
- Custom load methods (2)
Live tested
$ python3.12 -c "..." # DadJokesReader via plugin
✅ 1 joke: Why didn't the orange win the race? It ran out of juice.
Implementation
Branch: feat/generic-llama-index-reader-plugin
Files:
lexical-graph/src/.../providers/llama_index_plugin_reader_provider.py (302 lines)
lexical-graph/src/.../reader_provider_config.py (+34 lines — config dataclass)
lexical-graph/src/.../providers/__init__.py (+2 lines — registration)
lexical-graph/tests/.../test_llama_index_plugin_reader_provider.py (596 lines)
PR ready to submit on request.
Alternatives considered
- Keep writing bespoke providers — doesn't scale, duplicates error handling
- Auto-generate providers from LlamaIndex metadata — fragile, breaks on API changes
- Generic plugin (this proposal) — one file handles all readers, config-driven, tested
Package
lexical-graph
Problem statement
Adding support for new data sources (Confluence, Notion, Slack, Jira, Google Drive, etc.) currently requires writing a bespoke
*_reader_provider.pyfor each one. This doesn't scale — LlamaIndex has 100+ reader packages, and each one follows the same pattern: import, instantiate with credentials, callload_data().The real differences between readers are configuration (credentials, endpoints, filters), not code (import, instantiate, call, handle errors). We're writing the same boilerplate with different class names.
Proposed solution
A generic
LlamaIndexPluginReaderProviderthat wraps any LlamaIndex reader with just a config dict — no new Python file needed per reader.Same pattern for Notion, Slack, Jira, Google Drive, or any of the 100+ LlamaIndex readers.
Features
llama-index-readers-*package at runtimefail_on_error=Falsereturns[]instead of crashing pipelinesmetadata_fnto tag all documentsllama_index.readers.Xfrom package name automaticallyError handling guarantees
ReaderImportErrorwithpip installinstructionsReaderImportErrorlisting available classesValueErrorwith signature hintReaderAuthError, never retriedReaderTimeoutError, retried if attempts remainRuntimeErroror[]based on configTest coverage
30 unit tests covering:
Live tested
Implementation
Branch:
feat/generic-llama-index-reader-pluginFiles:
lexical-graph/src/.../providers/llama_index_plugin_reader_provider.py(302 lines)lexical-graph/src/.../reader_provider_config.py(+34 lines — config dataclass)lexical-graph/src/.../providers/__init__.py(+2 lines — registration)lexical-graph/tests/.../test_llama_index_plugin_reader_provider.py(596 lines)PR ready to submit on request.
Alternatives considered