Construye tu propio agente con la API de Anthropic: tools, flujos multi-step y manejo de errores
Tutorial avanzado: aprende a usar la API de Anthropic para construir agentes que usan herramientas, toman decisiones y completan tareas de múltiples pasos de forma autónoma. El nivel experto de trabajar con IA.
Equipo Claude IA Code Español
20 de agosto de 2026
¿Qué es un agente de IA?
Un agente de IA es un sistema donde el modelo de lenguaje puede decidir qué acciones tomar, ejecutarlas y usar los resultados para decidir qué hacer a continuación. Claude Code en sí mismo es un agente: decide qué archivos leer, qué comandos ejecutar y cómo interpretar los resultados.
En este tutorial vas a construir tu propio agente usando la API de Anthropic directamente, con TypeScript y Node.js.
Prerrequisitos
- Conocimiento sólido de TypeScript y async/await
- Node.js 20+ instalado
- API key de Anthropic (console.anthropic.com)
- Haber completado los tutoriales anteriores o tener experiencia con Claude Code
Qué vamos a construir
Un agente que analiza repositorios de GitHub: dado el nombre de un repo, puede leer el README, listar los archivos principales, analizar el package.json y generar un resumen técnico detallado. Herramientas disponibles: buscar en GitHub API, leer archivos y generar informes.
Paso 1 — Setup del proyecto
mkdir github-analyzer-agent
cd github-analyzer-agent
npm init -y
npm install @anthropic-ai/sdk typescript tsx dotenv
npx tsc --init
Crea el archivo .env:
ANTHROPIC_API_KEY=tu_api_key_aqui
Paso 2 — Definir las herramientas del agente
Las "tools" son funciones que el agente puede decidir llamar. Tienes que definirlas con su schema JSON para que Claude las entienda:
// src/tools.ts
import Anthropic from "@anthropic-ai/sdk";
export const tools: Anthropic.Tool[] = [
{
name: "get_repo_info",
description: "Obtiene información básica de un repositorio de GitHub: descripción, stars, lenguaje principal y topics.",
input_schema: {
type: "object" as const,
properties: {
owner: { type: "string", description: "Propietario del repositorio" },
repo: { type: "string", description: "Nombre del repositorio" },
},
required: ["owner", "repo"],
},
},
{
name: "get_file_content",
description: "Obtiene el contenido de un archivo específico del repositorio.",
input_schema: {
type: "object" as const,
properties: {
owner: { type: "string" },
repo: { type: "string" },
path: { type: "string", description: "Ruta del archivo, ej: README.md" },
},
required: ["owner", "repo", "path"],
},
},
{
name: "list_directory",
description: "Lista los archivos y carpetas en una ruta del repositorio.",
input_schema: {
type: "object" as const,
properties: {
owner: { type: "string" },
repo: { type: "string" },
path: { type: "string", description: "Ruta del directorio, usa '' para la raíz" },
},
required: ["owner", "repo", "path"],
},
},
];
Paso 3 — Implementar las funciones reales
// src/github.ts
const GITHUB_API = "https://api.github.com";
export async function getRepoInfo(owner: string, repo: string) {
const res = await fetch(`${GITHUB_API}/repos/${owner}/${repo}`);
if (!res.ok) throw new Error(`Repo no encontrado: ${owner}/${repo}`);
const data = await res.json();
return {
description: data.description,
stars: data.stargazers_count,
language: data.language,
topics: data.topics,
created_at: data.created_at,
};
}
export async function getFileContent(owner: string, repo: string, path: string) {
const res = await fetch(`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`);
if (!res.ok) return null;
const data = await res.json();
if (data.encoding === "base64") {
return Buffer.from(data.content, "base64").toString("utf-8");
}
return null;
}
export async function listDirectory(owner: string, repo: string, path: string) {
const res = await fetch(`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`);
if (!res.ok) return [];
const data = await res.json();
return Array.isArray(data)
? data.map((f: { name: string; type: string }) => ({ name: f.name, type: f.type }))
: [];
}
Paso 4 — El loop del agente
Este es el núcleo del agente: el ciclo donde Claude decide qué hacer, ejecuta la herramienta y usa el resultado para decidir qué hacer a continuación.
// src/agent.ts
import Anthropic from "@anthropic-ai/sdk";
import { tools } from "./tools";
import { getRepoInfo, getFileContent, listDirectory } from "./github";
const client = new Anthropic();
async function executeTool(name: string, input: Record<string, string>) {
switch (name) {
case "get_repo_info":
return getRepoInfo(input.owner, input.repo);
case "get_file_content":
return getFileContent(input.owner, input.repo, input.path);
case "list_directory":
return listDirectory(input.owner, input.repo, input.path);
default:
throw new Error(`Herramienta desconocida: ${name}`);
}
}
export async function analyzeRepo(repoPath: string) {
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content: `Analiza el repositorio de GitHub: ${repoPath}
Explora su estructura, lee el README, analiza el package.json o archivos de configuración
principales, y genera un informe técnico completo que incluya:
- Qué hace el proyecto y para quién es
- Stack tecnológico con versiones
- Estructura del proyecto
- Cómo ejecutarlo localmente
- Puntos fuertes y áreas de mejora técnica`,
},
];
console.log(`\n🔍 Analizando ${repoPath}...\n`);
// Loop del agente
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-5",
max_tokens: 4096,
tools,
messages,
});
// Añadir respuesta del asistente al historial
messages.push({ role: "assistant", content: response.content });
// Si el agente terminó, devolver el resultado
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find((b) => b.type === "text");
return textBlock ? textBlock.text : "Sin respuesta";
}
// Si el agente quiere usar herramientas, ejecutarlas
if (response.stop_reason === "tool_use") {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type !== "tool_use") continue;
console.log(` 🔧 Usando herramienta: ${block.name}`);
try {
const result = await executeTool(
block.name,
block.input as Record<string, string>
);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
} catch (error) {
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: `Error: ${error instanceof Error ? error.message : "Error desconocido"}`,
is_error: true,
});
}
}
// Devolver resultados al agente para que continúe
messages.push({ role: "user", content: toolResults });
}
}
}
Paso 5 — Punto de entrada
// src/index.ts
import "dotenv/config";
import { analyzeRepo } from "./agent";
const repo = process.argv[2] || "anthropics/anthropic-sdk-python";
analyzeRepo(repo)
.then((result) => {
console.log("\n📋 INFORME TÉCNICO\n");
console.log(result);
})
.catch(console.error);
Ejecuta el agente:
npx tsx src/index.ts vercel/next.js
Cómo extender este agente
Con esta base puedes añadir herramientas para: buscar en npm, consultar documentación, ejecutar código en un sandbox, guardar resultados en base de datos, enviar informes por email, o cualquier cosa que puedas implementar como función de TypeScript.
La arquitectura es siempre la misma: define tools, implementa las funciones reales, y el loop del agente se encarga del resto.
Para ir más lejos
La documentación oficial de tool use de Anthropic tiene ejemplos de agentes más complejos con memoria persistente, sub-agentes y flujos paralelos. Este tutorial te da la base para entenderlos y extenderlos.
