Saltar al contenido

Diseño de herramientas para agentes LLM: APIs que deben explicarse solas

La descripción de una herramienta es su documentación, su sistema de tipos y su introducción al uso.

Ingeniería de IA5 min de lectura

Ingeniero de software. Desarrollo extensiones de Chrome, aplicaciones web y herramientas de IA aplicada.

Contenido

Al diseñar una API para personas, das muchas cosas por hechas. El desarrollador leerá la documentación, buscará un error y aprenderá. La próxima vez lo recordará. Verá que sort acepta "asc" | "desc" y no enviará "ascending" dos veces.

Un modelo de lenguaje no ofrece esas garantías. En el caso que nos ocupa, recibe el esquema en cada llamada, no recuerda sesiones anteriores ni tiene documentación adicional a mano. Cuando el esquema es ambiguo, puede inventar una opción plausible en lugar de reconocer el problema.

Diseñar herramientas bajo esas restricciones merece atención propia. Corregir una herramienta mal diseñada puede ayudar más que seguir ajustando el prompt que la invoca.

La descripción contiene el contrato completo

Una definición incluye un nombre, una descripción y un esquema de parámetros. Esa puede ser toda la información disponible para el modelo, sin README, directorio de ejemplos ni compañero al que preguntar.

La descripción forma parte de la interfaz. Trátala con el cuidado de una firma de API pública.

Una descripción débil:

{
  "name": "search",
  "description": "Search for items",
  "parameters": { "query": { "type": "string" } }
}

El modelo tiene que deducir qué puede buscar, qué sintaxis acepta la consulta, qué devuelve y cuándo conviene usarla. Cada deducción introduce incertidumbre.

Una descripción que orienta mejor:

{
  "name": "search_customer_records",
  "description": "Search customer records by name, email, or company. Returns up to 20 matches with id, name, email, company, and signup date, not billing history or support tickets. Plain text only; no boolean operators or wildcards. Use get_customer with an id for full details on one record.",
  "parameters": {
    "query": {
      "type": "string",
      "description": "Plain text. Examples: 'jane@acme.com', 'Acme Corp', 'Jane Doe'"
    }
  }
}

Comprueba cuatro cosas en cada herramienta:

  1. Qué hace, con precisión suficiente para distinguirla de las demás.
  2. Qué devuelve, incluida la estructura y el límite.
  3. Qué queda fuera de su alcance, para evitar usos inventados.
  4. Qué alternativa usar cuando esta no corresponde.

«Devuelve fichas de clientes, no historial de facturación» evita que el modelo busque datos ausentes y termine inventando un campo que creía que debía existir.

Nombra las herramientas para ayudar a elegirlas

El modelo relaciona semánticamente la petición con el nombre de la herramienta. El nombre debe ayudar en ese momento de selección.

execute_query no explica cuándo utilizarla. search_customer_records sí. process encaja débilmente con demasiadas cosas.

Algunas reglas útiles:

  • Verbo y sustantivo concreto. create_invoice, en lugar de invoice o handle_invoice.
  • Prefijos distintos para dominios distintos. calendar_*, email_*, file_*. Agrupar nombres facilita la selección cuando crece el catálogo.
  • No dependas de los parámetros para distinguir acciones. Si tienes get_data(type: "user" | "order"), considera separarla. Los nombres permiten expresar la decisión con más claridad que un campo genérico.

Una herramienta genérica con un parámetro de modo te resulta cómoda, pero traslada al modelo la tarea de interpretar ese modo y completar sus requisitos.

Granularidad: una acción por herramienta

Una herramienta enorme parece atractiva: menos definiciones, menos esquema y menos contexto. manage_calendar(action, event_id?, title?, start?, end?, attendees?, ...) puede hacerlo todo.

También obliga al modelo a elegir la acción, los parámetros que corresponden y las combinaciones válidas dentro de un esquema plano que no siempre expresa esas dependencias.

Sepárala:

create_event(title, start, end, attendees?)
update_event(event_id, title?, start?, end?)
cancel_event(event_id, notify_attendees)
list_events(start_date, end_date)

Cada esquema es válido por sí mismo. No hay campos que solo importen en ciertos modos. El modelo no puede pasar attendees a una cancelación porque ese parámetro no existe allí.

El coste contrario existe: cada definición ocupa contexto y sesenta herramientas pueden resultar menos manejables que quince. La alternativa es exponerlas de forma gradual. Ofrece las pertinentes para la fase actual y una forma de buscar y cargar otras cuando hagan falta.

Mi criterio: separa cuando cambien los conjuntos de parámetros; combina cuando sean idénticos y solo cambie el destino.

Los errores son una oportunidad para corregir el siguiente paso

El modelo no conserva memoria de todos sus errores anteriores. El mensaje que devuelves en ese contexto puede ser su única corrección. Es una parte del diseño que suele recibir poca atención.

Error: Invalid date format

El modelo solo sabe que lo que intentó es incorrecto. No conoce el formato válido y tiene demasiadas alternativas. Puede repetir la misma conjetura.

Invalid date format: "next Tuesday"
Expected ISO 8601 date: YYYY-MM-DD (example: 2026-08-04)
Today is 2026-07-26, so next Tuesday is 2026-08-04.

Ahora dispone de una corrección que puede aplicar en el siguiente turno. Cuesta unos tokens y puede ahorrar varias iteraciones o un bucle fallido.

El patrón se aplica a muchos casos:

  • Muestra lo recibido, literalmente, para que vea su entrada incorrecta.
  • Explica la restricción con un ejemplo válido concreto.
  • Calcula una corrección cuando puedas. Si el validador entiende la intención, ofrece una solución. Tú tienes el conocimiento del dominio.
  • Distingue una entrada incorrecta de una avería del sistema. Un argumento mal formado debe corregirse. Una caída del servicio requiere otra respuesta del bucle. Presentarla como error de validación puede provocar reintentos sin fin.

Otra trampa: no uses semántica de 401 o 403 para un problema transitorio. Un agente puede interpretar que perdió acceso y abandonar o reducir privilegios. Los códigos de estado importan cuando quien los recibe actúa según su significado.

Diseña la respuesta para razonar con ella

Una API para clientes humanos suele devolver datos completos porque el programa seleccionará lo que necesita. Una herramienta introduce su resultado en una ventana de contexto. Cada campo consume presupuesto y cada dato irrelevante puede distraer al modelo.

Este es el problema de devolver todo sin procesar:

{ "id": "cus_9d8f7", "object": "customer", "created": 1753488000,
  "livemode": false, "metadata": {}, "preferred_locales": [],
  "invoice_settings": { "custom_fields": null, "footer": null },
  "tax_exempt": "none", "test_clock": null, ... }

Cuarenta campos, de los que importan tres. Selecciona la respuesta:

{ "id": "cus_9d8f7", "name": "Jane Doe", "email": "jane@acme.com",
  "plan": "pro", "status": "active", "signed_up": "2026-07-26" }

Además de recortar, aplica tres hábitos:

Devuelve lo que el modelo tendría que calcular. Si la siguiente llamada necesita customer_id, inclúyelo directamente. Si la decisión depende de un recuento, devuelve ese recuento.

Haz explícito el recorte e indica cómo continuar. «Se muestran 20 de 847 resultados; concreta la consulta o usa list_customers con paginación» es mejor que devolver veinte en silencio. El recorte oculto produce conclusiones erróneas con mucha seguridad.

Prefiere valores estables y legibles. "status": "active" comunica mejor su significado que "status": 2. El modelo conoce la palabra, pero no necesariamente tu codificación.

Idempotencia y reintentos

Los agentes reintentan por errores transitorios o por confusión. Si una petición agota el tiempo de espera, puede que no sepas si llegó a ejecutarse.

Las herramientas con efectos secundarios deben contemplarlo:

  • Acepta claves de idempotencia en escrituras y evita duplicados en el servidor.
  • Devuelve el estado actual, además del éxito. { "status": "already_sent", "sent_at": "..." } explica qué ocurrió sin otra consulta.
  • Exige identificadores concretos para operaciones destructivas. Un filtro puede abarcar más de lo previsto. delete_record(id) limita el alcance a un registro, aunque sigue necesitando controles de acceso y autorización.

Las acciones con consecuencias necesitan controles en código. Escribir «solo envía correos aprobados por el usuario» en una descripción no sustituye una comprobación de permisos en el harness.

La prueba que deja ver las ambigüedades

Da a un ingeniero competente solo las definiciones de tus herramientas y una tarea, sin código, documentación ni conversación. Observa cómo las usa.

Donde dude, el modelo probablemente adivinará. Si pregunta «¿esto incluye X?», el modelo puede darlo por hecho. Una selección confusa para una persona también lo será para el agente.

Cada ambigüedad que detectes merece una corrección.

Después observa la versión de esa prueba que puedes aplicar de forma continua: registra las llamadas en producción y ordena las herramientas por reintentos, correcciones y abandonos. La tasa de reintentos por herramienta ayuda a encontrar el próximo problema que corregir. Puede ofrecer más valor que otra ronda de ajustes al prompt.

Un pequeño voto de confianza

¿Valió la pena leer esta nota?