Modern Agent Engineering

Cómo crear agentes para aplicaciones web modernas

Una comparación detallada de cuatro enfoques para crear agentes de IA — OpenAI con fetch sin intermediarios, Vercel AI SDK, Claude Agent SDK y Agno con FastAPI — y cuál deberías elegir.

18 min de lectura Actualizado el 19 mar 2026

Todas las aplicaciones web se están volviendo agénticas. Barras de búsqueda que responden preguntas. Dashboards que investigan anomalías por sí solos. Interfaces de soporte que buscan pedidos, consultan políticas y redactan respuestas, todo antes de que intervenga una persona.

El patrón central detrás de todo esto es el mismo: un LLM que puede llamar herramientas en un bucle. Pero el ecosistema de herramientas para crear estos agentes está fragmentado. Puedes trabajar directamente con fetch, usar un SDK de TypeScript que abstrae el bucle, usar Claude Code como motor de agentes integrado o recurrir a un framework de Python que ofrece sesiones, memoria y una API lista para producción desde el inicio.

Exploré cuatro enfoques reales para crear el mismo tipo de agente. Esto es lo que aprendí.

Qué hace que un agente sea un agente

Antes de entrar al código, aclaremos el modelo mental. Un chatbot recibe un mensaje y devuelve una respuesta. Un agente recibe un objetivo y trabaja para alcanzarlo: decide qué acciones tomar, las ejecuta, observa los resultados y repite el proceso hasta terminar.

El bucle funciona así:

  1. Planear — el modelo lee el contexto actual (objetivo, historial de conversación, herramientas disponibles) y decide qué hacer a continuación
  2. Actuar — llama una herramienta (obtener datos, ejecutar una consulta, enviar un correo)
  3. Observar — el resultado de la herramienta se agrega a la conversación
  4. Repetir — el modelo decide si llama otra herramienta o responde al usuario

La llamada a herramientas es la capa crítica de entrada y salida. Sin ella, el modelo solo está adivinando. Con ella, puede acceder a tus sistemas y realizar trabajo real.

Los cuatro enfoques siguientes implementan este bucle. Se diferencian en cuánto escribes tú y cuánto maneja el framework por ti.

Enfoque 1: OpenAI con fetch simple

Este es el enfoque más fundamental. Llamas a la API de OpenAI Chat Completions usando fetch, defines tus herramientas como esquemas JSON y escribes el bucle del agente por tu cuenta.

Definición de herramientas

Las herramientas son objetos JSON que describen qué funciones puede llamar el modelo. Cada herramienta tiene un nombre, una descripción y un esquema de parámetros:

const tools = [
  {
    type: "function",
    function: {
      name: "get_weather",
      description: "Get the current weather for a city",
      parameters: {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "City name, e.g. 'San Francisco, CA'",
          },
          units: {
            type: "string",
            enum: ["celsius", "fahrenheit"],
            description: "Temperature units",
          },
        },
        required: ["location"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "search_knowledge_base",
      description: "Search internal documentation for relevant articles",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search query" },
        },
        required: ["query"],
      },
    },
  },
];

El bucle del agente

El bucle envía mensajes a la API, comprueba si el modelo quiere llamar herramientas, las ejecuta, agrega los resultados y vuelve a llamar a la API hasta que el modelo termina:

async function runAgent(userMessage: string): Promise<string> {
  const messages: Message[] = [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        model: "gpt-4o",
        messages,
        tools,
        tool_choice: "auto",
      }),
    });

    const data = await response.json();
    const choice = data.choices[0];
    messages.push(choice.message);

    if (choice.finish_reason === "stop") {
      return choice.message.content;
    }

    if (choice.message.tool_calls) {
      for (const toolCall of choice.message.tool_calls) {
        const args = JSON.parse(toolCall.function.arguments);
        const result = await executeTool(toolCall.function.name, args);
        messages.push({
          role: "tool",
          tool_call_id: toolCall.id,
          content: JSON.stringify(result),
        });
      }
    }
  }
}

Ejecución de herramientas

Asocias los nombres de las herramientas con funciones reales. Aquí es donde vive tu lógica de negocio:

async function executeTool(name: string, args: Record<string, unknown>) {
  switch (name) {
    case "get_weather":
      return await fetchWeatherAPI(args.location as string, args.units as string);
    case "search_knowledge_base":
      return await searchDocs(args.query as string);
    default:
      return { error: `Unknown tool: ${name}` };
  }
}

Qué obtienes

Control total. Controlas cada línea del bucle. Decides cómo manejar los errores, cuántas iteraciones permitir, cuándo inyectar mensajes de sistema y cómo gestionar la longitud del contexto. No hay dependencias de frameworks: esto funciona en Node, Deno, Bun, Cloudflare Workers o incluso en el navegador.

Qué no obtienes. Tienes que encargarte de todo: validación de argumentos, streaming, reintentos, conteo de tokens, cambio de proveedor y estado de la conversación. Es la opción que requiere más código, pero también la más transparente.

Enfoque 2: Vercel AI SDK

El Vercel AI SDK abstrae el bucle del agente en una sola llamada a función. Defines herramientas con esquemas Zod y el SDK maneja automáticamente la ejecución de múltiples pasos, la validación de argumentos y el streaming.

Definición de herramientas

Las herramientas usan la función auxiliar tool() con Zod para crear esquemas de parámetros con seguridad de tipos:

import { generateText, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const weatherTool = tool({
  description: "Get the current weather for a city",
  parameters: z.object({
    location: z.string().describe("City name, e.g. 'San Francisco, CA'"),
    units: z.enum(["celsius", "fahrenheit"]).optional(),
  }),
  execute: async ({ location, units }) => {
    return await fetchWeatherAPI(location, units ?? "celsius");
  },
});

const searchTool = tool({
  description: "Search internal documentation for relevant articles",
  parameters: z.object({
    query: z.string().describe("Search query"),
  }),
  execute: async ({ query }) => {
    return await searchDocs(query);
  },
});

El bucle del agente

No hay un bucle manual. El SDK lo ejecuta por ti:

const { text, steps } = await generateText({
  model: openai("gpt-4o"),
  system: "You are a helpful assistant.",
  prompt: userMessage,
  tools: { get_weather: weatherTool, search_knowledge_base: searchTool },
  stopWhen: stepCountIs(10),
});

Eso es todo. El SDK llama al modelo, ejecuta las llamadas a herramientas, devuelve los resultados al modelo y repite el proceso hasta 10 pasos. El arreglo steps contiene todas las llamadas intermedias para depuración.

Streaming

Para interfaces de usuario en tiempo real, cambia generateText por streamText:

import { streamText } from "ai";

const result = streamText({
  model: openai("gpt-4o"),
  system: "You are a helpful assistant.",
  prompt: userMessage,
  tools: { get_weather: weatherTool, search_knowledge_base: searchTool },
  stopWhen: stepCountIs(10),
  onStepFinish: ({ toolCalls, toolResults }) => {
    console.log("Step completed:", { toolCalls, toolResults });
  },
});

return result.toTextStreamResponse();

Cambio de proveedores

Una de las funciones más potentes del SDK: cambiar de proveedor sin modificar las definiciones de herramientas ni la lógica del bucle.

import { anthropic } from "@ai-sdk/anthropic";

const { text } = await generateText({
  model: anthropic("claude-sonnet-4-5"),
  tools: { get_weather: weatherTool, search_knowledge_base: searchTool },
  stopWhen: stepCountIs(10),
  prompt: userMessage,
});

Las mismas herramientas, la misma condición de parada, un modelo diferente. El código de tu agente no cambia.

Qué obtienes

Velocidad de desarrollo. El SDK elimina el código repetitivo del bucle y te ofrece validación con Zod, ejecución de múltiples pasos, streaming, hooks del ciclo de vida (onStepFinish) y abstracción de proveedores desde el inicio. Es el camino más rápido para pasar de cero a un agente funcional en TypeScript.

A qué renuncias. Pierdes algo de control de bajo nivel. Las estrategias de reintento personalizadas, la inyección dinámica de herramientas a mitad del bucle y el encadenamiento no convencional de mensajes requieren sortear las abstracciones del SDK en lugar de trabajar con ellas.

Enfoque 3: Claude Agent SDK

El Claude Agent SDK (@anthropic-ai/claude-agent-sdk) adopta un enfoque fundamentalmente distinto de los dos anteriores. En lugar de que tú definas herramientas y conectes su ejecución, el SDK incluye herramientas integradas — Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch — que Claude ya sabe usar. Le das un prompt y permisos. Él se encarga del resto.

Esto es Claude Code como biblioteca. El mismo motor que impulsa la CLI, empaquetado como un módulo de NPM que puedes integrar en tus propias aplicaciones.

Instalación

npm install @anthropic-ai/claude-agent-sdk

Un agente mínimo

La función principal es query(). Recibe un prompt y devuelve un generador asíncrono que transmite mensajes mientras el agente trabaja:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What files are in this directory?",
  options: { allowedTools: ["Bash", "Glob"] },
})) {
  if ("result" in message) console.log(message.result);
}

Sin esquemas de herramientas. Sin manejadores de ejecución. Sin bucle. Claude lee el directorio con sus herramientas integradas y transmite el resultado.

Control de permisos

Controlas exactamente a qué herramientas puede acceder el agente. Un revisor de código de solo lectura se ve así:

for await (const message of query({
  prompt: "Review this codebase for security issues",
  options: {
    allowedTools: ["Read", "Glob", "Grep"],
  },
})) {
  if ("result" in message) console.log(message.result);
}

Para agentes que modifican archivos, usa permissionMode para exigir aprobación antes de escribir:

for await (const message of query({
  prompt: "Refactor the authentication module to use JWT",
  options: {
    permissionMode: "acceptEdits",
    allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
  },
})) {
  if ("result" in message) console.log(message.result);
}

Subagentes

El SDK permite crear subagentes especializados para subtareas específicas. Cada subagente se ejecuta en su propio contexto, con sus propias instrucciones y permisos de herramientas:

for await (const message of query({
  prompt: "Use the code-reviewer agent to review this codebase",
  options: {
    allowedTools: ["Read", "Glob", "Grep", "Agent"],
    agents: {
      "code-reviewer": {
        description: "Expert code reviewer for quality and security.",
        prompt: "Analyze code quality and suggest improvements.",
        tools: ["Read", "Glob", "Grep"],
      },
    },
  },
})) {
  if ("result" in message) console.log(message.result);
}

Hooks para observabilidad

Ejecuta código personalizado en momentos clave del ciclo de vida del agente: antes de que se ejecute una herramienta, después de que termine y cuando el agente se detenga. Así puedes agregar registros, rastros de auditoría o controles de seguridad:

import { query, HookCallback } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "fs/promises";

const logFileChange: HookCallback = async (input) => {
  const filePath = (input as any).tool_input?.file_path ?? "unknown";
  await appendFile(
    "./audit.log",
    `${new Date().toISOString()}: modified ${filePath}\n`
  );
  return {};
};

for await (const message of query({
  prompt: "Refactor utils.py to improve readability",
  options: {
    permissionMode: "acceptEdits",
    hooks: {
      PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }],
    },
  },
})) {
  if ("result" in message) console.log(message.result);
}

Sesiones

Mantén el contexto a lo largo de múltiples intercambios. El agente recuerda los archivos que leyó, los análisis que realizó y el historial de la conversación:

let sessionId: string | undefined;

for await (const message of query({
  prompt: "Read the authentication module",
  options: { allowedTools: ["Read", "Glob"] },
})) {
  if (message.type === "system" && message.subtype === "init") {
    sessionId = message.session_id;
  }
}

for await (const message of query({
  prompt: "Now find all places that call it",
  options: { resume: sessionId },
})) {
  if ("result" in message) console.log(message.result);
}

Qué obtienes

El agente más potente listo para usar. No defines herramientas, implementas su ejecución ni escribes un bucle. El SDK incluye herramientas probadas para entrada y salida de archivos, comandos de shell, búsqueda web y edición de código. Los subagentes te permiten descomponer tareas complejas. Los hooks te dan control sobre el ciclo de vida. Las sesiones te dan memoria entre turnos. El soporte de MCP te permite conectar sistemas externos.

A qué renuncias. Esta opción solo funciona con Claude: no puedes cambiar de proveedor. Las herramientas integradas están diseñadas para tareas de manipulación de código y archivos, no para lógica de negocio arbitraria. Si tu agente necesita llamar tus propias APIs (consultar una base de datos, enviar una notificación, procesar un pago), tendrás que exponerlas mediante servidores MCP o scripts de Bash en lugar de definirlas como herramientas de primera clase. Y como Claude maneja el bucle de forma autónoma, tienes menos control sobre los pasos individuales que con los enfoques de fetch sin intermediarios o AI SDK.

Enfoque 4: Agno con FastAPI (Python)

Agno es un framework de Python que trata a los agentes como servicios desplegables de primera clase. Defines un agente, le proporcionas herramientas y memoria, y Agno lo encapsula en una aplicación FastAPI con sesiones, streaming y trazas integrados.

Definición de herramientas

Las herramientas son funciones normales de Python:

import httpx

def get_weather(location: str, units: str = "celsius") -> dict:
    """Get the current weather for a city."""
    response = httpx.get(
        "https://api.weather.example/v1/current",
        params={"q": location, "units": units},
    )
    return response.json()

def search_knowledge_base(query: str) -> list[dict]:
    """Search internal documentation for relevant articles."""
    response = httpx.post(
        "https://api.internal/search",
        json={"query": query, "limit": 5},
    )
    return response.json()["results"]

Definición del agente

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.db.sqlite import SqliteDb

agent = Agent(
    name="Web Assistant",
    model=OpenAIChat(id="gpt-4o"),
    tools=[get_weather, search_knowledge_base],
    instructions="You are a helpful assistant. Use tools when needed.",
    db=SqliteDb(db_file="agent_sessions.db"),
    add_history_to_context=True,
    num_history_runs=5,
    markdown=True,
)

Despliegue como aplicación FastAPI

from agno.os import AgentOS

agent_os = AgentOS(
    agents=[agent],
    tracing=True,
)

app = agent_os.get_app()

# Run: fastapi dev main.py
# API available at http://localhost:8000
# Docs at http://localhost:8000/docs

Esto te da una API REST lista para producción con documentación OpenAPI automática, historial de conversación por sesión almacenado en SQLite, respuestas en streaming y trazas integradas.

Cómo agregar herramientas MCP

Agno admite servidores de Model Context Protocol como fuentes de herramientas:

from agno.tools.mcp import MCPTools

agent = Agent(
    name="Docs Assistant",
    model=OpenAIChat(id="gpt-4o"),
    tools=[MCPTools(url="https://docs.example.com/mcp")],
    db=SqliteDb(db_file="sessions.db"),
    add_history_to_context=True,
    num_history_runs=3,
)

Qué obtienes

Todo incluido. Las sesiones, la memoria, las trazas, el streaming y el despliegue de la API están integrados. No tienes que conectar nada de eso por tu cuenta. Si estás creando un backend de Python que ofrece agentes, Agno te lleva a producción más rápido que cualquier otra opción.

A qué renuncias. Quedas ligado a Python y al diseño del bucle de agente de Agno. Si necesitas control detallado sobre cómo itera el modelo, cómo se seleccionan las herramientas o cómo se gestiona la conversación, tendrás que trabajar dentro de las decisiones de diseño de Agno en lugar de las tuyas.

Comparación lado a lado

AspectoOpenAI FetchVercel AI SDKClaude Agent SDKAgno + FastAPI
LenguajeCualquiera (JS, Python, etc.)TypeScript / JavaScriptTypeScript / PythonPython
Bucle del agenteLo escribes túIntegrado (stopWhen)Totalmente autónomoIntegrado
Esquemas de herramientasJSON Schema (manual)Zod (con seguridad de tipos)Integrados (Read, Edit, Bash, etc.)Funciones de Python (docstrings)
Herramientas personalizadasTú las defines y ejecutasTú las defines, el SDK las ejecutaMediante servidores MCP o BashTú las defines, el framework las ejecuta
Validación de argumentosManualCon ZodGestionada por el SDKGestionada por el framework
StreamingAnálisis manual de SSEstreamText() + funciones auxiliaresGenerador asíncronoIntegrado
Cambio de proveedorReescribir llamadas a la APICambiar una líneaSolo ClaudeCambiar la clase del modelo
Sesión / memoriaLo implementas túLo implementas túIntegrado (reanudación de sesión)Integrado (SQLite, etc.)
SubagentesLos implementas túLos implementas túNativos (herramienta Agent)Soporte multiagente
ObservabilidadAgregas tus propias trazasHooks del ciclo de vidaHooks (Pre/PostToolUse)Trazas integradas
DespliegueCualquier entorno de ejecuciónNode / Edge / ServerlessNode / pipelines de CI/CDFastAPI (uvicorn)
DependenciasNingunaai + paquetes de proveedor@anthropic-ai/claude-agent-sdkagno + fastapi
Código repetitivoEl mayorPocoEl menorMuy poco
ControlTotalAltoBajo (autónomo)Medio

El veredicto

No hay una opción que sea la mejor para todos. Pero sí hay una mejor opción para tu situación.

Elige OpenAI Fetch cuando necesites máximo control

Elige este enfoque cuando:

  • Trabajas en un entorno donde las dependencias de SDK son un problema: entornos de ejecución edge, extensiones de navegador, funciones serverless ligeras
  • Necesitas un bucle de agente personalizado con comportamiento no estándar: inyección dinámica de herramientas, estrategias de reintento personalizadas, ramificación condicional entre llamadas al modelo
  • Quieres entender exactamente qué ocurre a nivel de protocolo
  • Estás creando tu propio framework o capa de abstracción

El enfoque de fetch sin intermediarios te obliga a pensar en cada decisión que toma el agente. Eso es una desventaja para casos de uso simples, pero un superpoder para los complejos. Si estás creando algo que no encaja bien en el modelo de funcionamiento de agentes de un framework, empieza aquí.

Elige el Vercel AI SDK para aplicaciones web de TypeScript con herramientas personalizadas

Elige este enfoque cuando:

  • Trabajas con Next.js, React o cualquier stack de TypeScript con Node/Edge
  • Quieres cambiar entre OpenAI, Anthropic, Google y otros proveedores sin reescribir la lógica del agente
  • Valoras la seguridad de tipos y quieres validación con Zod en cada llamada a herramienta
  • Necesitas respuestas en streaming para una interfaz de chat
  • Tu agente llama a tu propia lógica de negocio: consultas a bases de datos, APIs internas, procesamiento de pagos
  • Quieres una abstracción limpia sin perder la capacidad de personalizar mediante hooks

El AI SDK logra un buen equilibrio entre comodidad y control para aplicaciones de TypeScript. La API stopWhen, los hooks del ciclo de vida y la abstracción de proveedores te ahorran cientos de líneas de código repetitivo, mientras mantienen las decisiones importantes en tus manos.

Elige el Claude Agent SDK para agentes centrados en código y automatización

Elige este enfoque cuando:

  • Tu agente trabaja con archivos, código, comandos de shell o contenido web: las herramientas integradas ya cubren estos casos
  • Estás creando automatización de CI/CD, bots de revisión de código, generadores de documentación o herramientas de desarrollo
  • Quieres subagentes que puedan descomponer tareas complejas en subtareas específicas
  • Necesitas sesiones que mantengan el contexto a lo largo de múltiples intercambios
  • Quieres la menor cantidad posible de código repetitivo: un agente funcional en cinco líneas de código

El Claude Agent SDK es la opción más potente cuando tu caso de uso se alinea con sus herramientas integradas. Un agente de revisión de código, un pipeline para corregir errores o un asistente de investigación que lee archivos y busca en la web: todos son triviales de crear porque no tienes que implementar la ejecución de ninguna herramienta. La contrapartida es clara: quedas ligado a Claude y las herramientas de lógica de negocio personalizada requieren servidores MCP en lugar de simples definiciones de funciones.

Elige Agno para backends de Python

Elige este enfoque cuando:

  • Ya trabajas con Python y FastAPI
  • Necesitas gestión de sesiones, memoria de conversación y persistencia integradas
  • Quieres una API de producción con documentación OpenAPI, trazas y streaming desde el primer día
  • Estás integrando servidores MCP o creando sistemas multiagente
  • Quieres el camino más corto desde la definición del agente hasta el servicio desplegado

Agno es el más prescriptivo de los cuatro, y esa es su fortaleza. Si tu caso de uso encaja con su modelo, entregarás más rápido que con cualquier otro enfoque. Si no, tendrás que luchar contra el framework.

Independientemente de lo que elijas

El framework no es la parte difícil. La ingeniería de agentes sí lo es. Estas prácticas importan más que tu elección de SDK:

Trata las herramientas como contratos estrictos de API. Cada herramienta debe tener entradas tipadas, salidas estructuradas y manejo explícito de errores. Una herramienta que devuelve texto no estructurado o ignora errores silenciosamente contaminará el razonamiento del agente.

Establece límites de pasos y presupuestos de costos. Un agente sin límites entrará en un bucle infinito o agotará tu presupuesto de API. Establece un máximo de pasos, monitorea el uso de tokens por ejecución y detén las ejecuciones que superen tu presupuesto. Esto no es negociable en producción.

Exige aprobación para las herramientas destructivas. Cualquier herramienta que envíe un correo, elimine datos, cobre dinero o modifique el estado de producción debe pasar por una aprobación humana. El modelo la llamará con seguridad, deba hacerlo o no.

Agrega observabilidad desde el principio. Registra cada llamada a herramienta, cada respuesta del modelo y cada paso. Sin trazas, no puedes depurar por qué el agente eligió la herramienta incorrecta, hizo una llamada adicional o alucinó un argumento. Este es el error más común en el desarrollo de agentes.

Valida los argumentos de las herramientas antes de ejecutarlas. No confíes en que el modelo produzca JSON válido siempre. Analiza y valida los argumentos antes de ejecutar la herramienta. Devuelve errores que las máquinas puedan interpretar cuando falle la validación, para que el modelo pueda corregirse.

Mantén baja la cantidad de herramientas. Los modelos funcionan mejor con menos herramientas bien descritas. Si tienes más de 15-20 herramientas, usa selección dinámica de herramientas (tool RAG) para presentar solo las relevantes en cada solicitud.

En resumen

El framework que elijas importa menos que cómo lo uses. Un agente bien diseñado con fetch sin intermediarios superará a uno descuidado construido con el SDK más sofisticado. Elige el enfoque que se ajuste a tu stack, aplica la disciplina de ingeniería y entrega el agente.

Empieza simple. Agrega herramientas una a la vez. Registra trazas de todo. Establece límites. Y no dejes que el modelo envíe ese correo sin pedir aprobación primero.