Skip to main content

konan logo

konan

Like the paper angel of the Akatsuki, konan folds your documents into precise pieces — Rust chunkers behind a Python API.

PyPI CI python-ci Python 3.12+ Rust License: MIT


Why konan?

  • 🦀 Rust core — all chunking runs in native code, no Python-loop overhead
  • Multithreaded by defaultchunk_many() fans out across all cores via rayon and releases the GIL
  • 🌀 Real asyncchunk_async / chunk_many_async are native async def, not thread-pool wrappers
  • 🎯 Char-accurate offsetstext[chunk.start:chunk.end] == chunk.text, always (Python slicing semantics, emoji-safe)
  • 🧠 Semantic chunking — splits on topic shifts using any OpenAI-compatible embeddings endpoint, or your own async embedder
  • 🔌 Ports & adapters — the Embedder port is injectable; bring your own backend

Strategies

Chunker Splits by Best for
NaiveChunker fixed word count quick & dirty baselines
FixedSizeChunker chars, sentence-aware, overlap classic RAG pipelines
RecursiveChunker separator hierarchy (\n\n\n → … ) general text, LangChain-compatible
SentenceChunker unicode sentence boundaries prose, multilingual text
MarkdownChunker document structure + heading breadcrumbs docs, wikis, READMEs
TokenChunker exact token counts (cl100k_base, o200k_base) embedding-model token limits
SemanticChunker embedding similarity drops topic-coherent chunks

Installation

uv add konan        # or: pip install konan

Quickstart

from konan import RecursiveChunker

chunker = RecursiveChunker(chunk_size=1000, chunk_overlap=200)

chunks = chunker.chunk(open("moby_dick.txt").read())
print(chunks[0].text, chunks[0].start, chunks[0].end, chunks[0].hash)

Parallel — all cores, one call

# rayon work-stealing across every core, GIL released:
all_chunks = chunker.chunk_many(documents)

Async — real async def, no thread-pool wrappers

chunks = await chunker.chunk_async(text)
batches = await chunker.chunk_many_async(documents)

Markdown with breadcrumbs

from konan import MarkdownChunker

chunks = MarkdownChunker(chunk_size=800).chunk(readme_text)
# chunk text is prefixed with its heading trail: "# Guide > ## Install\n\n..."
# code fences are never split

Token-exact chunks

from konan import TokenChunker

chunker = TokenChunker(chunk_size=512, chunk_overlap=64, encoding="o200k_base")

Semantic chunking

Point it at any OpenAI-compatible /embeddings endpoint (OpenAI, vLLM, Ollama, LiteLLM, …):

from konan import OpenAIEmbedder, SemanticChunker

embedder = OpenAIEmbedder(
    base_url="https://api.openai.com/v1",
    model="text-embedding-3-small",
    api_key="sk-...",
    batch_size=128,    # texts per request
    timeout=30.0,      # request timeout, seconds
    max_retries=2,     # exponential backoff on 429/5xx/connect errors
    dimensions=512,    # optional: shorten text-embedding-3-* vectors
)
chunker = SemanticChunker(embedder=embedder, threshold=0.75)
chunks = await chunker.chunk_async(article)

Or inject your own embedder — any async callable works:

async def my_embedder(texts: list[str]) -> list[list[float]]:
    return await my_model.embed(texts)

chunker = SemanticChunker(embedder=my_embedder, percentile=95.0)
chunks = await chunker.chunk_async(article)   # async-only for Python embedders

Python embedders must return list[list[float]] — call .tolist() on numpy arrays. They are async-only: chunk()/chunk_many() raise a RuntimeError pointing you at the _async variants.

The Chunk object

chunk.text    # the chunk's text
chunk.start   # char offset into the source (Python slicing semantics)
chunk.end     # char offset, exclusive
chunk.index   # 0-based position
chunk.hash    # xxh3-64 content hash

Benchmarks

Benchmarked on Apple M3 Pro (arm64), Python 3.12.10. Decimal MB/s, median of 5 runs, measured from Python (the numbers you actually get). Reproduce both tables and plots with uv run --extra bench benchmarks/bench.py; Rust-level criterion benches: cargo bench -p konan-core.

vs other libraries (same 1 MB document, identical configs)

konan vs other libraries

Strategy Library Throughput Chunks
recursive konan 282 MB/s 1298
recursive semantic-text-splitter 129 MB/s 1368
recursive chonkie 87 MB/s 1413
recursive langchain-text-splitters 24 MB/s 1468
token konan 68 MB/s 438
token semchunk 24 MB/s 616
token langchain-text-splitters 23 MB/s 438
token chonkie 18 MB/s 438
token semantic-text-splitter 4 MB/s 505
sentence konan 305 MB/s 1042
sentence chonkie 3 MB/s 2124
recursive (unicode) semantic-text-splitter 167 MB/s 1150
recursive (unicode) konan 166 MB/s 1092
recursive (unicode) chonkie 57 MB/s 1171

Throughput per strategy (1 MB document)

konan throughput per strategy

Chunker Config Throughput Chunks
NaiveChunker 200 words 553 MB/s 804
FixedSizeChunker 1000 chars, 200 overlap 1,602 MB/s 1255
RecursiveChunker 1000 chars, 200 overlap 282 MB/s 1298
SentenceChunker 1000 chars, 1 overlap 306 MB/s 1130
MarkdownChunker 1000 chars, 200 overlap 448 MB/s 1511
TokenChunker 512 tokens, 64 overlap (cl100k) 69 MB/s 438

Parallel scaling — rayon goes brrr

chunk_many parallel scaling

64 docs × 256 KB through RecursiveChunker:

Mode Time Throughput Speedup
sequential chunk() loop 61 ms 267 MB/s 1.0×
chunk_many() (rayon, GIL released) 10 ms 1,568 MB/s 5.9×

(Pure-Rust criterion puts the same workload at ~6 GiB/s; the Python numbers include chunk-object conversion.)

Caveats, honestly: recursive/token use identical configs across libraries (1000 chars / 200 overlap; cl100k, 512 / 64 — note the identical token chunk counts for the libraries with the same windowing semantics). Sentence configs are not directly comparable (konan groups by chars, chonkie by tokens) — read those rows as per-library cost, not head-to-head. The recursive (unicode) rows run mixed German/CJK/Cyrillic/emoji prose, off konan's ASCII fast path. konan tokenizes with bpe-openai (tiktoken-equivalent output, much faster encoder).

Development

uv sync                       # set up the venv (builds the extension)
uv run maturin develop --uv   # rebuild after Rust changes
cargo test --workspace        # rust unit tests
uv run pytest -q              # python integration tests

The workspace is hexagonal: crates/konan-core is pure Rust (no PyO3) with Chunker and Embedder ports; crates/konan-py adapts it to Python.

License

MIT


Named after Konan of the Akatsuki — the only one who could fold paper into anything. 🗞️

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

konan-0.3.0.tar.gz (48.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

konan-0.3.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.1 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ x86-64

konan-0.3.0-cp312-abi3-macosx_11_0_arm64.whl (29.1 MB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

File details

Details for the file konan-0.3.0.tar.gz.

File metadata

  • Download URL: konan-0.3.0.tar.gz
  • Upload date:
  • Size: 48.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for konan-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3dff4f1a738749d7a007cfe1a4052eef8423a496e5ef8950ff1a7d4beaddd58b
MD5 9f87d829aae0d35b4739efd3458efb2b
BLAKE2b-256 65e93e25adf2c947ac4b9fa2d4eb5e77b511056a7bae59befeed25ee7c5d68fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for konan-0.3.0.tar.gz:

Publisher: release-please.yaml on 4thel00z/konan

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file konan-0.3.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for konan-0.3.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6a537a4bb45935e21b5fedd8c49710e111614b4fa5d383ca0a55221e0ad8f0c8
MD5 13ab09659cfba2c383089ae7510eaa3c
BLAKE2b-256 1b7c7b2aaa9aad3f458055e4963afc05c7691193eb7e2d2e61eeb21e3b374711

See more details on using hashes here.

Provenance

The following attestation bundles were made for konan-0.3.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-please.yaml on 4thel00z/konan

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file konan-0.3.0-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: konan-0.3.0-cp312-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 29.1 MB
  • Tags: CPython 3.12+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for konan-0.3.0-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 87f69fe5ebe29ca5ce683bfbb27a906f3e6ef448c9f2a1ae2e016869ab0d7bd8
MD5 612cc8fbe38ec36f5a43e8ff11b91796
BLAKE2b-256 537c5ab0150be7c1012ef4ed452e67a186c598eb886199279b8a226b9d6242e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for konan-0.3.0-cp312-abi3-macosx_11_0_arm64.whl:

Publisher: release-please.yaml on 4thel00z/konan

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.0

3 files

This release

0.3.0 This release

3 files

0.2.4

3 files

0.2.3

3 files

0.2.2

3 files

0.2.1

3 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page