Tool use en la API de Claude con Python: tu primer agente que usa herramientas
Una cosa es pedirle a Claude que te explique cómo va un pedido y otra muy distinta que lo consulte de verdad en tu sistema y te responda con el dato real. Lo segundo se llama tool use (también lo verás como function calling o «llamadas a función»), y es la base de cualquier agente que hace cosas: buscar en una base de datos, calcular un presupuesto, crear una tarea o llamar a otra API.
En esta guía construyes, en Python y en unas 100 líneas, un pequeño agente de atención al cliente con dos herramientas. Verás cómo describirlas, cómo funciona el bucle tool_use → tool_result, cómo manejar errores y cuánto cuesta.
Respuesta corta. Con tool use le pasas a Claude, en el parámetro tools, una lista de funciones con su nombre, su descripción y un JSON Schema de sus parámetros. Si Claude decide usar una, responde con stop_reason: "tool_use" y un bloque tool_use con los argumentos. Tu código ejecuta la función y le devuelve el resultado en un bloque tool_result. Repites hasta que Claude responde con texto. Eso es un agente. Si no quieres escribir el bucle, el SDK de Python trae un tool runner (en beta) que lo hace por ti.
Qué es tool use y por qué importa
Claude no ejecuta tu código ni entra en tu base de datos. Lo que hace es decidir qué función conviene llamar y con qué argumentos, según la petición del usuario y la descripción que tú le das de cada herramienta. La ejecución siempre la hace tu programa.
La documentación oficial distingue dos tipos:
| Dónde se ejecuta | Ejemplos | |
|---|---|---|
| Herramientas de cliente | En tu aplicación | Las que defines tú (buscar_pedido), y otras con esquema publicado por Anthropic como bash o text_editor |
| Herramientas de servidor | En la infraestructura de Anthropic | web_search, web_fetch, code_execution, tool_search |
En este artículo nos centramos en las primeras, que son las que convierten a Claude en el «cerebro» de tu propia aplicación. Si lo que quieres es conectar Claude con herramientas ya hechas (GitHub, Drive, una base de datos), mira también qué es MCP: es el estándar abierto para empaquetar herramientas y reutilizarlas.
Antes de empezar
Necesitas tres cosas:
- Una API key de la Claude Console. Si aún no la tienes, aquí explicamos cómo obtener tu API key y hacer tu primera llamada.
- Python 3.10 o posterior (el requisito actual del SDK oficial).
- El SDK de Anthropic y la clave en una variable de entorno:
pip install anthropic
export ANTHROPIC_API_KEY="tu-api-key"
En Windows (PowerShell) la variable se define con $env:ANTHROPIC_API_KEY="tu-api-key".
En los ejemplos usamos Claude Sonnet 5.5 (claude-sonnet-5-5), que según Anthropic es «la mejor combinación de velocidad e inteligencia» y cuesta menos que Opus. Todos los modelos actuales admiten tool use, así que puedes cambiarlo por claude-opus-5-5 o claude-haiku-5-5 sin tocar nada más.
Cómo funciona el ciclo, en 4 pasos
- Tú envías la pregunta del usuario y la lista de herramientas (
tools). - Claude responde con
stop_reason: "tool_use"y uno o varios bloquestool_use. Cada bloque trae unid, elnamede la herramienta y uninputcon los argumentos. - Tu código ejecuta cada función y devuelve los resultados en un mensaje con
role: "user"que contiene bloquestool_result, cada uno con eltool_use_idcorrespondiente. - Claude lee los resultados y, o bien pide otra herramienta (vuelves al paso 3), o bien responde con texto. Cuando
stop_reasonya no es"tool_use", has terminado.
Una respuesta del paso 2 tiene este aspecto:
{
"stop_reason": "tool_use",
"content": [
{"type": "text", "text": "Voy a buscar tu pedido."},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "buscar_pedido",
"input": {"id_pedido": "A-1002"}
}
]
}
Y tu respuesta del paso 3, esta:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "{\"estado\": \"preparando\", \"pais\": \"CO\", \"peso_kg\": 3.5}"
}
]
}
Paso 1: describe bien tus herramientas (lo más importante)
Cada herramienta tiene tres campos obligatorios:
name: el nombre. Debe cumplir el patrón^[a-zA-Z0-9_-]{1,128}$, así que nada de espacios ni tildes.description: qué hace, cuándo usarla y qué devuelve.input_schema: un JSON Schema con los parámetros.
Anthropic lo dice sin rodeos: una descripción extremadamente detallada es «con diferencia el factor más importante» para que Claude use bien las herramientas. Recomienda al menos 3 o 4 frases por herramienta: qué hace, cuándo debe usarse (y cuándo no), qué significa cada parámetro y qué limitaciones tiene.
Compara:
- ❌
"Busca un pedido." - ✅
"Busca un pedido de la tienda por su identificador y devuelve su estado (preparando, enviado o entregado), el país de destino (código ISO de 2 letras) y el peso en kg. Úsala siempre que el usuario pregunte por un pedido concreto. No devuelve datos personales del cliente."
Con la segunda, Claude sabe cuándo llamarla, qué esperar y qué no puede pedirle.
Truco: si un parámetro solo admite unos valores concretos, usa enum en el esquema (como hacemos con pais en el ejemplo). Y si necesitas que los argumentos cumplan el esquema al 100 %, añade "strict": true a la herramienta: Anthropic garantiza entonces que el input siempre encaja con tu JSON Schema (strict tool use).
Paso 2: el agente completo en Python
Este es el código entero. Simula una tienda con dos herramientas: buscar_pedido y calcular_envio. Los datos son inventados; en tu caso vendrían de tu base de datos o de tu API.
Guárdalo como agente.py:
import json
import anthropic
client = anthropic.Anthropic() # lee ANTHROPIC_API_KEY del entorno
MODELO = "claude-sonnet-5-5"
# --- 1. Datos de ejemplo (en tu caso, tu base de datos o tu API) ---
PEDIDOS = {
"A-1001": {"estado": "enviado", "pais": "MX", "peso_kg": 1.2},
"A-1002": {"estado": "preparando", "pais": "CO", "peso_kg": 3.5},
}
TARIFAS_EUR_POR_KG = {"ES": 4.0, "MX": 9.0, "CO": 9.5, "AR": 11.0}
# --- 2. Las funciones de Python que Claude podrá pedir ---
def buscar_pedido(id_pedido: str) -> dict:
if id_pedido not in PEDIDOS:
raise ValueError(f"No existe el pedido {id_pedido}. Pide al usuario que revise el número.")
return PEDIDOS[id_pedido]
def calcular_envio(pais: str, peso_kg: float) -> dict:
if pais not in TARIFAS_EUR_POR_KG:
raise ValueError(f"No enviamos a {pais}. Países disponibles: {', '.join(TARIFAS_EUR_POR_KG)}.")
return {"pais": pais, "coste_eur": round(TARIFAS_EUR_POR_KG[pais] * peso_kg, 2)}
FUNCIONES = {"buscar_pedido": buscar_pedido, "calcular_envio": calcular_envio}
# --- 3. La descripción de las herramientas (lo que ve Claude) ---
TOOLS = [
{
"name": "buscar_pedido",
"description": (
"Busca un pedido de la tienda por su identificador y devuelve su estado "
"(preparando, enviado o entregado), el país de destino (código ISO de 2 letras) "
"y el peso en kg. Úsala siempre que el usuario pregunte por un pedido concreto. "
"No devuelve datos personales del cliente."
),
"input_schema": {
"type": "object",
"properties": {
"id_pedido": {"type": "string", "description": "Identificador del pedido, por ejemplo A-1001"}
},
"required": ["id_pedido"],
},
},
{
"name": "calcular_envio",
"description": (
"Calcula el coste de envío en euros para un país y un peso. "
"Úsala cuando el usuario quiera saber cuánto cuesta enviar algo. "
"Solo admite los países ES, MX, CO y AR."
),
"input_schema": {
"type": "object",
"properties": {
"pais": {"type": "string", "enum": ["ES", "MX", "CO", "AR"]},
"peso_kg": {"type": "number", "description": "Peso del paquete en kilogramos"},
},
"required": ["pais", "peso_kg"],
},
},
]
# --- 4. El bucle del agente ---
def preguntar(pregunta: str, max_vueltas: int = 10) -> str:
messages = [{"role": "user", "content": pregunta}]
for _ in range(max_vueltas):
response = client.messages.create(
model=MODELO,
max_tokens=2048,
system="Eres el asistente de atención al cliente de una tienda online. Responde en español.",
tools=TOOLS,
messages=messages,
)
# Guardamos la respuesta completa (incluidos los bloques de thinking) tal cual
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
# Ejecutamos TODAS las herramientas pedidas y devolvemos los resultados juntos
resultados = []
for bloque in response.content:
if bloque.type != "tool_use":
continue
print(f"→ Claude pide {bloque.name}({json.dumps(bloque.input, ensure_ascii=False)})")
try:
salida = FUNCIONES[bloque.name](**bloque.input)
resultados.append({
"type": "tool_result",
"tool_use_id": bloque.id,
"content": json.dumps(salida, ensure_ascii=False),
})
except Exception as e:
resultados.append({
"type": "tool_result",
"tool_use_id": bloque.id,
"content": str(e),
"is_error": True,
})
messages.append({"role": "user", "content": resultados})
return "He llegado al límite de vueltas sin una respuesta final."
if __name__ == "__main__":
print(preguntar("¿Cómo va mi pedido A-1002 y cuánto costaría enviarlo a Argentina en vez de a Colombia?"))
Ejecútalo con python agente.py. Verás algo así (el texto exacto cambia en cada ejecución):
→ Claude pide buscar_pedido({"id_pedido": "A-1002"})
→ Claude pide calcular_envio({"pais": "CO", "peso_kg": 3.5})
→ Claude pide calcular_envio({"pais": "AR", "peso_kg": 3.5})
Tu pedido A-1002 se está preparando. Enviarlo a Colombia cuesta 33,25 € y a Argentina 38,50 €, es decir, 5,25 € más.
Fíjate en que Claude ha encadenado las herramientas por su cuenta: primero ha buscado el pedido para saber el peso y luego ha calculado los dos envíos. Tú no le has dicho en qué orden hacerlo. Eso es lo que convierte un chatbot en un agente.
Paso 3: las 4 reglas que evitan el 90 % de los errores
Estas reglas salen de la documentación oficial y son las que más fallan al empezar.
1. Devuelve el content del asistente tal cual. En el código añadimos response.content completo al historial. Los modelos actuales piensan antes de responder (adaptive thinking), y cuando hay herramientas por medio hay que devolver los bloques de pensamiento completos y sin modificar. Si solo guardas el texto, la siguiente llamada fallará o perderá contexto.
2. Todos los resultados, en un solo mensaje y primero. Claude puede pedir varias herramientas a la vez (llamadas en paralelo, activadas por defecto). Debes ejecutar todas y devolver todos los tool_result en un único mensaje de usuario. Además, los tool_result tienen que ir al principio del content: si pones texto antes, la API devuelve un error 400.
3. Nada entre la petición y el resultado. El mensaje con los tool_result debe ir justo después del mensaje del asistente que pidió las herramientas. Si ves el error «tool_use ids were found without tool_result blocks immediately after», es esto.
4. Si algo falla, díselo a Claude con is_error. No lances la excepción y cortes el programa. Devuelve un tool_result con "is_error": true y un mensaje útil. Anthropic recomienda mensajes que expliquen qué ha pasado y qué probar después (por ejemplo, «No existe el pedido A-9999. Pide al usuario que revise el número»), no un simple «error». Así Claude puede recuperarse o pedirle el dato al usuario.
Seguridad: lo que devuelven tus herramientas (una web, un email, un formulario) puede contener instrucciones maliciosas. Anthropic recomienda dejar ese contenido dentro de los tool_result, nunca copiarlo al system ni a un mensaje de usuario normal.
Atajo: el tool runner del SDK de Python
Si no necesitas controlar cada paso (por ejemplo, para pedir aprobación humana antes de ejecutar algo), el SDK de Python incluye un tool runner, de momento en beta. Defines la función con el decorador @beta_tool, con type hints y un docstring, y el SDK genera el JSON Schema, ejecuta las herramientas, gestiona el historial y repite el bucle por ti:
import json
from anthropic import Anthropic, beta_tool
client = Anthropic()
@beta_tool
def calcular_envio(pais: str, peso_kg: float) -> str:
"""Calcula el coste de envío en euros para un país y un peso.
Args:
pais: Código ISO de 2 letras del país de destino (ES, MX, CO o AR)
peso_kg: Peso del paquete en kilogramos
"""
tarifas = {"ES": 4.0, "MX": 9.0, "CO": 9.5, "AR": 11.0}
if pais not in tarifas:
raise ValueError(f"No enviamos a {pais}.")
return json.dumps({"pais": pais, "coste_eur": round(tarifas[pais] * peso_kg, 2)})
runner = client.beta.messages.tool_runner(
model="claude-sonnet-5-5",
max_tokens=2048,
max_iterations=10,
tools=[calcular_envio],
messages=[{"role": "user", "content": "¿Cuánto cuesta enviar 2 kg a México?"}],
)
final = runner.until_done()
print("".join(b.text for b in final.content if b.type == "text"))
Si una herramienta lanza una excepción, el runner la captura y se la devuelve a Claude como tool_result con is_error: true. Usa max_iterations para poner un límite de vueltas y no llevarte sorpresas en la factura.
¿Cuándo usar cada uno? El runner es ideal para prototipos y agentes sencillos. El bucle manual es mejor cuando quieres registrar cada llamada, pedir confirmación antes de acciones delicadas (borrar, pagar, enviar) o decidir qué herramientas permitir según el usuario.
Controlar cuándo usa Claude las herramientas: tool_choice
Por defecto (tool_choice: {"type": "auto"}), Claude decide en cada turno si usa una herramienta o responde directamente. Puedes orientarlo desde el system prompt: según Anthropic, una frase como «Usa las herramientas para investigar antes de responder» aumenta su uso.
Otras opciones útiles:
{"type": "none"}: le prohíbe usar herramientas en ese turno.{"type": "auto", "disable_parallel_tool_use": true}: como mucho una herramienta por respuesta.{"type": "any"}o{"type": "tool", "name": "..."}: obligan a usar una herramienta. Ojo: a 21 de octubre de 2026, Claude Opus 5.5, Sonnet 5.5 y Fable 5.1 no admiten forzar herramientas y devuelven un error 400. Con esos modelos, Anthropic recomiendaautocombinado con strict tool use, o structured outputs si lo que necesitas es una respuesta con un formato JSON fijo.
¿Cuánto cuesta usar herramientas?
Tool use no tiene una tarifa aparte: pagas los tokens de siempre. Pero las herramientas añaden tokens de entrada:
- La definición de tus
tools(nombres, descripciones y esquemas) se envía en cada llamada. - Los bloques
tool_useytool_resultforman parte del historial. - La API añade un system prompt interno para activar las herramientas: 286 tokens con Claude Sonnet 5.5, Opus 5.5 y Haiku 5.5 (con
tool_choiceenautoonone).
Hagamos una estimación orientativa con Claude Sonnet 5.5 ($2 por millón de tokens de entrada y $10 por millón de salida). Una ejecución del agente de arriba hace unas 3 llamadas. Si cada una envía unos 1.500 tokens y genera unos 500 (contando el razonamiento), el total sale por unos $0,024, unos 2 céntimos de euro. Es una estimación: el coste real te lo da el campo usage de cada respuesta.
Las herramientas de servidor (como web_search) sí pueden tener un coste extra por uso. Para calcular tu gasto con más detalle, y ver cómo abaratarlo con la caché de prompts, tienes la guía de precio de la API de Claude.
Errores frecuentes con tool use (y cómo arreglarlos)
| Síntoma | Causa probable | Solución |
|---|---|---|
| Error 400 «tool_use ids were found without tool_result blocks immediately after» | Falta algún tool_result o hay un mensaje en medio |
Responde a todos los tool_use en el mensaje siguiente |
| Error 400 al enviar los resultados | Texto antes de los tool_result |
Pon los tool_result primero y el texto, si lo hay, después |
Error 400 con tool_choice: any o tool |
El modelo (Opus 5.5, Sonnet 5.5, Fable 5.1) no admite forzar herramientas | Usa auto y orienta con el system prompt, o strict tool use |
| Claude no usa la herramienta | La descripción es pobre o no dice cuándo usarla | Reescríbela con 3 o 4 frases: qué hace, cuándo, qué devuelve |
| Claude se inventa un argumento (por ejemplo, una ciudad) | Faltaba el dato y lo ha supuesto | Indica en la descripción que debe preguntar si falta, o en el system prompt |
| El agente da vueltas sin parar | No hay límite de iteraciones | Pon un máximo (max_vueltas en el bucle manual, max_iterations en el runner) |
Preguntas frecuentes sobre tool use en la API de Claude
¿Qué diferencia hay entre tool use y function calling?
Ninguna. Es el mismo concepto con dos nombres. Anthropic lo llama tool use y explica en su documentación que también se conoce como function calling: Claude decide qué función llamar y con qué argumentos, y tu aplicación la ejecuta.
¿Claude ejecuta mi código?
No, en el caso de las herramientas de cliente. Claude solo devuelve un bloque tool_use con el nombre de la función y los argumentos, y es tu programa el que la ejecuta y le devuelve el resultado. Solo las herramientas de servidor, como web_search o code_execution, se ejecutan en la infraestructura de Anthropic.
¿Cuánto cuesta usar herramientas en la API de Claude?
Se paga por tokens, igual que cualquier otra llamada. Las definiciones de las herramientas y los bloques tool_use y tool_result cuentan como tokens, y la API añade un system prompt interno de 286 tokens con Claude Sonnet 5.5, Opus 5.5 y Haiku 5.5. Algunas herramientas de servidor, como la búsqueda web, tienen un coste adicional por uso.
¿Puede Claude llamar a varias herramientas a la vez?
Sí. Las llamadas en paralelo están activadas por defecto en los modelos actuales. Tienes que ejecutar todas las herramientas pedidas y devolver todos los resultados en un único mensaje de usuario. Si prefieres una herramienta por turno, usa disable_parallel_tool_use: true dentro de tool_choice.
¿Qué relación tiene tool use con MCP?
MCP (Model Context Protocol) es un estándar para empaquetar herramientas en un servidor y reutilizarlas en distintas aplicaciones. Por debajo, Claude sigue usando tool use: MCP solo cambia de dónde salen las herramientas. Lo explicamos en qué es MCP.
Ya tienes tu primer agente. Ahora, llévalo a producción
En la ruta API para desarrolladores (22 lecciones) y la ruta MCP y agentes (16 lecciones) de MasterEnClaude pasas de este ejemplo a agentes reales: streaming, caché, servidores MCP propios y el bucle de agente completo, con un test en cada lección y en español.
La nueva edición, actualizada a Claude 5.5, abre el 17 de noviembre. Mismo precio en todos los países, con el equivalente en tu moneda. Hoy no pagas nada: reserva tu plaza y te avisamos el día de la apertura.
Reservar mi plaza gratis →Prefiero empezar con la guía gratuita