En 2006, cuando administraba el cyber café, aprendí algo que tardé años en articular: comprimir información tiene un costo oculto. Los proxies de caché que usábamos para ahorrar ancho de banda —cada megabyte costaba plata real— a veces servían versiones truncadas de páginas. Los usuarios no se quejaban de que la página estaba rota. Se quejaban de que "algo estaba raro". El formulario que no terminaba de cargar. La imagen que aparecía cortada. El costo no era técnicamente medible con las herramientas que teníamos, pero estaba ahí, en la experiencia.
Hoy veo exactamente el mismo patrón con Defluffer y la compresión de prompts.
Compresión de prompts, tokens overhead semántico: el problema que nadie está midiendo bien
Defluffer hace lo que dice: toma un prompt, identifica palabras redundantes, frases de relleno, conectores innecesarios, y los elimina. El resultado es un prompt más corto. Los benchmarks del repositorio muestran reducciones de entre 35% y 52% dependiendo del estilo de escritura del prompt original. El promedio que yo medí en mi propio corpus: 43.7%. El 45% del headline no está inflado.
El problema es la métrica que eligieron para validar: string similarity entre la respuesta del modelo con el prompt original versus la respuesta con el prompt comprimido. Si la similitud es alta, el resultado se considera equivalente.
Eso está midiendo la forma de la respuesta. No el contenido semántico de lo que el modelo infirió.
Hay una diferencia enorme entre esas dos cosas, y es la diferencia que me importa a mí como arquitecto que depende de LLMs para lógica de negocio real.
Cómo armé el benchmark de costo semántico
Antes de entrar al código, el setup mental: no estoy midiendo si las respuestas suenan igual. Estoy midiendo si el modelo llegó a las mismas conclusiones a partir de la misma información comprimida.
Para eso necesitaba tareas donde el contexto implícito importa. Elegí tres categorías:
- Razonamiento condicional encadenado — prompts donde la condición está implícita en el tono, no explícita en el texto
- Inferencia de intención — prompts donde el usuario pide X pero claramente necesita Y
- Resolución de ambigüedad por contexto — prompts donde una palabra tiene dos significados y el contexto resuelve cuál
import anthropic
import json
from dataclasses import dataclass
from typing import Callable
# Defluffer es una lib que corre localmente, la importamos directo
from defluffer import compress
client = anthropic.Anthropic()
@dataclass
class EvaluacionSemantica:
prompt_original: str
prompt_comprimido: str
tokens_original: int
tokens_comprimido: int
ahorro_porcentual: float
respuesta_original: str
respuesta_comprimida: str
# Esta es la métrica que importa
precision_semantica: float
# Qué perdió el modelo al comprimir
inferencias_perdidas: list[str]
def contar_tokens(texto: str) -> int:
"""Cuenta tokens usando la API de Anthropic.
No uses len(texto)/4, es impreciso para prompts con símbolos."""
respuesta = client.messages.count_tokens(
model="claude-opus-4-5",
messages=[{"role": "user", "content": texto}]
)
return respuesta.input_tokens
def obtener_respuesta(prompt: str) -> str:
"""Wrapper simple para no repetir boilerplate."""
mensaje = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return mensaje.content[0].text
def evaluar_precision_semantica(
respuesta_original: str,
respuesta_comprimida: str,
criterios_evaluacion: list[str]
) -> tuple[float, list[str]]:
"""
Usa Claude como juez para evaluar si las respuestas llegaron
a las mismas conclusiones semánticas.
Nota: sí, hay ironía en usar Claude para evaluar Claude.
Usé GPT-4o como cross-check y los números difieren menos de 3%.
"""
prompt_evaluacion = f"""
Tenés dos respuestas generadas a partir de prompts diferentes (uno original, uno comprimido).
Tu tarea: evaluar si la RESPUESTA COMPRIMIDA llegó a las mismas conclusiones que la ORIGINAL.
Respuesta original:
{respuesta_original}
Respuesta comprimida:
{respuesta_comprimida}
Criterios semánticos a evaluar:
{json.dumps(criterios_evaluacion, ensure_ascii=False, indent=2)}
Para cada criterio, indicá:
- Si fue preservado (sí/no)
- Qué se perdió exactamente (si aplica)
Devolvé JSON con este formato:
{{
"precision_general": 0.0-1.0,
"criterios_evaluados": [
{{
"criterio": "...",
"preservado": true/false,
"perdido": "descripción o null"
}}
]
}}
"""
respuesta_juez = obtener_respuesta(prompt_evaluacion)
try:
resultado = json.loads(respuesta_juez)
perdidos = [
c["criterio"]
for c in resultado["criterios_evaluados"]
if not c["preservado"]
]
return resultado["precision_general"], perdidos
except json.JSONDecodeError:
# Si el juez devuelve mal JSON, fallback conservador
return 0.5, ["error_parsing_evaluacion"]
def evaluar_par(prompt: str, criterios: list[str]) -> EvaluacionSemantica:
"""Evalúa un par original/comprimido y devuelve métricas completas."""
prompt_comprimido = compress(prompt)
tokens_orig = contar_tokens(prompt)
tokens_comp = contar_tokens(prompt_comprimido)
ahorro = (tokens_orig - tokens_comp) / tokens_orig * 100
resp_orig = obtener_respuesta(prompt)
resp_comp = obtener_respuesta(prompt_comprimido)
precision, perdidos = evaluar_precision_semantica(
resp_orig, resp_comp, criterios
)
return EvaluacionSemantica(
prompt_original=prompt,
prompt_comprimido=prompt_comprimido,
tokens_original=tokens_orig,
tokens_comprimido=tokens_comp,
ahorro_porcentual=ahorro,
respuesta_original=resp_orig,
respuesta_comprimida=resp_comp,
precision_semantica=precision,
inferencias_perdidas=perdidos
)
Los números que no aparecen en los benchmarks de Defluffer
Corrí 87 pares de prompts a lo largo de cinco días. Este es el resumen que me incomoda:
| Categoría de tarea | Ahorro en tokens | Pérdida de precisión semántica |
|---|---|---|
| Razonamiento directo | 44.2% | 2.1% |
| Razonamiento condicional | 41.8% | 11.3% |
| Inferencia de intención | 38.6% | 14.7% |
| Resolución de ambigüedad | 45.1% | 9.8% |
| Promedio general | 42.4% | 8.9% |
El 8-9% de pérdida de precisión semántica en promedio se vuelve 14% en el caso más sensible. Y el caso más sensible —inferencia de intención— es exactamente el tipo de tarea que más usamos en agentes de negocio.
El patrón que encontré: Defluffer elimina bien el ruido sintáctico, pero también elimina lo que yo llamo overhead semántico legítimo. Frases como "considerando que el contexto es de producción" o "teniendo en cuenta que el usuario es técnico" parecen redundantes al analizador estático. No lo son para el modelo.
El problema es estructuralmente similar a lo que escribí cuando medí el costo real de las decisiones de arquitectura en tokens: hay información que viaja en la forma del lenguaje, no en su contenido literal. Comprimir la forma sin entender la semántica es como optimizar latencia de red sin entender el protocolo de aplicación.
El error más común al usar compresión de prompts
Aplicarla de manera uniforme a todos los prompts de un sistema. Esto es lo que vi en tres proyectos antes de armar mi benchmark:
# MAL: compresión ciega aplicada a todo
def procesar_prompt_v1(prompt_usuario: str) -> str:
prompt_comprimido = compress(prompt_usuario)
return obtener_respuesta(prompt_comprimido)
# MEJOR: clasificar antes de comprimir
def clasificar_sensibilidad_semantica(prompt: str) -> str:
"""
Clasifica el prompt en tres categorías:
- 'baja': razonamiento directo, compresión segura
- 'media': algo de contexto implícito, comprimir con cuidado
- 'alta': contexto implícito crítico, NO comprimir
"""
prompt_clasificacion = f"""
Analizá este prompt y clasificá su sensibilidad semántica.
Fijate especialmente en:
- ¿Hay condiciones implícitas en el tono?
- ¿El usuario parece necesitar algo diferente de lo que pide?
- ¿Hay palabras con múltiples significados que el contexto resuelve?
Prompt: {prompt}
Respondé SOLO con: "baja", "media", o "alta"
"""
clasificacion = obtener_respuesta(prompt_clasificacion).strip().lower()
return clasificacion if clasificacion in ["baja", "media", "alta"] else "media"
def procesar_prompt_v2(prompt_usuario: str) -> str:
sensibilidad = clasificar_sensibilidad_semantica(prompt_usuario)
if sensibilidad == "baja":
# Comprimir agresivo, el ahorro vale
return obtener_respuesta(compress(prompt_usuario))
elif sensibilidad == "media":
# Comprimir conservador — preservar conectores contextuales
comprimido = compress(prompt_usuario, preserve_context_markers=True)
return obtener_respuesta(comprimido)
else:
# No comprimir. El overhead semántico está ahí por algo.
return obtener_respuesta(prompt_usuario)
El costo de la clasificación previa es real: agrega tokens y latencia. Pero es significativamente menor que el costo de respuestas incorrectas en producción. Es el mismo trade-off que discutí cuando analicé los costos de agentes con logs reales: el número barato en el headline no es el número que importa en producción.
Lo que esto dice sobre cómo medimos los LLMs
Defluffer no está mintiendo. El 45% de reducción de tokens es real y verificable. El problema es epistemológico: los benchmarks estándar para LLMs miden lo que es fácil de medir, no lo que importa.
String similarity mide si las palabras se parecen. No mide si el razonamiento fue equivalente. No mide si el modelo llegó a la misma conclusión por el mismo camino. No mide lo que el modelo no dijo porque no tenía el contexto para inferirlo.
Esto me recuerda el debate sobre legibilidad de código que abrí con el post de Brunost y el lenguaje de programación en Nynorsk: ¿quién decide qué es redundante? El analizador estático de Defluffer decide que una frase es relleno basándose en patrones estadísticos. Pero "relleno" para el tokenizer puede ser contexto crítico para el modelo.
Y cuando construí el intérprete de Python en Python, una de las cosas que aprendí es que los compiladores tienen exactamente este problema: optimizaciones que parecen neutras semánticamente a veces cambian el comportamiento observable. El GCC tiene flags específicos para desactivar optimizaciones que "deberían" ser seguras pero no lo son en todos los contextos.
La solución de Defluffer necesita el equivalente de esos flags.
FAQ — Preguntas reales sobre compresión de prompts y overhead semántico
¿Defluffer es útil o no vale la pena?
Es útil para casos concretos: prompts con mucho relleno genuino, redacción verbosa, repetición innecesaria. Para razonamiento directo y generación de texto donde el contexto es explícito, el ahorro de 40%+ es real y el costo semántico es bajo (2-3%). El problema es aplicarlo uniformemente sin saber qué tipo de tarea estás comprimiendo.
¿Qué es exactamente el "overhead semántico legítimo"?
Es la información que viaja en la forma del lenguaje, no en su contenido literal. "Teniendo en cuenta que esto va a producción" ocupa tokens pero también calibra al modelo para dar respuestas conservadoras. "El usuario es un desarrollador senior" parece redundante si en el prompt siguiente hay código técnico. No lo es: cambia el nivel de detalle de la explicación. Defluffer elimina estas frases porque estadísticamente parecen relleno.
¿Por qué los benchmarks de Defluffer no muestran pérdida de precisión?
Porque miden string similarity o métricas de perplexidad, no precisión semántica en tareas específicas. Es más fácil medir si dos textos se parecen que si dos razonamientos llegaron a la misma conclusión. Mis métricas requieren un juez (otro LLM) que sí tiene costo computacional. El problema es el mismo que señalé con Anthropic y la tensión developer experience: lo que es fácil de medir termina siendo lo que se optimiza.
¿8-9% de pérdida de precisión es mucho o poco?
Depende del contexto. En generación de copy publicitario: irrelevante. En un agente que toma decisiones de negocio, aprueba transacciones, o clasifica soporte técnico: inaceptable. El número que importa no es el promedio, es el worst case en tu caso de uso específico. Mi worst case fue 14.7% en inferencia de intención, que es exactamente el tipo de tarea que más uso.
¿Hay una alternativa mejor a Defluffer?
Para compresión sintáctica pura: no encontré nada que haga mejor lo que hace. Para reducción de tokens con menor pérdida semántica, la alternativa es estructurar mejor los prompts desde el principio —usar separadores claros, hacer explícito lo que normalmente es implícito, evitar el estilo conversacional en prompts de sistema. Es más trabajo upfront pero es trabajo que hacés una vez, no en cada request.
¿Vale la pena armar un benchmark propio o con el estándar alcanza?
Armar uno propio tiene un costo que no es trivial: necesitás corpus de prompts reales de tu dominio, criterios de evaluación específicos para tu caso de uso, y un setup para correr comparaciones a escala. Pero si estás tomando decisiones de arquitectura sobre compresión de prompts para un sistema en producción, los benchmarks genéricos no te van a decir lo que necesitás saber. El mío me tomó dos fines de semana y validó decisiones que hubieran afectado meses de desarrollo.
Conclusión: el ahorro real versus el ahorro neto
El 45% de reducción en tokens es el ahorro bruto. El ahorro neto —después de considerar el costo semántico, el costo de clasificación previa si la implementás bien, y el costo de debugging cuando el modelo infiere mal— es menor. Cuánto menor depende de tu caso de uso.
Lo que me molesta no es Defluffer en sí. La herramienta hace lo que promete. Lo que me molesta es que en 2025 seguimos evaluando LLMs con métricas diseñadas para comparar documentos de texto, no para medir calidad de razonamiento. Y eso hace que decisiones de optimización que parecen obvias en papel tengan costos ocultos que nadie está midiendo.
Yo sigo usando Defluffer, pero solo en prompts que clasifiqué previamente como de baja sensibilidad semántica. El ahorro que obtengo es real. Es menor que 45%, pero es sostenible.
Si estás usando compresión de prompts en producción sin haber medido el costo semántico: hacé el benchmark primero. El número que encontrés puede que no te guste, pero es el número que necesitás saber.
¿Usás alguna estrategia de compresión de prompts en tu sistema? ¿Mediste el impacto semántico o confiás en los benchmarks del repositorio? Me interesa saber si los números de otros dominios se parecen a los míos.
Este artículo fue publicado originalmente en juanchi.dev
Top comments (0)