Módulo 6: Prompt Composition y Chaining
5. Context Window Management
Descripción
El context window (ventana de contexto) es uno de los recursos más críticos y limitados cuando trabajas con LLMs. Entender cómo funciona, cómo medirlo con precisión, y qué estrategias usar cuando el contenido supera el límite es fundamental para construir sistemas robustos.
Esta cápsula cubre las tres estrategias principales: summarization chains, sliding window, y hierarchical summarization, junto con criterios para elegir cuándo usar cada una.
El Problema del Context Window
gpt-4o-mini: 128K tokens de contexto
gpt-4o: 128K tokens de contexto
claude-3-5-haiku: 200K tokens de contexto
¿Cuánto es un token? Aproximadamente:
- 1 token ≈ 0.75 palabras en inglés
- 1 token ≈ 0.5 palabras en español (más tokens por carácter)
- 1 página A4 de texto ≈ 500 tokens
- Un libro (200 páginas) ≈ 100,000 tokens
Problema real:
- Reporte anual de empresa: 150,000 tokens → NO CABE en un solo prompt
- Código base de 10K líneas: ~100,000 tokens → No cabe con el prompt de análisis
- Conversación de 2 horas en chat: ~30,000 tokens → Puede quedarse sin contexto
Pero incluso cuando CABE, hay otro problema: "Lost in the Middle"
El modelo tiene peor recall para información en el MEDIO del context window.
Lo mejor recordado: principio y final.
Contar Tokens con Precisión
from openai import OpenAI
import tiktoken
from typing import Union
client = OpenAI()
def contar_tokens(texto: str, modelo: str = "gpt-4o-mini") -> int:
"""
Cuenta tokens de forma precisa usando tiktoken.
Mucho más preciso que la regla heurística de palabras.
Args:
texto: Texto a tokenizar
modelo: Modelo de OpenAI para el que contar tokens
Returns:
Número exacto de tokens
"""
try:
encoding = tiktoken.encoding_for_model(modelo)
except KeyError:
# Fallback para modelos nuevos
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(texto))
def contar_tokens_mensajes(mensajes: list[dict], modelo: str = "gpt-4o-mini") -> int:
"""
Cuenta tokens para una lista de mensajes (formato chat).
Incluye los tokens overhead del formato de chat.
"""
try:
encoding = tiktoken.encoding_for_model(modelo)
except KeyError:
encoding = tiktoken.get_encoding("cl100k_base")
# Overhead por mensaje (aproximado para GPT-4)
tokens = 3 # Sistema base
for msg in mensajes:
tokens += 4 # Overhead por mensaje
tokens += len(encoding.encode(msg.get("content", "")))
tokens += len(encoding.encode(msg.get("role", "")))
return tokens
def chunks_por_tokens(
texto: str,
max_tokens_por_chunk: int = 4000,
overlap_tokens: int = 200,
modelo: str = "gpt-4o-mini"
) -> list[str]:
"""
Divide un texto en chunks de tamaño máximo en tokens.
Incluye overlap para mantener coherencia entre chunks.
Args:
texto: Texto a dividir
max_tokens_por_chunk: Máximo tokens por chunk
overlap_tokens: Tokens de overlap entre chunks consecutivos
modelo: Modelo para calcular tokens
Returns:
Lista de chunks de texto
"""
try:
encoding = tiktoken.encoding_for_model(modelo)
except KeyError:
encoding = tiktoken.get_encoding("cl100k_base")
tokens = encoding.encode(texto)
chunks = []
inicio = 0
while inicio < len(tokens):
fin = min(inicio + max_tokens_por_chunk, len(tokens))
chunk_tokens = tokens[inicio:fin]
chunk_texto = encoding.decode(chunk_tokens)
chunks.append(chunk_texto)
inicio = fin - overlap_tokens # Overlap para continuidad
if inicio >= len(tokens):
break
return chunks
# Ejemplo de uso:
if __name__ == "__main__":
texto_largo = "Este es un texto de ejemplo. " * 1000 # ~6K tokens
total_tokens = contar_tokens(texto_largo)
print(f"Total tokens: {total_tokens:,}")
chunks = chunks_por_tokens(texto_largo, max_tokens_por_chunk=1000, overlap_tokens=100)
print(f"Número de chunks: {len(chunks)}")
for i, chunk in enumerate(chunks[:3]):
print(f"Chunk {i+1}: {contar_tokens(chunk)} tokens")
Estrategia 1: Summarization Chain
La estrategia más simple: dividir el texto en chunks, resumir cada uno, y concatenar los resúmenes.
def summarization_chain(
texto: str,
max_tokens_chunk: int = 4000,
tokens_por_resumen: int = 200,
instruccion_adicional: str = "",
verbose: bool = False
) -> str:
"""
Estrategia básica de summarization:
1. Dividir en chunks
2. Resumir cada chunk
3. Concatenar resúmenes
Mejor para: Documentos donde el resumen de cada sección es suficiente.
No ideal cuando: Necesitas preservar detalles específicos.
Args:
texto: Texto largo a resumir
max_tokens_chunk: Tokens máximos por chunk
tokens_por_resumen: Tokens del resumen de cada chunk
instruccion_adicional: Qué preservar del resumen (ej: "mantén cifras exactas")
Returns:
Texto resumido concatenado
"""
chunks = chunks_por_tokens(texto, max_tokens_chunk)
if len(chunks) == 1:
return texto # No hace falta dividir
if verbose:
print(f"Dividido en {len(chunks)} chunks para summarization")
resumenes = []
for i, chunk in enumerate(chunks):
if verbose:
print(f" Resumiendo chunk {i+1}/{len(chunks)}...")
prompt = f"""Resume el siguiente fragmento de texto en {tokens_por_resumen} palabras.
{f'Instrucción adicional: {instruccion_adicional}' if instruccion_adicional else ''}
Preserva: nombres propios, fechas, números específicos, ideas principales.
Texto:
{chunk}"""
resumen = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=tokens_por_resumen * 2
).choices[0].message.content
resumenes.append(resumen)
return "\n\n".join(resumenes)
# Ejemplo con instrucción específica:
texto_informe = "El informe anual de TechCorp..." * 500 # Simular texto largo
resumen = summarization_chain(
texto_informe,
instruccion_adicional="mantén todas las cifras financieras y porcentajes",
verbose=True
)
print(f"Texto original: {contar_tokens(texto_informe):,} tokens")
print(f"Resumen: {contar_tokens(resumen):,} tokens")
Estrategia 2: Sliding Window
Procesa el texto en ventanas deslizantes con overlap, útil cuando necesitas mantener coherencia contextual.
def sliding_window_analysis(
texto: str,
pregunta: str,
window_tokens: int = 3000,
overlap_tokens: int = 300,
combinar: bool = True
) -> Union[list[str], str]:
"""
Análisis con ventana deslizante.
Útil para: búsqueda de información específica, análisis local coherente.
Args:
texto: Texto largo a analizar
pregunta: La pregunta o tarea a aplicar en cada ventana
window_tokens: Tamaño de la ventana en tokens
overlap_tokens: Tokens de overlap entre ventanas (para coherencia)
combinar: Si True, combina resultados en una síntesis final
Returns:
Lista de resultados por ventana, o síntesis combinada
"""
chunks = chunks_por_tokens(texto, window_tokens, overlap_tokens)
resultados = []
for i, chunk in enumerate(chunks):
# Indicar al modelo el contexto de la ventana
contexto = f"[Fragmento {i+1} de {len(chunks)} del documento]"
prompt = f"""{contexto}
{pregunta}
Fragmento:
{chunk}
Responde basándote SOLO en este fragmento. Si la información no está en este fragmento, indica "No encontrado en este fragmento"."""
resultado = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=400
).choices[0].message.content
resultados.append({
"fragmento": i + 1,
"total_fragmentos": len(chunks),
"resultado": resultado
})
if not combinar:
return resultados
# Combinar resultados de todas las ventanas
resultados_str = "\n\n".join([
f"Fragmento {r['fragmento']}:\n{r['resultado']}"
for r in resultados
if "No encontrado" not in r['resultado']
])
if not resultados_str.strip():
return "La información solicitada no fue encontrada en ningún fragmento del documento."
sintesis = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Sintetiza estos resultados de análisis por fragmentos:
{resultados_str}
Genera una respuesta coherente y sin repeticiones para: {pregunta}"""}],
temperature=0,
max_tokens=500
).choices[0].message.content
return sintesis
# Ejemplo: Buscar información específica en documento largo
documento = "Texto largo del contrato..." * 300
pregunta = "¿Cuáles son las penalizaciones por incumplimiento mencionadas en el documento?"
resultado = sliding_window_analysis(documento, pregunta, window_tokens=2000)
print(resultado)
Estrategia 3: Hierarchical Summarization
Para documentos muy largos, hacer resúmenes en múltiples niveles.
def hierarchical_summarize(
texto: str,
nivel_1_tokens: int = 3000,
nivel_1_resumen_tokens: int = 300,
nivel_2_resumen_tokens: int = 500,
objetivo: str = "resumen general"
) -> dict:
"""
Summarization jerárquica para documentos muy largos.
Nivel 1: Resumir cada chunk (granularidad fina)
Nivel 2: Resumir los resúmenes del nivel 1 (granularidad media)
Nivel 3: Resumen final de los resúmenes de nivel 2 (granularidad gruesa)
Args:
texto: Texto muy largo
nivel_1_tokens: Tamaño de chunks del nivel 1
nivel_1_resumen_tokens: Tokens por resumen en nivel 1
nivel_2_resumen_tokens: Tokens para el resumen de nivel 2
objetivo: El objetivo del resumen (orienta el foco)
Returns:
dict con resúmenes de cada nivel
"""
# NIVEL 1: Resumir cada chunk
print("Nivel 1: Resumiendo chunks individuales...")
chunks_n1 = chunks_por_tokens(texto, nivel_1_tokens)
resumenes_n1 = []
for i, chunk in enumerate(chunks_n1):
print(f" Chunk {i+1}/{len(chunks_n1)}")
resumen = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Resume este fragmento para el objetivo: {objetivo}
Sé conciso pero preserva datos clave (números, nombres, fechas).
Target: {nivel_1_resumen_tokens} palabras.
Fragmento:
{chunk}"""}],
temperature=0,
max_tokens=nivel_1_resumen_tokens * 2
).choices[0].message.content
resumenes_n1.append(resumen)
# Verificar si el nivel 1 ya es suficientemente pequeño
texto_n1 = "\n\n---\n\n".join(resumenes_n1)
tokens_n1 = contar_tokens(texto_n1)
if tokens_n1 <= 4000:
# El nivel 1 ya cabe en contexto, hacer solo un nivel adicional
print(f"Nivel 2: Síntesis final (nivel 1 ya es manejable: {tokens_n1} tokens)")
resumen_final = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Objetivo: {objetivo}
Basándote en estos resúmenes de secciones del documento, genera un resumen final coherente:
{texto_n1}
El resumen final debe ser de {nivel_2_resumen_tokens} palabras y sin repeticiones."""}],
temperature=0,
max_tokens=nivel_2_resumen_tokens * 2
).choices[0].message.content
return {
"niveles": 2,
"n_chunks_nivel1": len(chunks_n1),
"resumenes_nivel1": resumenes_n1,
"resumen_final": resumen_final
}
# NIVEL 2: Necesitamos otro nivel de summarization
print(f"Nivel 2: Los resúmenes de nivel 1 aún son grandes ({tokens_n1} tokens). Summarizando nivel 2...")
chunks_n2 = chunks_por_tokens(texto_n1, nivel_1_tokens)
resumenes_n2 = []
for i, chunk in enumerate(chunks_n2):
resumen = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Sintetiza estos resúmenes parciales (objetivo: {objetivo}):\n\n{chunk}"}],
temperature=0,
max_tokens=nivel_2_resumen_tokens
).choices[0].message.content
resumenes_n2.append(resumen)
# NIVEL FINAL: Síntesis de nivel 2
texto_n2 = "\n\n".join(resumenes_n2)
resumen_final = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Genera el resumen ejecutivo final del documento.
Objetivo: {objetivo}
Basado en:
{texto_n2}
Sé exhaustivo pero conciso. Resalta los puntos más importantes."""}],
temperature=0,
max_tokens=nivel_2_resumen_tokens * 2
).choices[0].message.content
return {
"niveles": 3,
"n_chunks_nivel1": len(chunks_n1),
"n_chunks_nivel2": len(chunks_n2),
"resumenes_nivel1": resumenes_n1,
"resumenes_nivel2": resumenes_n2,
"resumen_final": resumen_final
}
Cuándo Usar Cada Estrategia
def seleccionar_estrategia_contexto(
texto: str,
tarea: str,
modelo: str = "gpt-4o-mini"
) -> dict:
"""
Recomienda la estrategia de context management según el tamaño del texto
y el tipo de tarea.
Returns:
dict con estrategia recomendada y justificación
"""
tokens_texto = contar_tokens(texto, modelo)
LIMITES_MODELO = {
"gpt-4o-mini": 128_000,
"gpt-4o": 128_000,
"claude-3-5-haiku-20241022": 200_000
}
limite = LIMITES_MODELO.get(modelo, 128_000)
tokens_disponibles = limite - 2000 # Reservar para el prompt y respuesta
# Tipos de tarea que requieren consulta por secciones
tareas_busqueda = ["encontrar", "buscar", "extraer específico", "¿dónde", "¿cuándo", "¿quién"]
es_busqueda = any(t in tarea.lower() for t in tareas_busqueda)
if tokens_texto <= tokens_disponibles * 0.8:
# El texto cabe cómodamente en contexto
return {
"estrategia": "contexto_directo",
"descripcion": "El texto cabe en el context window. Usarlo directamente.",
"tokens_texto": tokens_texto,
"tokens_disponibles": tokens_disponibles,
"chunks_estimados": 1,
"llamadas_estimadas": 1
}
if tokens_texto <= tokens_disponibles * 2:
# Ligeramente excede el contexto
if es_busqueda:
return {
"estrategia": "sliding_window",
"descripcion": "Búsqueda por secciones con overlap para encontrar información específica.",
"tokens_texto": tokens_texto,
"chunks_estimados": 2,
"llamadas_estimadas": 3 # 2 chunks + 1 síntesis
}
return {
"estrategia": "summarization_chain",
"descripcion": "Resumir en 2-3 chunks, concatenar.",
"tokens_texto": tokens_texto,
"chunks_estimados": 2,
"llamadas_estimadas": 3
}
if tokens_texto <= tokens_disponibles * 10:
# Moderadamente largo
if es_busqueda:
return {
"estrategia": "sliding_window",
"descripcion": "Ventana deslizante con síntesis final.",
"tokens_texto": tokens_texto,
"chunks_estimados": tokens_texto // 3000,
"llamadas_estimadas": (tokens_texto // 3000) + 1
}
return {
"estrategia": "hierarchical_summarization_2_niveles",
"descripcion": "Resumir chunks, luego sintetizar resúmenes.",
"tokens_texto": tokens_texto,
"chunks_estimados": tokens_texto // 3000,
"llamadas_estimadas": tokens_texto // 3000 + 1
}
# Muy largo: necesita jerarquía completa
return {
"estrategia": "hierarchical_summarization_3_niveles",
"descripcion": "Summarization en 3 niveles jerárquicos.",
"tokens_texto": tokens_texto,
"chunks_estimados": tokens_texto // 3000,
"llamadas_estimadas": tokens_texto // 2000 # Estimación gruesa
}
Tabla de Comparación de Estrategias
| Estrategia | Tokens máximos | Preserva detalles | Costo | Cuándo usar |
|---|---|---|---|---|
| Contexto directo | < 100K | ✓✓✓ | 1x | Siempre que quepa |
| Summarization chain | Ilimitado | ✓✓ (con instrucciones) | N× chunks | Resúmenes donde la síntesis es suficiente |
| Sliding window | Ilimitado | ✓✓✓ por sección | N× chunks + síntesis | Búsqueda de info específica |
| Hierarchical | Ilimitado | ✓ (pierde detalle) | N² | Documentos extremadamente largos |
Integración con Anthropic (Claude)
import anthropic
client_anthropic = anthropic.Anthropic()
def summarization_chain_claude(
texto: str,
max_chars_chunk: int = 15000,
instruccion: str = "Resumen ejecutivo preservando datos clave"
) -> str:
"""
Summarization chain usando Claude.
Claude tiene ventana de 200K tokens, pero la misma estrategia
aplica para documentos extremadamente largos.
"""
# Claude-3-5-haiku soporta hasta 200K tokens de contexto
if len(texto) < 150000: # Aproximadamente 100K tokens
# El texto cabe directamente
message = client_anthropic.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=1000,
messages=[{"role": "user", "content": f"{instruccion}:\n\n{texto}"}]
)
return message.content[0].text
# Dividir en chunks si es demasiado largo
chunks = [texto[i:i+max_chars_chunk] for i in range(0, len(texto), max_chars_chunk)]
resumenes = []
for chunk in chunks:
message = client_anthropic.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=400,
messages=[{"role": "user", "content": f"Resume en 300 palabras preservando datos importantes:\n\n{chunk}"}]
)
resumenes.append(message.content[0].text)
# Síntesis final
sintesis_input = "\n\n---\n\n".join(resumenes)
sintesis_msg = client_anthropic.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=800,
messages=[{"role": "user", "content": f"Sintetiza estos resúmenes parciales en un informe ejecutivo coherente:\n\n{sintesis_input}"}]
)
return sintesis_msg.content[0].text
Manejo de "Lost in the Middle"
def procesar_con_posicion_optima(
contexto_largo: str,
pregunta: str,
n_fragmentos_clave: int = 3
) -> str:
"""
Mitiga el problema "Lost in the Middle" colocando la información
más relevante al principio y al final del contexto.
El modelo recuerda mejor el inicio y el final del context window.
"""
# Paso 1: Identificar los fragmentos más relevantes
chunks = chunks_por_tokens(contexto_largo, max_tokens_por_chunk=1000)
relevancia = []
for i, chunk in enumerate(chunks):
score_resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"¿Qué tan relevante es este fragmento para responder: '{pregunta}'?\nScore 0-10. Solo el número.\n\nFragmento: {chunk[:300]}"}],
temperature=0,
max_tokens=5
).choices[0].message.content.strip()
try:
score = float(score_resp[:3])
except ValueError:
score = 5.0
relevancia.append((score, i, chunk))
# Ordenar por relevancia y tomar los mejores
relevancia.sort(reverse=True)
fragmentos_clave = relevancia[:n_fragmentos_clave]
# Paso 2: Colocar fragmentos clave al principio del contexto
contexto_optimizado = "FRAGMENTOS MÁS RELEVANTES (para tu referencia):\n\n"
for score, i, chunk in sorted(fragmentos_clave, key=lambda x: x[0], reverse=True):
contexto_optimizado += f"[Fragmento {i+1}, relevancia {score:.1f}/10]:\n{chunk}\n\n"
# Paso 3: Responder con el contexto optimizado
respuesta = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"""{contexto_optimizado}
Con base en estos fragmentos relevantes, responde:
{pregunta}"""
}],
temperature=0,
max_tokens=500
).choices[0].message.content
return respuesta
Troubleshooting
Problema 1: Pérdida de información en chunks
Síntoma: El resumen de un chunk no menciona datos importantes que sí estaban en él.
Causa: El prompt de resumen es demasiado genérico y el modelo selecciona qué preservar.
Solución:
def resumir_preservando_especificos(
chunk: str,
tipos_a_preservar: list[str] = None
) -> str:
"""Resumen que garantiza preservación de tipos de datos específicos."""
if tipos_a_preservar is None:
tipos_a_preservar = ["números y porcentajes", "nombres propios", "fechas", "URLs o referencias"]
tipos_str = ", ".join(tipos_a_preservar)
return client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Resume el siguiente texto (máx 200 palabras).
CRÍTICO: Preserva LITERALMENTE todos los: {tipos_str}
Si no puedes incluirlos en el resumen, listarlos al final bajo "Datos clave:"
Texto: {chunk}"""}],
temperature=0,
max_tokens=350
).choices[0].message.content
Problema 2: Incoherencia entre resúmenes de chunks consecutivos
Síntoma: El resumen final parece desconectado; cada chunk parece no estar relacionado con el anterior.
Solución: Pasar contexto del chunk anterior al siguiente:
def summarization_chain_con_contexto(texto: str, max_tokens_chunk: int = 3000) -> str:
"""Summarization donde cada chunk conoce el contexto del anterior."""
chunks = chunks_por_tokens(texto, max_tokens_chunk)
resumenes = []
contexto_previo = ""
for i, chunk in enumerate(chunks):
prompt = f"""Resume este fragmento ({i+1} de {len(chunks)}).
{'Contexto de fragmentos anteriores: ' + contexto_previo[:300] if contexto_previo else ''}
Fragmento actual:
{chunk}
Genera un resumen de 150-200 palabras que sea coherente con el contexto previo."""
resumen = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=300
).choices[0].message.content
resumenes.append(resumen)
contexto_previo = resumen # El resumen actual se convierte en contexto para el siguiente
return "\n\n".join(resumenes)
Problema 3: Costo muy alto con muchos chunks
Síntoma: Un documento de 50K tokens con chunks de 2K = 25 llamadas solo para el primer nivel.
Solución: Usar chunks más grandes y resumir en 2 pasos si es necesario:
def summarization_eficiente(texto: str, budget_llamadas: int = 10) -> str:
"""
Summarization que respeta un budget de llamadas API.
Ajusta el tamaño de chunk automáticamente.
"""
tokens_total = contar_tokens(texto)
tokens_por_llamada = tokens_total // budget_llamadas
tokens_por_llamada = max(tokens_por_llamada, 2000) # Mínimo 2K por chunk
print(f"Texto: {tokens_total:,} tokens, Budget: {budget_llamadas} llamadas")
print(f"Tamaño de chunk ajustado: {tokens_por_llamada:,} tokens")
return summarization_chain(texto, max_tokens_chunk=tokens_por_llamada)
Ejercicios
Ejercicio 1: Implementar extractor de información específica
Crea una función que use sliding window para extraer TODAS las menciones de fechas, importes, y nombres de personas de un documento largo. El resultado debe ser una lista deduplicada y ordenada.
Ver solución
import re
def extraer_entidades_sliding_window(documento: str) -> dict:
"""Extrae entidades específicas usando sliding window."""
chunks = chunks_por_tokens(documento, max_tokens_por_chunk=2000, overlap_tokens=200)
fechas = set()
importes = set()
personas = set()
for i, chunk in enumerate(chunks):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"""Extrae del texto:
1. Fechas (formato: DD/MM/YYYY o texto como "enero 2026")
2. Importes monetarios (€/$) con valor exacto
3. Nombres de personas
Responde en JSON: {{"fechas": [], "importes": [], "personas": []}}
Texto: {chunk}"""}],
temperature=0,
response_format={"type": "json_object"}
).choices[0].message.content
try:
datos = json.loads(response)
fechas.update(datos.get("fechas", []))
importes.update(datos.get("importes", []))
personas.update(datos.get("personas", []))
except Exception:
pass
return {
"fechas": sorted(list(fechas)),
"importes": sorted(list(importes)),
"personas": sorted(list(personas))
}
Ejercicio 2: Comparar estrategias
Para un documento de ~10K tokens, mide y compara:
- Contexto directo (si cabe)
- Summarization chain (chunks de 2K)
- Hierarchical (2 niveles)
Compara: calidad del resumen, número de llamadas, tokens usados.
Ver solución
import time
def comparar_estrategias(texto: str, pregunta_evaluacion: str) -> dict:
"""Compara las 3 estrategias para el mismo documento."""
tokens = contar_tokens(texto)
resultados = {}
# Estrategia 1: Directo (si cabe)
if tokens < 100000:
t0 = time.time()
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Resume: {texto}"}],
temperature=0, max_tokens=400
).choices[0].message.content
resultados["directo"] = {
"resumen": resp, "tiempo": time.time()-t0, "llamadas": 1,
"tokens_input": tokens
}
# Estrategia 2: Summarization chain
t0 = time.time()
resumen_sc = summarization_chain(texto, max_tokens_chunk=2000, verbose=False)
resultados["summarization_chain"] = {
"resumen": resumen_sc, "tiempo": time.time()-t0,
"llamadas": len(chunks_por_tokens(texto, 2000)),
"tokens_input": tokens
}
# Comparar
print(f"Tokens originales: {tokens:,}")
for estrategia, datos in resultados.items():
print(f"\n{estrategia}:")
print(f" Tiempo: {datos['tiempo']:.2f}s")
print(f" Llamadas: {datos['llamadas']}")
print(f" Resumen: {datos['resumen'][:100]}...")
return resultados
Resumen
- Contexto directo: Siempre que el texto quepa (~80% del límite para dejar margen). Mejor calidad.
- Summarization chain: Para documentos moderadamente largos (2-3x el límite). Simple y efectivo.
- Sliding window: Para búsquedas de información específica. Preserva detalles locales.
- Hierarchical: Para documentos muy largos (>10x el límite). Pierde algo de detalle en cada nivel.
- Contar tokens: Usar tiktoken, no estimaciones heurísticas.
- Lost in the Middle: Colocar información crítica al inicio o final del contexto.