Lección 6: Manos a la obra: conectar tres herramientas a un agente
Objetivos de aprendizaje:
- Escribir un bucle de ejecución de herramientas completo que ponga a funcionar de verdad a un agente
- Registrar la definición de interfaz de una herramienta y su implementación en una sola tabla, para que los dos lados nunca se separen
- Equipar el bucle con válvulas de seguridad, y leer los logs para detectar cuándo una herramienta está mal conectada
Requisitos: Terminar las Lecciones 1-5 y saber leer JavaScript / Node.js básico | Anterior: Lección 5 <<
Primero la recompensa: una ejecución completa
Esto es a lo que llega esta lección. Escribes una frase en la terminal y el agente decide por su cuenta a qué herramientas llamar y cuántas veces:
Tres turnos, tres herramientas, y los argumentos de cada turno se construyen sobre el resultado del turno anterior: primero encontrar en qué archivos aparece lodash, después leer package.json para confirmar la versión, después tomar ese nombre y preguntarle a GitHub por él. Esto no es un script cableado: el propio modelo decide a qué herramienta llamar a continuación y qué argumentos pasar.
Esta lección lo construye desde cero: tres herramientas, un registro, un bucle de ejecución, unas cuantas válvulas de seguridad.
Qué pasa por debajo: una ida y vuelta de la API tras otra
Cada «turno» que viste arriba es, por debajo, una petición HTTP completa. La Lección 2, «La ida y vuelta completa de una llamada a herramienta», mostró cómo es una sola llamada; aquí simplemente la conectamos en bucle: el modelo devuelve stop_reason: "tool_use", tu código ejecuta la herramienta, cose el resultado de vuelta en la conversación y envía otra petición, hasta que el modelo deja de pedir llamadas a herramientas.1
Tres turnos con llamadas a herramientas son en realidad cuatro llamadas a messages.create: en las tres primeras el modelo sigue pidiendo herramientas, y en la cuarta ya tiene los datos de GitHub, decide que le basta y da directamente una respuesta de texto, terminando el bucle. El juicio de si seguir pidiendo herramientas vive por completo del lado del modelo; tu código solo ejecuta y devuelve resultados.
Paso 1: Escribir el contrato de cada herramienta
La Lección 4, «Diseñar interfaces de herramientas: nombre, descripción, parámetros, valor de retorno», cubrió los tres campos centrales de la interfaz de una herramienta: name, description e input_schema.2 Aquí los convertimos directamente en código. Las tres herramientas se corresponden con tres de los cinco tipos de la Lección 3, «Cinco tipos comunes de herramientas: leer, escribir, ejecutar, buscar, llamar»: search, read y call; write y execute se quedan para que las conectes tú en los ejercicios.
github_repo_info lleva un prefijo github_: la guía oficial es usar el servicio como espacio de nombres en los nombres de herramienta cuando una herramienta toca un servicio externo, lo que baja mucho la probabilidad de que el modelo elija la herramienta equivocada.3 search_files y read_file operan sobre el sistema de archivos local, donde no hay ambigüedad de «qué servicio», así que no necesitan prefijo.
Las tres descripciones detallan qué texto vuelve cuando no se encuentra nada, y eso no es relleno. La Lección 4 señalaba que una buena descripción elimina la ambigüedad en entradas y salidas;4 la ambigüedad aquí no está en los parámetros sino en cómo la herramienta expresa «no encontré nada», una trampa que salta en la sección «Válvulas de seguridad».
Paso 2: Registrar el contrato y la implementación en una sola tabla
Una trampa habitual: si la lista de esquemas y la tabla de handlers que se usa en tiempo de ejecución se escriben como dos copias separadas, tarde o temprano se separarán. Renombras search_files a find_in_files pero olvidas actualizar la clave en la tabla de handlers; el modelo emite una llamada contra el esquema nuevo, la tabla de handlers no tiene nada bajo esa clave y lanza un error.
El arreglo es mantener una sola tabla donde name, description, input_schema y la función que realmente se ejecuta vivan en el mismo objeto. La lista de esquemas que necesita la API y la tabla de handlers que necesita la ejecución se derivan las dos de esta única tabla:
toolSchemas y toolHandlers quedan sincronizados para siempre, porque son dos vistas calculadas a partir de los mismos datos, no dos copias escritas a mano. Renombrar una herramienta o añadir un parámetro significa cambiar TOOLS en exactamente un sitio.
Paso 3: Implementar las tres herramientas, con fronteras
searchFiles recorre el directorio por su cuenta en lugar de delegar en grep por shell: así se evita empalmar entrada del usuario en una línea de comandos e invitar a la inyección de comandos. El número de coincidencias está limitado, de modo que una sola búsqueda no puede meter miles de líneas en el contexto:
readFile hace una sola cosa: confirmar que la ruta de destino no se ha escapado de la raíz del proyecto. La idea de frontera de la Lección 5 aparece aquí como una única comprobación de prefijo con separador. Fíjate en que no es un startsWith(PROJECT_ROOT) pelado: supongamos que la raíz del proyecto es /Users/me/proj y el modelo pasa ../proj-backup/x; después del resolve obtienes /Users/me/proj-backup/x, y una coincidencia de prefijo pelada aún pasaría. Añade path.sep y la frontera aterriza por fin en el separador de directorios:
githubRepoInfo es la única herramienta que envía datos fuera del proyecto: contenido de archivos locales, destilado por el modelo en las dos cadenas owner y repo, y después enviado a la internet pública. Este es exactamente el escenario donde se encuentran dos condiciones de alto riesgo, «leer datos privados» más «comunicarse hacia fuera»,5 así que recibe una regla de permisos explícita: los argumentos tienen que coincidir con el formato de nombres válido de GitHub, nada más:
GITHUB_TOKEN se lee de una variable de entorno y nunca aparece en el código; también funciona sin él, solo que con límites de tasa más bajos en las peticiones anónimas. Esta es la misma idea que las reglas de permisos de la Lección 5 en otra forma: aquella lección cubrió las reglas declarativas allow/deny/ask del archivo de configuración de Claude Code,6 y esta es la versión imperativa escrita dentro del código de la herramienta; las dos trazan una línea que una operación de alto riesgo no puede cruzar.7
Paso 4: Escribir el bucle de ejecución
Con toolSchemas y toolHandlers en la mano, el bucle en sí no es complicado. La lógica central son cuatro pasos: enviar la petición, mirar stop_reason, devolver texto si no es tool_use y, si lo es, ejecutar cada bloque de llamada a herramienta y coser los resultados de vuelta.1
Hay aquí un detalle fácil de pasar por alto: for (const block of response.content) itera sobre todos los bloques de contenido devueltos en este turno, no solo el primero. El modelo pide a menudo dos o tres herramientas en paralelo en un mismo turno; cada una tiene que ejecutarse y producir su propio tool_result, con tool_use_id emparejado uno a uno, y no puede faltar ninguna.8 El ejercicio de nivel 2 te hará recorrer en primera persona la trampa de que falte una.
Válvulas de seguridad, y cómo detectar que una herramienta está mal conectada
El bucle de arriba funciona, pero le faltan dos salvaguardas. Vamos a añadirlas:
Salvaguarda uno: el fallo de una herramienta tiene que devolverse como información, no tumbar el bucle. Envuelve la llamada en crudo en un try/catch y, en caso de fallo, produce igualmente un tool_result, solo que marcado con is_error: true; cuando el modelo ve esa marca, normalmente ajusta los argumentos y reintenta, en vez de repetir el mismo error.9 8
Salvaguarda dos: la misma herramienta con los mismos argumentos, llamada tres veces seguidas, debería parar. Esto no es adivinar: se basa en registrar las firmas de las últimas llamadas:
Junto con MAX_TURNS como interruptor general, las tres válvulas de seguridad tienen trabajos distintos: MAX_TURNS protege contra «el modelo sigue pidiendo herramientas con variaciones nuevas y no para nunca»; la detección de llamadas repetidas protege contra «el modelo se queda dando vueltas con los mismos argumentos»; y las comprobaciones internas de ruta y formato de las herramientas (las escritas en el paso 3) protegen contra «el modelo se inventó un argumento fuera de límites y la herramienta lo ejecutó obedientemente igual». Quita cualquiera de las tres capas y el bucle corre el riesgo de desbocarse o de extralimitarse.7
¿Cómo se detecta en los logs que una herramienta está mal conectada? Dos de las señales más habituales:
- El modelo llama a la misma herramienta una y otra vez, con argumentos que varían solo dentro de un rango estrecho (cambios de mayúsculas, añadir o quitar una palabra). Nueve de cada diez veces el modelo no es tonto: el content del
tool_result es demasiado vago. «No encontrado» devuelve una cadena vacía, el modelo no puede distinguir «de verdad no hay nada» de «la herramienta está rota», y solo puede adivinar y volver a intentarlo.
- El modelo rellena argumentos adivinando, por ejemplo pasándole a
read_file una ruta que no existe. Rastrearlo hacia atrás suele revelar una de dos causas: la description no detallaba de dónde debería salir el argumento (eco de la Lección 4), o la salida de la herramienta anterior no daba una ruta precisa y el modelo tuvo que inventarse una.
Resumen
- Registra el esquema y el handler de una herramienta en la misma tabla (
TOOLS), con toolSchemas y toolHandlers derivados de ella, para que cambiar un sitio nunca deje el otro sin cambiar
- El núcleo del bucle de ejecución es: enviar la petición → comprobar si
stop_reason es tool_use → si lo es, iterar sobre todos los bloques de llamada a herramienta, ejecutar y coser de vuelta el tool_result → si no, devolver texto y terminar el bucle
- Un turno puede llevar varias llamadas a herramientas en paralelo; cada
tool_use necesita un tool_result emparejado de forma única, y si falta uno la siguiente petición da error
- Las tres válvulas de seguridad protegen cada una su capa:
MAX_TURNS impide que el modelo pida herramientas indefinidamente, la detección de llamadas repetidas impide que el modelo dé vueltas con el mismo conjunto de argumentos, y las comprobaciones internas de ruta y formato de las herramientas frenan los argumentos fuera de límites
- El content del
tool_result tiene que declarar con claridad «no encontrado» frente a «ocurrió un error»; un retorno vacío y vago es la causa número uno de que el modelo reintente una y otra vez y de que los logs parezcan los de una herramienta mal conectada
Ya has terminado las seis lecciones de este curso, desde «por qué los agentes necesitan herramientas» hasta escribir tú un bucle de ejecución de herramientas que funciona. Lo más provechoso que puedes hacer ahora no es leer otra lección: es elegir una tarea pequeña y real de tu propio proyecto, partirla en dos o tres herramientas y llevarte este esqueleto de bucle con unos pocos ajustes. Ponerlo a funcionar una vez vale más que leer diez explicaciones más. Cuando estés depurando y tengas dudas sobre un campo concreto, vuelve a sources.md y consulta S4 y S5, los dos documentos oficiales; son el texto normativo más primario para este bucle multiturno.