Skip to main content

A Python library for splitting text into smaller chunks while preserving as much local semantic context as possible.

Project description

semchunk 🧩

PyPI version Build status Code coverage Monthly downloads

semchunk is a Python library for splitting text into smaller chunks while preserving as much local semantic context as possible.

semchunk supports AI-powered chunking, chunk overlapping, and chunk offsets, and works with any tokenizer or token counter, including those from Tiktoken and Transformers.

Powered by a novel hierarchical chunking algorithm, semchunk delivers 15% better RAG performance than its closest competitors (see Benchmarks 📊).

semchunk is production-ready. It is downloaded millions of times per month and is used in Docling, the Microsoft Intelligence Toolkit, and the Isaacus API.

Setup 📦

semchunk can be installed with pip (or uv):

pip install semchunk

If you're using AI-powered chunking, you'll also want to install the Isaacus SDK and obtain an Isaacus API key:

pip install isaacus

semchunk is also available on conda-forge:

conda install conda-forge::semchunk
# or
conda install -c conda-forge semchunk

@dominictarro maintains a Rust port of semchunk named semchunk-rs.

Quickstart 👩‍💻

The code snippet below demonstrates how to chunk text with semchunk:

import semchunk
import tiktoken                        # Transformers and Tiktoken are not dependencies,
from transformers import AutoTokenizer # they're just here for demonstration purposes.

chunk_size = 4 # A low chunk size is used here for demonstration purposes. Keep in mind, semchunk
               # does not know how many special tokens, if any, your tokenizer adds to every input,
               # so you may want to deduct the number of special tokens added from your chunk size.
text = 'The quick brown fox jumps over the lazy dog.'

# You can construct a chunker with `semchunk.chunkerify()` by passing the name of an OpenAI model,
# OpenAI Tiktoken encoding or Hugging Face model, or a custom tokenizer that has an `encode()` method
# (like a Tiktoken or Transformers tokenizer) or a custom token counting function that takes a text and
# returns the number of tokens in it.
chunker = semchunk.chunkerify('isaacus/kanon-2-tokenizer', chunk_size) or \
          semchunk.chunkerify('gpt-4', chunk_size) or \
          semchunk.chunkerify('cl100k_base', chunk_size) or \
          semchunk.chunkerify(AutoTokenizer.from_pretrained('isaacus/kanon-2-tokenizer'), chunk_size) or \
          semchunk.chunkerify(tiktoken.encoding_for_model('gpt-4'), chunk_size) or \
          semchunk.chunkerify(lambda text: len(text.split()), chunk_size)

# If you give the resulting chunker a single text, it'll return a list of chunks. If you give it a
# list of texts, it'll return a list of lists of chunks.
assert chunker(text) == ['The quick brown fox', 'jumps over the', 'lazy dog.']
assert chunker([text], progress=True) == [['The quick brown fox', 'jumps over the', 'lazy dog.']]

# If you have a lot of texts and you want to speed things up, you can enable multiprocessing by
# setting `processes` to a number greater than 1.
assert chunker([text], processes=2) == [['The quick brown fox', 'jumps over the', 'lazy dog.']]

# You can also pass an `offsets` argument to return the offsets of chunks, as well as an `overlap`
# argument to overlap chunks by a ratio (if < 1) or an absolute number of tokens (if >= 1).
chunks, offsets = chunker(text, offsets=True, overlap=0.5)

To leverage AI-powered chunking, ensure the Isaacus SDK is installed and your ISAACUS_API_KEY environment variable is set, and then simply pass the name of an Isaacus enrichment model like kanon-2-enricher as the chunking_model argument of chunkerify() or chunk(), like so:

import requests  # For demonstration purposes, we'll use `requests` to download a long document.
import semchunk
from os import environ

# Set your `ISAACUS_API_KEY` environment variable to your Isaacus API key.
environ["ISAACUS_API_KEY"] = "INSERT_YOUR_API_KEY_HERE"

# Download a very long document to chunk.
text = requests.get("https://examples.isaacus.com/dred-scott-v-sandford.txt").text

# Construct a chunker that uses `kanon-2-enricher` for AI-powered chunking.
# NOTE Because we're using a Hugging Face Transformers tokenizer, the `transformers` library is required here,
# however, you can use any tokenizer or token counter you like.
chunker = semchunk.chunkerify("isaacus/kanon-2-tokenizer", 512, chunking_model="kanon-2-enricher")

# Chunk the document with AI-powered chunking.
chunks = chunker(text)

Usage 🕹️

chunkerify()

def chunkerify(
    tokenizer_or_token_counter: str | tiktoken.Encoding | transformers.PreTrainedTokenizer | \
                                tokenizers.Tokenizer | Callable[[str], int],
    chunk_size: int | None = None,
    *,
    chunking_model: str | None = None,
    isaacus_client: isaacus.Isaacus | None = None,
    tokenizer_kwargs: dict | None = None,
    max_token_chars: int | None = None,
    memoize: bool = True,
    cache_maxsize: int | None = None,
) -> Callable[[str | ILGSDocument | Sequence[str | ILGSDocument], int, bool, bool, int | float | None], list[str] | tuple[list[str], list[tuple[int, int]]] | list[list[str]] | tuple[list[list[str]], list[list[tuple[int, int]]]]]:

chunkerify() constructs a chunker that splits one or more texts into semantically meaningful chunks of a specified size as determined by the provided tokenizer or token counter.

tokenizer_or_token_counter is either: the name of a Tiktoken or Transformers tokenizer (with priority given to the former); a tokenizer with an encode() method (e.g., a Tiktoken or Transformers tokenizer); or a token counter that returns the number of tokens in an input.

chunk_size is the maximum number of tokens a chunk may contain. It defaults to None, in which case it will be set to the same value as the tokenizer's model_max_length attribute (minus the number of tokens returned by attempting to tokenize an empty string) if possible, otherwise a ValueError will be raised.

chunking_model is the name of the Isaacus enrichment model to use for AI chunking. This argument defaults to None, in which case, unless you provide an Isaacus Legal Graph Schema (ILGS) Document as input, AI chunking will be disabled.

isaacus_client is an instance of the isaacus.Isaacus API client to use for AI chunking instead of a client constructed with default parameters. This argument defaults to None, in which case a client will be constructed with default parameters if AI chunking is enabled.

tokenizer_kwargs is an optional dictionary of keyword arguments to be passed to the tokenizer or token counter whenever it is called. This can be used to disable the current default behavior of treating any encountered special tokens as if they are normal text when using a Tiktoken or Transformers tokenizer. This argument defaults to None, in which case no additional keyword arguments will be passed to the tokenizer or token counter.

max_token_chars is the maximum number of characters a token may contain. It is used to significantly speed up the token counting of long inputs. It defaults to None in which case it will either not be used or will, if possible, be set to the number of characters in the longest token in the tokenizer's vocabulary as determined by the token_byte_values or get_vocab methods.

memoize flags whether to memoize the token counter. It defaults to True.

cache_maxsize is the maximum number of text-token count pairs that can be stored in the token counter's cache. It defaults to None, which makes the cache unbounded. This argument is only used if memoize is True.

This function returns a chunker that takes either a single input or a sequence of inputs and returns, depending on whether multiple inputs have been provided, a list or list of lists of chunks up to chunk_size-tokens-long with the whitespace used to split the input removed, and, if the optional offsets argument to the chunker is True, a list or lists of tuples of the form (start, end) where start is the index of the first character of a chunk in an input and end is the index of the character succeeding the last character of the chunk such that chunks[i] == text[offsets[i][0]:offsets[i][1]].

The resulting chunker can be passed a processes argument that specifies the number of processes to be used when chunking multiple inputs.

It is also possible to pass a progress argument which, if set to True and multiple inputs are passed, will display a progress bar.

As described above, the offsets argument, if set to True, will cause the chunker to return the start and end offsets of each chunk.

The chunker accepts an overlap argument that specifies the proportion of the chunk size, or, if >=1, the number of tokens, by which chunks should overlap. It defaults to None, in which case no overlapping occurs.

chunk()

def chunk(
    text: str | ILGSDocument,
    chunk_size: int,
    token_counter: Callable[[str], int],
    *,
    memoize: bool = True,
    offsets: bool = False,
    overlap: float | int | None = None,
    chunking_model: str | None = None,
    isaacus_client: "isaacus.Isaacus | None" = None,
    cache_maxsize: int | None = None,
) -> list[str] | tuple[list[str], list[tuple[int, int]]]:

chunk() splits a text into semantically meaningful chunks of a specified size as determined by the provided token counter.

text is the input to be chunked. If you pass an Isaacus Legal Graph Schema (ILGS) Document, AI chunking will occur automatically without re-enriching the document.

chunk_size is the maximum number of tokens a chunk may contain.

token_counter is a callable that takes a string and returns the number of tokens in it.

memoize flags whether to memoize the token counter. It defaults to True.

offsets flags whether to return the start and end offsets of each chunk. It defaults to False.

overlap specifies the proportion of the chunk size, or, if >=1, the number of tokens, by which chunks should overlap. It defaults to None, in which case no overlapping occurs.

chunking_model is the name of the Isaacus enrichment model to use for AI chunking. This argument defaults to None, in which case, unless you provide an Isaacus Legal Graph Schema (ILGS) Document as input, AI chunking will be disabled.

isaacus_client is an instance of the isaacus.Isaacus API client to use for AI chunking instead of a client constructed with default parameters. This argument defaults to None, in which case a client will be constructed with default parameters if AI chunking is enabled.

cache_maxsize is the maximum number of text-token count pairs that can be stored in the token counter's cache. It defaults to None, which makes the cache unbounded. This argument is only used if memoize is True.

This function returns a list of chunks up to chunk_size-tokens-long, with any whitespace used to split the text removed, and, if offsets is True, a list of tuples of the form (start, end) where start is the index of the first character of the chunk in the original text and end is the index of the character after the last character of the chunk such that chunks[i] == text[offsets[i][0]:offsets[i][1]].

How It Works 🔍

semchunk works by recursively splitting texts until all resulting chunks are less than or equal to a specified chunk size.

In particular, it:

  1. splits text using the most structurally meaningful splitter possible;
  2. recursively splits the resulting chunks until a set of chunks less than or equal to the specified chunk size is produced;
  3. merges any chunks that are under the chunk size back together until the chunk size is reached;
  4. reattaches any non-whitespace splitters back to the ends of chunks except for the last chunk if doing so does not bring chunks over the chunk size, otherwise adds non-whitespace splitters as their own chunks; and
  5. since version 3.0.0, excludes chunks consisting entirely of whitespace characters.

To ensure that chunks are as semantically meaningful as possible, semchunk uses the following splitters, in order of precedence:

  1. the largest sequence of newlines (\n) and/or carriage returns (\r);
  2. the largest sequence of tabs;
  3. the largest sequence of whitespace characters (as defined by regex's \s character class) or, since version 3.2.0, if the largest sequence of whitespace characters is only a single character and there exist whitespace characters preceded by any of the structurally meaningful non-whitespace characters listed below (in the same order of precedence), then only those specific whitespace characters;
  4. sentence terminators (., ?, and !);
  5. clause separators (;, ,, (, ), [, ], , , , , ', ", `, and *);
  6. sentence interrupters (:, and );
  7. word joiners (/, \, , & and -); and
  8. all other characters.

Where AI-powered chunking is enabled, semchunk:

  1. splits text into chunks up to 1,000,000-characters-long using the above algorithm in order to avoid sending excessively long inputs to an enrichment model;
  2. enriches the resulting chunks with the enrichment model, pooling all unique spans extracted from each enriched chunk together;
  3. for each level of depth, creates new spans where necessary to ensure that all content at that level of depth is covered by a span;
  4. constructs a tree of spans based on containment;
  5. iterates through the span tree:
    1. adding spans to chunks until the chunk size is reached;
    2. discarding whitespace-only chunks;
    3. removing leading and trailing whitespace from chunks;
    4. entering into the children of spans where a span exceeds the chunk size; and
    5. falling back to the above algorithm where a span has no children.

If overlapping chunks have been requested, semchunk also:

  1. internally reduces the chunk size to min(overlap, chunk_size - overlap) (overlap being computed as floor(chunk_size * overlap) for relative overlaps and min(overlap, chunk_size - 1) for absolute overlaps); and
  2. merges every floor(original_chunk_size / reduced_chunk_size) chunks starting from the first chunk and then jumping by floor((original_chunk_size - overlap) / reduced_chunk_size) chunks until the last chunk is reached.

Benchmarks 📊

On Legal RAG QA, semchunk's AI chunking mode achieves the highest RAG correctness score of 37.7%, followed by semchunk's non-AI chunking mode at 35.5%. In comparison, LangChain's recursive chunker achieves correctness of 34.8% while fixed-size chunking achieves 33.3% correctness. Chonkie's semantic and recursive chunkers achieve the lowest correctness score of 32.6%. That is 15% lower than semchunk's AI chunking mode and 8% lower than semchunk's non-AI chunking mode.

A full write up of our evaluation methodology and findings may be found on our blog.

Citation 📝

If you use semchunk for research, please cite it as follows:

@software{butler2023semchunk,
  author       = {Butler, Umar},
  title        = {semchunk: a Python library for semantic chunking},
  year         = {2023},
  url          = {https://github.com/isaacus-dev/semchunk},
  version      = {4.0.0},
  publisher    = {Isaacus}
}

License 📜

This library is licensed under the MIT License.

Project details


Download files

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

Source Distribution

semchunk-4.0.0.tar.gz (23.2 kB view details)

Uploaded Source

Built Distribution

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

semchunk-4.0.0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file semchunk-4.0.0.tar.gz.

File metadata

  • Download URL: semchunk-4.0.0.tar.gz
  • Upload date:
  • Size: 23.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for semchunk-4.0.0.tar.gz
Algorithm Hash digest
SHA256 d1017b7572cfcb2337e6ba2cad8a7963716e8772463ba85a968d718496feec7e
MD5 c770c9d6e2619cb7eacd9ab02ccdf731
BLAKE2b-256 eed93206b435cc375452a075e49a928a6d0764cdfd9d693c4a9e7fce3d637548

See more details on using hashes here.

File details

Details for the file semchunk-4.0.0-py3-none-any.whl.

File metadata

  • Download URL: semchunk-4.0.0-py3-none-any.whl
  • Upload date:
  • Size: 17.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for semchunk-4.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 77dba8b6bd533ede115708372463b3703dbf0075d3f4ac2af0f4f3ed7b6af795
MD5 efd80e849516034bfd71f27ac4504670
BLAKE2b-256 b4d3159cb53cfa520a2b8375aae3784a76e65baf3e96fea596eb388daa88c441

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page