Fundamentos de un Agente y Gestión de Estado
Llevo tiempo diseñando sistemas donde la Inteligencia Artificial pasa de ser un simple loro estocástico a algo que realmente hace cosas. La diferencia técnica entre mandar una petición a una API y construir un agente no es trivial.
Para dummies: El bucle de decisión
En términos de marketing, te dirán que un agente de IA "razona y toma decisiones autónomas". Si bajamos esto a la realidad de la ingeniería, un agente no es más que un patrón de diseño. Concretamente, un bucle while.
Un script tradicional ejecuta pasos de forma secuencial: lee de base de datos, transforma, escribe. Un agente evalúa el estado actual, decide qué herramienta usar (si necesita alguna), ejecuta la herramienta, observa el resultado y vuelve a evaluar. Sigue atrapado en este ciclo de razonamiento (ReAct: Reason + Act) hasta que determina que tiene la respuesta final o hasta que choca con un límite de iteraciones. No hay magia, hay una máquina de estados determinista gobernando llamadas no deterministas.
Memoria: El problema del contexto
He visto a muchos programadores atascar la ventana de contexto de un LLM a la primera de cambio. Meten todo el historial de conversación, todos los logs, el esquema entero de la base de datos, y luego se sorprenden cuando el modelo alucina o la API les cobra una barbaridad.
Un agente en producción necesita memoria a corto y largo plazo. - Corto plazo: El contexto de la tarea actual. Qué intentamos resolver ahora mismo. - Largo plazo: Conocimiento almacenado en bases de datos vectoriales u otros sistemas persistentes.
La técnica real aquí es el windowing o resumen progresivo. No le pasas al agente los 50 últimos mensajes. Le pasas los últimos 3, y un resumen comprimido de los 47 anteriores.
Ejemplo Práctico: Un agente mínimo con retención controlada
Vamos a ver un esqueleto en Python de cómo estructurar un agente usando un enfoque de grafo (algo similar a lo que hace LangGraph, que me ha salvado la vida en más de un despliegue pesado).
import operator
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
# Definimos el estado global del agente.
# Annotated con operator.add indica que los mensajes se concatenan, no se sobrescriben.
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
summary: str
def agent_node(state: AgentState):
"""
El nodo principal de decisión.
Aquí llamaríamos al LLM pasándole el resumen y los mensajes recientes.
"""
# Filtramos para quedarnos solo con los últimos 3 mensajes
recent_messages = state["messages"][-3:]
context = f"Resumen previo: {state.get('summary', 'Sin contexto previo.')}"
# Simulación de la respuesta del modelo
response = AIMessage(content=f"Evaluando con contexto: {context}. Acción decidida.")
return {"messages": [response]}
def memory_manager_node(state: AgentState):
"""
Este nodo se encarga de evitar que el contexto explote.
Si hay más de 5 mensajes, comprime los antiguos en un resumen.
"""
messages = state["messages"]
if len(messages) > 5:
# Aquí llamaríamos a un modelo ligero para resumir.
new_summary = "El usuario pidió una tarea y el agente empezó a investigar."
# Nos quedamos con el resumen y purgamos los mensajes viejos
# Devolviendo una estructura que nuestra máquina de estado entienda como un "reinicio" de la lista
return {"summary": new_summary, "messages": messages[-2:]}
return state
# El flujo real (simplificado) sería:
# 1. agent_node()
# 2. ejecuta_herramienta() si aplica
# 3. memory_manager_node() para mantener limpio el contexto
# 4. Repetir hasta terminar
Estructurar el estado de forma explícita es lo que te salva de que el agente se pierda en su propio ruido. No dependas de que la librería gestione el contexto por debajo. Hazlo tú. Así es como mantienes el control cuando el sistema empieza a crecer.