RAG chunk¶
Chunking splits raw documents from the ingest stage into bounded units of
text that the embed stage can vectorise. Helix ships ten chunking
strategies plus a set of nine document-genre templates. Each strategy
implements chunk.Strategy.Chunk(*rag.Document) []*rag.Chunk. All
strategies are dispatchable via chunk.GetStrategy(name, chunkSize, overlap),
which is what the DSL processor rag-chunk calls.
The pipeline shape is ingest -> chunk -> embed. Every *rag.Chunk produced
here is later fed to an embedder (pkg/rag/embed) and stored in a vector
backend (pkg/rag/store). Empty chunks are dropped by ChunkProcessor
because downstream embedders reject empty input.
Strategies¶
fixed — split by fixed character count¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 512 | Target chunk size in characters. |
overlap |
int | 50 | Characters carried over between adjacent chunks. Clamped to size/2 if >=size. |
Slices the text on byte offsets — fastest and most predictable. Use it when
you need uniform input sizes and don't care about respecting word or
sentence boundaries (e.g. embedding logs, dense plain-text). Source:
pkg/rag/chunk/chunk.go.
sentence — group whole sentences up to a size cap¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1000 | Max chunk size in chars; current sentence is flushed when adding it would exceed this. |
overlap |
int | 100 | Tail-of-last-chunk carried into next chunk (chars). |
Splits at .!? followed by whitespace, then groups whole sentences. Always
breaks at sentence boundaries — never mid-word. There are two
implementations: chunk.go's SentenceStrategy (used by GetStrategy)
plus sentence.go's SentenceChunker, which adds paragraph-aware grouping
(ParagraphSep, default "\n\n") and trims whitespace. Use this for prose,
articles, transcripts. Source: pkg/rag/chunk/chunk.go,
pkg/rag/chunk/sentence.go.
recursive — split on a hierarchy of separators¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1000 | Target chunk size in chars. |
overlap |
int | 100 | Chars of overlap inserted between final chunks. |
Tries separators in order: "\n\n", "\n", ". ", " ", "". Falls back
to the next-finer separator only if a chunk is still over size. The
default — sensible for almost any prose, code, or mixed input. There are
two variants: chunk.go's RecursiveStrategy (no overlap) and
recursive.go's RecursiveChunker (post-hoc overlap pass). Source:
pkg/rag/chunk/chunk.go, pkg/rag/chunk/recursive.go.
token — split by estimated token budget¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 512 | Max tokens per chunk. Words are estimated at 1.3 tokens. |
overlap |
int | 50 | Token overlap between adjacent chunks. |
Estimates token count as words * 1.3 (rough English approximation, no
tokenizer dependency). Use this when chunks must fit a model context
budget — picks a size matching the LLM's per-call window. Faster than
shipping a real tokenizer. Source: pkg/rag/chunk/token.go.
semantic — split where vocabulary shifts¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1024 | Max chunk size in chars. |
similarity_threshold |
float | 0.3 | Jaccard threshold; breaks when adjacent-sentence vocabulary overlap falls below. |
Groups consecutive sentences whose word sets share enough vocabulary,
breaks when similarity drops. The default in GetStrategy uses Jaccard
similarity (no embedder, fast). A separate SemanticChunker in
semantic.go uses real embeddings (cosine similarity, configurable
embedder) — pass it to chunk.SemanticChunker.ChunkWithContext. Use when
topic boundaries matter more than uniform size. Source:
pkg/rag/chunk/chunk.go, pkg/rag/chunk/semantic.go.
code — split on language-aware declaration boundaries¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1500 | Max chunk size in chars (more generous than prose default). |
overlap |
int | 100 | Overlap when oversized blocks are recursively re-split. |
Default language is go (via GetStrategy). Other supported:
python, javascript/js/typescript/ts, java, rust. Splits at
language-specific declaration keywords (func, def, class,
function, fn, public, etc.). Each declaration stays as one block;
oversized blocks fall back to recursive splitting. Use for source-code
ingestion (best to also set language via the YAML). Source:
pkg/rag/chunk/code.go.
markdown — split by heading hierarchy¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1000 | Max section size in chars. |
overlap |
int | 100 | Chars of overlap when oversized sections are paragraph-split. |
retain_headers |
bool | false | Prepend parent headings (#, ##, …) to each chunk for context. |
Parses ATX-style headings (# … ######) into a heading stack. Each
section becomes a chunk (or, if oversized, is paragraph-split first then
hard-split). Use for technical docs, README files, knowledge bases —
retain_headers: true gives chunks self-contained context. Source:
pkg/rag/chunk/markdown.go.
html — split on block-level element boundaries¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 1000 | Max chunk size in chars. |
Splits at opening tags <div>, <section>, <article>, <p>,
<h1>-<h6>, <li>, <tr>, <blockquote>, <pre>. Pure-regex, no
external HTML parser. Use for scraped HTML where structure matters but
markdown conversion isn't desirable. Source: pkg/rag/chunk/html.go.
sliding_window — overlapping fixed-stride windows¶
| Field | Type | Default | Description |
|---|---|---|---|
size |
int | 500 | Window size in unit. |
stride |
int | size/2 | Step between window starts; smaller = more overlap. |
unit |
string | chars |
chars or words. |
Generates windows at fixed offsets, similar to fixed but with explicit
stride. Default in GetStrategy uses chunkSize/2 stride (50% overlap)
and char units. Use when retrieval should be robust to where a relevant
span sits inside a chunk. Source: pkg/rag/chunk/sliding_window.go.
hierarchical — multi-level parent-child chunks¶
| Field | Type | Default | Description |
|---|---|---|---|
levels |
[]int | [2048, 512, 128] |
Chunk sizes per level, descending. Coarsest first. |
overlap |
int | 50 | Overlap inside each level's fixed-size splitter. |
Produces chunks at every level with chunk_level, parent_id, and
children metadata. Each level is fixed-size split of the original; a
child is mapped to a parent by text containment. Designed for
auto-merging retrieval (retrieve.AutoMergingRetriever,
retrieve.RetrieverAutoMerging): you index children, retrieve children
that match, then collapse to the parent for context. There's also a
ParentChildStrategy in chunk.go for the simpler two-level case
(parent + child). Source: pkg/rag/chunk/hierarchical.go,
pkg/rag/chunk/chunk.go.
Document-genre templates¶
Beyond the ten general strategies, chunk.GetTemplate(name, maxSize, overlap)
returns nine domain-aware strategies that split on genre-specific
boundaries and tag each chunk with chunk_template:
| Template | Boundary | Metadata field |
|---|---|---|
qa |
Q: / Question: prefix, or short line ending in ? |
question |
paper |
Markdown/numbered headings, ALL-CAPS, known section names (Abstract, Methods, …) | section |
laws |
Article, Section, Clause, § markers |
article |
resume |
Common section headers (Experience, Education, Skills, …) | section |
manual |
Chapter, Section, Part, Appendix markers |
section |
book |
Chapter / Book / Part headings, top-level markdown (#, ##) |
chapter |
presentation |
Slide N, ---/***/===, markdown headings |
slide |
email |
RFC-822 headers (From:, To:, Subject:, …) split into metadata; body recursively chunked |
email_from, email_to, … |
table |
One chunk per data row of a markdown table, with header + separator prepended | row |
one |
Whole document as a single chunk | — |
Source: pkg/rag/chunk/templates.go.
Choosing between them¶
| If your input is… | Pick |
|---|---|
| Plain prose, general purpose | recursive (the default) |
| Logs or fixed-format text | fixed |
| Long-form articles, transcripts | sentence |
| Must fit an LLM context budget | token |
| Topic-segmented documents | semantic |
| Source code | code with explicit language |
| Technical docs, README | markdown (often with retain_headers: true) |
| Scraped HTML | html |
| Retrieval must be span-robust | sliding_window (50% stride) |
| Small-to-big / auto-merge retrieval | hierarchical |
When the document is recognisably one of the nine genres above, prefer the template — it preserves natural-unit metadata that the retriever and synthesizer can exploit.