Embedding Chunk Planner — Python source
Plan document chunking for RAG — chunk counts with overlap math, vector counts, and embedding costs per model.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""Embedding Chunk Planner — pure chunking math for RAG pipelines.
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the Embedding Chunk Planner
tool, ported from src/lib/embeddingPlanner.ts (the canonical
TypeScript implementation).
Tool page: https://dev.cosmolabs.org/tools/embedding-chunk-planner
License: display source — part of CosmoDev's polyglot tool pages.
Design goals:
- Pure + deterministic; never raises.
- Functionally equivalent to the TS reference: same inputs -> same outputs.
- Self-contained: stdlib only (no pip packages). The model price table is
inlined below, mirrored from src/lib/ai/embeddings.ts — prices NEVER live
in the planner itself.
Behavior (mirrors the TS source exactly):
- chunk_size <= 0 or total_tokens <= 0 -> ChunkPlan(0, 0, 0) (nothing to
embed).
- Negative overlap is treated as 0; overlap then clamps to at most
chunk_size // 2 so consecutive chunks always advance.
- chunks = max(1, ceil((total_tokens - overlap) / (chunk_size - overlap)))
— a tiny document still yields one chunk.
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Optional, Tuple
__all__ = [
"ChunkOptions",
"ChunkPlan",
"EmbeddingModel",
"EmbeddingPlan",
"EMBEDDING_MODELS",
"DEFAULT_CHUNK_OPTIONS",
"get_embedding_model",
"plan_chunks",
"plan_embedding",
]
@dataclass(frozen=True)
class ChunkOptions:
"""Chunking knobs, in tokens. Defaults: 512-token chunks, 64-token overlap.
Mirrors the TS ``ChunkOptions``; ``DEFAULT_CHUNK_OPTIONS`` mirrors the TS
default constant.
"""
chunk_size: int = 512
overlap: int = 64
@dataclass(frozen=True)
class ChunkPlan:
"""How a document splits into overlapping chunks."""
chunks: int
total_tokens_with_overlap: int
overhead_tokens: int
@dataclass(frozen=True)
class EmbeddingModel:
"""One embedding model's offered dimensions and pricing.
``input_per_m`` is USD per 1M input tokens.
"""
id: str
vendor: str
dims: Tuple[int, ...]
input_per_m: float
#: Embedding model price table — the SSOT for pricing, mirrored from
#: src/lib/ai/embeddings.ts. Refresh both files together.
EMBEDDING_MODELS: Tuple[EmbeddingModel, ...] = (
EmbeddingModel("text-embedding-3-small", "OpenAI", (512, 1536), 0.02),
EmbeddingModel("text-embedding-3-large", "OpenAI", (256, 1024, 3072), 0.13),
EmbeddingModel("embed-english-v3.0", "Cohere", (512, 1024, 1536), 0.1),
EmbeddingModel("voyage-3-lite", "Voyage AI", (512, 1024), 0.02),
)
DEFAULT_CHUNK_OPTIONS = ChunkOptions()
def get_embedding_model(model_id: str) -> Optional[EmbeddingModel]:
"""Look up an embedding model by id. Returns ``None`` for unknown ids."""
return next((m for m in EMBEDDING_MODELS if m.id == model_id), None)
def plan_chunks(
total_tokens: int,
chunk_size: Optional[int] = None,
overlap: Optional[int] = None,
) -> ChunkPlan:
"""Plan how ``total_tokens`` split into overlapping chunks.
``chunk_size`` / ``overlap`` are each optional (the TS ``Partial<ChunkOptions>``);
a ``None`` value falls back to the 512 / 64 default, and passing only one
leaves the other at its default.
"""
cs = DEFAULT_CHUNK_OPTIONS.chunk_size if chunk_size is None else chunk_size
ov_raw = DEFAULT_CHUNK_OPTIONS.overlap if overlap is None else overlap
if cs <= 0 or total_tokens <= 0:
return ChunkPlan(0, 0, 0)
# min(max(overlap, 0), chunk_size // 2) — the TS clamp.
ov = min(max(ov_raw, 0), cs // 2)
# Float division + ceil mirrors TS's Math.ceil exactly (a negative
# numerator below the max(1, ...) floor is handled next).
chunks = max(1, math.ceil((total_tokens - ov) / (cs - ov)))
total_with_overlap = total_tokens + (chunks - 1) * ov
return ChunkPlan(chunks, total_with_overlap, total_with_overlap - total_tokens)
@dataclass(frozen=True)
class EmbeddingPlan(ChunkPlan):
"""Chunk plan plus pricing for one embedding call.
``vectors`` is one per chunk; ``cost`` is USD:
``total_tokens_with_overlap / 1e6 * model.input_per_m``.
"""
model: EmbeddingModel
vectors: int
cost: float
def plan_embedding(
total_tokens: int,
model_id: str,
dims: int,
chunk_size: Optional[int] = None,
overlap: Optional[int] = None,
) -> Optional[EmbeddingPlan]:
"""Chunk a document AND price its embedding for ``model_id`` at ``dims``
dimensions. Unknown model, or dims the model does not offer -> ``None``."""
model = get_embedding_model(model_id)
if model is None or dims not in model.dims:
return None
plan = plan_chunks(total_tokens, chunk_size, overlap)
return EmbeddingPlan(
chunks=plan.chunks,
total_tokens_with_overlap=plan.total_tokens_with_overlap,
overhead_tokens=plan.overhead_tokens,
model=model,
vectors=plan.chunks,
cost=(plan.total_tokens_with_overlap / 1e6) * model.input_per_m,
)
Also available in 12 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →