No soy investigador. Tampoco desarrollador profesional. Mi formación es ingeniería civil. Empecé a construir un bot de IA porque quería entender qué pasa realmente dentro de estos sistemas — no de forma teórica, sino práctica.
Quería un asistente que pudiera convivir con una conversación, en lugar de tratar cada mensaje como una llamada aislada a la API.
Comenzó como un experimento. Luego el experimento creció.
En qué se convirtió
En algún punto tenía:
- routing entre perfiles de razonamiento
- múltiples herramientas
- búsqueda web
- memoria (plana + semántica + grafo)
- caché
- compresión de contexto
- bucles de retroalimentación
- experimentos de autoaprendizaje
- monitoreo de calidad
- procesamiento por lotes
- telemetría
- health checks
- y suficientes logs como para cuestionar mi propia cordura
No intentaba construir un benchmark. Intentaba que la cosa funcionara de verdad para mí.
Esa distinción lo cambió todo.
Un agente no es una llamada al LLM
Crees que es:
Usuario → LLM → Respuesta
La realidad:
Usuario
↓
Telegram
↓
manejo de entrada
↓
estado de sesión
↓
detección de intención
↓
routing
↓
selección de perfil
↓
construcción de contexto
↓
memoria
↓
caché
↓
herramientas
↓
LLM
↓
post-procesamiento
↓
controles de calidad
↓
Telegram
Cada capa es una oportunidad más de que algo se rompa.
El modelo casi nunca era el problema. La maquinaria alrededor sí lo era.
El asunto del caché
Estaba revisando el prompt caching y noté algo raro.
Mismo modelo — comportamiento diferente según el proveedor. Cambié de proveedor — el problema desapareció. Conecté el mismo proveedor desde otro IDE — el caché funcionaba bien. De vuelta en mi bot — a veces no.
Entonces me di cuenta: OpenRouter puede cambiar de proveedor silenciosamente bajo el mismo nombre de modelo. La tasa de acierto del caché cambia con eso.
Dejé de pensar “¿el modelo soporta caché?” y empecé a pensar “¿qué se está enviando exactamente, y qué considera el proveedor como idéntico?”.
Mucha mejor pregunta.
Por qué era diferente en el IDE
El IDE enviaba:
modelo → caché funciona
Mi bot enviaba:
routing
+ contexto dinámico
+ metadatos de sesión
+ memoria
+ cosas específicas del perfil
→ proveedor
→ modelo
→ caché puede o no coincidir
El modelo no había cambiado. El proveedor no había cambiado. La estructura de la petición sí.
El caché es extremadamente sensible a la estabilidad del prefijo. Una vez que entendí eso y estabilicé las partes relevantes — llegué a una tasa de acierto de caché de alrededor del 66% en promedio, hasta ~80% en condiciones favorables (mismo tema, estructura estable).
Esa es la parte que las capturas de pantalla de benchmarks no muestran. La tasa de acierto del caché no es un atributo del modelo. Es una propiedad de tu workload.
El “90% de ahorro de tokens”
Cuando veo esa afirmación ahora no pienso que sea falsa. Pienso: muéstrame el workload.
El 90% es posible si tu prefijo se ve así:
instrucciones del sistema (estables)
+ herramientas (estables)
+ contexto del proyecto (estable)
+ historial de conversación (estable)
+ mensaje nuevo pequeño
Los agentes reales a menudo se ven así:
instrucciones del sistema
+ herramientas cambiantes
+ memoria cambiante
+ metadatos de routing cambiantes
+ resúmenes cambiantes
+ documentos recuperados cambiantes
+ timestamps cambiantes
+ mensaje nuevo
El prefijo no es estable. El ahorro teórico y el ahorro práctico terminan siendo números muy diferentes.
El routing es más difícil de lo que parece
La idea obvia: enrutar cada petición al modelo más barato que pueda manejarla.
Funciona bien hasta que tienes una conversación real.
El usuario dice: “¿Cómo está el clima?” — fácil.
Luego: “Compáralo con el de ayer.” — ahora el contexto importa.
Luego: “Olvídate del clima. Estaba pensando en eso que hablamos ayer.” — ahora la memoria importa.
Luego: “No, no eso. Lo otro.” — el router necesita entender toda la conversación, no solo la oración actual.
La decisión de routing no es pregunta → modelo. Es:
estado de la conversación
+ intención del usuario
+ acciones previas
+ herramientas disponibles
+ riesgo
+ latencia
+ estado del caché
→ decisión de routing
Y aquí está el problema: una decisión de routing cambia la petición. Una petición cambiada afecta el caché. Un perfil cambiado afecta el contexto. Un contexto cambiado afecta la respuesta. Una respuesta diferente afecta la retroalimentación.
Una pequeña optimización de routing tiene consecuencias cinco capas más abajo.
Los números de latencia
Mediciones reales en un VPS en algún punto:
| Métrica | Valor |
|---|---|
| LLM p50 | ~2.2s |
| LLM p95 | ~17.3s |
| LLM max | ~51s |
| Agente mediana | ~6–11s |
| Agente p95 | ~20–57s |
| Colas del pipeline anterior | hasta ~116s |
El número interesante no es la latencia del LLM. Es la brecha entre la latencia del LLM y la del agente.
El modelo responde en 2 segundos. El agente tarda 10–57.
Porque el agente no es solo el modelo. Es todo lo que está antes y después.
La telemetría se volvió lo más importante
Terminé registrando: uso del LLM, tokens de prompt, tokens cacheados, latencia, decisiones de ruta, eventos de calidad, operaciones de memoria, retroalimentación, errores, trazas de conversación.
Al principio parecía excesivo. Luego se volvió obvio por qué.
Cuando algo se rompía podía preguntar “¿qué pasó realmente?” en lugar de “creo que el modelo estaba confundido”.
Estrategias de depuración muy diferentes.
Una lección sobre telemetría ruidosa
Una auditoría mostró miles de registros de route_risk. Suena catastrófico.
Pero la mayoría eran observaciones repetidas como:
quality_loop:search_skipped
quality_loop:price_hallucination
quality_loop:reply_echo
Eran señales útiles. Pero no miles de desastres independientes.
El volumen de telemetría no es el volumen de incidentes.
Un sistema puede producir una cantidad enorme de observaciones sobre un número pequeño de patrones de falla subyacentes. Tienes que agruparlas, o el propio sistema de monitoreo se vuelve ruidoso.
El autoaprendizaje es una trampa si tus datos son malos
Experimenté con bucles de retroalimentación. El bot podía recibir 👍 👎 y conectar eso con la calidad del routing, la reputación de las habilidades, el historial de escenarios.
Idea atractiva: el agente aprende de sus errores.
Pregunta peligrosa: ¿de qué exactamente está aprendiendo?
Si la telemetría es ruidosa — aprende ruido. Si una sonda sintética parece tráfico real de usuarios — aprende de la población equivocada. Si una llamada a herramienta fallida se registra como fallo de routing — se crea la lección equivocada.
Por eso el bucle de aprendizaje necesita otro bucle alrededor:
experiencia → evidencia → validación → lección → aplicación → nueva evidencia
De lo contrario estás automatizando la superstición.
Compresión de contexto
Agregué protección para los mensajes recientes — comprimir el historial antiguo, preservar los turnos más nuevos.
VIEJO VIEJO VIEJO VIEJO NUEVO NUEVO
→
[resumen] [resumen] NUEVO NUEVO
Razón: un resumen es una interpretación de la conversación, no la conversación.
A veces el detalle que falta es una sola oración. Y esa oración cambia el significado de todo.
Procesamiento por lotes
La ejecución paralela se ve genial en papel. 12 tareas × 2s cada una = 2s en lugar de 24s.
Pero las tareas en lenguaje natural no siempre son independientes.
- Encuentra tres productos.
- Compáralos.
- Dime cuál es el mejor.
La tarea 2 depende de la 1. La tarea 3 depende de la 2.
Terminé verificando referencias cruzadas, pronombres, lenguaje comparativo y dependencias explícitas antes de decidir paralelizar.
La optimización no era difícil. Saber cuándo es seguro sí lo era.
La memoria introdujo sus propios problemas
Agregué grafo de conocimiento + memoria semántica + persistencia plana + búsqueda vectorial + relaciones entre entidades.
Suena sofisticado.
Pero la memoria introduce una pregunta básica: ¿esto realmente debería recordarse?
Luego: ¿debería recuperarse ahora?
Luego: ¿esta memoria sigue siendo relevante?
Luego: ¿esta memoria es más importante que lo que el usuario acaba de decir?
Un sistema de memoria no hace automáticamente que un agente recuerde mejor. A veces hace que recuerde demasiado.
La lección más importante
Después de agregar suficiente maquinaria, eventualmente descubres que la maquinaria misma se convierte en el problema.
Un agente puede tener router + memoria + herramientas + caché + planificador + evaluador + autoaprendizaje + motor de escenarios + bucle de calidad y aun así rendir peor que:
prompt corto + un buen modelo
para una tarea simple.
A veces la optimización correcta es la resta.
Elimina contexto innecesario. Elimina routing innecesario. Elimina abstracción innecesaria. Quédate con la parte útil.
Qué haría diferente
Empezaría con:
un modelo
un proveedor
un prompt de sistema corto
un almacén de conversación
una estrategia de caché
herramientas mínimas
excelente telemetría
Lo ejecutaría. Durante mucho tiempo. Solo después de ver fallas reales agregaría otra capa.
No arquitectura primero → esperar que funcione.
Sino sistema simple → observar → medir → encontrar la falla → arreglar → medir → solo entonces agregar complejidad.
Se siente más lento. En la práctica, probablemente es más rápido.
Lo que el experimento realmente me enseñó
La parte difícil de un agente de IA no es hacer que el modelo responda.
La parte difícil es mantener un entorno estable alrededor del modelo mientras todo sigue cambiando.
El usuario cambia de tema. El contexto crece. El proveedor cambia. El caché se comporta distinto. Una herramienta hace timeout. El router toma una decisión diferente. Una respuesta anterior estaba mal. Una corrección llega seis turnos después.
Y de alguna manera se espera que el asistente se comporte como si nada de eso hubiera pasado.
Ese es el verdadero problema de ingeniería.
La prueba en la que confío ahora
No la cantidad de agentes. No la cantidad de herramientas. No el benchmark más grande. Ni siquiera la tasa de acierto de caché más alta.
Solo:
¿Sigue funcionando cuando una persona real lo usa mañana?
“El modelo casi nunca es el problema. La maquinaria alrededor sí lo es.”
Para quienes están construyendo sus primeros agentes con Python y LLMs, esta bitácora es un recordatorio valioso: la complejidad se justifica solo cuando nace de una falla real observada, no de una arquitectura impresionante en el papel. Sigue explorando estos temas en el blog de DojoFullStack.