Source code for fastrag.ingestion.steps.chunking.character

"""``CharacterTextSplitter``: split on one separator only, no fallback hierarchy."""

from __future__ import annotations

from collections.abc import Iterator, Mapping
from typing import Any, Literal

from fastrag.exceptions import ConfigurationError
from fastrag.ingestion.cache import CacheStore
from fastrag.models import Document
from fastrag.ingestion.policy import Policy
from fastrag.ingestion.steps.chunking.base import Chunker

try:
    from langchain_text_splitters import (
        CharacterTextSplitter as _CharacterTextSplitter,
    )

    _LANGCHAIN_AVAILABLE = True
except ImportError:  # pragma: no cover - exercised by installs without the extra
    _LANGCHAIN_AVAILABLE = False

#: The splitter class, or ``None`` when the optional "chunkers" extra is
#: missing; :meth:`CharacterTextSplitter.__init__` refuses to run without it.
_LangchainSplitter: Any = _CharacterTextSplitter if _LANGCHAIN_AVAILABLE else None

MISSING_EXTRA = 'CharacterTextSplitter needs the "chunkers" extra: pip install "fastrag-ai[chunkers]"'


[docs] class CharacterTextSplitter(Chunker): """Split on a single literal separator, with no separator hierarchy. Unlike :class:`~fastrag.ingestion.steps.chunking.recursive.RecursiveCharacterTextSplitter`, this never descends to a smaller separator when a piece is still too large after splitting on ``separator`` — the right choice when a source's structure already guarantees short-enough pieces at one boundary (e.g. a page's own section breaks), and the wrong one for prose of unpredictable length. .. code-block:: python from fastrag.ingestion.steps.chunking import CharacterTextSplitter chunker = CharacterTextSplitter(chunk_size=500, chunk_overlap=50, cache=cache) """ accepted_content_formats = frozenset(["text", "markdown", "html", "rst"]) def __init__( self, *, chunk_size: int = 1000, chunk_overlap: int = 200, separator: str = "\n\n", cache: CacheStore | Literal[False] | None = None, policy: Policy | None = None, ) -> None: """Configure the splitter, validating its bounds immediately. Args: chunk_size: Target chunk length in characters. Must be at least 1. chunk_overlap: Characters repeated between neighbours, so a sentence split across a boundary is still whole in one of them. Must be non-negative and smaller than ``chunk_size``. separator: The single literal string to split on. cache: Cache backend, or ``False``/``None`` — see :class:`~fastrag.ingestion.steps.base.PipelineStep`. policy: Execution policy overriding the pipeline default. Raises: fastrag.exceptions.ConfigurationError: The "chunkers" extra is not installed, ``chunk_size < 1``, or ``chunk_overlap`` is not in ``[0, chunk_size)``. """ if _LangchainSplitter is None: raise ConfigurationError(MISSING_EXTRA) if chunk_size < 1: raise ConfigurationError( "CharacterTextSplitter.chunk_size must be at least 1" ) if not 0 <= chunk_overlap < chunk_size: raise ConfigurationError( "CharacterTextSplitter.chunk_overlap must be " f"non-negative and smaller than chunk_size ({chunk_size})" ) super().__init__(cache=cache, policy=policy) self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap self.separator = separator self._splitter = _LangchainSplitter( chunk_size=chunk_size, chunk_overlap=chunk_overlap, separator=separator, keep_separator=True, )
[docs] def config(self) -> Mapping[str, Any]: """This splitter's configuration, for cache-identity hashing.""" return { "chunk_size": self.chunk_size, "chunk_overlap": self.chunk_overlap, "separator": self.separator, }
[docs] def split(self, document: Document) -> Iterator[str]: """Split ``document.content`` on ``self.separator`` only.""" self.validate_input(document) yield from self._splitter.split_text(document.content)