Lección 03
Tool use: dale superpoderes al chatbot
Tu bot deja de inventar y empieza a consultar APIs reales. Implementamos tool calling con Claude y el patrón loop completo.
La diferencia entre un demo y un producto
Sin tool use, el bot inventa. Le preguntas el tiempo en Madrid y te lo dirá (mal).
Con tool use, el bot mira. Le preguntas el tiempo y llama al endpoint de tu API meteorológica, recibe el JSON real y te lo cuenta.
Cómo funciona en Claude
- Defines un array de
toolscon nombre, descripción y esquema JSON de los parámetros. - Se lo pasas a
messages.createjunto con la conversación. - Claude puede decidir llamar a una herramienta. En vez de devolver
text, devuelve untool_usecon los argumentos. - Tú ejecutas la función en tu servidor.
- Envías el resultado como un mensaje con
role: "user"ycontenttipotool_result. - Claude lee el resultado y responde al usuario en lenguaje natural.
Es un loop. Hasta que Claude no devuelva stop_reason: "end_turn", sigues el bucle.
Ejemplo: herramienta de clima
1. Define la herramienta
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Obtiene el clima actual de una ciudad.",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "Nombre de la ciudad, ej. 'Madrid'." },
},
required: ["city"],
},
},
];
2. La implementación real
async function getWeather(city: string) {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(city)}?format=j1`,
);
const data = await res.json();
const cur = data.current_condition[0];
return {
city,
temperature: cur.temp_C + "°C",
description: cur.lang_es?.[0]?.value ?? cur.weatherDesc[0].value,
humidity: cur.humidity + "%",
};
}
3. El loop
async function chat(userMessages: Anthropic.MessageParam[]) {
const messages = [...userMessages];
while (true) {
const res = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools,
messages,
});
if (res.stop_reason === "end_turn") {
const text = res.content.find((c) => c.type === "text");
return text?.type === "text" ? text.text : "";
}
if (res.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: res.content });
const results: Anthropic.ToolResultBlockParam[] = [];
for (const block of res.content) {
if (block.type !== "tool_use") continue;
if (block.name === "get_weather") {
const input = block.input as { city: string };
const data = await getWeather(input.city);
results.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(data),
});
}
}
messages.push({ role: "user", content: results });
continue;
}
return "";
}
}
Probarlo
const reply = await chat([
{ role: "user", content: "¿Qué tiempo hace ahora en Buenos Aires y en Madrid?" },
]);
console.log(reply);
// → "En Buenos Aires hace 14°C con cielo despejado. En Madrid hace 22°C con sol."
Claude llama a get_weather dos veces (una por ciudad) en paralelo, recibe los resultados, y compone la respuesta.
Diseño de herramientas — reglas que duelen aprenderlas tarde
- Descripciones honestas: si tu herramienta falla a veces, dilo. El modelo decidirá si llamarla.
- Nombres explícitos:
search_products, nosearch. - Parámetros enumerados cuando puedas.
sort: "price_asc" | "price_desc"es mejor quesort: string. - Devuelve JSON estructurado, no texto plano. El modelo lo procesa mejor.
- Sin secrets en
descriptionni en el esquema. Va al modelo en cada turno.
Lo que sigue
Tu bot ya recuerda y puede consultar el mundo real. En la siguiente lección lo envolvemos en una UI tipo ChatGPT con streaming de tokens.
Reto Pro
Reto Pro de esta lección
Misma idea, sin pistas, evaluada por tests automáticos.
Hard Mode
Hard Mode
Variante extrema del reto. Tiempo límite y restricciones adicionales.