Cómo Construir un Agente de IA con Python: Guía Práctica 2026 — Cesar Ayala
← Todos los artículos

Cómo Construir un Agente de IA con Python: Guía Práctica 2026

Un agente de IA en Python es un LLM en un loop que llama herramientas y devuelve los resultados hasta terminar la tarea. Lo armas en ~40 líneas con el SDK del proveedor: defines herramientas como esquemas JSON, mandas el historial, ejecutas la herramienta pedida, agregas el resultado y repites con un tope de pasos. Usa un framework solo cuando topes con pared.

¿Qué es realmente un agente de IA (y qué no lo es)?

Antes de escribir una línea de Python, hay que matar el misterio. Un agente de IA es un LLM en un loop que decide qué herramientas llamar, observa los resultados y sigue hasta que termina la tarea o se rinde. Eso es todo. No hay magia dentro.

La línea que carga todo el peso —y que casi todos los artículos borran— es la distinción entre workflow y agente, en el sentido que le da Anthropic en su guía “Building Effective Agents”: un workflow tiene un flujo de control definido en código que orquestas (haz A, luego B, luego C); un agente deja que el modelo decida su propia trayectoria y cuántos pasos toma. Si puedes dibujar el diagrama de flujo completo de antemano, eso es un workflow. Si no puedes predecir la ruta, ahí sí necesitas un agente.

Aquí está la parte incómoda: casi todos los “agentes” de los que la gente presume en producción son en realidad workflows. Y eso es una virtud, no un defecto. Lo escribí más a fondo en mi guía de qué es un agente de IA, que es la lectura base si todavía estás peleándote con la definición.

Quítale el envoltorio y un agente tiene cuatro piezas:

  • Modelo + objetivo: el LLM con un system prompt que define la meta.
  • Herramientas: una lista de funciones que el modelo puede pedir que se ejecuten.
  • El loop: el código que ejecuta la herramienta y le devuelve el resultado al modelo.
  • Condición de paro: cuándo deja de iterar (terminó, o topó con un tope).

Mi versión honesta después de construir varios de estos: la mayoría de los agentes que yo entrego en producción son un modelo, tres herramientas y un loop con un tope. No un enjambre de bots. No un grafo de veinte nodos. Un loop con un tope.

¿Realmente necesito un framework para construirlo?

No. El default honesto en 2026 es sin framework.

Cada proveedor grande —Anthropic, OpenAI— lleva años con tool calling nativo en la API cruda. Un agente que funciona son unas 40 líneas de Python: mandas los mensajes más las herramientas, si el modelo pide una herramienta la ejecutas, le devuelves el resultado y repites. Ya está.

Los frameworks en su mayoría envuelven ese mismo loop. LangGraph, CrewAI, los handoffs del SDK de OpenAI: todo es estructura encima del loop que vas a escribir en un momento. Mi opinión, después de reescribir la misma app de varias formas: escribe el loop crudo de ~40 líneas una vez para entender qué te esconde cada framework. Porque los agentes se rompen dentro del loop —en el tool calling, en el historial, en la condición de paro— y si no entiendes el loop, no vas a poder depurar nada.

El 80% de los agentes del mundo real nunca necesitan más que el loop crudo. Y lo mejor: ese entendimiento se transfiere a cualquier framework, porque todos hacen lo mismo por debajo. Más adelante nombro frameworks con una regla de decisión, pero vas a tener un agente funcionando antes de que mencione el primero.

¿Cómo funciona realmente el loop del agente en Python?

Aquí está el corazón. Este es el agente completo con el SDK de Anthropic. Léelo de arriba a abajo —no es largo, y cada línea importa.

"""Agente mínimo en Python: un loop manual de tool calling con el SDK de Anthropic.
~40 líneas, correcto y legible. ESTO es el agente: un loop alrededor del modelo
que llama herramientas y devuelve los resultados hasta terminar.

Setup: pip install anthropic ; export ANTHROPIC_API_KEY=...
"""
from anthropic import Anthropic

client = Anthropic()  # lee ANTHROPIC_API_KEY del entorno

# 1) Define la herramienta: nombre + descripción (di CUÁNDO usarla) + JSON Schema.
TOOLS = [{
    "name": "get_weather",
    "description": "Obtiene el clima actual de una ciudad. Llama esta herramienta "
                   "cuando el usuario pregunte por el clima o la temperatura actual.",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string", "description": "Nombre de la ciudad"}},
        "required": ["city"],
    },
}]

# 2) Tus implementaciones: el harness, NO el modelo, ejecuta esto.
def run_tool(name: str, tool_input: dict) -> str:
    if name == "get_weather":
        # El código real pegaría a una API de clima aquí.
        return f"18 grados y despejado en {tool_input['city']}."
    return f"Herramienta desconocida: {name}"

# 3) El loop: objetivo -> (pensar) -> tool_use -> observar -> repetir -> end_turn
def run_agent(goal: str, max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": goal}]
    for _ in range(max_steps):  # SIEMPRE acota el loop, nunca while True
        resp = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=4096,
            thinking={"type": "adaptive"},  # razonamiento nativo entre llamadas = ReAct
                                            # OJO: en Opus 4.7/4.8 NO viene encendido por
                                            # default; si omites este campo, corre SIN thinking.
            tools=TOOLS,
            messages=messages,
        )

        if resp.stop_reason == "end_turn":
            return "".join(b.text for b in resp.content if b.type == "text")

        if resp.stop_reason == "tool_use":
            # Agrega TODO el turno del asistente (conserva los bloques tool_use).
            messages.append({"role": "assistant", "content": resp.content})
            # El modelo puede pedir varias herramientas a la vez: ejecútalas todas.
            results = []
            for block in resp.content:
                if block.type == "tool_use":
                    out = run_tool(block.name, block.input)  # input ya viene parseado
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,   # debe coincidir con la petición
                        "content": out,
                    })
            messages.append({"role": "user", "content": results})
            continue  # observar -> repetir

    return "Topé con el límite de pasos sin terminar."

if __name__ == "__main__":
    print(run_agent("¿Qué clima hace en Puebla ahorita?"))

Ese es el patrón entero. Ahora, las tres trampas de correctitud donde la gente se tropieza:

Las tres trampas

  1. Agrega el content completo del asistente, no solo el texto. Si guardas únicamente el texto y pierdes los bloques tool_use, el siguiente turno se rompe porque el modelo ya no encuentra a qué petición corresponde el resultado.
  2. Cada tool_result tiene que cargar el tool_use_id exacto. Es el pegamento que une la petición con su resultado. Si no coincide, la API lo rechaza o el modelo se confunde.
  3. El modelo puede pedir varias herramientas en un solo turno. Ejecútalas todas y devuelve todos los resultados en un solo mensaje de usuario, no uno por uno.

Y la regla de oro que casi todo principiante rompe: la API es stateless. No hay sesión que recuerde nada del lado del servidor. Tú reenvías el historial completo y creciente en cada llamada. Ese es el bug número uno de quien empieza, y también el que dispara el costo, porque cada iteración manda de nuevo todo el transcript.

Un detalle que el ejemplo deja claro pero conviene subrayar: el razonamiento nativo del modelo (adaptive thinking) no viene gratis en Opus 4.7/4.8. Si omites el campo thinking, el modelo corre sin razonamiento intermedio; hay que ponerlo explícito como en el código de arriba. No asumas que el “pensar entre pasos” está encendido por default.

Una nota de linaje para quien lo busca: este patrón es ReAct (Reason + Act + Observe), del paper de 2022. Pero en 2026 ya no escribes a mano el “Thought:/Action:/Observation:” ni lo parseas con regex. El tool calling nativo vuelve la acción y la observación estructuradas (bloques tool_use/tool_result tipados), y el adaptive thinking convierte el razonamiento en una función de primera clase del modelo. Eso es ReAct, ya productizado. Menciónalo por contexto, no lo reimplementes.

Último apunte: los SDKs traen un tool-runner que corre el loop por ti automáticamente —en el SDK de Python es una característica beta (decorador @beta_tool sobre client.beta.messages), útil pero todavía no GA. Úsalo cuando quieras velocidad. Usa el loop manual cuando necesites human-in-the-loop, logging propio o ejecución condicional —que es justo lo que vas a necesitar en producción, y otra razón para escribir el loop manual primero.

El loop del agente, de punta a punta

ObjetivoEl system prompt + lo que pides
El modelo piensaRazona y decide si llama una herramienta
tool_useEmite la petición con nombre + input
Ejecutas la herramientaTu harness corre el código real
tool_resultDevuelves el resultado con el tool_use_id
Repite ↻ / end_turnHasta terminar o topar con el tope de pasos
Cada iteración reenvía el historial completo. Ese es el motor — y el costo.

¿Cómo diseño herramientas que mi agente no use mal?

El tool calling es todo el juego, y el diseño de las herramientas es donde los agentes ganan o fracasan. El error específico de agentes más común no es que el modelo piense mal: es que llama la herramienta equivocada o le mete un argumento mal formado, y ese error en el paso 2 corrompe silenciosamente todo lo que viene después.

Reglas concretas, no obvias, de construir estos:

  • Escribe descripciones que digan CUÁNDO llamar la herramienta, no solo qué hace. “Llama esto cuando el usuario pregunte por precios actuales o eventos recientes” sube de forma medible la tasa de llamadas correctas. Los modelos de 2026 son más conservadores para usar herramientas, así que la condición de disparo en la descripción importa más que nunca.
  • Mantén el set de herramientas chico y enfocado. Demasiadas herramientas confunden al modelo. Empieza amplio (una herramienta de bash o de código), y promueve una acción a herramienta dedicada y tipada exactamente cuando necesites restringirla, auditarla o renderizarle UI.
  • Valida cada INPUT de herramienta contra un esquema con Pydantic. Un argumento alucinado falla limpio y se devuelve como corrección, en vez de corromper en silencio los pasos siguientes.

Aquí está la pieza que más se ignora: el harness, no el modelo, es dueño del borde de seguridad. El modelo solo emite la petición de herramienta; tu código decide si de verdad la ejecuta. Las acciones irreversibles o destructivas —send_email, un pago, un delete— van detrás de una confirmación humana en un loop manual, nunca en el auto-runner. Y tool_choice (auto/any/tool/none) te deja forzar o prohibir el uso de herramientas cuando lo necesites. Esto se ve así:

for block in resp.content:
    if block.type == "tool_use":
        if block.name in IRREVERSIBLE:        # send_email, pago, delete...
            if not human_approves(block.input):  # decisión fuera del modelo
                # Un rechazo humano NO es un error de ejecución: no marques is_error.
                out = {"type": "tool_result", "tool_use_id": block.id,
                       "content": "Acción rechazada por el usuario."}
            else:
                out = {"type": "tool_result", "tool_use_id": block.id,
                       "content": run_tool(block.name, block.input)}
        else:
            try:
                out = {"type": "tool_result", "tool_use_id": block.id,
                       "content": run_tool(block.name, block.input)}
            except Exception as e:
                # Un fallo REAL de la herramienta sí va marcado: el modelo lo ve y se autocorrige.
                out = {"type": "tool_result", "tool_use_id": block.id,
                       "content": f"Error: {e}", "is_error": True}

Nota la diferencia que casi nadie marca: un rechazo humano y un fallo de ejecución son cosas distintas. El rechazo se devuelve como texto normal; el fallo real de la herramienta se devuelve con is_error: true en el tool_result, para que el modelo sepa que algo tronó y se autocorrija en el siguiente turno en vez de tratar el error como un resultado válido.

¿Qué framework de agentes en Python debería elegir?

No te voy a dar un listicle neutral. Te doy una regla de decisión por framework, porque en 2026 el consenso es honestamente “depende del caso” y el valor está en la regla, no en un ganador:

  • Loop crudo con el SDK — el default, y para aprender. El 80% de los casos vive aquí.
  • OpenAI Agents SDK — cuando quieres algo de estructura ligera: handoffs entre agentes especialistas, guardrails, sessions. Es model-agnostic vía LiteLLM a pesar del nombre, así que no te casa con modelos de OpenAI.
  • Pydantic AI — outputs estructurados y tipados, UsageLimits integrado (topes de tokens y de llamadas a herramientas en la config), y backends de FastAPI limpios. Mi favorito para un agente único bien tipado.
  • LangGraph — workflows complejos, cíclicos o reanudables, con durable checkpointing y human-in-the-loop. Si tu agente se cae a medio camino y necesita retomar desde el último checkpoint, es esto. Es el más verboso y con la curva más empinada: úsalo solo cuando el flujo de control es el problema difícil.
  • CrewAI — prototipos rápidos multi-agente basados en roles (planeador/investigador/escritor). Rápido para demos, pero la metáfora de “roles” te tienta a armar 5 agentes donde 1 loop con 3 herramientas gana.
  • smolagents — chiquito, hackeable, con el paradigma de code-as-action (el agente escribe Python en vez de JSON). Necesita un sandbox (E2B, Docker, Modal) sí o sí: nunca corras código generado por el modelo en tu host.
  • LlamaIndex — cuando el problema difícil de verdad es la recuperación sobre tus documentos (RAG), no la orquestación.
  • Claude Agent SDK — un agente autónomo de coding/computer-use con pilas incluidas (herramientas de archivos, bash, web). Ojo: este paquete (claude-agent-sdk) NO es el mismo que el SDK crudo anthropic. Con el crudo tú escribes el loop; el Agent SDK es el harness de Claude Code con todo armado. Son paquetes y problemas distintos —no los confundas.

El contraste de líneas que viví de primera mano (aproximado): la misma app de chat de referencia me salió en ~160 líneas en Pydantic AI, ~280 en LangGraph y ~420 en CrewAI. La verbosidad es real; la reescribí de varias formas y se nota.

Una palabra sobre MCP (Model Context Protocol): es la capa de integración —cómo una herramienta llega a cualquier agente—, ya cross-vendor. Tanto el OpenAI Agents SDK como el Claude Agent SDK son clientes MCP. Pero MCP no es un framework para construir el agente; es cómo le enchufas las herramientas. No los mezcles.

La trampa de sobre-ingeniería que veo seguido: 5 agentes de CrewAI donde 1 loop con 3 herramientas sería más barato, más rápido y más confiable. Si necesitas que alguien construya esto bien acotado, ese es justo el servicio de automatización y agentes de IA que ofrezco.

Qué framework de agentes elegir en 2026

Quédate con el loop crudo / Pydantic AI / OpenAI Agents SDK cuando

  • Estás aprendiendo o es un agente único
  • Quieres outputs tipados y validados (Pydantic AI)
  • Necesitas handoffs y guardrails ligeros (OpenAI SDK)
  • El loop simple resuelve el 80% del caso

Sube a LangGraph / CrewAI / smolagents cuando

  • El flujo de control es el problema difícil: ciclos, reanudación, checkpoints (LangGraph)
  • Quieres un prototipo multi-agente por roles, rápido (CrewAI)
  • Quieres code-as-action y algo hackeable + sandbox (smolagents)
  • Ya topaste con pared con el loop crudo — no antes
La regla de decisión, no un ganador. Empieza a la izquierda; sube a la derecha solo cuando topes con pared.

¿Cómo evito que se descontrole y queme dinero?

La parte difícil no es el loop. Es la confiabilidad. Y el primer monstruo es el costo.

¿Por qué los agentes se vuelven caros? Porque cada iteración del loop reenvía el transcript completo y creciente, así que el costo escala de forma super-lineal con los pasos. Una tarea agéntica comúnmente dispara de 5 a 20+ llamadas al LLM y usa, grosso modo, entre 5 y 30 veces los tokens de una sola llamada de chat (cifras típicas, aproximadas). Un agente de coding sin restricciones puede correr varios dólares por tarea. Un caso documentado: un agente llamó una herramienta rota 400 veces en 5 minutos porque nadie le puso un tope.

Precios aproximados a mediados de 2026, por millón de tokens (entrada/salida): Claude Haiku 4.5 ~$1/$5, Sonnet 4.6 ~$3/$15, Opus 4.8 ~$5/$25. Y un dato que cambia el cálculo: Opus 4.8 ofrece una ventana de contexto de 1M de tokens, así que el transcript puede crecer mucho antes de topar el límite —razón de más para vigilar el costo, no para relajarte. Con eso en mente, cuatro palancas:

  1. Prompt caching. Las lecturas de caché cuestan ~0.1x la entrada base. Pon lo estable primero (system prompt fijo, lista de herramientas ordenada) y lo volátil al final. Un datetime.now() o un json.dumps sin sort_keys=True en el prefijo invalida la caché en silencio y pagas precio completo. Verifícalo: si cache_read_input_tokens es 0 entre llamadas con el mismo prefijo, algo lo está rompiendo.
  2. Model routing. Haiku para ruteo y extracción; el modelo frontera solo donde el razonamiento de verdad importa. Pero no rutees a ciegas: un modelo barato que falla y fuerza 3 reintentos sale más caro que hacerlo bien una vez.
  3. Llamadas de herramientas en paralelo o programáticas.
  4. Compaction / context-editing (beta, del lado del servidor) — la herramienta nativa del SDK para exactamente el problema del transcript creciente: comprime o recorta el historial viejo del lado del servidor para que no pagues por reenviarlo entero en cada paso. Es la palanca específica para corridas largas.

Y los límites aburridos, no negociables: un tope duro de iteraciones (nunca while True), un presupuesto de tokens/costo por corrida, timeouts y reintentos acotados por herramienta, y detectar llamadas idénticas repetidas (huele a loop). Falla fuerte al llegar al tope —no en silencio.

Los modos de falla que tienes que nombrar: tool misuse, loops de reintentos, y propagación de errores (una salida mala de herramienta lleva a una respuesta alucinada). La observabilidad es obligatoria —Langfuse, LangSmith, Braintrust, Sentry, Arize, varios de ellos con soporte OpenTelemetry—: no puedes llevar a producción lo que no puedes trazar. Y como el no-determinismo rompe los unit tests, necesitas eval sets, no aserciones estilo función pura.

Economía y confiabilidad de un agente (cifras aproximadas, mediados 2026)

Tokens por tarea~5-30x los de una sola llamada de chat
Llamadas al LLM por tareacomúnmente 5-20+
Haiku 4.5 vs Opus 4.8~$1/$5 vs ~$5/$25 por 1M tokens
Lectura de caché~0.1x el precio de entrada (≈90% menos)
Guardrail #1tope duro de iteraciones — nunca while True
Falla real documentadaagente llamó una herramienta rota 400 veces en 5 min
Por qué los agentes cuestan más que un chat — y la palanca número uno para controlarlo.

¿Cuándo NO debería construir un agente?

Esta es la sección que casi ningún artículo de “cómo construir un agente” se atreve a escribir, y es la que más confianza gana. Antes de construir, pasa la tarea por una compuerta de cuatro preguntas:

  1. Complejidad — ¿es genuinamente multi-paso y difícil de especificar de antemano, o puedes dibujar el diagrama de flujo? Si lo puedes dibujar, es un workflow.
  2. Valor — ¿el resultado justifica el mayor costo, la latencia y el no-determinismo?
  3. Viabilidad — ¿el modelo de verdad es bueno en este tipo de tarea hoy?
  4. Costo del error — ¿se pueden atrapar y revertir los errores (tests, revisión, rollback)?

Si alguna respuesta es “no”, baja un escalón. Casos concretos de no construir un agente:

  • Clasificación, extracción o resumen únicos → una sola llamada al LLM.
  • Un pipeline fijo de resumir → formatear → enviar → un workflow determinista: 3 llamadas en secuencia, sin loop.
  • Lógica determinista (aritmética, lookups de estatus, reglas) → código normal.

Los agentes ganan su costo solo cuando el modelo tiene que decidir qué paso viene después. RAG, extracción y clasificación no son trabajos de agente.

Mi línea sin rodeos: la pregunta casi nunca es “¿podemos construir un agente?” —es “¿es un agente lo más barato y confiable que resuelve esto?”, y casi siempre no lo es. La falla cara es echar mano de la autonomía cuando un script de 50 líneas bastaba. Si estás pesando esto para un producto, es exactamente la lógica de acotar primero que uso en cuánto cuesta construir un MVP de SaaS: la función más barata es la que acordaste no construir.

La compuerta de cuatro preguntas antes de construir un agente

  1. 1. Complejidad¿Multi-paso e impredecible, o puedes dibujar el diagrama? Si lo dibujas → workflow.
  2. 2. Valor¿Justifica el mayor costo, latencia y no-determinismo? Si no → una llamada.
  3. 3. Viabilidad¿El modelo de verdad es bueno en esta tarea hoy? Si no → no lo construyas.
  4. 4. Costo del error¿Se atrapan y revierten los errores? Si no → requiere confirmación humana o usa código.
Si fallas cualquiera, baja a workflow o a una sola llamada al LLM.

Preguntas frecuentes

¿Necesito un framework para construir un agente de IA en Python?

No. Empieza con el loop crudo de ~40 líneas usando el SDK del proveedor con tool calling nativo. El 80% de los agentes reales nunca necesita más. Escribir el loop a mano una vez te deja entender qué esconde cada framework, y ese entendimiento se transfiere a todos.

¿Qué framework es mejor para principiantes?

El loop crudo del SDK para aprender, y luego Pydantic AI para un agente único: es el más conciso, tiene outputs tipados y trae UsageLimits para que el control de presupuesto venga en la config, no pegado con cinta.

¿Cuánto cuesta correr un agente?

Grosso modo, de 5 a 30 veces una sola llamada de chat, porque el transcript se reenvía completo en cada paso. Las palancas: cachea agresivamente el prefijo estable (lecturas a ~0.1x), rutea barato-vs-inteligente (Haiku para extracción, Opus solo donde el razonamiento importa) y usa compaction/context-editing para no pagar por reenviar historial viejo. Pon siempre un tope de pasos.

¿Puedo usar modelos locales o abiertos?

Sí. La mayoría de los frameworks son model-agnostic vía LiteLLM, Ollama o transformers. smolagents y Pydantic AI corren modelos locales sin problema. El loop es idéntico; solo cambia el cliente.

Agente vs chatbot vs workflow vs RAG, ¿cómo decido?

Es una escalera: una sola llamada, luego workflow, luego agente. Sube solo cuando el escalón de abajo se queda corto. RAG es recuperación —traer contexto relevante—, no un agente; puede ser una herramienta dentro de un agente, pero no lo vuelve uno por sí solo.

¿Cómo detengo loops descontrolados y cómo manejo una herramienta que falla?

Tope duro de pasos (nunca while True) más un presupuesto de tokens. Cuando una herramienta falla de verdad, devuelve el error como un tool_result con is_error: true para que el modelo se autocorrija en el siguiente turno —no lo escondas, pero distínguelo de un rechazo humano, que va como texto normal. Y al llegar al tope, falla fuerte: mejor un error claro que quemar tokens en silencio.

Empieza con el loop; el framework se gana después

Recapitulando: el loop es un proyecto de fin de semana; los guardrails son el trabajo. Construye el loop crudo, mira dónde se rompe, y escala solo entonces. La demo te toma una tarde; los topes, la validación, las acciones irreversibles detrás de confirmación humana y la observabilidad te toman el resto del sprint —y eso es el trabajo de verdad.

Si estás acotando si un agente (o nada más un workflow) es lo correcto para tu producto, eso es exactamente el tipo de decisión que ayudo a los fundadores a resolver en Nixbly.