windlass.providers.chunkers.hierarchical¶
hierarchical
¶
Parent-child (small-to-big) chunking.
There is a tension at the heart of chunk sizing: small chunks retrieve better (a focused embedding matches a focused query) but large chunks answer better (the model needs surrounding context to reason).
Parent-child chunking refuses the trade-off. It indexes small child chunks for retrieval, but each child carries a pointer to the larger parent it came from. At query time the pipeline swaps matched children for their parents before building the prompt — so you retrieve with precision and generate with context.
Example
text = " ".join(f"Sentence number {i}." for i in range(40)) chunker = ParentChildChunker(parent_size=400, child_size=100) children = chunker.chunk_text(text) all(c.parent_id for c in children) True len(chunker.parents) < len(children) True
ParentChildChunker
¶
ParentChildChunker(
*,
parent_size: int = 2000,
child_size: int = 400,
child_overlap: int | None = None,
parent_chunker: Chunker | None = None,
child_chunker: Chunker | None = None,
**config: Any
)
Bases: Chunker
Two-level chunker producing indexable children and retrievable parents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parent_size
|
int
|
Character length of parent chunks — what the model reads. |
2000
|
child_size
|
int
|
Character length of child chunks — what gets embedded. |
400
|
child_overlap
|
int | None
|
Overlap between children within one parent. |
None
|
parent_chunker
|
Chunker | None
|
Strategy used to cut parents. Defaults to recursive. |
None
|
child_chunker
|
Chunker | None
|
Strategy used to cut children. Defaults to recursive. |
None
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
parents |
dict[str, Chunk]
|
Every parent chunk produced so far, keyed by id. The pipeline reads this to expand retrieval hits. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Note
Only children are written to the vector store. Parents live in this chunker (and are persisted alongside the index by the RAG pipeline), so the store holds no redundant copies of your corpus.
Thread safety
:attr:parents is guarded by a lock, so concurrent ingestion is safe.
Source code in src\windlass\providers\chunkers\hierarchical.py
split_text
¶
Return the child strings.
Provided for interface completeness; :meth:achunk_document is what
actually builds the parent links.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to split. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Child chunk strings. |
Source code in src\windlass\providers\chunkers\hierarchical.py
achunk_document
async
¶
Split a document into children, recording their parents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to split. |
required |
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
Child chunks. Each has |
list[Chunk]
|
|
list[Chunk]
|
parents up later. |
Source code in src\windlass\providers\chunkers\hierarchical.py
parent_of
¶
Return a child's parent chunk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunk
|
Chunk
|
The child chunk. |
required |
Returns:
| Type | Description |
|---|---|
Chunk | None
|
The parent, or |
Chunk | None
|
is not held by this chunker (for example after a process restart — |
Chunk | None
|
in that case the pipeline falls back to |
Source code in src\windlass\providers\chunkers\hierarchical.py
expand
¶
Swap child chunks for their parents, de-duplicated.
Two children of the same parent collapse to a single parent, which is exactly what you want in a prompt — the same paragraph should not appear twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunks
|
Sequence[Chunk]
|
Retrieved chunks, typically children. |
required |
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
Parents where available, originals otherwise, preserving input order. |
Example
from windlass.core.types import Chunk chunker = ParentChildChunker(parent_size=200, child_size=50) kids = chunker.chunk_text("word " * 100) expanded = chunker.expand(kids[:3]) len(expanded) <= 3 True