Errores de tools: cómo un agente se recupera y cuándo debe parar

Errores de tools: cómo un agente se recupera y cuándo debe parar

Un error también es un resultado, pero no es un dato

Consultar un peso puede devolver 76 kg, indicar que falta el registro o fallar porque no se puede leer el almacenamiento. Los tres casos producen una respuesta de la función, pero solo el primero contiene el dato solicitado. Si todo viaja como texto sin estado, el modelo tiene que deducir qué ocurrió a partir de la redacción.

Un error esperado forma parte del dominio: una fecha sin registro, un campo inexistente o argumentos que no cumplen el schema, la forma aceptada de la petición. Una excepción inesperada es otra cosa: una lectura o escritura que se interrumpe dentro de la aplicación. Separarlas permite corregir una petición sin tratar cada fallo como una caída del proveedor.

El harness debe distinguir ambos casos antes de volver a llamar al modelo. Una frase que empieza por «Error» no constituye un contrato. Tampoco se debe clasificar un texto guardado por el usuario como fallo porque contenga is_error: los datos siguen siendo datos.

Un contrato que indica cómo recuperarse

La ejecución devuelve dos piezas: el contenido que verá el modelo y una marca explícita de error que controla el programa. El ejemplo siguiente es un payload interno ilustrativo, no un campo obligatorio de todas las APIs. El mensaje está en español, como en el caso práctico. Además del código estable not_found, explica el alcance del fallo y propone una vía de recuperación.

{
  "kind": "tool_error",
  "version": 1,
  "is_error": true,
  "error": "not_found",
  "message": "No hay registro para esa fecha. No implica que no haya registros en otras fechas.",
  "recovery": "choose_alternative",
  "recoverable": true
}

correct_arguments permite corregir los argumentos; choose_alternative permite elegir otra consulta con información suficiente; stop_turn exige parar. Son etiquetas del contrato de esta aplicación. El modelo puede proponer una alternativa, pero el harness sigue validando cada llamada y aplicando el límite de rondas.

Resultado de la ejecución
  1. Argumentos inválidos → corregir
  2. Ausencia conocida → elegir alternativa
  3. Escritura incierta → parar

«No hay registro en esta fecha» no significa «no hay registros». Esa precisión evita convertir una ausencia local en una afirmación global. La marca de error ayuda a distinguir el estado; el mensaje ayuda a razonar sobre el siguiente paso.

El mismo fallo en tres proveedores

Los adapters, las piezas que traducen el contrato local a cada API, conservan el identificador que enlaza la llamada con su resultado. Cambia el envoltorio, no la decisión sobre si la ejecución falló.

  • Anthropic Messages: el bloque tool_result lleva is_error: true y el contenido seguro del fallo.
  • Google Interactions: el function_result también admite is_error. Esta comparación se refiere a Interactions.
  • OpenAI Responses: function_call_output.output contiene el JSON serializado del error. No añadimos un is_error ajeno a ese formato en el nivel superior.

La marca se conserva cuando se reproduce un resultado ya registrado. No se vuelve a inferir leyendo su texto. Por eso un resultado recuperado del historial mantiene el mismo significado que cuando se ejecutó.

Dónde poner el try/catch

Validar antes de ejecutar evita efectos con argumentos inválidos. Después, el límite que captura excepciones debe rodear la ejecución real del handler, la función que realiza la operación. Capturar solo la llamada al proveedor deja fuera los fallos locales.

validate the tool name and arguments
if validation fails:
    return a marked error with recovery = correct_arguments

try:
    result = execute the handler
    return result with its explicit success or error status
catch an unexpected exception:
    if the tool only reads:
        return a safe error that allows an alternative
    else:
        stop the turn locally
        say that completion cannot be confirmed
        do not repeat the action automatically

Este pseudocódigo resume la decisión, no todo el bucle. Los fallos esperados salen por el contrato normal; las excepciones se convierten en mensajes seguros. La excepción original puede servir para diagnosticar localmente, pero no debe aparecer como una confirmación de éxito ni exponerse al modelo con detalles internos. Un fallo fatal local tampoco se etiqueta como «Error de proveedor».

Una conversación real con Coach

Prueba exploratoria del 4 de octubre de 2026 en la web de Gymnasia, versión 1.51.0, con OpenAI gpt-6-luna. Se usaron fechas y pesos ficticios. Leer medidas permite consultar el peso guardado para un día; no modifica registros.

Usuario: consulta el peso del 2020-01-02. Si no existe, consulta el 2020-01-01. No crees ni modifiques datos.

Coach: Falta el registro del 2020-01-02. El 2020-01-01 sí tiene un peso guardado: 76 kg.

La petición del usuario está abreviada aquí. Las llamadas reales mostraron la secuencia completa: read_measurement para el día 2, error marcado con choose_alternative, lectura del día 1 y respuesta final. Se comprobó el contenido enviado al proveedor, no solo el texto visible.

Lo que no se reprodujo: en otra prueba aislada, el mismo modelo recibió el formato antiguo de texto sin marca y entendió que un campo no existía. No observamos una confusión de ese error con un dato válido.

Con una fecha ausente, el formato antiguo produjo «No tienes medidas registradas para el 2020-01-02». El contrato nuevo produjo «No hay un registro de medidas para el 2020-01-02. Esto no permite saber si tienes registros en otras fechas». El mensaje nuevo también era más preciso: esta comparación no aísla el efecto de la marca ni demuestra que todos los modelos mejoren. El valor del contrato es hacer explícita la decisión del programa, incluso cuando un modelo ya interpreta bien el texto.

Una escritura incierta cambia la decisión

Una lectura puede repetirse sin crear un registro. Una escritura interrumpida puede haber terminado antes de fallar la confirmación. «Falló» no siempre equivale a «no hizo nada». Repetir por defecto puede duplicar una acción.

En la misma sesión se provocó un fallo controlado al persistir un peso ficticio. El modelo era real; el fallo de almacenamiento era una inyección de QA. Coach mostró:

Gymnasia no puede confirmar si la acción llegó a completarse. Para evitar duplicarla, no la ha repetido. Revisa tus datos antes de solicitarla de nuevo.

Hubo una sola petición al modelo, ninguna ronda adicional ni reintento automático. El registro del 2020-01-03 no existía después de recargar. Se retiró la inyección y una escritura posterior del 2020-01-04, con 77,4 kg, se guardó y se pudo leer tras recargar. Esto verifica la recuperación del almacenamiento en ese escenario; no demuestra que toda escritura fallida deje los datos intactos.

El mensaje conservador sigue siendo necesario cuando el efecto es incierto. Si una acción debe permitir reintentos seguros, necesita una estrategia adicional, como una identidad estable y un registro durable del resultado. Cambiar el texto del error no proporciona esa garantía.

Qué pruebas protegen este contrato

Los tests deterministas comprueban el contrato sin depender de cómo responda un modelo ese día. La QA real añade evidencia de comportamiento, pero no sustituye esos límites.

  • Contrato: argumentos inválidos producen un error marcado sin ejecutar el handler; un texto de usuario parecido a un error sigue siendo un resultado válido.
  • Integración con proveedores falsos: OpenAI, Anthropic y Google reciben su formato correcto; una llamada corregida persiste una sola medición.
  • Regresión de escrituras: una excepción con las palabras network timeout no activa reintentos de transporte ni confirma éxito.
  • Persistencia: reproducir un resultado conserva su estado; recargar permite distinguir el registro confirmado del que no llegó a guardarse.

La entrega pasó 933 pruebas Vitest, 11 del almacén de desarrollo y 3 de fronteras móviles, además del type-check y los E2E con proveedores falsos. La prueba exploratoria descrita aquí se hizo en web con un solo modelo. No se presenta como una prueba nativa ni como una evaluación estadística de los tres proveedores.

Construir un agente, paso a paso

Este artículo pertenece a la serie sobre cómo construir un agente y su harness a través de una app de gimnasio. Las entregas anteriores explican cómo declarar tools, validar llamadas y cerrar el bucle; esta añade una decisión que debe vivir en el programa: cuándo un fallo permite continuar y cuándo exige parar.

Consulta el índice de la serie y la implementación de Gymnasia que sirve como caso práctico.

Seguir leyendo

Últimos posts -->

¿Has visto estos proyectos?

Gymnasia

Gymnasia Gymnasia
Expo
React Native
TypeScript
OpenAI
Anthropic

App de fitness con dos agentes que se ejecutan íntegramente en el dispositivo, sin backend, de forma que los datos del usuario nunca salen del móvil. Un coach conversacional BYOK con tools locales y system prompt remoto con fallback offline, y un estimador de comidas que saca los macronutrientes de una foto del plato, con lectura de códigos de barras contra OpenFoodFacts.

LangGraph Deep Researcher

LangGraph Deep Researcher LangGraph Deep Researcher
Python
LangGraph
FastAPI
React
TypeScript
Docker

Sistema multiagente de investigación construido con LangGraph. Un supervisor descompone tu pregunta en temas y lanza subagentes de búsqueda en paralelo; cada uno comprime sus hallazgos antes de pasarlos a un agente redactor que escribe el informe final en markdown con sus fuentes. Streaming en vivo por WebSockets, modelo configurable por rol y claves de API propias que nunca se guardan en el servidor.

Tau

Tau Tau
Python
LangChain

Sistema multiagente de tutoría para estudiantes de secundaria, con un agente por asignatura y material de curso elaborado y validado por un equipo de profesores. Llegó a usarse con alumnos reales en un colegio privado en España y en un instituto en Colombia.

Ver todos los proyectos -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres ver alguna charla?

Últimas charlas -->

¿Quieres mejorar con estos tips?

Últimos tips -->

Usa esto en local

Los espacios de Hugging Face nos permite ejecutar modelos con demos muy sencillas, pero ¿qué pasa si la demo se rompe? O si el usuario la elimina? Por ello he creado contenedores docker con algunos espacios interesantes, para poder usarlos de manera local, pase lo que pase. De hecho, es posible que si pinchas en alún botón de ver proyecto te lleve a un espacio que no funciona.

Flow edit

Flow edit Flow edit

Edita imágenes con este modelo de Flow. Basándose en SD3 o FLUX puedes editar cualquier imagen y generar nuevas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos los contenedores -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres entrenar tu modelo con estos datasets?

short-jokes-dataset

HuggingFace

Dataset de chistes en inglés

Uso: Fine-tuning de modelos de generación de texto humorístico

231K filas 2 columnas 45 MB
Ver en HuggingFace →

opus100

HuggingFace

Dataset con traducciones de inglés a español

Uso: Entrenamiento de modelos de traducción inglés-español

1M filas 2 columnas 210 MB
Ver en HuggingFace →

netflix_titles

HuggingFace

Dataset con películas y series de Netflix

Uso: Análisis de catálogo de Netflix y sistemas de recomendación

8.8K filas 12 columnas 3.5 MB
Ver en HuggingFace →
Ver más datasets -->