Una tool es un contrato
Cuando un LLM decide usar una tool no ve la función de Python, JavaScript o TypeScript que acabará ejecutándose. Solo recibe un contrato que le explica qué capacidad tiene disponible la tool y qué argumentos debe entregar.
Ese contrato necesita como mínimo tres piezas. Puede incluir una cuarta para describir el resultado. Si son claras, el modelo puede elegir bien; si son ambiguas, el harness tendrá que lidiar con llamadas incorrectas aunque la tool esté perfectamente programada.
Es la etiqueta corta de la tool. Si el modelo responde «quiero usar read_measurement», el harness lee esa etiqueta y sabe qué acción concreta debe ejecutar.
Es el texto que hace que el LLM entienda que la tool existe, qué hace y cuándo debe usarla. También puede explicar qué devuelve y qué pasos previos necesita.
Es el JSON Schema de los argumentos aceptados: qué argumentos existen, qué tipo tiene cada uno y cuáles son obligatorios.
Describe la forma del resultado cuando la tool devuelve datos estructurados. No todos los proveedores aceptan este campo con el mismo nombre: puede mantenerse como contrato interno, enviarse si la API lo soporta o documentarse en description.
En el ejemplo de Gymnasia, la lista central del agente no incluye outputSchema: sus handlers devuelven texto o JSON serializado y la descripción anticipa al modelo qué recibirá. Añadir un schema de salida sería útil si el harness necesitase validar resultados estructurados antes de devolverlos al LLM.
Qué ocurre cuando se ejecuta una tool
El siguiente flujo representa una respuesta que incluye la llamada a una tool. Empieza cuando la petición necesita una capacidad externa y muestra el ciclo completo de ejecución de una tool: el modelo la solicita, el harness la ejecuta y el resultado vuelve al modelo.
read_measurement + fechaDefine todas las tools una sola vez y en un único lugar
Si declaras las tools en diferentes lugares del código y más adelante cambia la forma común de describirlas, es fácil modificar unas y olvidarse de otras. Es más sencillo mantener una única lista central: cada tool se define una vez y el resto del sistema lee esa lista para informar al proveedor, aceptar solo nombres conocidos y comprobar que cada acción tiene código capaz de ejecutarla.
En un proyecto pequeño esa lista puede vivir en un solo archivo. En uno grande puede repartirse por áreas y reunirse en un único módulo exportado. Lo importante no es el archivo concreto, sino evitar copias independientes del mismo contrato.
Como ejemplo, un agente para una aplicación de gimnasio puede necesitar recordar datos personales, consultar medidas, registrar comidas y crear rutinas. A continuación tienes la definición de una tool, read_measurement:
{
name: "read_measurement",
description:
"Lee las medidas corporales del usuario para una fecha específica. " +
"Devuelve el registro de medidas de ese día si existe. " +
"Usa esta tool cuando el usuario pregunte por sus medidas de un día concreto.",
inputSchema: {
type: "object",
properties: {
date: stringProperty("Fecha en formato YYYY-MM-DD")
},
required: ["date"]
}
}El ejemplo permite leer el contrato sin conocer el resto de la tool: el nombre conecta la llamada con su acción, la descripción da al modelo el criterio de uso y el esquema exige una fecha.
Las 15 tools de Gymnasia
En total aparecen 15 declaraciones con forma de tool. Trece pertenecen al agente conversacional principal; las otras dos ayudan a un flujo especializado en estimar comidas. Agruparlas por función permite entender para qué sirve cada una sin conocer las pantallas ni la estructura interna de Gymnasia.
Memoria personal
save_personal_dataGuarda o actualiza el perfil que el agente debe recordar.list_personal_data_keysEnumera qué tipos de datos personales hay disponibles sin leer todavía sus valores.read_field_descriptionExplica qué significa un campo de memoria para poder elegir el adecuado.read_field_valueRecupera el valor de un campo una vez identificado.
Medidas corporales
read_measurementConsulta las medidas registradas en una fecha concreta.write_measurementGuarda o actualiza las medidas proporcionadas por la persona.
Alimentación
read_meal_foodsLee los alimentos anotados en una comida y una fecha.search_foodsBusca alimentos por nombre, categoría o valores nutricionales.add_meal_foodAñade a una comida un alimento previamente identificado.
Entrenamiento
search_exercisesBusca ejercicios por músculo, material o dificultad.read_routinesRecupera las rutinas de entrenamiento guardadas.create_routineCrea una rutina con ejercicios y series concretas.
Mejoras del producto
create_feature_issueCrea en GitHub una issue estructurada a partir de una petición de mejora del usuario.
Estimación de comidas
scan_barcodeConsulta un código de barras para recuperar los datos nutricionales de un alimento; es una acción ejecutable de un flujo especializado.extract_nutritionPide a Anthropic una respuesta nutricional con una estructura concreta. Tiene forma de tool, pero actúa como mecanismo de salida estructurada y no tiene un handler local.
Una de estas tools es diferente al resto: create_feature_issue no modifica la aplicación ni los datos guardados por la persona. Su único efecto ocurre fuera de Gymnasia, donde crea una issue en GitHub. Así, si alguien quiere proponer una mejora, solo tiene que pedírsela al agente; el LLM identifica la petición y solicita al harness que ejecute esta tool con la información necesaria para registrar la propuesta.
Por tanto, hay 15 declaraciones: 14 representan acciones ejecutables y una, extract_nutrition, se utiliza para obtener JSON estructurado.
La descripción también es prompt
Una tool puede estar perfectamente implementada y aun así no servir de nada si el modelo no sabe que existe. La description es texto visible para el LLM: forma parte del contexto con el que decide si debe responder directamente o pedir que se ejecute esa tool.
Una descripción pobre como "Lee medidas" deja preguntas abiertas. ¿Lee la última medida o la de una fecha? ¿Qué devuelve si no hay datos? ¿Debe llamarse para responder una pregunta general sobre entrenamiento? Una descripción útil responde cuatro cuestiones:
Describe una acción concreta, no el nombre de una pantalla o módulo.
Da al modelo una señal reconocible en la petición del usuario.
Anticipa el dato que el modelo recibirá para continuar razonando.
Indica precondiciones, límites y orden entre tools.
Este último punto es importante en cualquier dominio. Si una tool añade un elemento seleccionado y otra busca candidatos, la descripción debe decir que primero se busque y después se añada. El modelo no puede deducir esa dependencia a partir de comentarios internos que nunca recibe.
Cómo llegan el system prompt y las tools al LLM
La descripción de cada tool no sustituye al system prompt. En Gymnasia, el system prompt define el papel general del agente y reglas que afectan a varias tools. Por ejemplo, una regla de alimentación puede indicar esta secuencia:
## Tools de alimentación
Cuando la persona pida añadir un alimento:
1. Usa search_foods para encontrar el alimento exacto.
2. Usa add_meal_food con el resultado seleccionado.Antes de llamar al proveedor, ese prompt base se completa con políticas locales. Las definiciones de las tools no se concatenan dentro de ese texto: viajan en el campo tools de la misma petición. El proveedor presenta ambas piezas al modelo para que las reglas generales y los contratos concretos trabajen juntas.
name + description + schemasDe forma simplificada, la petición de OpenAI queda así:
const systemPrompt = composeAiSystemPrompt(basePrompt);
const request = {
instructions: systemPrompt,
tools: CHAT_TOOLS.openai,
input: messages
};OpenAI llama instructions a ese campo; Anthropic usa system y Google, systemInstruction. Los nombres cambian, pero la idea es la misma: el prompt fija el comportamiento global y cada definición explica una capacidad concreta.
Tres proveedores, una definición
De momento, Gymnasia puede usar sus tools con OpenAI, Anthropic y Google. Se añadirán más proveedores, por eso la lista central no depende de ninguno de ellos: incorporar otro debería requerir un nuevo adaptador, no volver a definir todas las tools.
Un harness puede permitir cambiar de modelo sin cambiar las capacidades del agente. El problema es que OpenAI, Anthropic y Google envuelven el mismo contrato con estructuras distintas. La solución es mantener una definición neutral y crear un adaptador pequeño para cada API.
Añade type: "function" y coloca el esquema en parameters.
Recibe nombre y descripción directamente, y el esquema en input_schema.
Agrupa las declaraciones en functionDeclarations y usa parameters.
const providerTools = {
openai: definitions.map(tool => ({
type: "function", name: tool.name,
description: tool.description, parameters: tool.inputSchema
})),
anthropic: definitions.map(tool => ({
name: tool.name, description: tool.description,
input_schema: tool.inputSchema
})),
google: [{ functionDeclarations: definitions.map(tool => ({
name: tool.name, description: tool.description,
parameters: tool.inputSchema
})) }]
};La envoltura cambia, pero el significado no. Si se mejora una descripción o se añade un campo obligatorio, los tres proveedores reciben el mismo cambio. Copiar y mantener tres listas a mano convierte cualquier corrección en una posible divergencia.
El contrato común del ejemplo se limita a los campos que los tres proveedores comparten bien. Un outputSchema puede añadirse a la definición interna cuando resulte útil, pero el adaptador debe tratarlo según cada API: Google admite un schema de respuesta, MCP define outputSchema como opcional y otros formatos devuelven texto o JSON sin ese campo en la declaración de la tool.
De la definición a la ejecución
Declarar una tool no implementa la acción. El proveedor solo devuelve una propuesta parecida a { name: "read_measurement", arguments: { date: "2026-08-18" } }. El harness conserva el control y decide qué ocurre a continuación.
- 1
Leer el nombre y los argumentos de la llamada del modelo.
- 2
Comprobar que el nombre está permitido y localizar su handler.
- 3
Validar tipos, campos obligatorios, permisos y reglas de negocio.
- 4
Ejecutar código propio, en Python, JavaScript, TypeScript u otro lenguaje, y serializar el resultado.
- 5
Enviar ese resultado al modelo para que continúe y redacte una respuesta útil.
El handler puede vivir en un servidor, una función edge o el dispositivo del usuario. En el caso de Gymnasia se ejecuta en el móvil porque los datos son local-first y no necesitan backend. La lección general es la misma: el modelo solicita una acción, pero el harness valida, autoriza y ejecuta el código.
Tests contra la divergencia
La divergencia aparece cuando una capa cambia y las demás no: se declara una tool sin handler, se añade un argumento obligatorio solo para un proveedor o se acepta en tiempo de ejecución un tipo distinto del publicado en el esquema. Los tests deben comprobar el contrato completo, no únicamente funciones aisladas.
Nombres únicos, descripciones con contenido y esquemas de tipo objeto.
Un validador compatible con JSON Schema comprueba que cada esquema es válido y que required coincide con properties.
El catálogo, los handlers y los adaptadores de proveedores contienen las mismas tools.
Argumentos ausentes o de tipo incorrecto producen errores controlados.
Valores arbitrarios nunca hacen fallar el validador de forma inesperada.
Un proveedor falso confirma llamada, ejecución, resultado y respuesta final.
for (const tool of tools) {
expect(() => schemaValidator.compile(tool.inputSchema)).not.toThrow();
}No importa qué librería compatible con JSON Schema se elija; lo importante es que los tests compilen cada contrato y fallen antes de enviar una definición inválida a producción. El objetivo no es probar que el proveedor “sea inteligente”, sino que el puente construido alrededor del modelo sea coherente.
Aprender a construir un agente completo
Este artículo pertenece a una serie sobre cómo desarrollar un agente o harness de principio a fin. Gymnasia, una aplicación de gimnasio, sirve como ejemplo práctico para estudiar memoria, tools, proveedores, ejecución local, evaluación y seguridad; cada entrega está escrita para que esas ideas puedan trasladarse a otros productos.
Ver el índice completo de la serie sobre el agente de Gymnasia.