Chunking
Split documents into retrieval-ready chunks and deliver them to Kafka or a vector database
RAG and semantic search work on chunks, pieces of a document small enough for an embedding model. Docling for IBM watsonx chunks documents along their structure:
- a chunk never mixes two sections,
- tables and lists stay whole whenever they fit,
- every chunk carries its headings and page numbers, so answers can cite their source.
On the service, chunking is part of a batch request. You add chunking_options and pick a chunk target. The service then converts, chunks and delivers each chunk straight to your system:
| Target | kind | Delivers |
|---|---|---|
| Kafka (including Confluent Cloud) | kafka_chunks | One message per chunk, for event-driven pipelines |
| OpenSearch | opensearch_chunks | Indexed chunks (embed them with an index ingest pipeline) |
| AstraDB | astradb_chunks | Chunks with server-side embeddings |
Example
Convert two PDFs and stream their chunks to a Kafka topic:
curl -X POST "${DOCLING_SERVICE_URL}/v1/convert/source/batch" \
-H "X-Api-Key: ${DOCLING_SERVICE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"sources": [
{ "kind": "http", "url": "https://arxiv.org/pdf/2408.09869" },
{ "kind": "http", "url": "https://arxiv.org/pdf/2206.01062" }
],
"options": {
"chunking_options": {
"chunker": "hybrid",
"tokenizer": "ibm-granite/granite-embedding-278m-multilingual",
"max_tokens": 512
}
},
"target": {
"kind": "kafka_chunks",
"bootstrap_servers": ["pkc-xxxxx.us-east-1.aws.confluent.cloud:9092"],
"topic": "docs.chunks",
"auth": { "kind": "sasl", "mechanism": "PLAIN", "username": "API_KEY", "password": "API_SECRET" },
"headings_field": "headings",
"page_field": "page_numbers"
}
}'Each chunk arrives as a JSON record:
{
"text": "DocLayNet: A Large Human-Annotated Dataset …\nThe dataset contains 80863 pages …",
"doc_id": "a1b2c3…",
"chunk_index": 4,
"chunk_id": "9f8e7d…",
"headings": ["3 The DocLayNet dataset"],
"page_numbers": [2, 3],
"metadata": { "origin": { … } }
}Always send the chunker field explicitly, even for the default "hybrid". Without it the request is rejected with a 422 validation error. Some SDKs drop fields that equal their default value, so check the request your code actually sends.
Chunking options
| Option | Default | Description |
|---|---|---|
chunker | (required) | hybrid (recommended) or hierarchical |
tokenizer | sentence-transformers/all-MiniLM-L6-v2 | Hugging Face model whose tokenizer counts tokens. Use the tokenizer of your embedding model. Hybrid only |
max_tokens | taken from the tokenizer | Maximum tokens per chunk. Hybrid only |
merge_peers | true | Merge small consecutive chunks that share the same headings. Hybrid only |
use_markdown_tables | false | Serialize tables as Markdown tables instead of row, column = value triplets |
use_markdown_images | false | Include image references in chunks and flag those chunks with has_image |
image_placeholder | ![IMAGE] | Text that stands in for an image when use_markdown_images is off |
include_raw_text | false | Also include the raw chunk text next to the contextualized text |
Which chunker?
hybridfirst splits along the document structure, then splits or merges pieces so each fitsmax_tokensfor your tokenizer. It is the right choice for embeddings.hierarchicalemits one chunk per structural element (paragraph, table, list) with its headings, and does not count tokens. Use it when you want to do your own grouping.
Picking tokenizer and max_tokens
Use the same model your pipeline will embed with. Set max_tokens to the model's input limit, or a bit lower to leave room for the headings that are added as context.
| Embedding model | tokenizer | max_tokens |
|---|---|---|
| IBM Granite Embedding 278M (watsonx.ai) | ibm-granite/granite-embedding-278m-multilingual | 512 |
| IBM Granite Embedding 30M English | ibm-granite/granite-embedding-30m-english | 512 |
| all-MiniLM-L6-v2 | sentence-transformers/all-MiniLM-L6-v2 | 256 |
The text field
Each chunk's text is contextualized: it starts with the chunk's headings, followed by the content. That gives the embedding model the context the chunk lives in, and it measurably improves retrieval. Use include_raw_text if you also need the bare content, for example for display.