Introducción: El Agente Autónomo con Acceso al Mundo Exterior
En el desarrollo de agentes de IA avanzados, la capacidad de interactuar con herramientas externas es lo que transforma un modelo de lenguaje aislado en un sistema autónomo y útil. Un modelo por sí solo, por más potente que sea, está limitado por su conocimiento estático y su incapacidad para acceder a información en tiempo real o realizar acciones concretas. LangGraph proporciona el marco perfecto para orquestar este tipo de agentes, modelando su flujo de decisión como un grafo de estados, donde cada nodo representa una función o una herramienta, y las aristas definen las transiciones basadas en los resultados.
Esta lección se centra en un hito fundamental: la implementación de un agente que puede utilizar una herramienta de búsqueda web. Este es el primer paso hacia agentes que pueden investigar, verificar datos, obtener noticias actualizadas o encontrar información específica que no está en su conjunto de entrenamiento. Dominar esta integración es esencial para construir asistentes de investigación, analistas de mercado, o cualquier sistema que requiera datos frescos y contextuales. Utilizaremos LangChain como ecosistema base y Tavily Search API como nuestra herramienta de búsqueda, aunque los conceptos son aplicables a cualquier herramienta similar.
Concepto Clave: El Ciclo de Decisión Agente-Herramienta
Imagina que eres un detective experto (el agente) en una sala de control. Tienes una intuición y conocimiento base fenomenales (el LLM), pero para resolver el caso, necesitas información del mundo exterior. En la pared hay un teléfono especial (la herramienta) que solo puede llamar a un archivo central de registros públicos (la API de búsqueda). Tu proceso no es adivinar; es sistemático: 1) Analizas la pista (el prompt del usuario), 2) Decides si necesitas hacer una llamada, 3) Si es sí, formulas la pregunta exacta para el archivo (la query), 4) Esperas la respuesta, 5) Sintetizas esa nueva información con tu conocimiento previo, y 6) Das una conclusión o decides hacer otra llamada. Este ciclo de pensar, actuar, observar es el núcleo del agente que construiremos.
En términos técnicos, LangGraph formaliza este ciclo. El grafo tiene un nodo para el agente (que decide), un nodo para la ejecución de herramientas y un mecanismo de enrutamiento (routing) que, basado en la salida del agente, decide si se ha generado una respuesta final o una llamada a una herramienta. El estado (State) del grafo es un diccionario compartido que fluye a través de todos los nodos, llevando consigo la historia de la conversación, los resultados de las búsquedas y el contexto acumulado. La memoria de este ciclo es inherente al flujo del estado a través del grafo.
Tip Clave: La herramienta no es una extensión "tonta" del LLM. El agente debe aprender a usarla de forma estratégica. Parte del diseño del prompt (instrucciones al agente) es enseñarle cuándo y cómo usar la búsqueda. Por ejemplo, "Usa la búsqueda para obtener información actual sobre eventos o datos no públicos. No la uses para preguntas generales sobre conceptos básicos."
Cómo Funciona en la Práctica: Paso a Paso en el Grafo
Vamos a desglosar el flujo de ejecución de nuestro agente de búsqueda, paso a paso, siguiendo la arquitectura de LangGraph. El proceso comienza cuando un usuario plantea una pregunta. Este mensaje se inyecta en el Estado inicial del grafo, típicamente en una clave como "messages" que contiene una lista del historial de chat. El grafo se inicializa y el primer nodo en ejecutarse es el del agente. Este nodo toma el estado actual, formatea los mensajes y el contexto en un prompt, y lo envía al LLM configurado (como GPT-4 o Claude).
La magia ocurre en la especificación del LLM. Nosotros lo vinculamos (bind) con la descripción de las herramientas disponibles (en este caso, solo la búsqueda web). Cuando el LLM procesa el prompt, no solo genera texto; puede decidir "invocar" una de las herramientas vinculadas. LangChain transforma esta decisión en un objeto estructurado llamado AIMessage con un campo especial tool_calls. Este objeto es la salida del nodo agente. A continuación, el enrutador condicional (condition) examina esta salida. Si detecta que hay una llamada a herramienta (tool_calls no está vacío), dirige el flujo al nodo tools. Si no, dirige el flujo al nodo que finaliza y devuelve la respuesta al usuario.
Al llegar al nodo tools, este se encarga de ejecutar la función real de búsqueda web. Toma los argumentos generados por el LLM (la query de búsqueda), llama a la API de Tavily, y obtiene los resultados. Estos resultados se añaden al estado global en forma de un ToolMessage que contiene la respuesta de la búsqueda. Críticamente, el flujo luego vuelve al nodo agente. Ahora, cuando el agente se ejecute de nuevo, su prompt incluirá el historial completo: la pregunta original, su decisión anterior de buscar, y los resultados frescos de esa búsqueda. Con esta nueva información, puede sintetizar una respuesta final o, si es necesario, decidir realizar otra búsqueda para profundizar, repitiendo el ciclo hasta estar satisfecho.
Código en Acción: Implementación Completa del Agente Buscador
A continuación, presentamos una implementación completa, funcional y lista para ejecutar de un agente con LangGraph que utiliza la herramienta de búsqueda web Tavily. Asegúrate de tener instaladas las bibliotecas necesarias (langchain, langchain_openai, langchain_community, langgraph, tavily-python) y de configurar tus API keys correspondientes.
Configuración Inicial y Definición de la Herramienta
import os
from typing import TypedDict, Annotated, List
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langgraph.graph import StateGraph, END
import operator
# 1. Configurar Claves API (¡Usa variables de entorno en producción!)
os.environ["OPENAI_API_KEY"] = "tu-clave-openai"
os.environ["TAVILY_API_KEY"] = "tu-clave-tavily"
# 2. Definir el Esquema del Estado del Grafo
class AgentState(TypedDict):
messages: Annotated[List, operator.add] # Lista acumulativa de mensajes
# 3. Inicializar el Modelo de Lenguaje y la Herramienta
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
search_tool = TavilySearchResults(max_results=3) # Limitamos a 3 resultados
llm_with_tools = llm.bind_tools([search_tool])
# 4. Definir la Función del Nodo Agente
def agent_node(state: AgentState):
"""Nodo que llama al LLM. El LLM decide si usar herramientas o responder."""
print("[NODO AGENTE] Procesando mensajes...")
messages = state['messages']
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# 5. Definir la Función del Nodo de Herramientas
def tool_node(state: AgentState):
"""Nodo que ejecuta las herramientas solicitadas por el agente."""
print("[NODO HERRAMIENTA] Ejecutando búsqueda...")
last_message = state['messages'][-1] # El último mensaje es del agente (AIMessage)
tool_calls = last_message.tool_calls
tool_messages = []
for tool_call in tool_calls:
tool_name = tool_call['name']
if tool_name == "tavily_search_results_json":
result = search_tool.invoke(tool_call['args'])
else:
raise ValueError(f"Herramienta desconocida: {tool_name}")
tool_messages.append(ToolMessage(content=str(result), tool_call_id=tool_call['id']))
return {"messages": tool_messages}
# 6. Definir la Lógica de Enrutamiento Condicional
def should_continue(state: AgentState) -> str:
"""Determina si el grafo debe continuar a herramientas o finalizar."""
last_message = state['messages'][-1]
if last_message.tool_calls:
return "tools" # Hay llamadas a herramienta, ir al nodo 'tools'
else:
return END # No hay llamadas, finalizar el grafo
Construcción y Ejecución del Grafo
# 7. Construir el Grafo de Flujo de Trabajo
workflow = StateGraph(AgentState)
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)
workflow.set_entry_point("agent")
workflow.add_conditional_edges(
"agent",
should_continue,
{"tools": "tools", END: END}
)
workflow.add_edge("tools", "agent") # Después de usar herramientas, volver al agente
# Compilar el grafo en una aplicación ejecutable
app = workflow.compile()
# 8. Función para Ejecutar una Consulta
def run_agent_query(user_query: str):
"""Función de conveniencia para ejecutar una consulta y mostrar el resultado."""
print(f"\n=== Consulta del Usuario: '{user_query}' ===")
initial_state = AgentState(messages=[HumanMessage(content=user_query)])
final_state = app.invoke(initial_state)
final_message = final_state['messages'][-1]
print(f"\n=== Respuesta Final del Agente ===")
print(final_message.content)
return final_state
# 9. Ejecutar Ejemplos Prácticos
if __name__ == "__main__":
# Ejemplo 1: Pregunta que requiere búsqueda
result1 = run_agent_query("¿Cuáles han sido los avances más significativos en inteligencia artificial durante los primeros meses de 2024?")
# Ejemplo 2: Pregunta conversacional de seguimiento (aprovecha la memoria del estado)
result2 = run_agent_query("¿Y cuáles son las implicaciones éticas mencionadas en esos avances?")
# Ejemplo 3: Pregunta que no debería requerir búsqueda
result3 = run_agent_query("¿Qué es un grafo?")
Este código construye un agente completamente funcional. Observa cómo el grafo maneja el ciclo: agent -> (¿herramientas?) -> tools -> agent. La memoria se mantiene automáticamente porque cada mensaje (HumanMessage, AIMessage, ToolMessage) se va añadiendo a la lista state['messages'] gracias al operador Annotated[List, operator.add] en la definición del estado. El agente puede hacer múltiples búsquedas en un solo turno si el LLM lo considera necesario.
Errores Comunes y Cómo Evitarlos
1. Prompting Inadecuado del Agente: El error más común es no dar instrucciones claras al LLM sobre cuándo usar la herramienta. Si el prompt es vago, el agente puede abusar de la búsqueda (incrementando costes y latencia) o no usarla cuando es necesario. Solución: Incluye instrucciones explícitas en el primer mensaje del sistema (system message). Por ejemplo, estructura el estado inicial con un mensaje que diga: "Eres un asistente útil. Para preguntas sobre eventos actuales, noticias o información específica posterior a tu fecha de corte, utiliza la herramienta de búsqueda. Para conceptos generales o conocimiento establecido, responde directamente."
2. Mal Manejo del Estado y la Memoria: Olvidar que el estado es mutable y que cada nodo debe devolver solo los fragmentos que modifica. Un error típico es sobrescribir la lista completa de mensajes en lugar de añadirlos.
Solución: Usa el patrón Annotated[List, operator.add] como se muestra en el código. Esto garantiza que cada nodo devuelva una lista de nuevos mensajes que se concatenarán automáticamente al estado existente. Nunca devuelvas {"messages": state['messages'] + [new_message]} manualmente, es propenso a errores.
3. Falta de Manejo de Errores en la Herramienta: Las APIs externas fallan (límite de tasa, tiempo de espera agotado, errores 5xx). Si tu nodo de herramientas no captura estas excepciones, el grafo completo fallará.
Solución: Envuelve la llamada a search_tool.invoke() en un bloque try-except. Devuelve un ToolMessage con un contenido de error claro (ej: "La búsqueda no está disponible en este momento. Por favor, intenta reformular tu pregunta o prueba más tarde.") para que el agente pueda manejarlo de forma inteligente en el siguiente ciclo.
4. No Limitar la Profundidad del Ciclo (Loop Infinito): Un agente demasiado meticuloso podría entrar en un ciclo infinito de búsqueda: busca, no queda satisfecho con los resultados, decide buscar de nuevo con términos similares, y así sucesivamente.
Solución: Implementa un mecanismo de interrupción. LangGraph permite añadir una variable de "pasos" al estado. Incrementa un contador en cada ciclo agente->herramientas y, en la función should_continue, si supera un límite (ej: 5), fuerza la ruta a END. También puedes instruir al LLM para que sea conciso en sus búsquedas.
Checklist de Dominio
Antes de considerar esta lección completamente dominada, asegúrate de poder verificar y realizar cada uno de los siguientes puntos:
- Puedo explicar el ciclo de "pensar, actuar, observar" en el contexto de un agente de LangGraph y su analogía con un proceso de investigación humano.
- He configurado correctamente una herramienta de búsqueda web (como Tavily o SerpAPI) y la he vinculado (
bind_tools) a un modelo de lenguaje como GPT-4 o Claude. - Comprendo la función del Estado (State) como contenedor de la memoria de la conversación y sé cómo definir un esquema de estado con
TypedDictyAnnotatedpara acumulación automática de mensajes. - Sé construir un grafo con LangGraph que incluya al menos dos nodos (agente y herramientas) y las aristas condicionales necesarias para enrutar entre ellos y el final.
- Puedo identificar en la salida del LLM el objeto
AIMessagecon el campotool_callsy extraer de él los argumentos para ejecutar la herramienta correspondiente. - Sé cómo el nodo de herramientas toma los resultados de la API y los empaqueta en un
ToolMessagepara que sean incorporados al contexto del agente en el siguiente ciclo. - He implementado un mecanismo básico de control (como un contador de pasos) para prevenir ciclos infinitos en el grafo.
- Puedo depurar el flujo del grafo interpretando los mensajes de log (como los
printdel ejemplo) para seguir la secuencia: Agente -> (¿Herramientas?) -> [Tools] -> Agente -> Fin.