El mes pasado mi agente llamó con confianza a una herramienta llamada search_invoices. Esa herramienta no existía. Nunca existió. El modelo inventó el nombre, inventó los argumentos, y emitió un bloque JSON perfectamente formado para ella. Mi harness, confiado e ingenuo, intentó despacharlo y se cayó.
Si construyes agentes, has visto esto. El modelo llama a una herramienta que no está en tu schema, o llama a una herramienta real con argumentos que no coinciden con el schema de input, o narra una llamada a herramienta en texto plano en vez de emitir el bloque tool_use real. La gente llama a todo esto "alucinando llamadas a herramientas". Quiero explicar por qué ocurre a nivel de mecanismo, porque una vez que lo ves, las correcciones dejan de parecer superstición.
El modelo predice tokens, no ejecuta tu código
Aquí está lo que nadie te dice cuando empiezas. Una llamada a herramienta no es una invocación de función desde el lado del modelo. Es texto. Texto estructurado, claro, pero texto. El modelo emite un bloque tool_use de la misma manera que emite un párrafo: prediciendo los tokens siguientes más probables dado todo lo anterior. Tu schema es parte de ese contexto, pero no es una restricción dura a menos que la hagas una.
¿Entonces cuándo va mal? Cuando la distribución de los tokens siguientes plausibles apunta a algún lugar que tu schema no cubre. Si tu historial de conversación está lleno de referencias a facturas, y el modelo decide que necesita datos de facturas, search_invoices es una secuencia de tokens tremendamente plausible — aunque la herramienta que le diste se llame realmente query_billing. El modelo hace pattern-matching contra todo lo que ha visto, y "search_" + sustantivo es una de las formas más comunes de nombres de herramientas en sus datos de entrenamiento.
Por eso las llamadas a herramientas alucinadas aumentan en dos situaciones: cuando tus nombres de herramientas son inusuales, y cuando tu contexto es largo y ruidoso.
Corrección uno: haz que la llamada correcta sea la más probable
Nombra tus herramientas de la manera en que el modelo espera que se nombren. get_weather, send_email, search_database. Aburrido es bueno. Aburrido es predecible, y predecible significa que la suposición del siguiente token del modelo cae en tu herramienta real en vez de en una prima inventada.
Luego escribe descripciones que digan cuándo llamar, no solo qué hace la herramienta. Esto importa más en modelos Claude recientes — Opus 4.7 y 4.8 son más conservadores al usar herramientas que 4.6, así que una descripción como "Llama a esto cuando el usuario pregunte sobre precios actuales o eventos recientes" aumenta mediblemente la tasa de llamada en los casos que deberían. La condición desencadenante es parte de lo que el modelo condiciona. Ponla en la descripción, no enterrada en el system prompt.
Corrección dos: no dejes pasar las malas llamadas
Cuando el modelo emite una llamada malformada, tu harness debería rechazarla ruidosamente, no caerse. Devuelve un tool_result con is_error: true y un mensaje que el modelo pueda leer:
{
"type": "tool_result",
"tool_use_id": "toolu_abc",
"content": "No hay herramienta llamada 'search_invoices'. Disponibles: query_billing, get_customer.",
"is_error": true
}
El modelo lee ese error en el turno siguiente y corrige. He visto a Opus 4.8 recuperarse de un nombre de herramienta alucinado en un solo turno de esta manera — ve el error, mira la lista disponible, y re-emite la llamada correcta. No descartes silenciosamente la llamada fallida. No reintentes el mismo prompt con la esperanza de que funcione. Devuelve el error.
Corrección tres: schemas estrictos para el problema de argumentos
La alucinación de nombre y la alucinación de argumentos son bugs diferentes. Para argumentos — tipos incorrectos, campos requeridos faltantes, campos extra — usa el uso estricto de herramientas. Establece strict: true en la definición de la herramienta misma (no en tool_choice, eso no hace nada), con additionalProperties: false y una lista required. Ahora la API garantiza que el input se valida exactamente contra tu schema. El modelo literalmente no puede emitir una forma de argumento que no encaje.
Una advertencia: el modo estricto no es compatible con todo. No funcionará con llamadas programáticas a herramientas, tool_choice forzado, o herramientas MCP. Para esos tienes que validar tú mismo.
Corrección cuatro: acorta el contexto del que depende la llamada
¿Recuerdas el desencadenante de largo-y-ruidoso? En un bucle agéntico largo, los resultados antiguos de herramientas se acumulan. Para el turno 40 el modelo está condicionando sobre 39 turnos de output mayoritariamente irrelevante, y la señal para "qué herramienta, qué argumentos" queda ahogada. Aquí es donde la edición de contexto gana su lugar — borrando los resultados de tool_use obsoletos para que la siguiente predicción del modelo esté basada en lo que es realmente relevante. Es una función beta (clear_tool_uses_20250919), y no es lo mismo que la compactación, que resume en vez de borrar. Para la precisión de llamadas a herramientas específicamente, borrar gana a resumir, porque un resumen de output antiguo de herramientas sigue empujando la distribución.
La que sorprende a la gente
Cuando el modelo narra una llamada a herramienta en vez de emitirla — "Ahora voy a buscar eso en la base de datos" sin ningún bloque tool_use real — eso no es realmente una alucinación. Eso es el modelo terminando su turno antes de tiempo. En ejecuciones largas de Claude ocasionalmente escribe una declaración de intención sin la llamada. La corrección es un nudge en el system prompt: dile que antes de terminar un turno, si su último párrafo es una promesa sobre trabajo que no ha hecho, debería hacer ese trabajo ahora con una llamada a herramienta. Un simple "continúa" lo recupera interactivamente. Para pipelines autónomos, hornea la instrucción.
Lo que quiero que te lleves es esto: ninguna de estas correcciones es magia. Todas hacen lo mismo — doblar la distribución de probabilidad hacia la llamada que quieres, o capturar la llamada que no quieres. El modelo no está mal funcionando cuando inventa search_invoices. Está haciendo exactamente lo que fue construido para hacer, con un contexto que apuntaba en la dirección equivocada. Apúntalo mejor.
