Summary
There is no working way — through the REST API — to prepend per-chunk metadata (e.g. source filename, path, document as-of date) to the chunk text that the extraction LLM sees. The one documented customization point for per-chunk text, chunking_func, is silently bypassed on the /documents/scan and /documents/upload paths, with no error and no log line. This makes per-chunk provenance/temporal grounding impossible for API-driven ingestion.
The problem (silent bypass)
chunking_func is described in lightrag/lightrag.py as a "Legacy chunking-function customization point," and its docstring is explicit:
"If process_options explicitly contains a chunking selector char (F/R/V/P), the dispatcher routes to a chunker that follows the new file-chunker contract … This chunking_func is NOT called in that case — it is a legacy escape hatch and is intentionally bypassed when the user opted into a specific strategy."
Meanwhile, in lightrag/api/routers/document_routes.py, the scan/upload enqueue path does:
api_process_options = process_options or PROCESS_OPTION_CHUNK_FIXED
So both endpoints always name a strategy (F by default) → chunking_func is never invoked. The only way to reach the no-selector path where chunking_func actually runs is the SDK (ainsert) with an empty/non-chunking process_options — not the HTTP API most deployments use.
Net effect: a user who supplies a chunking_func that, say, prepends a SOURCE / PATH / DATE breadcrumb to every chunk gets 0 of their chunks augmented, silently. The chunks are created, entities extracted, the graph builds — and the customization simply never ran.
Why this matters (use case)
The extraction LLM sees only chunk text, never chunk metadata (file_path, headings, created_at). For multi-document corpora this loses information that matters at extraction time, not query time:
- Temporal grounding: a document's as-of date (often only present in the filename or a header line) lands in chunk 1 and is lost on chunks 2..N. Entities extracted from later chunks get no date grounding, so the graph can't self-document when a fact was attested.
- Source provenance: which document a chunk came from — useful for any multi-doc corpus, essential when the same entity is mentioned across many documents with different roles/statuses.
- Section / table disambiguation: a chunk cut mid-table or mid-section loses the heading that scopes it (e.g. a membership roster whose column headers landed in a different chunk).
A per-chunk prefix ("breadcrumb") carrying SOURCE / PATH / DATE closes all three, because it puts the context into the text the extractor reads. ENABLE_CONTENT_HEADINGS helps at query time but not at extraction time, so it is not a substitute.
What I tried
Supplying a custom chunking_func that prepends the breadcrumb. It works on the SDK ainsert path (no selector → legacy path → chunking_func runs), but is a complete no-op when ingesting via POST /documents/scan or /documents/upload, because those force PROCESS_OPTION_CHUNK_FIXED. There is no process_options value I can send through the API to opt out of all four strategies and reach the legacy path.
Proposed solution
The cleanest general fix is a strategy-agnostic per-chunk transform hook, decoupled from "how to split":
LightRAG(
...,
chunking_func=..., # how to split (existing)
chunk_transform_func=my_transform, # NEW: decorate every chunk after splitting
)
# my_transform(chunk: dict, doc_meta: dict) -> dict
# receives one chunk {"content": ..., ...} + {"file_path":..., "doc_date":..., ...}
# and may rewrite chunk["content"] (e.g. prepend a breadcrumb).
# runs AFTER the strategy chunker (F/R/V/P) on every chunk, on every ingest path.
Why a new hook rather than extending chunking_func:
As a smaller interim, the F-branch dispatch in lightrag/pipeline.py could delegate to self.chunking_func when it is a non-default callable (identity-gated: is not chunking_by_token_size), via the legacy 6-arg signature — this restores chunking_func on the scan default path without a new API. We have this working locally (with regression tests). It doesn't help R/V/P, but it unblocks the scan default immediately. I'm happy to open a PR for either.
At minimum, the docstring / docs should call out that chunking_func is a no-op on the HTTP scan/upload paths, so users don't spend time wiring a customization that can't run there.
Minimal reproduction
calls = {"n": 0}
def my_chunking_func(tokenizer, content, split_by_character, split_by_character_only, overlap, size):
calls["n"] += 1
chunks = chunking_by_token_size(tokenizer, content, split_by_character, split_by_character_only, overlap, size)
for c in chunks:
c["content"] = "SOURCE: my-doc\n---\n" + c["content"]
return chunks
rag = LightRAG(working_dir=..., chunking_func=my_chunking_func, ...)
# SDK path: works — chunks carry "SOURCE: my-doc"
await rag.ainsert(open("doc.md").read())
# HTTP path: my_chunking_func is NEVER called; chunks have no breadcrumb
# POST /documents/scan → process_options defaults to PROCESS_OPTION_CHUNK_FIXED
# → F-branch bypasses chunking_func → 0 augmented chunks
Environment
- LightRAG
main (as of Aug 2026).
- Python 3.11, Postgres + Neo4j backends.
- Ingestion via
/documents/scan (a supervisor polling inputs/<workspace>/).
Summary
There is no working way — through the REST API — to prepend per-chunk metadata (e.g. source filename, path, document as-of date) to the chunk text that the extraction LLM sees. The one documented customization point for per-chunk text,
chunking_func, is silently bypassed on the/documents/scanand/documents/uploadpaths, with no error and no log line. This makes per-chunk provenance/temporal grounding impossible for API-driven ingestion.The problem (silent bypass)
chunking_funcis described inlightrag/lightrag.pyas a "Legacy chunking-function customization point," and its docstring is explicit:Meanwhile, in
lightrag/api/routers/document_routes.py, the scan/upload enqueue path does:So both endpoints always name a strategy (
Fby default) →chunking_funcis never invoked. The only way to reach the no-selector path wherechunking_funcactually runs is the SDK (ainsert) with an empty/non-chunkingprocess_options— not the HTTP API most deployments use.Net effect: a user who supplies a
chunking_functhat, say, prepends aSOURCE / PATH / DATEbreadcrumb to every chunk gets 0 of their chunks augmented, silently. The chunks are created, entities extracted, the graph builds — and the customization simply never ran.Why this matters (use case)
The extraction LLM sees only chunk text, never chunk metadata (
file_path, headings,created_at). For multi-document corpora this loses information that matters at extraction time, not query time:A per-chunk prefix ("breadcrumb") carrying
SOURCE / PATH / DATEcloses all three, because it puts the context into the text the extractor reads.ENABLE_CONTENT_HEADINGShelps at query time but not at extraction time, so it is not a substitute.What I tried
Supplying a custom
chunking_functhat prepends the breadcrumb. It works on the SDKainsertpath (no selector → legacy path →chunking_funcruns), but is a complete no-op when ingesting viaPOST /documents/scanor/documents/upload, because those forcePROCESS_OPTION_CHUNK_FIXED. There is noprocess_optionsvalue I can send through the API to opt out of all four strategies and reach the legacy path.Proposed solution
The cleanest general fix is a strategy-agnostic per-chunk transform hook, decoupled from "how to split":
Why a new hook rather than extending
chunking_func:As a smaller interim, the F-branch dispatch in
lightrag/pipeline.pycould delegate toself.chunking_funcwhen it is a non-default callable (identity-gated:is not chunking_by_token_size), via the legacy 6-arg signature — this restoreschunking_funcon the scan default path without a new API. We have this working locally (with regression tests). It doesn't help R/V/P, but it unblocks the scan default immediately. I'm happy to open a PR for either.At minimum, the docstring / docs should call out that
chunking_funcis a no-op on the HTTP scan/upload paths, so users don't spend time wiring a customization that can't run there.Minimal reproduction
Environment
main(as of Aug 2026)./documents/scan(a supervisor pollinginputs/<workspace>/).