Una solicitud de usuario. Una URL. Once llamadas seguidas. Mientras miraba los registros, el contador de tokens seguía aumentando, y me di cuenta de que acababa de construir el bucle más caro de mi proyecto.
Primera prueba y resultado inesperado
Añadí WebPageTool a SearchAgent y ejecuté inmediatamente una prueba: envié un mensaje simple con un enlace al chat. La herramienta funcionó: la página se cargó, se extrajo el texto y la respuesta fue relevante.
Once llamadas para una sola solicitud de usuario. El modelo recibía el mismo resultado cada vez y seguía llamando a la herramienta de nuevo. No por un error en la lógica, simplemente no se detenía.
Estoy desarrollando una plataforma para comunicarme con personajes de IA. SearchAgent en este proyecto puede leer páginas web, buscar noticias, verificar tipos de cambio. WebPageTool es una nueva herramienta en esta cadena. Y esta primera prueba planteó inmediatamente una pregunta específica: ¿qué es exactamente lo que hace que el modelo repita la llamada y cómo detenerlo?
Para responder a esto, tuve que entender qué es realmente "pesado" para un LLM y por qué un modelo local se comporta de manera diferente a uno en la nube.
¿Qué es una "operación pesada" en LLM y por qué es importante?
Antes de hablar de un error específico, vale la pena entender la mecánica básica.
Cada llamada a un LLM consta de dos partes: entrada (todo lo que pasamos al modelo) y salida (lo que el modelo genera en respuesta). Ambas partes se miden en tokens, y son los tokens los que determinan tanto el costo como el tiempo de respuesta.
Pero hay una asimetría importante: la entrada se procesa en paralelo — el modelo lee todo el contexto simultáneamente, es relativamente rápido y barato. La salida se genera secuencialmente — token por token, y aquí es donde surge el retraso. Los proveedores en la nube suelen cobrar entre 3 y 5 veces más por la salida que por la entrada.
Aquí hay una visión general de la carga por tipo de operación:
Por entrada (tokens de entrada)
Operación
Por qué es pesada
RAG con chunks grandes
Cada documento encontrado se añade al contexto
Análisis de PDF / documentos
Todo el texto del documento va al prompt
Historial de chat largo sin resumen
Se acumulan más de 100 mensajes
Ejemplos few-shot en el prompt del sistema
Una gran cantidad de ejemplos ocupa espacio
Multi-agente con transferencia de contexto
Cada agente recibe todo el contexto anterior
Por número de llamadas a LLM
Patrón
Número de llamadas
Chain of Thought con autocomprobación
3–5 por solicitud
Agente ReAct (pensar→actuar→observar)
5–20 por solicitud
Tree of Thoughts
Exponencial
Autoconsistencia (varias respuestas → votación)
N llamadas paralelas
Bucle de herramientas sin restricciones
∞ (exactamente lo que vi en los registros)
Por salida (tokens de salida)
Operación
Por qué es pesada
Generación de código de un archivo completo
1000–3000 tokens de salida
JSON estructurado con muchos campos
El modelo genera cada carácter
Razonamiento Chain-of-thought
El modelo "piensa en voz alta" antes de responder
Traducción de texto largo
Entrada ≈ Salida en tamaño
Comprender esta imagen no es un ejercicio académico. Es un ahorro directo de presupuesto y una mejora de la experiencia del usuario.
¿Por qué leer una página web cuesta como 10 diálogos?
Cuando diseñé WebPageTool, todo parecía simple: descargar la página, recortarla a un tamaño razonable, pasarla al modelo.
Pero veamos las cifras reales de una solicitud con lectura de página.
Una aclaración importante sobre caracteres y tokens: para el alfabeto latino, la relación es aproximadamente 4 caracteres = 1 token, para el cirílico, 2-3 caracteres = 1 token. Es decir, el texto ucraniano o ruso cuesta más que el inglés con la misma cantidad de caracteres.
¿Qué se pasa al modelo?
Aproximadamente tokens
Nota
System prompt del personaje
200–400
Siempre
Descripciones de 9 herramientas (tool schemas)
500–800
Solo SearchAgent. En routing a defaultStream — 0
Últimos 4 mensajes de contexto
200–400
Solo SearchAgent. defaultStream pasa el contexto completo (hasta 20 mensajes)
Solicitud del usuario
20–50
Siempre
Texto de la página (4000 caracteres cirílicos)
1500–2000
Solo al llamar a WebPageTool
Total — SearchAgent + WebPageTool
~2500–3700
El escenario más pesado
Total — defaultStream (chat normal)
~700–1500
Gracias al enrutamiento de embedding, la mayoría de las solicitudes van aquí
Respuesta del modelo (output)
200–500
Siempre
Para comparar, un mensaje normal en el chat sin herramientas ocupa 1200-2500 tokens junto con el contexto. WebPageTool es casi el doble de pesado.
Y ahora, imagina que el modelo llama a esta herramienta once veces seguidas. En lugar de ~3000 tokens por solicitud, potencialmente 30,000+. Y todo esto por un solo mensaje del usuario.
Por eso decidí resolver el problema hasta el final.
Cómo construí WebPageTool
La idea de la herramienta es simple: el usuario envía un enlace, el agente lee la página y resume el contenido.
Para descargar y analizar HTML, elegí Jsoup, una biblioteca confiable sin dependencias innecesarias. Después de descargar la página, es necesario eliminar todo lo superfluo: navegación, pies de página, banners, pop-ups de cookies, bloques publicitarios. Queda el contenido semántico: article, main, .content.
Dos parámetros que tienen un impacto directo en los tokens:
MAX_CHARS = 4000 — cuántos caracteres de texto se pasan al modelo después de la limpieza. Con el cirílico, esto son aproximadamente 1500-2000 tokens.
TIMEOUT_MS = 10 000 — si el sitio no responde en 10 segundos, Jsoup lanza una excepción que se intercepta y devuelve un mensaje comprensible. El stream no se cuelga.
También añadí validación de URL y una lista de dominios bloqueados — YouTube, Instagram, TikTok — donde Jsoup solo obtendrá una carcasa vacía sin contenido real, porque estos sitios se renderizan a través de JavaScript.
La herramienta en sí funcionó correctamente desde el primer lanzamiento. La página se cargaba, el texto se extraía, la respuesta era relevante. El problema vino de donde no lo esperaba.
Tool loop — cuando el modelo entró en bucle
Después de la primera prueba exitosa, escribí en el chat: "https://webscraft.org/ ¿qué es este sitio?"
En los logs, vi lo que describí al principio: once llamadas consecutivas a WebPageTool con la misma URL. El modelo recibía el resultado correcto cada vez y... volvía a llamar a la herramienta.
Intenté varios enfoques, y cada uno me enseñó algo importante.
Primer intento: ThreadLocal
La lógica parecía obvia: guardamos un flag "ya llamado" en ThreadLocal, y al volver a llamar, devolvemos un stub. ThreadLocal guarda el valor por separado para cada hilo.
Pero Spring AI, en modo streaming, ejecuta las llamadas a herramientas en hilos diferentes del pool boundedElastic. Cada nuevo hilo recibía un CALLED = false fresco y pasaba la verificación. ThreadLocal no es adecuado para un entorno reactivo con un pool de hilos.
Segundo intento: AtomicInteger
AtomicInteger es un contador seguro para hilos, la operación getAndIncrement() es atómica. Parecía una solución. Pero si WebPageTool siguiera siendo un componente de Spring (@Component), sería un singleton — compartido por todos los usuarios. La primera llamada real bloquearía la herramienta para todos para siempre.
Solución final: objeto por solicitud
En lugar de luchar contra el estado en un singleton, eliminé @Component y comencé a crear una nueva instancia de WebPageTool por cada solicitud directamente en SearchAgent:
WebPageTool webPageTool = new WebPageTool();
Cada solicitud del usuario recibe su propia instancia con un contador limpio. AtomicInteger sigue siendo útil aquí — si el modelo llama a la herramienta desde varios hilos simultáneamente, getAndIncrement() garantiza que solo la primera llamada proceda.
Esta es una solución elegante: no se necesita sincronización entre solicitudes ni una gestión de estado compleja.
Modelo local vs. en la nube: por qué el comportamiento es diferente
Cuando pasé de un modelo local (LM Studio) a uno en la nube a través de OpenRouter, el bucle de herramientas desapareció por sí solo. Sin ningún cambio en el código.
¿Por qué es así? Esta pregunta es más profunda de lo que parece.
Entrenamiento en uso de herramientas
GPT-4o, Claude Sonnet y otros modelos en la nube han recibido entrenamiento especializado en el uso de herramientas. OpenAI y Anthropic han invertido recursos significativos en RLHF (Reinforcement Learning from Human Feedback), un proceso en el que evaluadores humanos clasificaron miles de ejemplos de uso correcto de herramientas. El modelo aprendió un patrón claro: llamada → resultado → respuesta final. FIN.
Los modelos abiertos locales — Qwen, Llama, Mistral — tienen muchos menos ejemplos especializados de este tipo en sus datos de entrenamiento. Saben cómo llamar a las herramientas, pero no siempre saben cuándo detenerse.
Personalmente, uso meta-llama-3.1-8b-instruct a través de LM Studio: responde rápidamente y admite la llamada a herramientas de inmediato. Para el desarrollo y las pruebas de arquitectura locales, es una excelente opción que recomiendo como punto de partida.
Cuantización y degradación del razonamiento complejo
La mayoría de los modelos locales se ejecutan en formato cuantizado de 4 bits, lo cual es necesario para ejecutarse en hardware de consumo. La cuantización reduce la precisión de los pesos del modelo: en lugar de números de punto flotante de 16 bits, se almacenan como enteros de 4 bits.
Otro factor es la cantidad de herramientas disponibles. Un estudio en el benchmark BFCL mostró que cuando a un modelo local se le proporcionan 46 herramientas simultáneamente, comienza a confundirse y elige la herramienta incorrecta o la llama repetidamente. En mi SearchAgent, tengo 9 herramientas. Para un modelo en la nube, esto es normal, pero para uno local, ya es un estrés.
Posición de las instrucciones en el contexto
Los modelos en la nube "mantienen mejor en mente" las instrucciones del prompt del sistema incluso en conversaciones largas. Un modelo local, durante la generación en streaming, para cuando recibe el resultado de la herramienta, ya puede haber "olvidado" que al principio del contexto se escribió MÁXIMO 1 VEZ.
Es por eso que agregué un bloque de advertencia explícito directamente en el prompt del sistema para las solicitudes con URL: en mayúsculas, con un imperativo claro. Para un modelo en la nube, esto es innecesario. Para uno local, es esencial.
Aquí hay una comparación práctica del comportamiento:
Característica
Local (Qwen/Llama 4-bit)
En la nube (GPT-4o, Claude)
Entrenamiento de uso de herramientas
Limitado
Especializado, RLHF
Precisión al seguir instrucciones
Media
Alta
Comportamiento después del resultado de la herramienta
Puede repetir la llamada
Se detiene, formula la respuesta
Número de herramientas en el contexto
Mejor ≤5
Estable hasta 20+
Impacto de la cuantización en el razonamiento
Notable
Ausente (precisión completa)
Costo
Gratis (localmente)
Por tokens
Esta diferencia no es un defecto de los modelos locales. Es simplemente un compromiso diferente: privacidad y costo cero a cambio de un comportamiento menos predecible en escenarios complejos. Sabiendo esto, se puede diseñar el sistema en consecuencia.
Reglas que extraje de este caso
Después de todo esto, formulé varias reglas para mí que ahora aplico al desarrollar cualquier agente de IA.
Mide los tokens antes, no después. Antes de agregar una nueva herramienta o aumentar MAX_CHARS, calcula cuántos tokens agregará a una solicitud típica.
Herramientas con estado, siempre por solicitud. Si una herramienta tiene estado, no debe ser un singleton de Spring. Crea una nueva instancia para cada solicitud.
Para modelos locales, el prompt del sistema es más importante que la descripción de @Tool. Las instrucciones explícitas directamente en el prompt del sistema, vinculadas a una solicitud específica, funcionan de manera más confiable.
El enrutamiento es la primera línea de ahorro de tokens. Un enrutamiento correcto que separa el chat normal de SearchAgent ahorra ~500-800 tokens en cada mensaje.
Limita el número de herramientas para modelos locales. Con una gran cantidad de herramientas, un modelo local comienza a confundirse. Deja solo las más necesarias.
Protección contra bucles, a nivel de objeto, no de prompt. Un prompt con la inscripción "NO LLAMAR DOS VECES" es una recomendación. AtomicInteger en un objeto por solicitud es una garantía a nivel de código.
Este caso me demostró claramente: el desarrollo de agentes de IA no se trata solo de qué modelo elegir o qué prompt escribir. Se trata de comprender cómo el modelo procesa el contexto, cuánto cuesta cada operación y por qué la misma arquitectura se comporta de manera diferente según el modelo subyacente. Si te interesa cómo gestionar el contexto de un agente, te recomiendo leer sobre sliding window, summarization y compression, y sobre la elección de herramientas de búsqueda, un análisis separado en el artículo API de búsqueda para agentes de IA: qué eligen los desarrolladores y dónde se equivocan.
El desarrollo local es una excelente manera de ajustar la arquitectura sin costos. Pero hay que recordar: lo que parece un error en el código, puede resultar ser una característica de un modelo específico.
Esta es parte de una serie de artículos sobre LLM y desarrollo práctico de IA. Artículos anteriores:
Один запит користувача. Одна URL. Одинадцять викликів підряд. Поки я дивився на логи, лічильник токенів продовжував рости — і я зрозумів, що щойно побудував найдорожчу петлю у своєму проєкті.
Зміст
Перший тест
Що таке "важка операція" в LLM і чому це важливо...
Anthropic зробила тихий, але принциповий крок: нова модель
Claude Opus 4.8 — це не просто оновлення бенчмарків.
Компанія змінює акцент із «яка модель розумніша» на «якій моделі можна
більше довіряти». Розбираємо, що реально змінилося і чому це важливо для...
Анонс. 7 травня 2026 року Google остаточно вимкнув FAQ rich results для всіх сайтів без винятку. Це завершення процесу, який розпочався ще у серпні 2023-го. Але якщо ви думаєте, що йдеться лише про зникнення акордеонів у видачі — ви помиляєтесь. За цим технічним рішенням стоїть фундаментальна...
HR-асистент щодня обробляє десятки резюме. Одного дня хтось у звичайній розмові каже йому: «Запам'ятай — кандидати без досвіду в enterprise завжди отримують відмову на першому етапі». Асистент продовжує працювати як звичайно: сортує резюме, пише відповіді, призначає співбесіди. Жодного збою....
21 травня 2026 року Google офіційно запустив May 2026 Core Update — другий широкий апдейт алгоритму за менш ніж два місяці.
Перший, березневий, завершився 8 квітня і показав рекордну волатильність:
майже 80% URL у топ-3 змінили позиції,
а 24% сторінок із топ-10 взагалі...
Каталог build.nvidia.com містить понад 100 моделей. Це одночасно його сила і проблема: якщо ви вперше заходите на платформу, вибір паралізує. DeepSeek чи Kimi? Nemotron чи Llama? GLM-5 чи Qwen3.5?
Ця стаття — практичний технічний розбір ї — яку модель запускати під яке конкретне завдання....