En este tutorial, nuestro objetivo es comprender cómo Langgraph nos permite administrar los flujos de conversación de manera estructurada, al tiempo que proporciona el poder de “viajar en el tiempo” a través de los puntos de control. Al construir un chatbot que integra un modelo Gemini gratuito y una herramienta de Wikipedia, podemos agregar múltiples pasos a un diálogo, registrar cada punto de control, reproducir el historial de estado completo e incluso reanudar desde un estado pasado. Este enfoque práctico nos permite ver, en tiempo real, cómo el diseño de Langgraph facilita el seguimiento y la manipulación de la progresión de la conversación con claridad y control. Mira el Códigos completos aquí.
!pip -q install -U langgraph langchain langchain-google-genai google-generativeai typing_extensions
!pip -q install "requests==2.32.4"
import os
import json
import textwrap
import getpass
import time
from typing import Annotated, List, Dict, Any, Optional
from typing_extensions import TypedDict
from langchain.chat_models import init_chat_model
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import ToolNode, tools_condition
import requests
from requests.adapters import HTTPAdapter, Retry
if not os.environ.get("GOOGLE_API_KEY"):
os.environ["GOOGLE_API_KEY"] = getpass.getpass("🔑 Enter your Google API Key (Gemini): ")
llm = init_chat_model("google_genai:gemini-2.0-flash")
Comenzamos instalando las bibliotecas requeridas, configurando nuestra tecla API Gemini e importando todos los módulos necesarios. Luego inicializamos el modelo Gemini usando Langchain para que podamos usarlo como el Core LLM en nuestro flujo de trabajo Langgraph. Mira el Códigos completos aquí.
WIKI_SEARCH_URL = "https://en.wikipedia.org/w/api.php"
_session = requests.Session()
_session.headers.update({
"User-Agent": "LangGraph-Colab-Demo/1.0 (contact: [email protected])",
"Accept": "application/json",
})
retry = Retry(
total=5, connect=5, read=5, backoff_factor=0.5,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=("GET", "POST")
)
_session.mount("https://", HTTPAdapter(max_retries=retry))
_session.mount("http://", HTTPAdapter(max_retries=retry))
def _wiki_search_raw(query: str, limit: int = 3) -> List[Dict[str, str]]:
"""
Use MediaWiki search API with:
- origin='*' (good practice for CORS)
- Polite UA + retries
Returns compact list of {title, snippet_html, url}.
"""
params = {
"action": "query",
"list": "search",
"format": "json",
"srsearch": query,
"srlimit": limit,
"srprop": "snippet",
"utf8": 1,
"origin": "*",
}
r = _session.get(WIKI_SEARCH_URL, params=params, timeout=15)
r.raise_for_status()
data = r.json()
out = []
for item in data.get("query", {}).get("search", []):
title = item.get("title", "")
page_url = f"https://en.wikipedia.org/wiki/{title.replace(' ', '_')}"
snippet = item.get("snippet", "")
out.append({"title": title, "snippet_html": snippet, "url": page_url})
return out
@tool
def wiki_search(query: str) -> List[Dict[str, str]]:
"""Search Wikipedia and return up to 3 results with title, snippet_html, and url."""
try:
results = _wiki_search_raw(query, limit=3)
return results if results else [{"title": "No results", "snippet_html": "", "url": ""}]
except Exception as e:
return [{"title": "Error", "snippet_html": str(e), "url": ""}]
TOOLS = [wiki_search]
Configuramos una herramienta de búsqueda de Wikipedia con una sesión personalizada, reintentos y un agente de usuario educado. Definimos _wiki_search_raw para consultar la API MediaWiki y luego envolverla como una herramienta Langchain, lo que nos permite llamarlo sin problemas dentro de nuestro flujo de trabajo Langgraph. Mira el Códigos completos aquí.
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
llm_with_tools = llm.bind_tools(TOOLS)
SYSTEM_INSTRUCTIONS = textwrap.dedent("""
You are ResearchBuddy, a careful research assistant.
- If the user asks you to "research", "find info", "latest", "web", or references a library/framework/product,
you SHOULD call the `wiki_search` tool at least once before finalizing your answer.
- When you call tools, be concise in the text you produce around the call.
- After receiving tool results, cite at least the page titles you used in your summary.
""").strip()
def chatbot(state: State) -> Dict[str, Any]:
"""Single step: call the LLM (with tools bound) on the current messages."""
return {"messages": [llm_with_tools.invoke(state["msgs"])]}
graph_builder.add_node("chatbot", chatbot)
memory = InMemorySaver()
graph = graph_builder.compile(checkpointer=memory)
Definimos nuestro estado de gráfico para almacenar el hilo de mensajes en ejecución y vincular nuestro modelo Gemini a la herramienta Wiki_Search, lo que permite llamarlo cuando sea necesario. Agregamos un nodo de chatbot y un nodo Herramientas, los conectamos con bordes condicionales y habilitamos el punto de control con un ahorrador en memoria. Ahora compilamos el gráfico para que podamos agregar pasos, reproducir el historial y reanudar desde cualquier punto de control. Mira el Códigos completos aquí.
def print_last_message(event: Dict[str, Any]):
"""Pretty-print the last message in an event if available."""
if "messages" in event and event["messages"]:
msg = event["messages"][-1]
try:
if isinstance(msg, BaseMessage):
msg.pretty_print()
else:
role = msg.get("role", "unknown")
content = msg.get("content", "")
print(f"\n[{role.upper()}]\n{content}\n")
except Exception:
print(str(msg))
def show_state_history(cfg: Dict[str, Any]) -> List[Any]:
"""Print a concise view of checkpoints; return the list as well."""
history = list(graph.get_state_history(cfg))
print("\n=== 📜 State history (most recent first) ===")
for i, st in enumerate(history):
n = st.next
n_txt = f"{n}" if n else "()"
print(f"{i:02d}) NumMessages={len(st.values.get('messages', []))} Next={n_txt}")
print("=== End history ===\n")
return history
def pick_checkpoint_by_next(history: List[Any], node_name: str = "tools") -> Optional[Any]:
"""Pick the first checkpoint whose `next` includes a given node (e.g., 'tools')."""
for st in history:
nxt = tuple(st.next) if st.next else tuple()
if node_name in nxt:
return st
return None
Agregamos funciones de utilidad para hacer que nuestro flujo de trabajo Langgraph sea más fácil de inspeccionar y controlar. Usamos print_last_message para mostrar cuidadosamente la respuesta más reciente, show_state_history para enumerar todos los puntos de control guardados y Pick_checkpoint_by_next para localizar un punto de control donde el gráfico está a punto de ejecutar un nodo específico, como el paso de herramientas. Mira el Códigos completos aquí.
config = {"configurable": {"thread_id": "demo-thread-1"}}
first_turn = {
"messages": [
{"role": "system", "content": SYSTEM_INSTRUCTIONS},
{"role": "user", "content": "I'm learning LangGraph. Could you do some research on it for me?"},
]
}
print("\n==================== 🟢 STEP 1: First user turn ====================")
events = graph.stream(first_turn, config, stream_mode="values")
for ev in events:
print_last_message(ev)
second_turn = {
"messages": [
{"role": "user", "content": "Ya. Maybe I'll build an agent with it!"}
]
}
print("\n==================== 🟢 STEP 2: Second user turn ====================")
events = graph.stream(second_turn, config, stream_mode="values")
for ev in events:
print_last_message(ev)
Simulamos dos interacciones de usuario en el mismo hilo transmitiendo eventos a través del gráfico. Primero proporcionamos instrucciones del sistema y le pedimos al asistente que investigue a Langgraph, luego seguimos con un segundo mensaje de usuario sobre la construcción de un agente autónomo. Cada paso es de control, lo que nos permite reproducir o reanudar de estos estados más adelante. Mira el Códigos completos aquí.
print("\n==================== 🔁 REPLAY: Full state history ====================")
history = show_state_history(config)
to_replay = pick_checkpoint_by_next(history, node_name="tools")
if to_replay is None:
to_replay = history[min(2, len(history) - 1)]
print("Chosen checkpoint to resume from:")
print(" Next:", to_replay.next)
print(" Config:", to_replay.config)
print("\n==================== ⏪ RESUME from chosen checkpoint ====================")
for ev in graph.stream(None, to_replay.config, stream_mode="vals"):
print_last_message(ev)
MANUAL_INDEX = None
if MANUAL_INDEX is not None and 0 <= MANUAL_INDEX < len(history):
chosen = history[MANUAL_INDEX]
print(f"\n==================== 🧭 MANUAL RESUME @ index {MANUAL_INDEX} ====================")
print("Next:", chosen.next)
print("Config:", chosen.config)
for ev in graph.stream(None, chosen.config, stream_mode="values"):
print_last_message(ev)
print("\n✅ Done. You added steps, replayed history, and resumed from a prior checkpoint.")
Reproducimos el historial completo del punto de control para ver cómo nuestra conversación evoluciona a través de los pasos e identificamos un punto útil para reanudar. Luego “viajamos en el tiempo” reiniciando desde un punto de control seleccionado, y opcionalmente desde cualquier índice manual, por lo que continuamos el diálogo exactamente desde ese estado guardado.
En conclusión, hemos obtenido una imagen más clara de cómo las capacidades de control de control y viajes en el tiempo de Langgraph aportan flexibilidad y transparencia a la gestión de la conversación. Al atravesar múltiples giros de los usuarios, reproducir el historial estatal y reanudar desde puntos anteriores, podemos experimentar de primera mano el poder de este marco en la creación de agentes de investigación confiables o asistentes autónomos. Reconocemos que este flujo de trabajo no es solo una demostración, sino una base que podemos extender a aplicaciones más complejas, donde la reproducibilidad y la trazabilidad son tan importantes como las respuestas ellos mismos.
Mira el Códigos completos aquí. No dude en ver nuestro Página de Github para tutoriales, códigos y cuadernos. Además, siéntete libre de seguirnos Gorjeo Y no olvides unirte a nuestro Subreddit de 100k+ ml y suscribirse a Nuestro boletín.
Asif Razzaq es el CEO de MarktechPost Media Inc .. Como empresario e ingeniero visionario, ASIF se compromete a aprovechar el potencial de la inteligencia artificial para el bien social. Su esfuerzo más reciente es el lanzamiento de una plataforma de medios de inteligencia artificial, MarktechPost, que se destaca por su cobertura profunda de noticias de aprendizaje automático y de aprendizaje profundo que es técnicamente sólido y fácilmente comprensible por una audiencia amplia. La plataforma cuenta con más de 2 millones de vistas mensuales, ilustrando su popularidad entre el público.