Introducción a Agentes de IA: Del Concepto a la Práctica
¿Qué pasaría si tu LLM pudiera decidir qué hacer en lugar de solo responder? Los grandes modelos de lenguaje son increíbles respondiendo preguntas, pero tienen una limitación fundamental: solo reaccionan a lo que les preguntas. Un agente de IA cambia el juego completamente: razona, decide qué herramientas usar y actúa de forma autónoma hasta resolver el problema.
En este post descubrirás qué son los agentes de IA, cómo funcionan mediante el patrón ReAct, y construirás tus primeros agentes con código funcional. Incluye un chat interactivo para que experimentes directamente. Spoiler: vamos a usar la PokeAPI porque si vas a aprender sobre agentes, mejor que sea con Pokémon 🎮.
¿Qué es un Agente de IA?
La diferencia entre un LLM tradicional y un agente es simple pero poderosa:
LLM Tradicional (pasivo):
Usuario: "¿Cuánto pesa Pikachu?"
LLM: "No tengo esa información en mi training data" ❌
Agente de IA (activo):
Usuario: "¿Cuánto pesa Pikachu?"
Agente:
1. Razona: "Necesito buscar info de Pikachu en una API"
2. Actúa: Llama a get_pokemon_info("pikachu")
3. Recibe: {"weight": 60, "unit": "hectograms"}
4. Responde: "Pikachu pesa 6 kg" ✅
Componentes de un Agente
Todo agente tiene tres piezas fundamentales:
- LLM - El cerebro que razona y toma decisiones
- Herramientas (Tools) - Las manos que ejecutan acciones (APIs, bases de datos, cálculos)
- Loop de Control - El ciclo que coordina razonamiento → acción → razonamiento
El Patrón ReAct: Reasoning + Acting
ReAct es el patrón fundamental de los agentes modernos. Fue introducido en el paper "ReAct: Synergizing Reasoning and Acting in Language Models" y funciona así:
- Thought (Razonamiento): El LLM analiza la situación y decide qué hacer
- Action (Acción): Ejecuta una herramienta específica
- Observation (Observación): Recibe el resultado de la herramienta
- Repeat: Vuelve al paso 1 hasta completar la tarea
Este ciclo permite que el agente:
- Descomponga problemas complejos en pasos
- Se adapte según resultados intermedios
- Maneje errores y reintente con estrategias diferentes
Agente Simple con OpenAI
Vamos a construir un agente básico usando OpenAI function calling. Primero, instala las dependencias:
pip install openai
Definiendo Herramientas
Creamos dos herramientas simples:
import json
from datetime import datetime
def calculate(expression: str) -> float:
"""Calcula expresiones matemáticas simples"""
try:
# Usar eval con precaución (solo en ejemplos educativos)
result = eval(expression)
return float(result)
except Exception as e:
return f"Error: {str(e)}"
def get_current_time() -> str:
"""Retorna la fecha y hora actual en UTC"""
return datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
# Definición de herramientas para OpenAI
tools = [
{
"type": "function",
"function": {
"name": "calculate",
"description": "Calcula expresiones matemáticas",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Expresión matemática a evaluar (ej: '25 * 4 + 10')"
}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Obtiene la fecha y hora actual en UTC",
"parameters": {"type": "object", "properties": {}}
}
}
]
Implementando el Loop del Agente
Ahora el código que orquesta el ciclo ReAct:
from openai import OpenAI
client = OpenAI(api_key="tu-api-key")
def run_agent(user_message: str, max_iterations: int = 10):
"""Ejecuta el agente con un mensaje del usuario"""
messages = [{"role": "user", "content": user_message}]
for iteration in range(max_iterations):
# Paso 1: LLM decide qué hacer (Reasoning)
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
messages.append(assistant_message)
# Si no hay tool calls, el agente terminó
if not assistant_message.tool_calls:
return assistant_message.content
# Paso 2: Ejecutar herramientas (Acting)
for tool_call in assistant_message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Dispatcher de funciones
if function_name == "calculate":
result = calculate(arguments["expression"])
elif function_name == "get_current_time":
result = get_current_time()
else:
result = f"Error: Función {function_name} no encontrada"
# Paso 3: Agregar resultado (Observation)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
return "Max iterations alcanzadas"
# Ejemplo de uso
if __name__ == "__main__":
# Pregunta simple
print(run_agent("¿Cuánto es 25 * 4 + 10?"))
# Output: "El resultado es 110"
# Pregunta con timestamp
print(run_agent("¿Qué hora es y cuántas horas faltan para medianoche UTC?"))
# Output: "Son las 14:30:00 UTC. Faltan 9.5 horas para medianoche."
Agente con API Real: PokeAPI
Ahora escalamos a un ejemplo más interesante usando la PokeAPI. Porque sí, otra vez pip install (en Python siempre hay "solo una dependencia más" 🙃):
pip install requests
Herramientas Pokemon
import requests
def get_pokemon_info(name: str) -> dict:
"""Obtiene información de un Pokemon desde PokeAPI"""
try:
url = f"https://pokeapi.co/api/v2/pokemon/{name.lower()}"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
return {
"name": data["name"].capitalize(),
"weight": data["weight"] / 10, # Convertir hectogramos a kg
"height": data["height"] / 10, # Convertir decímetros a metros
"types": [t["type"]["name"] for t in data["types"]],
"abilities": [a["ability"]["name"] for a in data["abilities"]]
}
except Exception as e:
return {"error": f"No se pudo obtener info de {name}: {str(e)}"}
def compare_pokemon(pokemon1: str, pokemon2: str) -> str:
"""Compara dos Pokemon y retorna análisis"""
info1 = get_pokemon_info(pokemon1)
info2 = get_pokemon_info(pokemon2)
if "error" in info1 or "error" in info2:
return "Error al obtener información de uno o ambos Pokemon"
comparison = f"""Comparación entre {info1['name']} y {info2['name']}:
Peso: {info1['name']} ({info1['weight']} kg) vs {info2['name']} ({info2['weight']} kg)
Altura: {info1['name']} ({info1['height']} m) vs {info2['name']} ({info2['height']} m)
Tipos: {', '.join(info1['types'])} vs {', '.join(info2['types'])}
"""
if info1['weight'] > info2['weight']:
comparison += f"{info1['name']} es más pesado por {info1['weight'] - info2['weight']:.1f} kg"
else:
comparison += f"{info2['name']} es más pesado por {info2['weight'] - info1['weight']:.1f} kg"
return comparison
Agente Pokemon Completo
Actualiza la lista de tools agregando las nuevas funciones:
# Agregar a la lista de tools existente
pokemon_tools = [
{
"type": "function",
"function": {
"name": "get_pokemon_info",
"description": "Obtiene información detallada de un Pokemon",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Nombre del Pokemon (ej: 'pikachu', 'charizard')"
}
},
"required": ["name"]
}
}
},
{
"type": "function",
"function": {
"name": "compare_pokemon",
"description": "Compara dos Pokemon y retorna análisis detallado",
"parameters": {
"type": "object",
"properties": {
"pokemon1": {"type": "string"},
"pokemon2": {"type": "string"}
},
"required": ["pokemon1", "pokemon2"]
}
}
}
]
# Combinar todas las herramientas
all_tools = tools + pokemon_tools
# Actualizar el dispatcher en run_agent()
# ... (mismo código, agregar casos para get_pokemon_info y compare_pokemon)
Ejemplos de uso
# Pregunta básica
print(run_agent("¿Cuánto pesa Pikachu?"))
# Output: "Pikachu pesa 6 kg"
# Pregunta compleja con múltiples tools
print(run_agent("Compara Charizard y Blastoise. ¿Cuál pesa más y por cuánto?"))
# Output: "Charizard (90.5 kg) vs Blastoise (85.5 kg). Charizard es más pesado por 5 kg"
# Combinando tools: Pokemon + calculadora
print(run_agent("¿Cuánto pesan juntos Pikachu y Charizard?"))
# Agente razona:
# 1. get_pokemon_info("pikachu") → 6 kg
# 2. get_pokemon_info("charizard") → 90.5 kg
# 3. calculate("6 + 90.5") → 96.5
# Output: "Juntos pesan 96.5 kg"
Piensa en el agente como un NPC de videojuego que finalmente puede tomar decisiones propias. Ya no es un Magikarp que solo hace Splash (responder lo que sabe) — ahora es tu Pikachu entrenado que decide cuándo usar Thunderbolt y cuándo usar Agility según la situación.
Escalando con LangGraph
El código que escribimos funciona, pero en producción te encontrarás con:
- State management: ¿Cómo persistir conversaciones?
- Error handling: ¿Qué pasa si una API falla?
- Debugging: ¿Cómo inspeccionar qué decidió el agente en cada paso?
- Human-in-the-loop: ¿Cómo pedir aprobación antes de acciones críticas?
Ahí es donde entran frameworks como LangGraph, diseñados específicamente para agentes de producción.
¿Qué es LangGraph?
LangGraph modela agentes como grafos de estado:
- Nodos: Acciones (llamar LLM, ejecutar tool, pedir input humano)
- Aristas: Decisiones condicionales
- Estado: Información que fluye entre nodos
pip install langgraph langchain-openai
Agente Básico con LangGraph
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
# Definir herramientas con decorador
@tool
def calculate(expression: str) -> float:
"""Calcula expresiones matemáticas"""
return eval(expression)
@tool
def get_pokemon_info(name: str) -> dict:
"""Obtiene info de Pokemon desde PokeAPI"""
# ... (mismo código de antes)
pass
# Definir estado del agente
class AgentState(TypedDict):
messages: Annotated[list, "Lista de mensajes"]
# Crear nodos
def call_model(state: AgentState):
"""Nodo que llama al LLM"""
llm = ChatOpenAI(model="gpt-4")
tools = [calculate, get_pokemon_info]
llm_with_tools = llm.bind_tools(tools)
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: AgentState):
"""Decide si continuar o terminar"""
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return END
# Construir grafo
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", ToolNode([calculate, get_pokemon_info]))
workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue)
workflow.add_edge("tools", "agent")
# Compilar
app = workflow.compile()
# Usar
inputs = {"messages": [("user", "¿Cuánto pesa Pikachu en kg?")]}
for output in app.stream(inputs):
print(output)
Beneficios de LangGraph
- Persistence: Guarda y resume conversaciones automáticamente
- Streaming: Muestra progreso en tiempo real
- Debugging: Visualiza el grafo y cada decisión
- Human-in-the-loop: Pausas para aprobación humana
- Error handling: Retries y fallbacks configurables
En producción, no reinventes la rueda. Frameworks como LangGraph han resuelto los problemas que todavía no sabes que vas a tener.
Pruébalo Tú Mismo: Chat Interactivo
¿Quieres experimentar directamente con un agente? En el repositorio incluimos un chat interactivo en terminal que te permite conversar con el agente en tiempo real:
python 04_interactive_chat.py
Características del Chat
- Conversación contextual: El agente recuerda toda la conversación
- Transparencia total: Ves qué herramientas ejecuta en cada momento
- Comandos útiles:
/ayuda- Muestra ejemplos de preguntas/limpiar- Reinicia el historial/salir- Cierra el chat
Ejemplo de Sesión
🤖 CHAT INTERACTIVO CON AGENTE DE IA
======================================================================
Tú: ¿Cuánto pesa Pikachu?
🔧 Ejecutando: get_pokemon_info({'pokemon_name': 'pikachu'})
🤖 Agente: Pikachu pesa 6.0 kilogramos.
Tú: ¿Y Charizard?
🔧 Ejecutando: get_pokemon_info({'pokemon_name': 'charizard'})
🤖 Agente: Charizard pesa 90.5 kilogramos.
Tú: ¿Cuánto pesan juntos?
🔧 Ejecutando: calculate({'expression': '6.0 + 90.5'})
🤖 Agente: Juntos pesan 96.5 kilogramos.
Observa cómo el agente:
- Recuerda el contexto - Sabe que "Y Charizard?" se refiere al peso
- Usa las herramientas correctas - Consulta PokeAPI y hace cálculos
- Encadena razonamiento - Suma los pesos consultados anteriormente sin volver a preguntar
Este ejemplo demuestra el verdadero poder de los agentes: conversaciones naturales con acceso a herramientas externas.
Multi-Agentes: El Siguiente Nivel
Hasta ahora vimos agentes individuales (un LLM con herramientas). Pero ¿qué pasa cuando necesitas especialización?
Sistema Multi-Agente:
- Agente Investigador: Busca información en internet
- Agente Escritor: Redacta el contenido
- Agente Crítico: Revisa y mejora el texto
Cada agente tiene su propio conjunto de herramientas y objetivo. Se comunican entre sí, colaboran y dividen el trabajo.
Ejemplo real: Un sistema que escribe artículos de blog podría tener:
- Researcher agent → Busca fuentes y datos
- Outline agent → Estructura el contenido
- Writer agent → Escribe cada sección
- Editor agent → Revisa y mejora
Esto lo veremos en profundidad en un próximo post sobre Sistemas Multi-Agente con AutoGen y CrewAI.
Conclusión
Los agentes de IA transforman LLMs pasivos en sistemas autónomos que razonan y actúan:
✅ Patrón ReAct es fundamental: Reasoning → Acting → Observation → Repeat ✅ OpenAI function calling te da el control total pero requiere código manual ✅ LangGraph maneja complejidad de producción (estado, debugging, humanos en el loop) ✅ Multi-agentes llevan la especialización al siguiente nivel
Próximos Pasos
- Experimenta: Clona el repositorio de ejemplos y prueba el chat interactivo
- Construye tu propio agente: Añade nuevas herramientas (búsqueda web, base de datos, APIs de tu negocio)
- Profundiza: Explora LangGraph para llevar tus agentes a producción con state management y debugging avanzado
Recursos
Documentación Oficial
Artículos Relacionados
- Construyendo Sistemas RAG con LangChain - Si quieres que tu agente acceda a bases de conocimiento externas
Research Papers
Código de Ejemplo
- Blog Code Examples - GitHub - Todos los ejemplos de este post
¿Tienes preguntas sobre agentes de IA? Contáctame o conectemos en LinkedIn.