Módulo 4: Evaluación y Chunking Strategies
Fixed-Size Chunking: Implementación Práctica
Descripción de la cápsula
Fixed-size chunking es la estrategia más simple: dividir texto cada N caracteres o tokens con overlap opcional. Aunque no respeta boundaries semánticas, es determinístico, rápido, y suficiente para muchos casos de uso.
En esta cápsula aprenderás la diferencia entre character-based y token-based chunking, implementarás chunking con tiktoken (token counting preciso), overlap strategies con sliding window, y trade-offs de fixed-size. También verás código production-ready reutilizable.
Al final, podrás implementar fixed-size chunking robusto para tus proyectos RAG.
Character-Based vs Token-Based
Character-based chunking:
# Dividir cada N caracteres
text = "Python is a programming language. It is very popular."
def char_chunking(text, chunk_size=20, overlap=5):
"""Character-based chunking"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start += (chunk_size - overlap)
return chunks
chunks = char_chunking(text, chunk_size=30, overlap=5)
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1}: '{chunk}'")
Output:
Chunk 1: 'Python is a programming la'
Chunk 2: 'ming language. It is very '
Chunk 3: ' very popular.'
❌ Problema: Corta palabras ("language", "ming").
Token-based chunking (mejor):
import tiktoken
def token_chunking(text, chunk_size=10, overlap=2):
"""Token-based chunking con tiktoken"""
encoding = tiktoken.encoding_for_model("gpt-4")
# Tokenizar texto completo
tokens = encoding.encode(text)
chunks = []
start = 0
while start < len(tokens):
end = start + chunk_size
chunk_tokens = tokens[start:end]
# Decodificar tokens a texto
chunk_text = encoding.decode(chunk_tokens)
chunks.append(chunk_text)
start += (chunk_size - overlap)
return chunks
text = "Python is a programming language. It is very popular for AI."
chunks = token_chunking(text, chunk_size=10, overlap=2)
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1}: '{chunk}'")
Output:
Chunk 1: 'Python is a programming language.'
Chunk 2: 'language. It is very popular'
Chunk 3: 'popular for AI.'
✅ Mejor: No corta palabras, respeta tokenización del modelo.
Implementación Production-Ready
Clase TokenChunker:
import tiktoken
from typing import List, Dict
class TokenChunker:
"""
Token-based chunker con overlap
Características:
- Token counting preciso (tiktoken)
- Overlap configurable
- Metadata tracking
"""
def __init__(
self,
chunk_size: int = 500,
overlap: int = 50,
model: str = "gpt-4"
):
"""
Args:
chunk_size: Tamaño de chunk en tokens
overlap: Tokens de overlap entre chunks
model: Modelo para tokenization (gpt-4, text-embedding-3-small)
"""
self.chunk_size = chunk_size
self.overlap = overlap
self.encoding = tiktoken.encoding_for_model(model)
def chunk(self, text: str) -> List[Dict]:
"""
Dividir texto en chunks
Returns:
Lista de dicts con keys: text, start_token, end_token, chunk_id
"""
# Tokenizar
tokens = self.encoding.encode(text)
chunks = []
chunk_id = 0
start = 0
while start < len(tokens):
end = min(start + self.chunk_size, len(tokens))
chunk_tokens = tokens[start:end]
# Decodificar
chunk_text = self.encoding.decode(chunk_tokens)
chunks.append({
'chunk_id': chunk_id,
'text': chunk_text,
'start_token': start,
'end_token': end,
'n_tokens': len(chunk_tokens)
})
chunk_id += 1
start += (self.chunk_size - self.overlap)
return chunks
def get_stats(self, chunks: List[Dict]) -> Dict:
"""Estadísticas de chunking"""
return {
'n_chunks': len(chunks),
'avg_tokens': sum(c['n_tokens'] for c in chunks) / len(chunks) if chunks else 0,
'total_tokens': sum(c['n_tokens'] for c in chunks)
}
# Uso
chunker = TokenChunker(chunk_size=500, overlap=50)
text = """
Python is a high-level programming language known for its simplicity
and readability. It was created by Guido van Rossum and first released
in 1991. Python supports multiple programming paradigms including
procedural, object-oriented, and functional programming.
""" * 10 # Repetir para hacer más largo
chunks = chunker.chunk(text)
print(f"Total chunks: {len(chunks)}")
print(f"\nFirst chunk:")
print(chunks[0]['text'][:100] + "...")
print(f"Tokens: {chunks[0]['n_tokens']}")
stats = chunker.get_stats(chunks)
print(f"\nStats:")
print(f" Chunks: {stats['n_chunks']}")
print(f" Avg tokens/chunk: {stats['avg_tokens']:.0f}")
Output:
Total chunks: 3
First chunk:
Python is a high-level programming language known for its simplicity
and readability. It was...
Tokens: 500
Stats:
Chunks: 3
Avg tokens/chunk: 483
Overlap Strategies
Por qué overlap:
# Sin overlap:
Chunk 1: [tokens 0-500]
Chunk 2: [tokens 500-1000]
# Problema:
# Si info importante está en boundary (tokens 495-505),
# se divide entre 2 chunks:
# Chunk 1: "...Python supports multiple programming par"
# Chunk 2: "adigms including procedural..."
# ❌ "paradigms" se cortó
# Con overlap (50 tokens):
Chunk 1: [tokens 0-500]
Chunk 2: [tokens 450-950] ← overlap de 50 tokens
# Ahora boundary info aparece completa en ambos chunks:
# Chunk 1: "...Python supports multiple programming paradigms"
# Chunk 2: "Python supports multiple programming paradigms including..."
# ✅ "paradigms" completo en ambos
Optimal overlap:
# Rule of thumb: 10-20% del chunk size
chunk_size = 500
overlap = int(chunk_size * 0.15) # 15% = 75 tokens
# Trade-off:
# - Overlap 0%: Riesgo de perder contexto en boundaries
# - Overlap 50%: Redundancia excesiva (storage desperdiciado)
# - Overlap 10-20%: Balance óptimo
Trade-Offs de Fixed-Size
Ventajas:
✅ 1. Simple y determinístico
# Mismos inputs → mismos chunks (reproducible)
# No depende de modelos NLP (spaCy, NLTK)
✅ 2. Rápido
# Solo tokenizar + slice
# No procesamiento semántico complejo
# ~1ms para document de 10K tokens
✅ 3. Tamaño predecible
# Todos los chunks ~500 tokens (±overlap)
# Fácil estimar storage, latencia, costos
Desventajas:
❌ 1. No respeta boundaries semánticas
# Puede cortar en medio de:
# - Frase
# - Párrafo
# - Lista
# - Tabla
# Ejemplo:
# Chunk 1: "...The three main types are: 1. Type A, 2. Type B,"
# Chunk 2: "3. Type C. Each type has different..."
# ❌ Lista dividida (chunk 1 incompleto)
❌ 2. Pierde contexto estructural
# Headers, secciones, jerarquía se pierden
# Chunk puede no tener suficiente contexto para entender
Optimizar Chunk Size
Experimento: Diferentes tamaños
import tiktoken
from openai import OpenAI
import numpy as np
from dotenv import load_dotenv
import os
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Documento largo (sample)
document = """
[Tu documentación técnica aquí - 5000 tokens]
"""
# Test diferentes chunk sizes
chunk_sizes = [200, 400, 600, 800, 1000]
for size in chunk_sizes:
chunker = TokenChunker(chunk_size=size, overlap=int(size*0.1))
chunks = chunker.chunk(document)
# Generar embeddings (para calcular storage)
n_embeddings = len(chunks)
storage_gb = n_embeddings * 1536 * 4 / (1024**3) # 1536 dims, 4 bytes/float
print(f"Chunk size {size}:")
print(f" Chunks: {len(chunks)}")
print(f" Storage: {storage_gb*1000:.2f} MB (por 1M docs)")
print()
Output típico:
Chunk size 200:
Chunks: 27
Storage: 158.20 MB
Chunk size 400:
Chunks: 14
Storage: 82.03 MB
Chunk size 600:
Chunks: 9
Storage: 52.73 MB
Chunk size 800:
Chunks: 7
Storage: 41.01 MB
Chunk size 1000:
Chunks: 6
Storage: 35.16 MB
Trade-off: Chunks más grandes → menos storage, pero menos precision en retrieval.
Ejercicios
Ejercicio 1: Implementar chunker básico
Implementa función que divide texto en chunks de N tokens:
import tiktoken
def simple_chunker(text, chunk_size=500):
"""Chunking sin overlap"""
# Implementa aquí
pass
# Test
text = "Python is great" * 200
chunks = simple_chunker(text, chunk_size=100)
print(f"Total chunks: {len(chunks)}")
Ver solución
import tiktoken
def simple_chunker(text, chunk_size=500):
"""Chunking sin overlap"""
encoding = tiktoken.encoding_for_model("gpt-4")
tokens = encoding.encode(text)
chunks = []
for i in range(0, len(tokens), chunk_size):
chunk_tokens = tokens[i:i+chunk_size]
chunk_text = encoding.decode(chunk_tokens)
chunks.append(chunk_text)
return chunks
# Test
text = "Python is great. " * 200 # ~600 tokens
chunks = simple_chunker(text, chunk_size=100)
print(f"Total chunks: {len(chunks)}") # ~6 chunks
print(f"First chunk: {chunks[0][:50]}...")
Ejercicio 2: Agregar overlap
Modifica la función anterior para agregar overlap de 10%:
Ver solución
def chunker_with_overlap(text, chunk_size=500, overlap_pct=0.1):
"""Chunking con overlap porcentual"""
encoding = tiktoken.encoding_for_model("gpt-4")
tokens = encoding.encode(text)
overlap = int(chunk_size * overlap_pct)
step = chunk_size - overlap
chunks = []
start = 0
while start < len(tokens):
end = start + chunk_size
chunk_tokens = tokens[start:end]
chunk_text = encoding.decode(chunk_tokens)
chunks.append(chunk_text)
start += step
return chunks
# Test
chunks = chunker_with_overlap("Python is great. " * 200, chunk_size=100, overlap_pct=0.1)
print(f"Total chunks: {len(chunks)}") # ~7 chunks (más que sin overlap)
Resumen
Qué aprendiste:
- ✅ Character vs Token: Token-based mejor (no corta palabras)
- ✅ Implementación: TokenChunker con tiktoken
- ✅ Overlap: 10-20% óptimo (balance contexto/storage)
- ✅ Trade-offs: Simple/rápido pero no semántico
- ✅ Chunk size: 400-800 tokens típico
Conceptos clave:
- Token-based chunking preserva palabras
- Overlap crítico para boundaries
- Fixed-size = simple pero no perfecto
Recursos adicionales
- tiktoken GitHub - Token counting
- LangChain CharacterTextSplitter - Implementación alternativa
- Chunking Strategies - Pinecone guide
En la siguiente cápsula
Cápsula 03: Semantic Chunking
Aprenderás:
- Sentence-based chunking
- Paragraph-based chunking
- Topic segmentation
- Trade-offs vs fixed-size
De fixed-size a semantic boundaries.
Módulo 4 - Embeddings Deep Dive Guide Fixed-size chunking: simple, rápido, y efectivo