API Reference

This page contains the complete API reference for phrasplit.

spaCy Model Resolver

The stable resolver API is available directly from phrasplit:

from phrasplit import (
    SpacyModelResolution,
    SpacyModelSize,
    SpacyModelAttempt,
    SpacyModelResolutionError,
    SpacyNotInstalledError,
    NoCompatibleSpacyModelError,
    ExplicitSpacyModelError,
    normalize_spacy_language,
    resolve_spacy_model,
)

resolution = resolve_spacy_model(language="en", require=False)
print(resolution.model)       # concrete selected package or None
print(resolution.model_size)  # sm, md, lg, or trf
print(resolution.attempts)    # local loadability diagnostics

Resolution is local-only and never downloads a model. use_spacy=False skips resolution entirely; use_spacy=True requires a loadable result; use_spacy=None uses spaCy only when a compatible result exists and otherwise uses regex splitting. Downstream consumers can use language, language_model, and model_size with the same semantics as the splitting APIs.

SpacyModelResolutionError is the base class for resolver failures. Forced resolution raises SpacyNotInstalledError when spaCy is absent or NoCompatibleSpacyModelError when no compatible installed model loads. An explicit package failure raises ExplicitSpacyModelError; its resolution field contains attempts and accurate available/loadable flags. SpacyModelAttempt records each candidate load outcome.

Main Functions

split_sentences

phrasplit.split_sentences(text: str, language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split text into sentences.

By default, selects the highest-quality compatible installed and loadable spaCy model for the requested language, otherwise falls back to regex-based splitting. You can force a specific implementation with use_spacy.

Parameters:
  • text – Input text

  • language_model – Language model name (e.g., “en_core_web_sm”, “de_core_news_sm”) For spaCy mode: Name of the spaCy model to use For simple mode: Used to determine language for abbreviation handling

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling). Default is True. The regex fallback applies the same corrections.

  • split_on_colon – Deprecated. Kept for API compatibility (currently unused). spaCy’s default colon behavior is used. Default is True.

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting without spaCy.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier (sm, md, lg, or trf).

Returns:

List of sentences

Raises:
  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

>>> # Auto-select a compatible local model, otherwise use regex
>>> sentences = split_sentences(text)
>>>
>>> # Force simple mode (even if spaCy is installed)
>>> sentences = split_sentences(text, use_spacy=False)
>>>
>>> # Force spaCy mode (requires a compatible local model)
>>> sentences = split_sentences(text, use_spacy=True)

Note

The simple mode (regex-based) is faster and has no ML dependencies, but is less accurate (~85-90% vs ~95%+ for spaCy) on complex text. For best results with complex text, install spaCy:

pip install phrasplit[nlp]

Example:

from phrasplit import split_sentences

text = "Dr. Smith is here. She has a Ph.D. in Chemistry."
sentences = split_sentences(text)
# ['Dr. Smith is here.', 'She has a Ph.D. in Chemistry.']

# Use simple mode (no spaCy required)
sentences = split_sentences(text, use_spacy=False)

# Automatic highest-available selection, exact overrides, and regex forcing
sentences = split_sentences(text, language="en")
sentences = split_sentences(text, language="de")
sentences = split_sentences(text, language_model="en_core_web_sm")
sentences = split_sentences(text, language="en", model_size="lg")

# split_on_colon is deprecated (kept for compatibility only)
text = "Note: This is important."
sentences = split_sentences(text, split_on_colon=False)

split_clauses

phrasplit.split_clauses(text: str, language_model: str | None = None, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split text into comma-separated parts for audiobook creation.

Uses sentence detection, then splits each sentence at commas. The comma stays at the end of each part, creating natural pause points for text-to-speech processing.

Parameters:
  • text – Input text

  • language_model – Language model name (e.g., “en_core_web_sm”)

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

Returns:

List of comma-separated parts

Raises:
  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

Input: "I do like coffee, and I like wine."
Output: ["I do like coffee,", "and I like wine."]

Example:

from phrasplit import split_clauses

text = "I like coffee, and I like tea."
clauses = split_clauses(text)
# ['I like coffee,', 'and I like tea.']

# Use simple mode for faster processing
clauses = split_clauses(text, use_spacy=False)

detect_clause_boundaries

phrasplit.detect_clause_boundaries(text: str, *, use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[ClauseBoundary][source]

Detect high-confidence syntactic comma boundaries in text.

This opt-in API is intentionally independent of split_clauses(), which continues to split every comma-separated part. Detection requires spaCy-like dependency, subject, and finite-predicate annotations; regex mode never guesses.

Supplied documents are inspected directly and supplied pipelines are called once. Neither caller-owned object is retained or mutated.

Detects opt-in clausal_comma boundaries only when both sides of a comma have an explicit local syntactic subject and conservative finite-predicate evidence. It is independent of split_clauses(), which continues to return every comma-separated part.

from phrasplit import ClauseBoundary, detect_clause_boundaries

text = "I left, but she stayed."
boundaries = detect_clause_boundaries(text, use_spacy=True)
assert isinstance(boundaries[0], ClauseBoundary)
assert boundaries[0].text == text[boundaries[0].char_start : boundaries[0].char_end]

The function uses token offsets and returns boundaries in source order without duplicates. use_spacy=False and automatic mode without a compatible local model return []; automatic and forced model selection never download models. Forced spaCy preserves the existing resolver errors. A supplied doc= is validated against text, reused without another pipeline call, and caller-owned analysis is not mutated or retained. A supplied nlp= pipeline is called once.

detect_parenthetical_boundaries

phrasplit.detect_parenthetical_boundaries(text: str, *, language: str | None = None) → list[ClauseBoundary][source]

Detect high-confidence balanced round-parenthesis boundaries in text.

Detects high-confidence balanced round-parenthesis asides without requiring spaCy or a parsed document. It returns DetectedBoundary values in source order. Each boundary covers exactly one original source character, so boundary.text == text[boundary.char_start:boundary.char_end].

from phrasplit import detect_parenthetical_boundaries

text = "They changed clothes (stained with blood) before leaving."
boundaries = detect_parenthetical_boundaries(text)
assert [boundary.kind for boundary in boundaries] == [
    "parenthetical_open",
    "parenthetical_close",
]

The detector reports semantic source boundaries only. It does not assign pause durations or TTS policy. It scans balanced outer pairs, ignores empty and whitespace-only pairs and obvious attached lexical or function notation, and does not emit an opening boundary at position zero. A closing boundary is omitted when the parenthesis is sentence-final or followed by stronger punctuation such as a comma, colon, semicolon, question mark, exclamation mark, ellipsis, or em dash.

split_paragraphs

phrasplit.split_paragraphs(text: str) → list[str][source]

Split text into paragraphs (separated by double newlines).

Applies preprocessing to fix hyphenated line breaks and normalize whitespace.

Parameters:

text – Input text

Returns:

List of paragraphs (non-empty, stripped)

Example:

from phrasplit import split_paragraphs

text = "First paragraph.\n\nSecond paragraph."
paragraphs = split_paragraphs(text)
# ['First paragraph.', 'Second paragraph.']

split_text

phrasplit.split_text(text: str, mode: str = 'sentence', language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[Segment][source]

Compatibility wrapper returning only segments from a detailed split.

Example:

from phrasplit import split_text, Segment

text = "First sentence. Second sentence.\n\nNew paragraph."
segments = split_text(text, mode="sentence")

for seg in segments:
    print(f"P{seg.paragraph} S{seg.sentence}: {seg.text}")
# P0 S0: First sentence.
# P0 S1: Second sentence.
# P1 S0: New paragraph.

# Clause mode for finer granularity
text = "Hello, world.\n\nGoodbye, friend."
segments = split_text(text, mode="clause")
# Returns clauses with paragraph and sentence indices

# Use simple mode (no spaCy)
segments = split_text(text, mode="sentence", use_spacy=False)

split_text_with_diagnostics

phrasplit.split_text_with_diagnostics(text: str, mode: str = 'sentence', language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → SplitTextResult[source]

Split text and return segments with the resolution used for the operation.

This function provides a unified interface for text splitting with different granularity levels, preserving paragraph and sentence structure information while exposing backend/model diagnostics without a second resolution pass. Useful for integrations that need to report or map the selected backend. Args:

text: Input text to split mode: Splitting mode. Valid values are "paragraph", "sentence",

and "clause".

language_model: Language model name (e.g., “en_core_web_sm”) apply_corrections: Whether to apply post-processing corrections for

common spaCy errors (URL splitting, abbreviation handling). Default is True. Applies to sentence/clause modes for both backends.

split_on_colon: Deprecated. Kept for API compatibility (currently unused).

spaCy’s default colon behavior is used. Default is True.

use_spacy: Choose implementation:

None (default) selects a compatible installed/loadable model or regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

language: Language or locale hint used for model selection and abbreviations. model_size: Optional exact model tier; it never falls back to another tier.

Returns:

A SplitTextResult containing the list of Segment objects and SplitDiagnostics from the same backend/model resolution operation.

Raises:

ValueError: If mode is not one of “paragraph”, “sentence”, “clause” SpacyNotInstalledError: If use_spacy=True and spaCy is not installed. NoCompatibleSpacyModelError: If forced spaCy has no compatible loadable model. ExplicitSpacyModelError: If an explicit model cannot be loaded.

Example:

>>> result = split_text_with_diagnostics(
...     "Hello world. How are you?
New paragraph.”

… ) >>> print(result.diagnostics.backend) spacy >>> for seg in result.segments: … print(f”P{seg.paragraph} S{seg.sentence}: {seg.text}”) P0 S0: Hello world. P0 S1: How are you? P1 S0: New paragraph.

Use this additive API when a caller needs the backend/model chosen by the actual split operation. It returns a SplitTextResult with segments and frozen SplitDiagnostics:

from phrasplit import split_text_with_diagnostics

result = split_text_with_diagnostics("Hello. World.", language="en")
print(result.diagnostics.backend)
print(result.diagnostics.selected_model)
print(result.diagnostics.selected_model_size)

SplitDiagnostics.backend is "none" for paragraph mode, "regex" for the regex backend, and "spacy" for spaCy. resolution is None for forced regex and paragraph mode; automatic fallback retains a resolution whose selected model is None. The existing split_text() function remains a compatibility wrapper returning list[Segment].

split_long_lines

phrasplit.split_long_lines(text: str, max_length: int, language_model: str | None = None, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split lines exceeding max_length at clause/sentence boundaries.

Strategy: 1. First try to split at sentence boundaries 2. If still too long, split at clause boundaries (commas, semicolons, etc.) 3. If still too long, split at word boundaries

Parameters:
  • text – Input text

  • max_length – Maximum line length in characters (must be positive)

  • language_model – Language model name (e.g., “en_core_web_sm”)

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

Returns:

List of lines, each within max_length (except single words exceeding limit)

Raises:
  • ValueError – If max_length is less than 1

  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

from phrasplit import split_long_lines

text = "This is a very long sentence that needs to be split into smaller parts."
lines = split_long_lines(text, max_length=40)

# Use simple mode
lines = split_long_lines(text, max_length=40, use_spacy=False)

Offset APIs

phrasplit.split_with_offsets_with_diagnostics(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → SplitWithOffsetsResult[source]

Split text into offset-preserving segments with backend diagnostics.

The returned SplitWithOffsetsResult contains the exact segments produced by this operation and the SplitDiagnostics for its backend/model choice. This is the main API for offset-preserving segmentation, designed for downstream processing where exact character positions are critical.

Exact-Slice Policy

This function implements the exact-slice policy. For every returned segment, the following invariant ALWAYS holds:

segment.text == text[segment.char_start:segment.char_end]

This guarantee means:

  • Offsets map precisely to the original input text

  • No whitespace normalization or stripping breaks the mapping

  • Downstream code can reliably use offsets for span slicing

  • Integration with token alignment and markup slicing is safe

Key features:

  • Returns segments with precise character offsets (char_start, char_end)

  • Generates stable, hierarchical IDs (e.g., “p0s1”, “p0s2c3”)

  • Maintains exact-slice invariant in all modes

  • Supports both spaCy (accurate) and regex (fast) backends

  • Optional max_chars safety splitting with deterministic boundaries

Parameters:
  • text – Input text to split

  • mode – Splitting granularity. Valid values are "paragraph", "sentence", and "clause".

  • use_spacy – Backend selection. None (default) selects a compatible installed/loadable local model or falls back to regex; True forces spaCy; False forces regex-based splitting.

  • language_model – Language model name (e.g., “en_core_web_sm”, “de_core_news_sm”) Used for both spaCy model selection and abbreviation handling

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling, ellipsis). Default is True. Only applies to spaCy mode.

  • max_chars – Optional maximum segment length. Segments exceeding this will be split further at whitespace/punctuation boundaries while maintaining the exact-slice invariant. Split segments get IDs like “p0s1:m0”, “p0s1:m1”, etc.

  • inline_markup – Opt-in inline XHTML markup handling (default False). When True, the regex backend keeps inline tags balanced across sentence boundaries: closing tags after punctuation stay in the previous segment and opening tags after whitespace start the next segment. Requires the regex backend; passing use_spacy=True with inline_markup=True raises ValueError. Default behavior (plain text) is unchanged when False.

Returns:

A SplitWithOffsetsResult with segments containing SplitSegment values and diagnostics describing the backend/model resolution used for this exact operation.

Raises:
  • ValueError – If mode is invalid or max_chars < 1

  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

>>> text = "Hello world. How are you?\n\nNew paragraph."
>>> segments = split_with_offsets(text, mode="sentence")
>>> for seg in segments:
...     # Verify exact-slice invariant
...     assert text[seg.char_start:seg.char_end] == seg.text
...     print(f"{seg.id}: {seg.text!r}")
p0s0: 'Hello world.'
p0s1: 'How are you?'
p1s0: 'New paragraph.'

>>> # With max_chars safety splitting
>>> long_text = "word " * 100
>>> segments = split_with_offsets(long_text, max_chars=50)
>>> all(len(seg.text) <= 50 for seg in segments)
True
>>> # Exact-slice invariant still holds
>>> all(long_text[s.char_start:s.char_end] == s.text for s in segments)
True

Note

  • Segments may include leading/trailing whitespace from the original text

  • IDs are stable and deterministic across runs with same input and settings

  • For SSMD/markup integration, offsets are in the coordinate space of the input text (before or after escaping, depending on your workflow)

phrasplit.split_with_offsets(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[SplitSegment][source]

Compatibility wrapper returning only offset-preserving segments.

phrasplit.iter_split_with_offsets(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → Iterator[SplitSegment][source]

Iterator facade over split_with_offsets().

The current implementation computes the complete segment list before yielding. It is useful for iterator-oriented integrations, but it does not yet provide incremental parsing, bounded memory, or earlier time-to-first-segment behavior.

Parameters:
  • text – Input text to split

  • mode – Splitting granularity (“paragraph”, “sentence”, or “clause”)

  • use_spacy – Backend selection (None=auto, True=spaCy, False=regex)

  • language_model – Language model name for NLP/abbreviations

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier.

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling, ellipsis). Default is True. Only applies to spaCy mode.

  • max_chars – Optional maximum segment length

Yields:

SplitSegment objects in document order

Example:

>>> text = "First sentence. Second sentence.\n\nNew paragraph."
>>> for segment in iter_split_with_offsets(text, mode="sentence"):
...     print(f"{segment.id}: {segment.text}")
p0s0: First sentence.
p0s1: Second sentence.
p1s0: New paragraph.

Note

  • Segments are yielded in document order

  • Same offset guarantees as split_with_offsets()

  • The underlying splitter may use its documented model cache/global diagnostic state; callers must not interpret this facade as a thread-isolated stream.

Use split_with_offsets_with_diagnostics() when an integration needs both exact source offsets and the backend/model chosen by the operation that produced them. It returns a SplitWithOffsetsResult containing segments and SplitDiagnostics:

from phrasplit import split_with_offsets_with_diagnostics

text = "Hello. World."
result = split_with_offsets_with_diagnostics(text, language="en")

print(result.diagnostics.backend)
print(result.diagnostics.selected_model)

for segment in result.segments:
    assert segment.text == text[segment.char_start:segment.char_end]

split_with_offsets() remains a compatibility wrapper returning list[SplitSegment]. Both APIs preserve the exact-slice invariant. inline_markup=True is regex-only and raises ValueError when explicitly combined with spaCy. The iterator is currently a facade over the list API and does not claim incremental or bounded-memory processing.

Data Types

Segment

class phrasplit.Segment(text: str, paragraph: int, sentence: int | None = None)[source]

Bases: NamedTuple

A text segment with position information.

- ``text``

The text content of the segment.

- ``paragraph``

Paragraph index (0-based) within the document.

- ``sentence``

Sentence index (0-based) within the paragraph. None for paragraph mode.

text: str

Alias for field number 0

paragraph: int

Alias for field number 1

sentence: int | None

Alias for field number 2

A named tuple representing a text segment with position information.

Fields:

  • text (str): The text content of the segment

  • paragraph (int): Paragraph index (0-based) within the document

  • sentence (int | None): Sentence index (0-based) within the paragraph. None for paragraph mode.

Example:

from phrasplit import split_text, Segment

segments = split_text("Hello world.", mode="sentence")
seg = segments[0]

# Access by name
print(seg.text)       # "Hello world."
print(seg.paragraph)  # 0
print(seg.sentence)   # 0

# Access by index
print(seg[0])  # "Hello world."
print(seg[1])  # 0
print(seg[2])  # 0

# Unpack
text, para, sent = seg

Module Contents

splitter module

Text splitting utilities using spaCy for NLP-based sentence and clause detection.

This module provides two implementations: 1. spaCy-based (default when available): High accuracy, handles complex cases 2. Regex-based (fallback): Faster, simpler, good for common cases

The implementation is selected automatically based on spaCy availability, or can be controlled via the use_spacy parameter.

class phrasplit.splitter.AnalyzedToken(*args, **kwargs)[source]

Bases: Protocol

Minimal token surface required from an analyzed document.

idx: int
text: str
class phrasplit.splitter.AnalyzedSpan(*args, **kwargs)[source]

Bases: Protocol

Minimal sentence-span surface required from an analyzed document.

start_char: int
end_char: int
text: str
class phrasplit.splitter.AnalyzedDocument(*args, **kwargs)[source]

Bases: Protocol

Provider-neutral protocol for a pre-analyzed document.

text: str
property sents: Iterable[AnalyzedSpan]
class phrasplit.splitter.Segment(text: str, paragraph: int, sentence: int | None = None)[source]

Bases: NamedTuple

A text segment with position information.

- ``text``

The text content of the segment.

- ``paragraph``

Paragraph index (0-based) within the document.

- ``sentence``

Sentence index (0-based) within the paragraph. None for paragraph mode.

text: str

Alias for field number 0

paragraph: int

Alias for field number 1

sentence: int | None

Alias for field number 2

class phrasplit.splitter.SplitDiagnostics(backend: Literal['none', 'regex', 'spacy'], language: str, resolution: SpacyModelResolution | None, analysis_source: Literal['regex', 'resolved-model', 'provided-pipeline', 'provided-document'] = 'regex', model_owned_by_caller: bool = False)[source]

Bases: object

Resolution details produced by one public split operation.

backend: Literal['none', 'regex', 'spacy']
language: str
resolution: SpacyModelResolution | None
analysis_source: Literal['regex', 'resolved-model', 'provided-pipeline', 'provided-document'] = 'regex'
model_owned_by_caller: bool = False
property selected_model: str | None

Return the concrete model selected for this split, if any.

property selected_model_size: Literal['sm', 'md', 'lg', 'trf'] | None

Return the selected model tier, if any.

class phrasplit.splitter.SplitTextResult(segments: list[Segment], diagnostics: SplitDiagnostics)[source]

Bases: object

Segments and backend diagnostics from a detailed split operation.

segments: list[Segment]
diagnostics: SplitDiagnostics
class phrasplit.splitter.SplitWithOffsetsResult(segments: list[SplitSegment], diagnostics: SplitDiagnostics)[source]

Bases: object

Offset-preserving segments and diagnostics from one split operation.

segments: list[SplitSegment]
diagnostics: SplitDiagnostics
phrasplit.splitter.detect_parenthetical_boundaries(text: str, *, language: str | None = None) → list[ClauseBoundary][source]

Detect high-confidence balanced round-parenthesis boundaries in text.

phrasplit.splitter.split_paragraphs(text: str) → list[str][source]

Split text into paragraphs (separated by double newlines).

Applies preprocessing to fix hyphenated line breaks and normalize whitespace.

Parameters:

text – Input text

Returns:

List of paragraphs (non-empty, stripped)

phrasplit.splitter.split_sentences(text: str, language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split text into sentences.

By default, selects the highest-quality compatible installed and loadable spaCy model for the requested language, otherwise falls back to regex-based splitting. You can force a specific implementation with use_spacy.

Parameters:
  • text – Input text

  • language_model – Language model name (e.g., “en_core_web_sm”, “de_core_news_sm”) For spaCy mode: Name of the spaCy model to use For simple mode: Used to determine language for abbreviation handling

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling). Default is True. The regex fallback applies the same corrections.

  • split_on_colon – Deprecated. Kept for API compatibility (currently unused). spaCy’s default colon behavior is used. Default is True.

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting without spaCy.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier (sm, md, lg, or trf).

Returns:

List of sentences

Raises:
  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

>>> # Auto-select a compatible local model, otherwise use regex
>>> sentences = split_sentences(text)
>>>
>>> # Force simple mode (even if spaCy is installed)
>>> sentences = split_sentences(text, use_spacy=False)
>>>
>>> # Force spaCy mode (requires a compatible local model)
>>> sentences = split_sentences(text, use_spacy=True)

Note

The simple mode (regex-based) is faster and has no ML dependencies, but is less accurate (~85-90% vs ~95%+ for spaCy) on complex text. For best results with complex text, install spaCy:

pip install phrasplit[nlp]
phrasplit.splitter.split_clauses(text: str, language_model: str | None = None, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split text into comma-separated parts for audiobook creation.

Uses sentence detection, then splits each sentence at commas. The comma stays at the end of each part, creating natural pause points for text-to-speech processing.

Parameters:
  • text – Input text

  • language_model – Language model name (e.g., “en_core_web_sm”)

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

Returns:

List of comma-separated parts

Raises:
  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

Input: "I do like coffee, and I like wine."
Output: ["I do like coffee,", "and I like wine."]
phrasplit.splitter.split_long_lines(text: str, max_length: int, language_model: str | None = None, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[str][source]

Split lines exceeding max_length at clause/sentence boundaries.

Strategy: 1. First try to split at sentence boundaries 2. If still too long, split at clause boundaries (commas, semicolons, etc.) 3. If still too long, split at word boundaries

Parameters:
  • text – Input text

  • max_length – Maximum line length in characters (must be positive)

  • language_model – Language model name (e.g., “en_core_web_sm”)

  • use_spacy – Choose implementation: None (default) selects a compatible local model or uses regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

Returns:

List of lines, each within max_length (except single words exceeding limit)

Raises:
  • ValueError – If max_length is less than 1

  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

phrasplit.splitter.split_text_with_diagnostics(text: str, mode: str = 'sentence', language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → SplitTextResult[source]

Split text and return segments with the resolution used for the operation.

This function provides a unified interface for text splitting with different granularity levels, preserving paragraph and sentence structure information while exposing backend/model diagnostics without a second resolution pass. Useful for integrations that need to report or map the selected backend. Args:

text: Input text to split mode: Splitting mode. Valid values are "paragraph", "sentence",

and "clause".

language_model: Language model name (e.g., “en_core_web_sm”) apply_corrections: Whether to apply post-processing corrections for

common spaCy errors (URL splitting, abbreviation handling). Default is True. Applies to sentence/clause modes for both backends.

split_on_colon: Deprecated. Kept for API compatibility (currently unused).

spaCy’s default colon behavior is used. Default is True.

use_spacy: Choose implementation:

None (default) selects a compatible installed/loadable model or regex. True requires spaCy and a compatible loadable model. False forces simple regex-based splitting.

language: Language or locale hint used for model selection and abbreviations. model_size: Optional exact model tier; it never falls back to another tier.

Returns:

A SplitTextResult containing the list of Segment objects and SplitDiagnostics from the same backend/model resolution operation.

Raises:

ValueError: If mode is not one of “paragraph”, “sentence”, “clause” SpacyNotInstalledError: If use_spacy=True and spaCy is not installed. NoCompatibleSpacyModelError: If forced spaCy has no compatible loadable model. ExplicitSpacyModelError: If an explicit model cannot be loaded.

Example:

>>> result = split_text_with_diagnostics(
...     "Hello world. How are you?
New paragraph.”

… ) >>> print(result.diagnostics.backend) spacy >>> for seg in result.segments: … print(f”P{seg.paragraph} S{seg.sentence}: {seg.text}”) P0 S0: Hello world. P0 S1: How are you? P1 S0: New paragraph.

phrasplit.splitter.split_text(text: str, mode: str = 'sentence', language_model: str | None = None, apply_corrections: bool = True, split_on_colon: bool = True, use_spacy: bool | None = None, *, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[Segment][source]

Compatibility wrapper returning only segments from a detailed split.

phrasplit.splitter.split_with_offsets_with_diagnostics(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → SplitWithOffsetsResult[source]

Split text into offset-preserving segments with backend diagnostics.

The returned SplitWithOffsetsResult contains the exact segments produced by this operation and the SplitDiagnostics for its backend/model choice. This is the main API for offset-preserving segmentation, designed for downstream processing where exact character positions are critical.

Exact-Slice Policy

This function implements the exact-slice policy. For every returned segment, the following invariant ALWAYS holds:

segment.text == text[segment.char_start:segment.char_end]

This guarantee means:

  • Offsets map precisely to the original input text

  • No whitespace normalization or stripping breaks the mapping

  • Downstream code can reliably use offsets for span slicing

  • Integration with token alignment and markup slicing is safe

Key features:

  • Returns segments with precise character offsets (char_start, char_end)

  • Generates stable, hierarchical IDs (e.g., “p0s1”, “p0s2c3”)

  • Maintains exact-slice invariant in all modes

  • Supports both spaCy (accurate) and regex (fast) backends

  • Optional max_chars safety splitting with deterministic boundaries

Parameters:
  • text – Input text to split

  • mode – Splitting granularity. Valid values are "paragraph", "sentence", and "clause".

  • use_spacy – Backend selection. None (default) selects a compatible installed/loadable local model or falls back to regex; True forces spaCy; False forces regex-based splitting.

  • language_model – Language model name (e.g., “en_core_web_sm”, “de_core_news_sm”) Used for both spaCy model selection and abbreviation handling

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier; it never falls back to another tier.

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling, ellipsis). Default is True. Only applies to spaCy mode.

  • max_chars – Optional maximum segment length. Segments exceeding this will be split further at whitespace/punctuation boundaries while maintaining the exact-slice invariant. Split segments get IDs like “p0s1:m0”, “p0s1:m1”, etc.

  • inline_markup – Opt-in inline XHTML markup handling (default False). When True, the regex backend keeps inline tags balanced across sentence boundaries: closing tags after punctuation stay in the previous segment and opening tags after whitespace start the next segment. Requires the regex backend; passing use_spacy=True with inline_markup=True raises ValueError. Default behavior (plain text) is unchanged when False.

Returns:

A SplitWithOffsetsResult with segments containing SplitSegment values and diagnostics describing the backend/model resolution used for this exact operation.

Raises:
  • ValueError – If mode is invalid or max_chars < 1

  • SpacyNotInstalledError – If use_spacy=True and spaCy is not installed.

  • NoCompatibleSpacyModelError – If forced spaCy has no compatible loadable model.

  • ExplicitSpacyModelError – If an explicit model cannot be loaded.

Example:

>>> text = "Hello world. How are you?\n\nNew paragraph."
>>> segments = split_with_offsets(text, mode="sentence")
>>> for seg in segments:
...     # Verify exact-slice invariant
...     assert text[seg.char_start:seg.char_end] == seg.text
...     print(f"{seg.id}: {seg.text!r}")
p0s0: 'Hello world.'
p0s1: 'How are you?'
p1s0: 'New paragraph.'

>>> # With max_chars safety splitting
>>> long_text = "word " * 100
>>> segments = split_with_offsets(long_text, max_chars=50)
>>> all(len(seg.text) <= 50 for seg in segments)
True
>>> # Exact-slice invariant still holds
>>> all(long_text[s.char_start:s.char_end] == s.text for s in segments)
True

Note

  • Segments may include leading/trailing whitespace from the original text

  • IDs are stable and deterministic across runs with same input and settings

  • For SSMD/markup integration, offsets are in the coordinate space of the input text (before or after escaping, depending on your workflow)

phrasplit.splitter.detect_clause_boundaries(text: str, *, use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[ClauseBoundary][source]

Detect high-confidence syntactic comma boundaries in text.

This opt-in API is intentionally independent of split_clauses(), which continues to split every comma-separated part. Detection requires spaCy-like dependency, subject, and finite-predicate annotations; regex mode never guesses.

Supplied documents are inspected directly and supplied pipelines are called once. Neither caller-owned object is retained or mutated.

phrasplit.splitter.split_with_offsets(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → list[SplitSegment][source]

Compatibility wrapper returning only offset-preserving segments.

phrasplit.splitter.iter_split_with_offsets(text: str, *, mode: str = 'sentence', use_spacy: bool | None = None, language_model: str | None = None, language: str = 'en', model_size: Literal['sm', 'md', 'lg', 'trf'] | None = None, apply_corrections: bool = True, max_chars: int | None = None, inline_markup: bool = False, nlp: Any | None = None, doc: AnalyzedDocument | None = None) → Iterator[SplitSegment][source]

Iterator facade over split_with_offsets().

The current implementation computes the complete segment list before yielding. It is useful for iterator-oriented integrations, but it does not yet provide incremental parsing, bounded memory, or earlier time-to-first-segment behavior.

Parameters:
  • text – Input text to split

  • mode – Splitting granularity (“paragraph”, “sentence”, or “clause”)

  • use_spacy – Backend selection (None=auto, True=spaCy, False=regex)

  • language_model – Language model name for NLP/abbreviations

  • language – Language or locale hint used for model selection and abbreviations.

  • model_size – Optional exact model tier.

  • apply_corrections – Whether to apply post-processing corrections for common spaCy errors (URL splitting, abbreviation handling, ellipsis). Default is True. Only applies to spaCy mode.

  • max_chars – Optional maximum segment length

Yields:

SplitSegment objects in document order

Example:

>>> text = "First sentence. Second sentence.\n\nNew paragraph."
>>> for segment in iter_split_with_offsets(text, mode="sentence"):
...     print(f"{segment.id}: {segment.text}")
p0s0: First sentence.
p0s1: Second sentence.
p1s0: New paragraph.

Note

  • Segments are yielded in document order

  • Same offset guarantees as split_with_offsets()

  • The underlying splitter may use its documented model cache/global diagnostic state; callers must not interpret this facade as a thread-isolated stream.

Type Information

phrasplit is fully typed and includes a py.typed marker file for PEP 561 compliance. You can use it with mypy and other type checkers.

Function signatures:

from typing import NamedTuple

class Segment(NamedTuple):
    text: str
    paragraph: int
    sentence: int | None = None

def split_sentences(
    text: str,
    language_model: str | None = None,
    apply_corrections: bool = True,
    split_on_colon: bool = True,
    use_spacy: bool | None = None,
    *,
    language: str = "en",
    model_size: str | None = None,
) -> list[str]: ...

def split_clauses(
    text: str,
    language_model: str | None = None,
    use_spacy: bool | None = None,
    *,
    language: str = "en",
    model_size: str | None = None,
) -> list[str]: ...

def split_paragraphs(text: str) -> list[str]: ...

def split_text(
    text: str,
    mode: str = "sentence",
    language_model: str | None = None,
    apply_corrections: bool = True,
    split_on_colon: bool = True,
    use_spacy: bool | None = None,
    *,
    language: str = "en",
    model_size: str | None = None,
) -> list[Segment]: ...

def split_long_lines(
    text: str,
    max_length: int,
    language_model: str | None = None,
    use_spacy: bool | None = None,
    *,
    language: str = "en",
    model_size: str | None = None,
) -> list[str]: ...

Injected spaCy analysis

The splitter can consume analysis prepared by an orchestrator. doc must expose text, sents, and sentence spans with start_char and end_char; it is checked against the exact input string. nlp is a caller-owned callable pipeline used when doc is not supplied.

prepared = spokenform.prepare(raw_text)
doc = nlp(prepared.spoken_text)
result = split_with_offsets_with_diagnostics(
    prepared.spoken_text, language="de", doc=doc
 )

A supplied doc is preferred over nlp, bypasses model resolution and inference, and cannot be combined with use_spacy=False. Diagnostics report analysis_source as provided-document or provided-pipeline, set model_owned_by_caller to True, and leave selected_model unset when no model identity is available. Phrasplit still applies its corrections and exact-slice offset projection.

Phrasplit only reads injected resources during the call. It does not mutate, cache, close, unload, or take ownership of a supplied document or pipeline.