El malentendido que rompe medio agente
Cuando alguien monta su primer agente y ve al modelo “consultar el tiempo” o “buscar en la base de datos”, asume que el LLM ejecuta esas acciones. No lo hace. El modelo no tiene acceso a tu API, a tu base de datos ni a internet. Lo único que produce es texto. Tool calling es la convención que convierte parte de ese texto en una petición estructurada: “quiero llamar a consultar_tiempo con ciudad = "Bilbao"”. Quien ejecuta la función eres tú, en tu código. El modelo solo rellena el formulario.
Esa distinción parece pedante hasta que depuras tu primer agente que “no llama a la herramienta”. Casi siempre el problema no está en el modelo: está en que nadie ejecutó lo que el modelo pidió, o en que el resultado nunca volvió a la conversación. Entender el ciclo completo te ahorra tardes enteras.
Qué es realmente una herramienta
Una herramienta, para el modelo, es una descripción. No es código: es un esquema JSON que le dice tres cosas. Cómo se llama la función, para qué sirve y qué argumentos acepta. El modelo nunca ve tu implementación; ve la ficha.
tiempo_tool = {
"name": "consultar_tiempo",
"description": "Devuelve el tiempo actual de una ciudad. "
"Úsala cuando el usuario pregunte por el clima.",
"input_schema": {
"type": "object",
"properties": {
"ciudad": {
"type": "string",
"description": "Nombre de la ciudad, p. ej. 'Bilbao'.",
},
"unidad": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Unidad de temperatura. Por defecto, celsius.",
},
},
"required": ["ciudad"],
},
}
El campo description no es documentación de cortesía: es el prompt que decide si el modelo usa la herramienta y cuándo. Un description vago (“gestiona el tiempo”) produce llamadas erráticas. Uno concreto (“úsala cuando el usuario pregunte por el clima”) produce llamadas fiables. La mitad de los problemas de tool calling se arreglan escribiendo mejores descripciones, no tocando el código.
El ciclo, paso a paso
Tool calling no es una llamada: son al menos dos viajes al modelo con trabajo tuyo en medio. Este es el ciclo que se repite en cualquier framework, por debajo de la abstracción.
Primero mandas la conversación y la lista de herramientas. El modelo responde, pero en lugar de texto final devuelve una intención: tool_use, con el nombre de la función y los argumentos ya validados contra tu esquema. Ahí para. No sabe el resultado y no puede saberlo.
Segundo, tu código lee esa intención, ejecuta la función de verdad —la que sí habla con la API del tiempo— y captura lo que devuelve. Este paso es tuyo por completo; el modelo está esperando.
Tercero, devuelves el resultado a la conversación como un mensaje de rol tool y vuelves a llamar al modelo. Ahora sí, con el dato en mano, redacta la respuesta para el usuario. O decide que necesita otra herramienta y el ciclo se repite.
mensajes = [{"role": "user", "content": "¿Qué tiempo hace en Bilbao?"}]
respuesta = cliente.messages.create(
model="claude-sonnet-5",
tools=[tiempo_tool],
messages=mensajes,
)
# El modelo NO ejecutó nada: solo pidió la herramienta.
if respuesta.stop_reason == "tool_use":
peticion = next(b for b in respuesta.content if b.type == "tool_use")
# Este paso es TUYO: aquí sí se ejecuta la función real.
resultado = consultar_tiempo(**peticion.input) # -> "18 °C, nublado"
# Devolvemos el resultado y pedimos la respuesta final.
mensajes.append({"role": "assistant", "content": respuesta.content})
mensajes.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": peticion.id,
"content": resultado,
}],
})
final = cliente.messages.create(
model="claude-sonnet-5",
tools=[tiempo_tool],
messages=mensajes,
)
print(final.content[0].text) # "En Bilbao hay 18 °C y está nublado."
Si tu agente “no funciona”, recorre estos tres pasos. En el 90 % de los casos el fallo está en el segundo o el tercero: se ejecutó mal la función, o el tool_result nunca volvió con el tool_use_id correcto.
Por qué no basta con pedir un JSON en el prompt
Antes de que existiera tool calling nativo, todos hacíamos lo mismo: pedir en el prompt “responde con un JSON así” y parsear la salida con un try/except. Funciona en la demo y se rompe en producción. El modelo añade un “¡Claro! Aquí tienes:” antes del JSON, o mete una coma de más, o inventa un campo. Tú acabas escribiendo un parser defensivo que es más frágil que el propio problema.
Tool calling mueve esa carga al proveedor. El modelo se entrena para emitir argumentos que validan contra tu esquema, y muchos runtimes garantizan que la salida cumple el JSON Schema. Dejas de parsear prosa y empiezas a recibir estructura.
| Prompt + parsear | Tool calling nativo | |
|---|---|---|
| Formato de salida | Texto que esperas que sea JSON | Estructura validada contra tu esquema |
| Fiabilidad | Se rompe con prosa extra o campos inventados | El runtime garantiza la forma |
| Código de pegamento | Parser defensivo a mano | El SDK te da el objeto ya tipado |
| Varias herramientas | Lógica manual para elegir cuál | El modelo elige y lo dice explícito |
| Cuándo usarlo | Modelos sin soporte de tools | Cualquier modelo moderno |
La regla es sencilla: si tu modelo soporta tool calling, no parsees JSON del texto. Estás reintroduciendo a mano un problema que el proveedor ya resolvió.
Los fallos que verás de verdad
El modelo alucina argumentos que no le diste. Si tu esquema no marca ciudad como required, tarde o temprano llamará a la herramienta sin ciudad. El esquema no es decoración: es tu primera línea de validación.
El modelo llama a la herramienta cuando no debía, o no la llama cuando debía. Casi siempre es culpa del description. Antes de tocar temperatura o añadir ejemplos, reescribe la descripción para que diga exactamente cuándo aplica.
El modelo encadena diez llamadas para una tarea de dos. Con muchas herramientas disponibles, algunos modelos se vuelven ansiosos. Limita el número de herramientas por petición, pon un tope de iteraciones en tu bucle y observa las trazas: un agente sin límite de pasos es una factura sin límite.
Y el clásico silencioso: ejecutas la función pero olvidas devolver el tool_result. El modelo se queda esperando un dato que nunca llega y responde con una vaguedad. Sin el tercer paso del ciclo, no hay respuesta.
Por dónde empezar
Coge una única función que ya tengas en tu código —una que consulte algo o haga un cálculo— y escríbele un esquema con un description honesto y los required bien puestos. Monta el ciclo de tres pasos a mano una vez, sin framework, para ver los dos viajes al modelo y el trabajo que va en medio. Solo cuando eso funcione y lo entiendas, pasa a un framework de agentes: entonces la abstracción te ahorra tiempo en lugar de esconderte dónde falla.
Tool calling no le da manos al modelo. Le da la capacidad de pedírtelas con precisión. Las manos, y la responsabilidad de lo que hagan, siguen siendo tuyas.