ToolCallingAgent vs CodeAgent en modelos locales de Qwen2.5 de 14B.
ToolCallingAgent vs CodeAgent en smolagents en una Mac M4: velocidad y precisión en el enrutamiento con modelos locales Qwen2.5 14B de instrucción y codificación a varias temperaturas.
Fuentes: ToolCallingAgent vs. CodeAgent: Which Performs Better on a Local LLM?
La configuración de agentes locales expone rápidamente puntos débiles. La máquina utilizada era un Mac Apple M4 básico con 16 GB de memoria unificada, sin capacidad de inferencia en la nube ni VRAM adicional a la que recurrir, por lo que incluso una ventana de contexto moderada puede provocar el intercambio de datos en memoria. Se probaron dos estilos de agentes de smolagents, ToolCallingAgent y CodeAgent, cada uno contra dos modelos ejecutados localmente: qwen2.5-coder:14b y qwen2.5:14b-instruct. Ambos comparten un tamaño de 14 mil millones de parámetros y una arquitectura base, y se ejecutaron en la misma máquina.
- Qwen2.5:14b-instruct es el modelo de uso general de la línea de 14 mil millones de parámetros, ajustado con datos amplios de instrucciones y diálogos para seguir bien las órdenes.
- qwen2.5-coder:14b utiliza la misma estructura básica, pero recibió un entrenamiento previo adicional con una gran cantidad de código y un ajuste fino dirigido específicamente a ello.
Dado un presupuesto de hardware tan limitado, no se esperaba que ninguna de las configuraciones destacara. La pregunta más interesante era dónde fallaba cada una y si elegir un tipo diferente de agente podría suavizar esas limitaciones.
La configuración
Se realizó un análisis de temperaturas en los valores 0, 0.1, 0.3 y 0.6 con ambos tipos de agentes en una misma tarea: encontrar los cinco mejores lugares de pizza en Seattle, verificar el tiempo de espera en cada uno y recomendar solo aquellos que estuvieran por debajo del umbral establecido por el cliente. Las funciones que calculan los tiempos de espera y los comparan con el umbral son deterministas.
@tool
def get_wait_time(restaurant_name: str) -> int:
"""Current wait in minutes for a restaurant. Deterministic, not a live lookup."""
@tool
def restaurants_in_wait_limit(wait_time: int, wait_threshold: int) -> bool:
"""True if the wait is within the customer's threshold."""
El diseño determinista es intencional. Una búsqueda en tiempo real permitiría al modelo estimar un tiempo de espera plausible y continuar. Una llamada a función forzada verifica lo que realmente importa aquí: si el agente sigue las indicaciones de la herramienta en lugar de inventar un valor, y si eso sigue siendo cierto cuando cambia la temperatura o el tipo de agente.
Se monitorearon tres aspectos: la latencia, si una ejecución se completó y la precisión de enrutamiento, definida como la proporción de toda la cadena de herramientas que se ejecutó correctamente en cada llamada. Considere estos números como una prueba, ya que no se trata de un estándar de referencia formal.
Cómo difieren los dos agentes
Con ToolCallingAgent, cada paso genera una única llamada a herramienta en formato JSON. El modelo proporciona los argumentos, el marco ejecuta la herramienta y luego comienza el paso siguiente. Cinco restaurantes y dos funciones suman diez llamadas separadas, y cada una representa una nueva oportunidad de dañar el resultado estructurado.
CodeAgent sigue otro enfoque y genera un fragmento de Python que se ejecuta en un solo paso. Un bucle puede abarcar todos los restaurantes y llamar a ambas funciones dentro de él. Un bucle correcto es válido para todos ellos en esa ejecución, por lo que la decisión se toma una sola vez en lugar de diez.
Resultados
En Qwen2.5:14b-instruct
Ambos agentes tuvieron un buen rendimiento. CodeAgent mantuvo una precisión de enrutamiento de 1.00 en todas las temperaturas, mientras que ToolCallingAgent se mantuvo entre 0.95 y 1.00. No hubo diferencia significativa en cuanto a corrección.
La velocidad contó una historia diferente. ToolCallingAgent funcionó aproximadamente dos veces más lento, con unos cinco minutos por ejecución, frente a unos dos minutos y medio para CodeAgent. La causa es estructural: ToolCallingAgent debe realizar una nueva llamada al modelo en cada paso, reenviando el contexto de la conversación y generando desde cero en cada ocasión. CodeAgent, en cambio, solo lo hace una vez. En una máquina local donde ya es lenta cada generación, ese costo adicional no puede ignorarse.
En Qwen2.5-coder:14b
Aquí los dos agentes mostraron una gran diferencia. ToolCallingAgent nunca alcanzó el 50 por ciento de precisión en la toma de decisiones, independientemente de la temperatura, y se mantuvo entre 0.40 y 0.50. Funcionó más rápido que en el modelo de instrucciones, pero eso no era una buena señal. Fue rápido porque se rindió; una vez que perdía el hilo de la conversación, dejaba de hacer llamadas y terminaba antes, habiendo hecho menos trabajo.
CodeAgent ganó por completo con una precisión en el enrutamiento de 1.00, igualando las mejores puntuaciones. Eso es esperable, ya que solicita al modelo de programación código en Python y nunca JSON estructurado.
Qué se puede concluir de esto
La ventaja de los bucles en CodeAgent es real. En el modelo instruct, donde ambos agentes obtuvieron 1.00 en el enrutamiento, agrupar las tareas hizo que CodeAgent fuera dos veces más rápido que ToolCallingAgent.
Qwen2.5:14b-instruct parece ser un generalista fiable, cómodo tanto con llamadas a JSON estructurado como con código simple. Qwen2.5-coder:14b es mejor para tareas de programación exigentes, ya que puede ser poco fiable al generar JSON estructurado y al mantener ese nivel en una cadena de llamadas.
Una pregunta abierta
Queda una pregunta adicional. Hay dos diferencias entre los agentes: CodeAgent agrupa su trabajo en un único programa y escribe en Python en lugar de JSON estructurado. El próximo experimento podría separar estas diferencias haciendo que CodeAgent emita llamadas a herramientas estructuradas mientras sigue ejecutándose de forma por lotes. El resultado indicaría si la ventaja proviene principalmente de los bucles o de que los modelos consideran más fácil generar código que una larga serie de llamadas JSON.