LangGraph (3/4): memoria a largo plazo y human-in-the-loop

LangGraph (3/4): memoria a largo plazo y human-in-the-loop

En las partes anteriores creamos un chatbot con herramientas (Parte 1) y memoria a corto plazo dentro de un hilo (Parte 2). Ahora damos dos pasos más: **memoria a largo plazo** (entre hilos) para recordar información entre conversaciones, y el patrón **human-in-the-loop** para pausar el grafo y pedir aprobación humana.

⚠️ Este capítulo continúa el código de las partes anteriores (Parte 1 · Parte 2).

📚 **Esta entrada es parte de la serie _Guía completa de LangGraph_**, dividida en cuatro capítulos que se leen en orden:

> * Parte 1: Chatbot básico y herramientas

* Parte 2: Memoria a corto plazo

* 👉 **Parte 3: Memoria a largo plazo y human-in-the-loop**

* Parte 4: Personalización del estado y checkpoints

Memoria a largo plazo, memoria entre hiloslink image 1

La memoria es una función cognitiva que permite a las personas almacenar, recuperar y utilizar información para comprender, a partir de su pasado, su presente y su futuro.

Existen varios tipos de memoria a largo plazo que pueden utilizarse en aplicaciones de IA.

Introducción a LangGraph Memory Storelink image 2

LangGraph proporciona el LangGraph Memory Store, que es una forma de guardar y recuperar memoria a largo plazo entre diferentes hilos. De esta manera, en una conversación, un usuario puede indicar que le gusta algo, y en otra conversación, el chatbot puede recuperar esa información para generar una respuesta más personalizada.

Se trata de una clase para almacenes persistentes de clave-valor (key-value).

Cuando se almacenan objetos en la memoria se necesitan tres cosas:

  • Un namespace para el objeto, se hace mediante una tupla
  • Una key única
  • El valor del objeto

Vamos a ver un ejemplo

	
< > Input
Python
import uuid
from langgraph.store.memory import InMemoryStore
in_memory_store = InMemoryStore()
# Namespace for the memory to save
user_id = "1"
namespace_for_memory = (user_id, "memories")
# Save a memory to namespace as key and value
key = str(uuid.uuid4())
# The value needs to be a dictionary
value = {"food_preference" : "I like pizza"}
# Save the memory
in_memory_store.put(namespace_for_memory, key, value)
Copied

El objeto in_memory_store que hemos creado tiene varios métodos y uno de ellos es search, que nos permite buscar por namespace

	
< > Input
Python
# Search
memories = in_memory_store.search(namespace_for_memory)
type(memories), len(memories)
Copied
>_ Output
			
(list, 1)

Es una lista de un único valor, lo cual es lógico, porque solo hemos almacenado un valor, así que vamos a verlo

	
< > Input
Python
value = memories[0]
value.dict()
Copied
>_ Output
			
{'namespace': ['1', 'memories'],
'key': '70006131-948a-4d7a-bdce-78351c44fc4d',
'value': {'food_preference': 'I like pizza'},
'created_at': '2025-05-11T07:24:31.462465+00:00',
'updated_at': '2025-05-11T07:24:31.462468+00:00',
'score': None}

Podemos ver su key y su value

	
< > Input
Python
# The key, value
memories[0].key, memories[0].value
Copied
>_ Output
			
('70006131-948a-4d7a-bdce-78351c44fc4d', {'food_preference': 'I like pizza'})

También podemos usar el método get para obtener un objeto de la memoria a partir de su namespace y su key

	
< > Input
Python
# Get the memory by namespace and key
memory = in_memory_store.get(namespace_for_memory, key)
memory.dict()
Copied
>_ Output
			
{'namespace': ['1', 'memories'],
'key': '70006131-948a-4d7a-bdce-78351c44fc4d',
'value': {'food_preference': 'I like pizza'},
'created_at': '2025-05-11T07:24:31.462465+00:00',
'updated_at': '2025-05-11T07:24:31.462468+00:00'}

Al igual que para la memoria a corto plazo hemos usado los checkpoints, para la memoria a largo plazo vamos a usar LangGraph Store

Chatbot con memoria a largo plazolink image 3

Creamos un chatbot básico, con memoria a largo plazo y memoria a corto plazo.

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.graph.message import add_messages
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langgraph.checkpoint.memory import MemorySaver # Short-term memory
from langgraph.store.base import BaseStore # Long-term memory
from langchain_core.runnables.config import RunnableConfig
from langgraph.store.memory import InMemoryStore
from huggingface_hub import login
from IPython.display import Image, display
import os
import dotenv
dotenv.load_dotenv()
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")
class State(TypedDict):
messages: Annotated[list, add_messages]
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Create the LLM model
login(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the model
MODEL = "Qwen/Qwen2.5-72B-Instruct"
model = HuggingFaceEndpoint(
repo_id=MODEL,
task="text-generation",
max_new_tokens=512,
do_sample=False,
repetition_penalty=1.03,
)
# Create the chat model
llm = ChatHuggingFace(llm=model)
# Chatbot instruction
MODEL_SYSTEM_MESSAGE = """You are a helpful assistant that can answer questions and help with tasks.
You have access to a long-term memory that you can use to answer questions and help with tasks.
Here is the memory (it may be empty): {memory}"""
# Create new memory from the chat history and any existing memory
CREATE_MEMORY_INSTRUCTION = """You are a helpful assistant that gets information from the user to personalize your responses.
# INFORMATION FROM THE USER:
{memory}
# INSTRUCTIONS:
1. Carefully review the chat history
2. Identify new information from the user, such as:
- Personal details (name, location)
- Preferences (likes, dislikes)
- Interests and hobbies
- Past experiences
- Goals or future plans
3. Combine any new information with the existing memory
4. Format the memory as a clear, bulleted list
5. If new information conflicts with existing memory, keep the most recent version
Remember: Only include factual information directly stated by the user. Do not make assumptions or inferences.
Based on the chat history below, please update the user information:"""
# Nodes
def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Load memory from the store and use it to personalize the chatbot's response."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve memory from the store
namespace = ("memory", user_id)
key = "user_memory"
existing_memory = store.get(namespace, key)
# Extract the actual memory content if it exists and add a prefix
if existing_memory:
# Value is a dictionary with a memory key
existing_memory_content = existing_memory.value.get('memory')
else:
existing_memory_content = "No existing memory found."
if isinstance(existing_memory_content, str):
print(f" [Call model debug] Existing memory: {existing_memory_content}")
else:
print(f" [Call model debug] Existing memory: {existing_memory_content.content}")
# Format the memory in the system prompt
system_msg = MODEL_SYSTEM_MESSAGE.format(memory=existing_memory_content)
# Respond using memory as well as the chat history
response = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])
return {"messages": response}
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Reflect on the chat history and save a memory to the store."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve existing memory from the store
namespace = ("memory", user_id)
existing_memory = store.get(namespace, "user_memory")
# Extract the memory
if existing_memory:
existing_memory_content = existing_memory.value.get('memory')
else:
existing_memory_content = "No existing memory found."
if isinstance(existing_memory_content, str):
print(f" [Write memory debug] Existing memory: {existing_memory_content}")
else:
print(f" [Write memory debug] Existing memory: {existing_memory_content.content}")
# Format the memory in the system prompt
system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=existing_memory_content)
new_memory = llm.invoke([SystemMessage(content=system_msg)]+state['messages'])
if isinstance(new_memory, str):
print(f" [Write memory debug] New memory: {new_memory}")
else:
print(f" [Write memory debug] New memory: {new_memory.content}")
# Overwrite the existing memory in the store
key = "user_memory"
# Write value as a dictionary with a memory key
store.put(namespace, key, {"memory": new_memory.content})
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("write_memory", write_memory)
# Connect nodes
graph_builder.add_edge(START, "call_model")
graph_builder.add_edge("call_model", "write_memory")
graph_builder.add_edge("write_memory", END)
# Store for long-term (across-thread) memory
long_term_memory = InMemoryStore()
# Checkpointer for short-term (within-thread) memory
short_term_memory = MemorySaver()
# Compile the graph
graph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Vamos a probarlo

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "1", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="Hi, my name is Maximo")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi, my name is Maximo
[Call model debug] Existing memory: No existing memory found.
================================== Ai Message ==================================
Hello Maximo! It's nice to meet you. How can I assist you today?
[Write memory debug] Existing memory: No existing memory found.
[Write memory debug] New memory:
Here's the updated information I have about you:
- Name: Maximo
	
< > Input
Python
# User input
input_messages = [HumanMessage(content="I like to bike around San Francisco")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I like to bike around San Francisco
[Call model debug] Existing memory:
Here's the updated information I have about you:
- Name: Maximo
================================== Ai Message ==================================
That sounds like a great way to explore the city! San Francisco has some fantastic biking routes. Are there any specific areas or routes you enjoy biking the most, or are you looking for some new recommendations?
[Write memory debug] Existing memory:
Here's the updated information I have about you:
- Name: Maximo
[Write memory debug] New memory:
Here's the updated information about you:
- Name: Maximo
- Location: San Francisco
- Interest: Biking around San Francisco

Si recuperamos la memoria a largo plazo

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memory", user_id)
existing_memory = long_term_memory.get(namespace, "user_memory")
existing_memory.dict()
Copied
>_ Output
			
{'namespace': ['memory', '1'],
'key': 'user_memory',
'value': {'memory': " Here's the updated information about you: - Name: Maximo - Location: San Francisco - Interest: Biking around San Francisco"},
'created_at': '2025-05-11T09:41:26.739207+00:00',
'updated_at': '2025-05-11T09:41:26.739211+00:00'}

Obtenemos su valor

	
< > Input
Python
print(existing_memory.value.get('memory'))
Copied
>_ Output
			
Here's the updated information about you:
- Name: Maximo
- Location: San Francisco
- Interest: Biking around San Francisco

Ahora podemos empezar un nuevo hilo de conversación, pero con la misma memoria a largo plazo. Veremos que el chatbot recuerda la información del usuario.

	
< > Input
Python
# We supply a user ID for across-thread memory as well as a new thread ID
config = {"configurable": {"thread_id": "2", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="Hi! Where would you recommend that I go biking?")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi! Where would you recommend that I go biking?
[Call model debug] Existing memory:
Here's the updated information about you:
- Name: Maximo
- Location: San Francisco
- Interest: Biking around San Francisco
================================== Ai Message ==================================
Hi there! Given my interest in biking around San Francisco, I'd recommend a few great routes:
1. **Golden Gate Park**: This is a fantastic place to bike, with wide paths that are separated from vehicle traffic. You can start at the eastern end near Stow Lake and bike all the way to the western end at Ocean Beach. There are plenty of scenic spots to stop and enjoy along the way.
2. **The Embarcadero**: This route follows the waterfront from Fisherman’s Wharf to the Bay Bridge. It’s relatively flat and offers beautiful views of the San Francisco Bay and the city skyline. You can also stop by the Ferry Building for some delicious food and drinks.
3. **Presidio**: The Presidio is a large park with numerous trails that offer diverse landscapes, from forests to coastal bluffs. The Crissy Field area is especially popular for its views of the Golden Gate Bridge.
4. **Golden Gate Bridge**: Riding across the Golden Gate Bridge is a must-do experience. You can start from the San Francisco side, bike across the bridge, and then continue into Marin County for a longer ride with stunning views.
5. **Lombard Street**: While not a long ride, biking down the famous crooked section of Lombard Street can be a fun and memorable experience. Just be prepared for the steep hill on the way back up!
Each of these routes offers a unique experience, so you can choose based on your interests and the type of scenery you enjoy. Happy biking!
[Write memory debug] Existing memory:
Here's the updated information about you:
- Name: Maximo
- Location: San Francisco
- Interest: Biking around San Francisco
[Write memory debug] New memory: 😊
Let me know if you have any other questions or if you need more recommendations!

He abierto un nuevo hilo de conversación, le he preguntado dónde podría ir a montar en bici, ha recordado que le había dicho que me gusta ir a montar en bici por San Francisco y me ha respondido con lugares de San Francisco a los que podría ir

Chatbot con perfil de usuariolink image 4

Nota: Este apartado lo vamos a hacer usando Sonnet 3.7, ya que la integración de HuggingFace no tiene la funcionalidad de with_structured_output que proporciona una salida estructurada con una estructura definida.

Podemos crear tipados para que el LLM genere una salida con una estructura definida por nosotros.

Vamos a crear un tipado para el perfil de usuario.

	
< > Input
Python
from typing import TypedDict, List
class UserProfile(TypedDict):
"""User profile schema with typed fields"""
user_name: str # The user's preferred name
interests: List[str] # A list of the user's interests
Copied

Ahora volvemos a crear el grafo, pero ahora con el tipado UserProfile

Vamos a usar with_structured_output para que el LLM genere una salida con una estructura definida por nosotros, esa estructura la vamos a definir con la clase Subjects que es una clase de tipo BaseModel de Pydantic.

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.graph.message import add_messages
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langgraph.checkpoint.memory import MemorySaver # Short-term memory
from langgraph.store.base import BaseStore # Long-term memory
from langchain_core.runnables.config import RunnableConfig
from langgraph.store.memory import InMemoryStore
from IPython.display import Image, display
from pydantic import BaseModel, Field
import os
import dotenv
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
class State(TypedDict):
messages: Annotated[list, add_messages]
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
llm_with_structured_output = llm.with_structured_output(UserProfile)
# Chatbot instruction
MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
If you have memory for this user, use it to personalize your responses.
Here is the memory (it may be empty): {memory}"""
# Create new memory from the chat history and any existing memory
CREATE_MEMORY_INSTRUCTION = """Create or update a user profile memory based on the user's chat history.
This will be saved for long-term memory. If there is an existing memory, simply update it.
Here is the existing memory (it may be empty): {memory}"""
# Nodes
def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Load memory from the store and use it to personalize the chatbot's response."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve memory from the store
namespace = ("memory", user_id)
existing_memory = store.get(namespace, "user_memory")
# Format the memories for the system prompt
if existing_memory and existing_memory.value:
memory_dict = existing_memory.value
formatted_memory = (
f"Name: {memory_dict.get('user_name', 'Unknown')} "
f"Interests: {', '.join(memory_dict.get('interests', []))}"
)
else:
formatted_memory = None
# if isinstance(existing_memory_content, str):
print(f" [Call model debug] Existing memory: {formatted_memory}")
# else:
# print(f" [Call model debug] Existing memory: {existing_memory_content.content}")
# Format the memory in the system prompt
system_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)
# Respond using memory as well as the chat history
response = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])
return {"messages": response}
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Reflect on the chat history and save a memory to the store."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve existing memory from the store
namespace = ("memory", user_id)
existing_memory = store.get(namespace, "user_memory")
# Format the memories for the system prompt
if existing_memory and existing_memory.value:
memory_dict = existing_memory.value
formatted_memory = (
f"Name: {memory_dict.get('user_name', 'Unknown')} "
f"Interests: {', '.join(memory_dict.get('interests', []))}"
)
else:
formatted_memory = None
print(f" [Write memory debug] Existing memory: {formatted_memory}")
# Format the existing memory in the instruction
system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=formatted_memory)
# Invoke the model to produce structured output that matches the schema
new_memory = llm_with_structured_output.invoke([SystemMessage(content=system_msg)]+state['messages'])
print(f" [Write memory debug] New memory: {new_memory}")
# Overwrite the existing use profile memory
key = "user_memory"
store.put(namespace, key, new_memory)
# Create graph builder
graph_builder = StateGraph(MessagesState)
# Add nodes
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("write_memory", write_memory)
# Connect nodes
graph_builder.add_edge(START, "call_model")
graph_builder.add_edge("call_model", "write_memory")
graph_builder.add_edge("write_memory", END)
# Store for long-term (across-thread) memory
long_term_memory = InMemoryStore()
# Checkpointer for short-term (within-thread) memory
short_term_memory = MemorySaver()
# Compile the graph
graph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Ejecutamos el grafo

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "1", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="Hi, my name is Maximo and I like to bike around Madrid and eat salads.")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi, my name is Maximo and I like to bike around Madrid and eat salads.
[Call model debug] Existing memory: None
================================== Ai Message ==================================
Hello Maximo! It's nice to meet you. I see you enjoy biking around Madrid and eating salads - those are great healthy habits! Madrid has some beautiful areas to explore by bike, and the city has been improving its cycling infrastructure in recent years.
Is there anything specific about Madrid's cycling routes or perhaps some good places to find delicious salads in the city that you'd like to know more about? I'd be happy to help with any questions you might have.
[Write memory debug] Existing memory: None
[Write memory debug] New memory: {'user_name': 'Maximo', 'interests': ['biking', 'Madrid', 'salads']}

Como vemos, el LLM ha generado una salida con la estructura definida por nosotros.

Vamos a ver cómo se ha guardado la memoria a largo plazo.

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memory", user_id)
existing_memory = long_term_memory.get(namespace, "user_memory")
existing_memory.value
Copied
>_ Output
			
{'user_name': 'Maximo', 'interests': ['biking', 'Madrid', 'salads']}

Máslink image 5

Actualizar esquemas estructurados con Trustcalllink image 6

En el ejemplo anterior, hemos creado perfiles de usuario con datos estructurados

En realidad, lo que se hace por debajo, es regenerar el perfil de usuario en cada interacción. Lo cual genera un gasto innecesario de tokens y puede hacer que se pierda información importante del perfil del usuario.

Así que para solucionarlo vamos a usar la librería TrustCall, que es una librería open source para actualizar esquemas JSON. Cuando tiene que actualizar un esquema JSON, lo hace de manera incremental, es decir, no borra el esquema anterior, sino que va añadiendo los nuevos campos.

Vamos a crear un ejemplo de conversación para ver cómo funciona.

	
< > Input
Python
from langchain_core.messages import HumanMessage, AIMessage
# Conversation
conversation = [HumanMessage(content="Hi, I'm Maximo."),
AIMessage(content="Nice to meet you, Maximo."),
HumanMessage(content="I really like playing soccer.")]
Copied

Creamos un esquema estructurado y un modelo de LLM

	
< > Input
Python
from pydantic import BaseModel, Field
from typing import List
# Schema
class UserProfile(BaseModel):
"""User profile schema with typed fields"""
user_name: str = Field(description="The user's preferred name")
interests: List[str] = Field(description="A list of the user's interests")
from langchain_anthropic import ChatAnthropic
import os
import dotenv
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
Copied

Utilizamos la función create_extractor de trustcall para crear un extractor de datos estructurados

	
< > Input
Python
from trustcall import create_extractor
# Create the extractor
trustcall_extractor = create_extractor(
llm,
tools=[UserProfile],
tool_choice="UserProfile"
)
Copied

Como se puede ver, al método trustcall_extractor se le da un llm, que va a usarse como motor de búsqueda

Extrajimos los datos estructurados

	
< > Input
Python
from langchain_core.messages import SystemMessage
# Instruction
system_msg = "Extract the user profile from the following conversation"
# Invoke the extractor
result = trustcall_extractor.invoke({"messages": [SystemMessage(content=system_msg)]+conversation})
result
Copied
>_ Output
			
{'messages': [AIMessage(content=[{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'input': {'user_name': 'Maximo', 'interests': ['soccer']}, 'name': 'UserProfile', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01TEB3FeDKLAeHJtbKo5noyW', 'model': 'claude-3-7-sonnet-20250219', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 497, 'output_tokens': 56}, 'model_name': 'claude-3-7-sonnet-20250219'}, id='run-8a15289b-fd39-4a2d-878a-fa6feaa805c5-0', tool_calls=[{'name': 'UserProfile', 'args': {'user_name': 'Maximo', 'interests': ['soccer']}, 'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'type': 'tool_call'}], usage_metadata={'input_tokens': 497, 'output_tokens': 56, 'total_tokens': 553, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}})],
'responses': [UserProfile(user_name='Maximo', interests=['soccer'])],
'response_metadata': [{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY'}],
'attempts': 1}

Vamos a ver los mensajes que se han generado para extraer los datos estructurados

	
< > Input
Python
for m in result["messages"]:
m.pretty_print()
Copied
>_ Output
			
================================== Ai Message ==================================
[{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'input': {'user_name': 'Maximo', 'interests': ['soccer']}, 'name': 'UserProfile', 'type': 'tool_use'}]
Tool Calls:
UserProfile (toolu_01WfgbD1fG3rJYAXGrjqjfVY)
Call ID: toolu_01WfgbD1fG3rJYAXGrjqjfVY
Args:
user_name: Maximo
interests: ['soccer']

El esquema de UserProfile se ha actualizado con el nuevo dato.

	
< > Input
Python
schema = result["responses"]
schema
Copied
>_ Output
			
[UserProfile(user_name='Maximo', interests=['soccer'])]

Como vemos, el esquema es una lista, vamos a ver el tipo de dato de su único elemento

	
< > Input
Python
type(schema[0])
Copied
>_ Output
			
__main__.UserProfile

Podemos convertirlo a un diccionario con model_dump

	
< > Input
Python
schema[0].model_dump()
Copied
>_ Output
			
{'user_name': 'Maximo', 'interests': ['soccer']}

Gracias a haberle dado un LLM a trustcall_extractor, podemos pedirle qué queremos que extraiga

Vamos a simular que continúa la conversación para ver cómo se actualiza el esquema

	
< > Input
Python
# Update the conversation
updated_conversation = [HumanMessage(content="Hi, I'm Maximo."),
AIMessage(content="Nice to meet you, Maximo."),
HumanMessage(content="I really like playing soccer."),
AIMessage(content="It is great to play soccer! Where do you go after playing soccer?"),
HumanMessage(content="I really like to go to a bakery after playing soccer."),]
Copied

Le pedimos al modelo que actualice el esquema (un JSON) mediante la librería trustcall

	
< > Input
Python
# Update the instruction
system_msg = f"""Update the memory (JSON doc) to incorporate new information from the following conversation"""
# Invoke the extractor with the updated instruction and existing profile with the corresponding tool name (UserProfile)
result = trustcall_extractor.invoke({"messages": [SystemMessage(content=system_msg)]+updated_conversation},
{"existing": {"UserProfile": schema[0].model_dump()}})
result
Copied
>_ Output
			
{'messages': [AIMessage(content=[{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'input': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'name': 'UserProfile', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01RYUJvCdzL4b8kBYKo4BtQf', 'model': 'claude-3-7-sonnet-20250219', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 538, 'output_tokens': 60}, 'model_name': 'claude-3-7-sonnet-20250219'}, id='run-06994472-5ba0-46cc-a512-5fcacce283fc-0', tool_calls=[{'name': 'UserProfile', 'args': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'type': 'tool_call'}], usage_metadata={'input_tokens': 538, 'output_tokens': 60, 'total_tokens': 598, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}})],
'responses': [UserProfile(user_name='Maximo', interests=['soccer', 'bakeries'])],
'response_metadata': [{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb'}],
'attempts': 1}

Vamos a ver los mensajes que se han generado para actualizar el esquema

	
< > Input
Python
for m in result["messages"]:
m.pretty_print()
Copied
>_ Output
			
================================== Ai Message ==================================
[{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'input': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'name': 'UserProfile', 'type': 'tool_use'}]
Tool Calls:
UserProfile (toolu_01K1zTh33kXDAw1h18Yh2HBb)
Call ID: toolu_01K1zTh33kXDAw1h18Yh2HBb
Args:
user_name: Maximo
interests: ['soccer', 'bakeries']

Vemos el esquema actualizado

	
< > Input
Python
updated_schema = result["responses"][0]
updated_schema.model_dump()
Copied
>_ Output
			
{'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}

Chatbot con perfil de usuario actualizado con Trustcalllink image 7

Volvemos a crear el grafo que actualiza el perfil de usuario, pero ahora con la librería trustcall

	
< > Input
Python
from pydantic import BaseModel, Field
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.graph.message import add_messages
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langgraph.checkpoint.memory import MemorySaver # Short-term memory
from langgraph.store.base import BaseStore # Long-term memory
from langchain_core.runnables.config import RunnableConfig
from langgraph.store.memory import InMemoryStore
from IPython.display import Image, display
from pydantic import BaseModel, Field
import os
import dotenv
from trustcall import create_extractor
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Schema
class UserProfile(BaseModel):
""" Profile of a user """
user_name: str = Field(description="The user's preferred name")
user_location: str = Field(description="The user's location")
interests: list = Field(description="A list of the user's interests")
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
# Create the extractor
trustcall_extractor = create_extractor(
llm,
tools=[UserProfile],
tool_choice="UserProfile", # Enforces use of the UserProfile tool
)
# Chatbot instruction
MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
If you have memory for this user, use it to personalize your responses.
Here is the memory (it may be empty): {memory}"""
# Create new memory from the chat history and any existing memory
TRUSTCALL_INSTRUCTION = """Create or update the memory (JSON doc) to incorporate information from the following conversation:"""
# Nodes
def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Load memory from the store and use it to personalize the chatbot's response."""
"""Load memory from the store and use it to personalize the chatbot's response."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve memory from the store
namespace = ("memory", user_id)
existing_memory = store.get(namespace, "user_memory")
# Format the memories for the system prompt
if existing_memory and existing_memory.value:
memory_dict = existing_memory.value
formatted_memory = (
f"Name: {memory_dict.get('user_name', 'Unknown')} "
f"Location: {memory_dict.get('user_location', 'Unknown')} "
f"Interests: {', '.join(memory_dict.get('interests', []))}"
)
else:
formatted_memory = None
print(f" [Call model debug] Existing memory: {formatted_memory}")
# Format the memory in the system prompt
system_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)
# Respond using memory as well as the chat history
response = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])
return {"messages": response}
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Reflect on the chat history and save a memory to the store."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve existing memory from the store
namespace = ("memory", user_id)
existing_memory = store.get(namespace, "user_memory")
# Get the profile as the value from the list, and convert it to a JSON doc
existing_profile = {"UserProfile": existing_memory.value} if existing_memory else None
print(f" [Write memory debug] Existing profile: {existing_profile}")
# Invoke the extractor
result = trustcall_extractor.invoke({"messages": [SystemMessage(content=TRUSTCALL_INSTRUCTION)]+state["messages"], "existing": existing_profile})
# Get the updated profile as a JSON object
updated_profile = result["responses"][0].model_dump()
print(f" [Write memory debug] Updated profile: {updated_profile}")
# Save the updated profile
key = "user_memory"
store.put(namespace, key, updated_profile)
# Create graph builder
graph_builder = StateGraph(MessagesState)
# Add nodes
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("write_memory", write_memory)
# Connect nodes
graph_builder.add_edge(START, "call_model")
graph_builder.add_edge("call_model", "write_memory")
graph_builder.add_edge("write_memory", END)
# Store for long-term (across-thread) memory
long_term_memory = InMemoryStore()
# Checkpointer for short-term (within-thread) memory
short_term_memory = MemorySaver()
# Compile the graph
graph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Iniciamos la conversación

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "1", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="Hi, my name is Maximo")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi, my name is Maximo
[Call model debug] Existing memory: None
================================== Ai Message ==================================
Hello Maximo! It's nice to meet you. How can I help you today? Whether you have questions, need information, or just want to chat, I'm here to assist you. Is there something specific you'd like to talk about?
[Write memory debug] Existing profile: None
[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': '&lt;UNKNOWN&gt;', 'interests': []}

Como vemos, no sabe ni la localización ni los intereses del usuario. Vamos a actualizar el perfil del usuario.

	
< > Input
Python
# User input
input_messages = [HumanMessage(content="I like to play soccer and I live in Madrid")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I like to play soccer and I live in Madrid
[Call model debug] Existing memory: Name: Maximo
Location: &lt;UNKNOWN&gt;
Interests:
================================== Ai Message ==================================
Hello Maximo! It's great to learn that you live in Madrid and enjoy playing soccer. Madrid is a fantastic city with a rich soccer culture, being home to world-famous clubs like Real Madrid and Atlético Madrid.
Soccer is truly a way of life in Spain, so you're in a perfect location for your interest. Do you support any particular team in Madrid? Or perhaps you enjoy playing soccer recreationally in the city's parks and facilities?
Is there anything specific about Madrid or soccer you'd like to discuss further?
[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': '&lt;UNKNOWN&gt;', 'interests': []}}
[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}

Ha actualizado el perfil con la localización y los intereses del usuario

Vamos a ver la memoria actualizada

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memory", user_id)
existing_memory = long_term_memory.get(namespace, "user_memory")
existing_memory.dict()
Copied
>_ Output
			
{'namespace': ['memory', '1'],
'key': 'user_memory',
'value': {'user_name': 'Maximo',
'user_location': 'Madrid',
'interests': ['soccer']},
'created_at': '2025-05-12T17:35:03.583258+00:00',
'updated_at': '2025-05-12T17:35:03.583259+00:00'}

Vemos el esquema con el perfil del usuario actualizado

	
< > Input
Python
# The user profile saved as a JSON object
existing_memory.value
Copied
>_ Output
			
{'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}

Vamos a añadir un nuevo interés del usuario

	
< > Input
Python
# User input
input_messages = [HumanMessage(content="I also like to play basketball")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I also like to play basketball
[Call model debug] Existing memory: Name: Maximo
Location: Madrid
Interests: soccer
================================== Ai Message ==================================
That's great to know, Maximo! It's nice that you enjoy both soccer and basketball. Basketball is also quite popular in Spain, with Liga ACB being one of the strongest basketball leagues in Europe.
In Madrid, you have the opportunity to follow Real Madrid's basketball section, which is one of the most successful basketball teams in Europe. The city offers plenty of courts and facilities where you can play basketball too.
Do you play basketball casually with friends, or are you part of any local leagues in Madrid? And how do you balance your time between soccer and basketball?
[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}}
[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}

Volvemos a ver la memoria actualizada

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memory", user_id)
existing_memory = long_term_memory.get(namespace, "user_memory")
existing_memory.value
Copied
>_ Output
			
{'user_name': 'Maximo',
'user_location': 'Madrid',
'interests': ['soccer', 'basketball']}

Ha añadido bien el nuevo interés del usuario.

Con esta memoria a largo plazo guardada, podemos iniciar un nuevo hilo y el chatbot tendrá acceso a nuestro perfil actualizado.

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "2", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="What soccer players do you recommend for me?")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
What soccer players do you recommend for me?
[Call model debug] Existing memory: Name: Maximo
Location: Madrid
Interests: soccer, basketball
================================== Ai Message ==================================
Based on your interest in soccer, I can recommend some players who might appeal to you. Since you're from Madrid, you might already follow Real Madrid or Atlético Madrid players, but here are some recommendations:
From La Liga:
- Vinícius Júnior and Jude Bellingham (Real Madrid)
- Antoine Griezmann (Atlético Madrid)
- Robert Lewandowski (Barcelona)
- Lamine Yamal (Barcelona's young talent)
International stars:
- Kylian Mbappé
- Erling Haaland
- Mohamed Salah
- Kevin De Bruyne
You might also enjoy watching players with creative playing styles since you're interested in basketball as well, which is a sport that values creativity and flair - players like Rodrigo De Paul or João Félix.
Is there a particular league or playing style you prefer in soccer?
[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}}
[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}

Como sabe que vivo en Madrid, primero me ha sugerido jugadores de fútbol de la Liga española. Y luego me ha sugerido jugadores de otras ligas

Chatbot con colecciones de documentos de usuario actualizadas con Trustcalllink image 8

Otro enfoque es que en vez de guardar en un solo documento el perfil del usuario, guardar una colección de documentos, de esta manera no estamos atados a un solo esquema cerrado

Vamos a ver cómo hacerlo

	
< > Input
Python
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_core.messages import merge_message_runs
from langgraph.checkpoint.memory import MemorySaver # Short-term memory
from langgraph.store.base import BaseStore # Long-term memory
from langchain_core.runnables.config import RunnableConfig
from langgraph.store.memory import InMemoryStore
from IPython.display import Image, display
from trustcall import create_extractor
from pydantic import BaseModel, Field
import uuid
import os
import dotenv
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Memory schema
class Memory(BaseModel):
"""A memory item representing a piece of information learned about the user."""
content: str = Field(description="The main content of the memory. For example: User expressed interest in learning about French.")
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
# Create the extractor
trustcall_extractor = create_extractor(
llm,
tools=[Memory],
tool_choice="Memory",
# This allows the extractor to insert new memories
enable_inserts=True,
)
# Chatbot instruction
MODEL_SYSTEM_MESSAGE = """You are a helpful chatbot. You are designed to be a companion to a user.
You have a long term memory which keeps track of information you learn about the user over time.
Current Memory (may include updated memories from this conversation):
{memory}"""
# Create new memory from the chat history and any existing memory
TRUSTCALL_INSTRUCTION = """Reflect on following interaction.
Use the provided tools to retain any necessary memories about the user.
Use parallel tool calling to handle updates and insertions simultaneously:"""
# Nodes
def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Load memory from the store and use it to personalize the chatbot's response."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Retrieve memory from the store
namespace = ("memories", user_id)
memories = store.search(namespace)
print(f" [Call model debug] Memories: {memories}")
# Format the memories for the system prompt
info = " ".join(f"- {mem.value['content']}" for mem in memories)
system_msg = MODEL_SYSTEM_MESSAGE.format(memory=info)
# Respond using memory as well as the chat history
response = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])
return {"messages": response}
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
"""Reflect on the chat history and save a memory to the store."""
# Get the user ID from the config
user_id = config["configurable"]["user_id"]
# Define the namespace for the memories
namespace = ("memories", user_id)
# Retrieve the most recent memories for context
existing_items = store.search(namespace)
# Format the existing memories for the Trustcall extractor
tool_name = "Memory"
existing_memories = ([(existing_item.key, tool_name, existing_item.value)
for existing_item in existing_items]
if existing_items
else None
)
print(f" [Write memory debug] Existing memories: {existing_memories}")
# Merge the chat history and the instruction
updated_messages=list(merge_message_runs(messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION)] + state["messages"]))
# Invoke the extractor
result = trustcall_extractor.invoke({"messages": updated_messages,
"existing": existing_memories})
# Save the memories from Trustcall to the store
for r, rmeta in zip(result["responses"], result["response_metadata"]):
store.put(namespace,
rmeta.get("json_doc_id", str(uuid.uuid4())),
r.model_dump(mode="json"),
)
print(f" [Write memory debug] Saved memories: {result['responses']}")
# Create graph builder
graph_builder = StateGraph(MessagesState)
# Add nodes
graph_builder.add_node("call_model", call_model)
graph_builder.add_node("write_memory", write_memory)
# Connect nodes
graph_builder.add_edge(START, "call_model")
graph_builder.add_edge("call_model", "write_memory")
graph_builder.add_edge("write_memory", END)
# Store for long-term (across-thread) memory
long_term_memory = InMemoryStore()
# Checkpointer for short-term (within-thread) memory
short_term_memory = MemorySaver()
# Compile the graph
graph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Empezamos una nueva conversación

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "1", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="Hi, my name is Maximo")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi, my name is Maximo
[Call model debug] Memories: []
================================== Ai Message ==================================
Hello Maximo! It's nice to meet you. I'm your companion chatbot, here to chat, help answer questions, or just be someone to talk to.
I'll remember your name is Maximo for our future conversations. What would you like to talk about today? How are you doing?
[Write memory debug] Existing memories: None
[Write memory debug] Saved memories: [Memory(content="User's name is Maximo.")]

Añadimos un nuevo interés del usuario

	
< > Input
Python
# User input
input_messages = [HumanMessage(content="I like to play soccer")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I like to play soccer
[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None)]
================================== Ai Message ==================================
That's great to know, Maximo! Soccer is such a wonderful sport. Do you play on a team, or more casually with friends? I'd also be curious to know what position you typically play, or if you have a favorite professional team you follow. I'll remember that you enjoy soccer for our future conversations.
[Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."})]
[Write memory debug] Saved memories: [Memory(content='User enjoys playing soccer.')]

Como vemos, se ha añadido el nuevo interés del usuario a la memoria.

Vamos a ver la memoria actualizada

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memories", user_id)
memories = long_term_memory.search(namespace)
for m in memories:
print(m.dict())
Copied
>_ Output
			
{'namespace': ['memories', '1'], 'key': '6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'value': {'content': "User's name is Maximo."}, 'created_at': '2025-05-12T18:32:38.070902+00:00', 'updated_at': '2025-05-12T18:32:38.070903+00:00', 'score': None}
{'namespace': ['memories', '1'], 'key': '25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'value': {'content': 'User enjoys playing soccer.'}, 'created_at': '2025-05-12T18:32:42.558787+00:00', 'updated_at': '2025-05-12T18:32:42.558789+00:00', 'score': None}
	
< > Input
Python
for m in memories:
print(m.value)
Copied
>_ Output
			
{'content': "User's name is Maximo."}
{'content': 'User enjoys playing soccer.'}

Vemos que se guardan documentos de memoria, no un perfil del usuario.

Vamos a añadir un nuevo interés del usuario

	
< > Input
Python
# User input
input_messages = [HumanMessage(content="I also like to play basketball")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I also like to play basketball
[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None), Item(namespace=['memories', '1'], key='25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', value={'content': 'User enjoys playing soccer.'}, created_at='2025-05-12T18:32:42.558787+00:00', updated_at='2025-05-12T18:32:42.558789+00:00', score=None)]
================================== Ai Message ==================================
That's awesome, Maximo! Both soccer and basketball are fantastic sports. I'll remember that you enjoy basketball as well. Do you find yourself playing one more than the other? And similar to soccer, do you play basketball with a team or more casually? Many people enjoy the different skills and dynamics each sport offers - soccer with its continuous flow and footwork, and basketball with its fast pace and shooting precision. Any favorite basketball teams you follow?
[Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."}), ('25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'Memory', {'content': 'User enjoys playing soccer.'})]
[Write memory debug] Saved memories: [Memory(content='User enjoys playing basketball.')]

Volvemos a ver la memoria actualizada

	
< > Input
Python
# Namespace for the memory to save
user_id = "1"
namespace = ("memories", user_id)
memories = long_term_memory.search(namespace)
for m in memories:
print(m.value)
Copied
>_ Output
			
{'content': "User's name is Maximo."}
{'content': 'User enjoys playing soccer.'}
{'content': 'User enjoys playing basketball.'}

Iniciamos una nueva conversación con un nuevo hilo

	
< > Input
Python
# We supply a thread ID for short-term (within-thread) memory
# We supply a user ID for long-term (across-thread) memory
config = {"configurable": {"thread_id": "2", "user_id": "1"}}
# User input
input_messages = [HumanMessage(content="What soccer players do you recommend for me?")]
# Run the graph
for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
What soccer players do you recommend for me?
[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None), Item(namespace=['memories', '1'], key='25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', value={'content': 'User enjoys playing soccer.'}, created_at='2025-05-12T18:32:42.558787+00:00', updated_at='2025-05-12T18:32:42.558789+00:00', score=None), Item(namespace=['memories', '1'], key='965f2e52-bea0-44d4-8534-4fce2bbc1c4b', value={'content': 'User enjoys playing basketball.'}, created_at='2025-05-12T18:33:38.613626+00:00', updated_at='2025-05-12T18:33:38.613629+00:00', score=None)]
================================== Ai Message ==================================
Hi Maximo! Since you enjoy soccer, I'd be happy to recommend some players you might find interesting to follow or learn from.
Based on your interests in both soccer and basketball, I might suggest players who are known for their athleticism and skill:
1. Lionel Messi - Widely considered one of the greatest players of all time
2. Cristiano Ronaldo - Known for incredible athleticism and dedication
3. Kylian Mbappé - Young talent with amazing speed and technical ability
4. Kevin De Bruyne - Master of passing and vision
5. Erling Haaland - Goal-scoring phenomenon
Is there a particular position or playing style you're most interested in? That would help me refine my recommendations further. I could also suggest players from specific leagues or teams if you have preferences!
[Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."}), ('25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'Memory', {'content': 'User enjoys playing soccer.'}), ('965f2e52-bea0-44d4-8534-4fce2bbc1c4b', 'Memory', {'content': 'User enjoys playing basketball.'})]
[Write memory debug] Saved memories: [Memory(content='User asked for soccer player recommendations, suggesting an active interest in following professional soccer beyond just playing it.')]

Vemos que se acordaba de que nos gustaba el fútbol y el baloncesto.

Human in the looplink image 9

Aunque un agente puede realizar tareas, para según qué tareas, es necesario que haya una supervisión humana. A esto se le llama human in the loop. Así que vamos a ver cómo se puede hacer esto con LangGraph.

La capa de persistencia de LangGraph admite flujos de trabajo con humanos en el bucle, lo que permite que la ejecución se detenga y reanude en función de los comentarios de los usuarios. La interfaz principal de esta funcionalidad es la función interrupt. Llamando a interrupt dentro de un nodo se detendrá la ejecución. La ejecución se puede reanudar, junto con la nueva aportación del humano, pasada en una primitiva Command. interrupt es similar al comando de Python input(), pero con algunas consideraciones extra.

Vamos a añadir al chatbot que tiene memoria a corto plazo y acceso a tools, pero haremos un cambio, que es agregar una simple herramienta human_assistance. Esta herramienta utiliza interrupt para recibir información de un humano.

Primero cargamos los valores de las API KEYs

	
< > Input
Python
import os
import dotenv
dotenv.load_dotenv()
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")
TAVILY_API_KEY = os.getenv("TAVILY_LANGGRAPH_API_KEY")
Copied

Creamos el grafo

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
Copied

Definimos la tool de búsqueda

	
< > Input
Python
from langchain_community.utilities.tavily_search import TavilySearchAPIWrapper
from langchain_community.tools.tavily_search import TavilySearchResults
wrapper = TavilySearchAPIWrapper(tavily_api_key=TAVILY_API_KEY)
search_tool = TavilySearchResults(api_wrapper=wrapper, max_results=2)
Copied

Ahora creamos la tool de ayuda humana

	
< > Input
Python
from langgraph.types import Command, interrupt
from langchain_core.tools import tool
@tool
def human_assistance(query: str) -&gt; str:
"""
Request assistance from a human expert. Use this tool ONLY ONCE per conversation.
After receiving the expert's response, you should provide an elaborated response to the user based on the information received
based on the information received, without calling this tool again.
Args:
query: The query to ask the human expert.
Returns:
The response from the human expert.
"""
human_response = interrupt({"query": query})
return human_response["data"]
Copied

LangGraph obtiene información de las herramientas mediante la documentación de la herramienta, es decir, el docstring de la función. Por lo que es muy importante generar un buen docstring para la herramienta.

Creamos una lista de tools

	
< > Input
Python
tools_list = [search_tool, human_assistance]
Copied

A continuación, el LLM con las bind_tools y lo añadimos al grafo

	
< > Input
Python
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from huggingface_hub import login
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Create the LLM
login(token=HUGGINGFACE_TOKEN)
MODEL = "Qwen/Qwen2.5-72B-Instruct"
model = HuggingFaceEndpoint(
repo_id=MODEL,
task="text-generation",
max_new_tokens=512,
do_sample=False,
repetition_penalty=1.03,
)
# Create the chat model
llm = ChatHuggingFace(llm=model)
# Modification: tell the LLM which tools it can call
llm_with_tools = llm.bind_tools(tools_list)
# Define the chatbot function
def chatbot_function(state: State):
message = llm_with_tools.invoke(state["messages"])
assert len(message.tool_calls) &lt;= 1
return {"messages": [message]}
# Add the chatbot node
graph_builder.add_node("chatbot_node", chatbot_function)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x10764b380&gt;

Si te fijas, hemos cambiado la forma de definir la función chatbot_function, ya que ahora tiene que manejar la interrupción.

Añadimos la tool_node al grafo

	
< > Input
Python
from langgraph.prebuilt import ToolNode, tools_condition
tool_node = ToolNode(tools=tools_list)
graph_builder.add_node("tools", tool_node)
graph_builder.add_conditional_edges("chatbot_node", tools_condition)
graph_builder.add_edge("tools", "chatbot_node")
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x10764b380&gt;

Añadimos el nodo de START al grafo

	
< > Input
Python
graph_builder.add_edge(START, "chatbot_node")
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x10764b380&gt;

Creamos un checkpointer MemorySaver.

	
< > Input
Python
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
Copied

Compilamos el grafo con el checkpointer

	
< > Input
Python
graph = graph_builder.compile(checkpointer=memory)
Copied

Lo representamos gráficamente

	
< > Input
Python
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception as e:
print(f"Error al visualizar el grafo: {e}")
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Ahora solicitemos al chatbot con una pregunta que involucrará la nueva herramienta human_assistance:

	
< > Input
Python
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
config = {"configurable": {"thread_id": "1"}}
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I need some expert guidance for building an AI agent. Could you request assistance for me?
================================== Ai Message ==================================
Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need some expert guidance for building an AI agent. Could you provide me with some advice?

Cómo se puede ver, el chatbot generó una llamada a la herramienta de asistencia humana.

Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need some expert guidance for building an AI agent. Could you provide advice on key considerations, best practices, and potential pitfalls to avoid?

Pero luego la ejecución se ha interrumpido. Vamos a ver el estado del grafo

	
< > Input
Python
snapshot = graph.get_state(config)
snapshot.next
Copied
>_ Output
			
('tools',)

Vemos que se detuvo en el nodo de tools. Analizamos cómo se ha definido la herramienta human_assistance.

from langgraph.types import Command, interrupt
from langchain_core.tools import tool

@tool
def human_assistance(query: str) -> str:
"""
Request assistance from a human expert. Use this tool ONLY ONCE per conversation.
After receiving the expert's response, you should provide an elaborated response to the user based on the information received
based on the information received, without calling this tool again.

Args:
query: The query to ask the human expert.

Returns:
The response from the human expert.
"""
human_response = interrupt({"query": query})
return human_response["data"]

Llamando a la herramienta interrupt se detendrá la ejecución, similar a la función de Python input().

El progreso se mantiene en función de nuestra elección de checkpointer. Es decir, la elección de dónde se guarda el estado del grafo. Así que si estamos persistiendo (guardando el estado del grafo) con una base de datos como SQLite, Postgres, etc., podemos reanudar la ejecución en cualquier momento siempre y cuando la base de datos esté viva.

Aquí estamos persistiendo (guardando el estado del grafo) con el puntero de verificación en memoria RAM, por lo que podemos reanudar en cualquier momento mientras nuestro kernel de Python se esté ejecutando. En mi caso, mientras no reseete el kernel de mi Jupyter Notebook.

Para reanudar la ejecución, pasamos un objeto Command que contiene los datos esperados por la herramienta. El formato de estos datos se puede personalizar en función de nuestras necesidades. Aquí, solo necesitamos un diccionario con una key data

	
< > Input
Python
human_response = (
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
"It's much more reliable and extensible than simple autonomous agents."
)
human_command = Command(resume={"data": human_response})
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================== Ai Message ==================================
Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need some expert guidance for building an AI agent. Could you provide me with some advice?
================================= Tool Message =================================
Name: human_assistance
We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.It's much more reliable and extensible than simple autonomous agents.
================================== Ai Message ==================================
The experts recommend checking out LangGraph for building your AI agent. It's known for being more reliable and extensible compared to simple autonomous agents.

Como vemos, el chatbot ha esperado a que un humano le proporcione la respuesta y luego ha generado una respuesta basada en la información recibida. Le hemos pedido ayuda sobre un experto sobre cómo crear agentes, el humano le ha dicho que lo mejor es usar LangGraph, y el chatbot ha generado una respuesta basada en esa información.

Pero sigue teniendo la posibilidad de realizar búsquedas en la web. Así que ahora le vamos a pedir las últimas noticias sobre LangGraph.

	
< > Input
Python
user_input = "What's the latest news about LangGraph?"
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
What's the latest news about LangGraph?
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: latest news LangGraph
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "LangChain - Changelog", "url": "https://changelog.langchain.com/", "content": "LangGraph `interrupt`: Simplifying human-in-the-loop agents --------------------------------------------------- Our latest feature in LangGraph, interrupt , makes building human-in-the-loop workflows easier. Agents aren’t perfect, so keeping humans “in the loop”... December 16, 2024 [...] LangGraph 🔁 Modify graph state from tools in LangGraph --------------------------------------------- LangGraph's latest update gives you greater control over your agents by enabling tools to directly update the graph state. This is a game-changer for use... December 18, 2024 [...] LangGraph Platform Custom authentication &amp; access control for LangGraph Platform ------------------------------------------------------------- Today, we're thrilled to announce Custom Authentication and Resource-Level Access Control for Python deployments in LangGraph Cloud and self-hosted... December 20, 2024", "score": 0.78650844}, {"title": "LangGraph 0.3 Release: Prebuilt Agents - LangChain Blog", "url": "https://blog.langchain.dev/langgraph-0-3-release-prebuilt-agents/", "content": "LangGraph 0.3 Release: Prebuilt Agents 2 min read Feb 27, 2025 By Nuno Campos and Vadym Barda Over the past year, we’ve invested heavily in making LangGraph the go-to framework for building AI agents. With companies like Replit, Klarna, LinkedIn and Uber choosing to build on top of LangGraph, we have more conviction than ever that we are on the right path. [...] Up to this point, we’ve had one higher level abstraction and it’s lived in the main langgraph package. It was create_react_agent, a wrapper for creating a simple tool calling agent. Today, we are splitting that out of langgraph as part of a 0.3 release, and moving it into langgraph-prebuilt. We are also introducing a new set of prebuilt agents built on top of LangGraph, in both Python and JavaScript. Over the past three weeks, we’ve already released a few of these: [...] Published Time: 2025-02-27T15:09:15.000Z LangGraph 0.3 Release: Prebuilt Agents Skip to content Case Studies In the Loop LangChain Docs Changelog Sign in Subscribe", "score": 0.72348577}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: latest news about LangGraph
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "LangChain - Changelog", "url": "https://changelog.langchain.com/", "content": "LangGraph 🔁 Modify graph state from tools in LangGraph --------------------------------------------- LangGraph's latest update gives you greater control over your agents by enabling tools to directly update the graph state. This is a game-changer for use... December 18, 2024 [...] LangGraph `interrupt`: Simplifying human-in-the-loop agents --------------------------------------------------- Our latest feature in LangGraph, interrupt , makes building human-in-the-loop workflows easier. Agents aren’t perfect, so keeping humans “in the loop”... December 16, 2024 [...] LangGraph Platform Custom authentication &amp; access control for LangGraph Platform ------------------------------------------------------------- Today, we're thrilled to announce Custom Authentication and Resource-Level Access Control for Python deployments in LangGraph Cloud and self-hosted... December 20, 2024", "score": 0.79732054}, {"title": "LangGraph 0.3 Release: Prebuilt Agents - LangChain Blog", "url": "https://blog.langchain.dev/langgraph-0-3-release-prebuilt-agents/", "content": "LangGraph 0.3 Release: Prebuilt Agents 2 min read Feb 27, 2025 By Nuno Campos and Vadym Barda Over the past year, we’ve invested heavily in making LangGraph the go-to framework for building AI agents. With companies like Replit, Klarna, LinkedIn and Uber choosing to build on top of LangGraph, we have more conviction than ever that we are on the right path. [...] Up to this point, we’ve had one higher level abstraction and it’s lived in the main langgraph package. It was create_react_agent, a wrapper for creating a simple tool calling agent. Today, we are splitting that out of langgraph as part of a 0.3 release, and moving it into langgraph-prebuilt. We are also introducing a new set of prebuilt agents built on top of LangGraph, in both Python and JavaScript. Over the past three weeks, we’ve already released a few of these: [...] Published Time: 2025-02-27T15:09:15.000Z LangGraph 0.3 Release: Prebuilt Agents Skip to content Case Studies In the Loop LangChain Docs Changelog Sign in Subscribe", "score": 0.7552947}]
================================== Ai Message ==================================
The latest news about LangGraph includes several updates and releases. Firstly, the 'interrupt' feature has been added, which simplifies creating human-in-the-loop workflows, essential for maintaining oversight of AI agents. Secondly, an update allows tools to modify the graph state directly, providing more control over the agents. Lastly, custom authentication and resource-level access control have been implemented for Python deployments in LangGraph Cloud and self-hosted environments. In addition, LangGraph released version 0.3, which introduces prebuilt agents in both Python and JavaScript, aimed at making it even easier to develop AI agents.

Ha buscado las últimas noticias sobre LangGraph y ha generado una respuesta basada en la información recibida.

Vamos a escribir todo junto para que sea más comprensible

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from huggingface_hub import login
from langchain_community.utilities.tavily_search import TavilySearchAPIWrapper
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.types import Command, interrupt
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from IPython.display import Image, display
import json
import os
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
import dotenv
dotenv.load_dotenv()
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")
TAVILY_API_KEY = os.getenv("TAVILY_LANGGRAPH_API_KEY")
# State
class State(TypedDict):
messages: Annotated[list, add_messages]
# Tools
wrapper = TavilySearchAPIWrapper(tavily_api_key=TAVILY_API_KEY)
tool_search = TavilySearchResults(api_wrapper=wrapper, max_results=2)
@tool
def human_assistance(query: str) -&gt; str:
"""
Request assistance from a human expert. Use this tool ONLY ONCE per conversation.
After receiving the expert's response, you should provide an elaborated response to the user based on the information received
based on the information received, without calling this tool again.
Args:
query: The query to ask the human expert.
Returns:
The response from the human expert.
"""
human_response = interrupt({"query": query})
return human_response["data"]
tools_list = [tool_search, human_assistance]
# Create the LLM model
login(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the model
MODEL = "Qwen/Qwen2.5-72B-Instruct"
model = HuggingFaceEndpoint(
repo_id=MODEL,
task="text-generation",
max_new_tokens=512,
do_sample=False,
repetition_penalty=1.03,
)
# Create the chat model
llm = ChatHuggingFace(llm=model)
# Create the LLM with tools
llm_with_tools = llm.bind_tools(tools_list)
# Tool node
tool_node = ToolNode(tools=tools_list)
# Functions
def chatbot_function(state: State):
message = llm_with_tools.invoke(state["messages"])
assert len(message.tool_calls) &lt;= 1
return {"messages": [message]}
# Start to build the graph
graph_builder = StateGraph(State)
# Add nodes to the graph
graph_builder.add_node("chatbot_node", chatbot_function)
graph_builder.add_node("tools", tool_node)
# Add edges
graph_builder.add_edge(START, "chatbot_node")
graph_builder.add_conditional_edges( "chatbot_node", tools_condition)
graph_builder.add_edge("tools", "chatbot_node")
# Compile the graph
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
# Display the graph
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception as e:
print(f"Error al visualizar el grafo: {e}")
Copied
>_ Output
			
Error al visualizar el grafo: Failed to reach https://mermaid.ink/ API while trying to render your graph after 1 retries. To resolve this issue:
1. Check your internet connection and try again
2. Try with higher retry settings: `draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`
3. Use the Pyppeteer rendering method which will render your graph locally in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`

Volvemos a pedirle ayuda al chatbot para crear agentes. Le pedimos que busque ayuda

	
< > Input
Python
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
config = {"configurable": {"thread_id": "1"}}
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
I need some expert guidance for building an AI agent. Could you request assistance for me?
================================== Ai Message ==================================
Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need expert guidance for building an AI agent.

Vemos en qué estado se ha quedado el grafo

	
< > Input
Python
snapshot = graph.get_state(config)
snapshot.next
Copied
>_ Output
			
('tools',)

Le damos la asistencia que está pidiendo

	
< > Input
Python
human_response = (
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
"It's much more reliable and extensible than simple autonomous agents."
)
human_command = Command(resume={"data": human_response})
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================== Ai Message ==================================
Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need expert guidance for building an AI agent.
================================= Tool Message =================================
Name: human_assistance
We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.It's much more reliable and extensible than simple autonomous agents.
================================== Ai Message ==================================
Tool Calls:
human_assistance (0)
Call ID: 0
Args:
query: I need some expert guidance for building an AI agent. Could you recommend a platform and any tips for getting started?

Y por último le pedimos que busque en internet las últimas noticias de LangGraph

	
< > Input
Python
user_input = "What's the latest news about LangGraph?"
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
What's the latest news about LangGraph?
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: latest news about LangGraph
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "LangChain Blog", "url": "https://blog.langchain.dev/", "content": "LangSmith Incident on May 1, 2025 Requests to the US LangSmith API from both the web application and SDKs experienced an elevated error rate for 28 minutes on May 1, 2025 Featured How Klarna's AI assistant redefined customer support at scale for 85 million active users Is LangGraph Used In Production? Introducing Interrupt: The AI Agent Conference by LangChain Top 5 LangGraph Agents in Production 2024 [...] See how Harmonic uses LangSmith and LangGraph products to streamline venture investing workflows. Why Definely chose LangGraph for building their multi-agent AI system See how Definely used LangGraph to design a multi-agent system to help lawyers speed up their workflows. Introducing End-to-End OpenTelemetry Support in LangSmith LangSmith now provides end-to-end OpenTelemetry (OTel) support for applications built on LangChain and/or LangGraph.", "score": 0.6811549}, {"title": "LangGraph + UiPath: advancing agentic automation together", "url": "https://www.uipath.com/blog/product-and-updates/langgraph-uipath-advancing-agentic-automation-together", "content": "Raghu Malpani, Chief Technology Officer at UiPath, emphasizes the significance of these announcements for the UiPath developer community: Our collaboration with LangChain on LangSmith and Agent Protocol advances interoperability across agent frameworks. Further, by enabling the deployment of LangGraph agents into UiPath's enterprise-grade infrastructure, we are expanding the capabilities of our platform and opening up more possibilities for our developer community. [...] Today, we’re excited to announce: Native support for LangSmith observability in the UiPath LLM Gateway via OpenTelemetry (OTLP), enabling developers to monitor, debug, and evaluate LLM-powered features in UiPath using LangSmith either in LangChain’s cloud or self-hosted on-premises. This feature is currently in private preview.", "score": 0.6557114}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: latest news about LangGraph
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "LangChain Blog", "url": "https://blog.langchain.dev/", "content": "LangSmith Incident on May 1, 2025 Requests to the US LangSmith API from both the web application and SDKs experienced an elevated error rate for 28 minutes on May 1, 2025 Featured How Klarna's AI assistant redefined customer support at scale for 85 million active users Is LangGraph Used In Production? Introducing Interrupt: The AI Agent Conference by LangChain Top 5 LangGraph Agents in Production 2024 [...] See how Harmonic uses LangSmith and LangGraph products to streamline venture investing workflows. Why Definely chose LangGraph for building their multi-agent AI system See how Definely used LangGraph to design a multi-agent system to help lawyers speed up their workflows. Introducing End-to-End OpenTelemetry Support in LangSmith LangSmith now provides end-to-end OpenTelemetry (OTel) support for applications built on LangChain and/or LangGraph.", "score": 0.6811549}, {"title": "LangGraph + UiPath: advancing agentic automation together", "url": "https://www.uipath.com/blog/product-and-updates/langgraph-uipath-advancing-agentic-automation-together", "content": "Raghu Malpani, Chief Technology Officer at UiPath, emphasizes the significance of these announcements for the UiPath developer community: Our collaboration with LangChain on LangSmith and Agent Protocol advances interoperability across agent frameworks. Further, by enabling the deployment of LangGraph agents into UiPath's enterprise-grade infrastructure, we are expanding the capabilities of our platform and opening up more possibilities for our developer community. [...] Today, we’re excited to announce: Native support for LangSmith observability in the UiPath LLM Gateway via OpenTelemetry (OTLP), enabling developers to monitor, debug, and evaluate LLM-powered features in UiPath using LangSmith either in LangChain’s cloud or self-hosted on-premises. This feature is currently in private preview.", "score": 0.6557114}]
...
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "LangGraph - LangChain", "url": "https://www.langchain.com/langgraph", "content": "“As Ally advances its exploration of Generative AI, our tech labs is excited by LangGraph, the new library from LangChain, which is central to our experiments", "score": 0.98559}, {"title": "Evaluating LangGraph Framework : Series 1 | by Jalaj Agrawal", "url": "https://medium.com/@jalajagr/evaluating-langgraph-as-a-multiagent-framework-a-10-dimensional-framework-series-1-c7203b7f4659", "content": ": LangGraph excels with its intuitive graph-based abstraction that allows new developers to build working multi-agent systems within hours.", "score": 0.98196}]
================================== Ai Message ==================================
It looks like LangGraph has been generating some significant buzz in the AI community, especially for its capabilities in building multi-agent systems. Here are a few highlights from the latest news:
1. **LangGraph in Production**: Companies like Klarna and Definely are already using LangGraph to build and optimize their AI systems. Klarna has leveraged LangGraph to enhance their customer support, and Definely has used it to design a multi-agent system to speed up legal workflows.
2. **Integration with UiPath**: LangChain and UiPath have collaborated to advance agentic automation. This partnership includes native support for LangSmith observability in UiPath’s LLM Gateway via OpenTelemetry, which will allow developers to monitor, debug, and evaluate LLM-powered features more effectively.
3. **Intuitive Design**: LangGraph is praised for its intuitive graph-based abstraction, which enables developers to build working multi-agent systems quickly, even if they are new to the field.
4. **Community and Conferences**: LangChain is also hosting an AI Agent Conference called "Interrupt," which could be a great opportunity to learn more about the latest developments and best practices in building AI agents.
If you're considering using LangGraph for your project, these resources and updates might provide valuable insights and support. Would you like more detailed information on any specific aspect of LangGraph?

Máslink image 10

Aprobación del uso de herramientaslink image 11

Nota: Este apartado lo vamos a hacer usando Sonnet 3.7, ya que a día de la escritura del post, es el mejor modelo para uso con agentes, y es el único que entiende cuándo tiene que llamar a las tools y cuándo no para este ejemplo

Podemos meter un human in the loop para aprobar el uso de herramientas. Vamos a crear un chatbot con varias herramientas para hacer operaciones matemáticas, para ello a la hora de construir el grafo indicamos dónde queremos meter el breakpoint (graph_builder.compile(interrupt_before=["tools"], checkpointer=memory))

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from IPython.display import Image, display
import os
import dotenv
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
memory = MemorySaver()
class State(TypedDict):
messages: Annotated[list, add_messages]
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Tools
@tool
def multiply(a: int, b: int) -&gt; int:
"""Multiply a and b.
Args:
a: first int
b: second int
Returns:
The product of a and b.
"""
return a * b
@tool
def add(a: int, b: int) -&gt; int:
"""Adds a and b.
Args:
a: first int
b: second int
Returns:
The sum of a and b.
"""
return a + b
@tool
def subtract(a: int, b: int) -&gt; int:
"""Subtract b from a.
Args:
a: first int
b: second int
Returns:
The difference between a and b.
"""
return a - b
@tool
def divide(a: int, b: int) -&gt; float:
"""Divide a by b.
Args:
a: first int
b: second int
Returns:
The quotient of a and b.
"""
return a / b
tools_list = [multiply, add, subtract, divide]
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
llm_with_tools = llm.bind_tools(tools_list)
# Nodes
def chat_model_node(state: State):
system_message = "You are a helpful assistant that can use tools to answer questions. Once you have the result of a tool, provide a final answer without calling more tools."
messages = [SystemMessage(content=system_message)] + state["messages"]
return {"messages": [llm_with_tools.invoke(messages)]}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("chatbot_node", chat_model_node)
tool_node = ToolNode(tools=tools_list)
graph_builder.add_node("tools", tool_node)
# Connecto nodes
graph_builder.add_edge(START, "chatbot_node")
graph_builder.add_conditional_edges("chatbot_node", tools_condition)
graph_builder.add_edge("tools", "chatbot_node")
graph_builder.add_edge("chatbot_node", END)
# Compile the graph
graph = graph_builder.compile(interrupt_before=["tools"], checkpointer=memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Como vemos en el grafo, hay un interrupt antes de usar las tools. Eso significa que se va a parar antes de usarlas para pedirnos permiso

	
< > Input
Python
# Input
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}
config = {"configurable": {"thread_id": "1"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, config, stream_mode="updates"):
if 'chatbot_node' in event:
print(event['chatbot_node']['messages'][-1].pretty_print())
else:
print(event)
Copied
>_ Output
			
================================== Ai Message ==================================
[{'text': "I'll multiply 2 and 3 for you.", 'type': 'text'}, {'id': 'toolu_01QDuind1VBHWtvifELN9SPf', 'input': {'a': 2, 'b': 3}, 'name': 'multiply', 'type': 'tool_use'}]
Tool Calls:
multiply (toolu_01QDuind1VBHWtvifELN9SPf)
Call ID: toolu_01QDuind1VBHWtvifELN9SPf
Args:
a: 2
b: 3
None
{'__interrupt__': ()}

Como podemos ver, el LLM sabe que tiene que usar la herramienta multiply, pero se interrumpe la ejecución, porque tiene que esperar a que un humano le autorice el uso de la herramienta.

Podemos ver el estado en el que se ha quedado el grafo

	
< > Input
Python
state = graph.get_state(config)
state.next
Copied
>_ Output
			
('tools',)

Como vemos, se ha quedado en el nodo de tools.

Podemos crear una función (no en el grafo, sino fuera del grafo, para mejorar la experiencia de usuario y que entienda por qué se para la ejecución) que le pida al usuario que apruebe el uso de la herramienta.

Creamos un nuevo thread_id para que se cree un nuevo estado.

	
< > Input
Python
# Input
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}
config = {"configurable": {"thread_id": "2"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, config, stream_mode="updates"):
function_name = None
function_args = None
if 'chatbot_node' in event:
for element in event['chatbot_node']['messages'][-1].content:
if element['type'] == 'text':
print(element['text'])
elif element['type'] == 'tool_use':
function_name = element['name']
function_args = element['input']
print(f"The LLM wants to use the tool {function_name} with the arguments {function_args}")
elif '__interrupt__' in event:
pass
else:
print(event)
question = f"Do you approve the use of the tool {function_name} with the arguments {function_args}? (y/n)"
user_approval = input(question)
print(f"{question}: {user_approval}")
if user_approval.lower() == 'y':
print("User approved the use of the tool")
for event in graph.stream(None, config, stream_mode="updates"):
if 'chatbot_node' in event:
for element in event['chatbot_node']['messages'][-1].content:
if isinstance(element, str):
print(element, end="")
elif 'tools' in event:
result = event['tools']['messages'][-1].content
tool_used = event['tools']['messages'][-1].name
print(f"The result of the tool {tool_used} is {result}")
else:
print(event)
Copied
>_ Output
			
I'll multiply 2 and 3 for you.
The LLM wants to use the tool multiply with the arguments {'a': 2, 'b': 3}
Do you approve the use of the tool None with the arguments None? (y/n): y
User approved the use of the tool
The result of the tool multiply is 6
The result of multiplying 2 and 3 is 6.

Podemos ver que nos ha preguntado si aprobamos el uso de la tool de multiplicación, la hemos aprobado y el grafo ha terminado la ejecución. Vemos el estado del grafo.

	
< > Input
Python
state = graph.get_state(config)
state.next
Copied
>_ Output
			
()

Vemos que el siguiente estado del grafo está vacío, eso indica que ha terminado la ejecución del grafo

Modificación del estadolink image 12

Nota: Este apartado lo vamos a hacer usando Sonnet 3.7, ya que a día de la escritura del post, es el mejor modelo para uso con agentes, y es el único que entiende cuándo tiene que llamar a las tools y cuándo no para este ejemplo

Vamos a repetir el ejemplo de antes, pero en vez de interrumpir el grafo antes del uso de una tool, lo vamos a interrumpir en el LLM, para ello, a la hora de construir el grafo indicamos que queremos pararlo en el agente (graph_builder.compile(interrupt_before=["chatbot_node"], checkpointer=memory))

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from IPython.display import Image, display
import os
import dotenv
dotenv.load_dotenv()
ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")
memory = MemorySaver()
class State(TypedDict):
messages: Annotated[list, add_messages]
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Tools
@tool
def multiply(a: int, b: int) -&gt; int:
"""Multiply a and b.
Args:
a: first int
b: second int
Returns:
The product of a and b.
"""
return a * b
@tool
def add(a: int, b: int) -&gt; int:
"""Adds a and b.
Args:
a: first int
b: second int
Returns:
The sum of a and b.
"""
return a + b
@tool
def subtract(a: int, b: int) -&gt; int:
"""Subtract b from a.
Args:
a: first int
b: second int
Returns:
The difference between a and b.
"""
return a - b
@tool
def divide(a: int, b: int) -&gt; float:
"""Divide a by b.
Args:
a: first int
b: second int
Returns:
The quotient of a and b.
"""
return a / b
tools_list = [multiply, add, subtract, divide]
# Create the LLM model
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)
llm_with_tools = llm.bind_tools(tools_list)
# Nodes
def chat_model_node(state: State):
system_message = "You are a helpful assistant that can use tools to answer questions. Once you have the result of a tool, provide a final answer without calling more tools."
messages = [SystemMessage(content=system_message)] + state["messages"]
return {"messages": [llm_with_tools.invoke(messages)]}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("chatbot_node", chat_model_node)
tool_node = ToolNode(tools=tools_list)
graph_builder.add_node("tools", tool_node)
# Connecto nodes
graph_builder.add_edge(START, "chatbot_node")
graph_builder.add_conditional_edges("chatbot_node", tools_condition)
graph_builder.add_edge("tools", "chatbot_node")
graph_builder.add_edge("chatbot_node", END)
# Compile the graph
graph = graph_builder.compile(interrupt_before=["chatbot_node"], checkpointer=memory)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Vemos en la representación del grafo que hay un interrupt antes de la ejecución de chatbot_node, así que antes de que se ejecute el chatbot se interrumpirá la ejecución y tendremos que hacer nosotros que continúe

Ahora le volvemos a pedir una multiplicación

	
< > Input
Python
# Input
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}
config = {"configurable": {"thread_id": "1"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, config, stream_mode="updates"):
if 'chatbot_node' in event:
print(event['chatbot_node']['messages'][-1].pretty_print())
else:
print(event)
Copied
>_ Output
			
{'__interrupt__': ()}

Podemos ver que no ha hecho nada. Si vemos el estado

	
< > Input
Python
state = graph.get_state(config)
state.next
Copied
>_ Output
			
('chatbot_node',)

Vemos que el siguiente nodo es el de chatbot. Además, si vemos sus valores, vemos el mensaje que le hemos mandado

	
< > Input
Python
state.values
Copied
>_ Output
			
{'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='08fd6084-ecd2-4156-ab24-00d2d5c26f00')]}

Ahora procedemos a modificar el estado, añadiendo un nuevo mensaje

	
< > Input
Python
graph.update_state(
config,
{"messages": [HumanMessage(content="No, actually multiply 3 and 3!")]}
)
Copied
>_ Output
			
{'configurable': {'thread_id': '1',
'checkpoint_ns': '',
'checkpoint_id': '1f027eb6-6c8b-6b6a-8001-bc0f8942566c'}}

Obtenemos el nuevo estado

	
< > Input
Python
new_state = graph.get_state(config)
new_state.next
Copied
>_ Output
			
('chatbot_node',)

El siguiente nodo sigue siendo el del chatbot, pero si ahora vemos los mensajes

	
< > Input
Python
new_state.values
Copied
>_ Output
			
{'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='08fd6084-ecd2-4156-ab24-00d2d5c26f00'),
HumanMessage(content='No, actually multiply 3 and 3!', additional_kwargs={}, response_metadata={}, id='e95394c2-e62e-47d2-b9b2-51eba40f3e22')]}

Vemos que se ha añadido el nuevo. Así que hacemos que continúe la ejecución

	
< > Input
Python
for event in graph.stream(None, config, stream_mode="values"):
event['messages'][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
No, actually multiply 3 and 3!
================================== Ai Message ==================================
[{'text': "I'll multiply 3 and 3 for you.", 'type': 'text'}, {'id': 'toolu_01UABhLnEdg5ZqxVQTE5pGUx', 'input': {'a': 3, 'b': 3}, 'name': 'multiply', 'type': 'tool_use'}]
Tool Calls:
multiply (toolu_01UABhLnEdg5ZqxVQTE5pGUx)
Call ID: toolu_01UABhLnEdg5ZqxVQTE5pGUx
Args:
a: 3
b: 3
================================= Tool Message =================================
Name: multiply
9

Se ha hecho la multiplicación de 3 por 3, que es la modificación del estado que hemos hecho, y no 2 por 3, que es lo que le pedimos la primera vez

Esto puede ser útil cuando tenemos un agente y queremos revisar que lo que hace esté bien, por lo que podemos entrar en la ejecución y modificar el estado

Breakpoints dinámicoslink image 13

Hasta ahora hemos creado breakpoints estáticos mediante la compilación del grafo, pero podemos crear breakpoints dinámicos mediante NodeInterrupt. Esto es útil porque se puede interrumpir la ejecución por reglas lógicas introducidas por programación

Estos NodeInterrupt permiten personalizar cómo se va a notificar al usuario de la interrupción

	
< > Input
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.errors import NodeInterrupt
from huggingface_hub import login
from IPython.display import Image, display
import os
import dotenv
dotenv.load_dotenv()
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")
memory_saver = MemorySaver()
class State(TypedDict):
messages: Annotated[list, add_messages]
os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing
# Create the LLM model
login(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the model
MODEL = "Qwen/Qwen2.5-72B-Instruct"
model = HuggingFaceEndpoint(
repo_id=MODEL,
task="text-generation",
max_new_tokens=512,
do_sample=False,
repetition_penalty=1.03,
)
# Create the chat model
llm = ChatHuggingFace(llm=model)
# Nodes
def chatbot_function(state: State):
max_len = 15
input_message = state["messages"][-1]
# Check len message
if len(input_message.content) &gt; max_len:
raise NodeInterrupt(f"Received input is longer than {max_len} characters --&gt; {input_message}")
# Invoke the LLM with the messages
response = llm.invoke(state["messages"])
# Return the LLM's response in the correct state format
return {"messages": [response]}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("chatbot_node", chatbot_function)
# Connecto nodes
graph_builder.add_edge(START, "chatbot_node")
graph_builder.add_edge("chatbot_node", END)
# Compile the graph
graph = graph_builder.compile(checkpointer=memory_saver)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Como se puede ver, hemos creado una interrupción en caso de que el mensaje sea largo. Vamos a probarlo

	
< > Input
Python
initial_input = {"messages": HumanMessage(content="Hello, how are you? My name is Máximo")}
config = {"configurable": {"thread_id": "1"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, config, stream_mode="updates"):
if 'chatbot_node' in event:
print(event['chatbot_node']['messages'][-1].pretty_print())
else:
print(event)
Copied
>_ Output
			
{'__interrupt__': (Interrupt(value="Received input is longer than 15 characters --&gt; content='Hello, how are you? My name is Máximo' additional_kwargs={} response_metadata={} id='2bdc6d41-0cfe-4d3c-8748-ca7d46fd5a60'", resumable=False, ns=None),)}

Efectivamente se ha parado la interrupción y nos ha dado el mensaje de error que hemos creado

Si vemos el nodo en el que se ha parado

	
< > Input
Python
state = graph.get_state(config)
state.next
Copied
>_ Output
			
('chatbot_node',)

Vemos que está parado en el nodo del chatbot. Podemos volver a hacer que continúe con la ejecución, pero nos va a dar el mismo error

	
< > Input
Python
for event in graph.stream(None, config, stream_mode="updates"):
if 'chatbot_node' in event:
print(event['chatbot_node']['messages'][-1].pretty_print())
else:
print(event)
Copied
>_ Output
			
{'__interrupt__': (Interrupt(value="Received input is longer than 15 characters --&gt; content='Hello, how are you? My name is Máximo' additional_kwargs={} response_metadata={} id='2bdc6d41-0cfe-4d3c-8748-ca7d46fd5a60'", resumable=False, ns=None),)}

Así que tenemos que modificar el estado

	
< > Input
Python
graph.update_state(
config,
{"messages": [HumanMessage(content="How are you?")]}
)
Copied
>_ Output
			
{'configurable': {'thread_id': '1',
'checkpoint_ns': '',
'checkpoint_id': '1f027f13-5827-6a18-8001-4209d5a866f0'}}

Volvemos a ver el estado y sus valores

	
< > Input
Python
new_state = graph.get_state(config)
print(f"Siguiente nodo: {new_state.next}")
print("Valores:")
for value in new_state.values["messages"]:
print(f" {value.content}")
Copied
>_ Output
			
Siguiente nodo: ('chatbot_node',)
Valores:
Hello, how are you? My name is Máximo
How are you?

El último mensaje es más corto, por lo que intentamos reanudar la ejecución del grafo

	
< > Input
Python
for event in graph.stream(None, config, stream_mode="updates"):
if 'chatbot_node' in event:
print(event['chatbot_node']['messages'][-1].pretty_print())
else:
print(event)
Copied
>_ Output
			
================================== Ai Message ==================================
Hello Máximo! I'm doing well, thank you for asking. How about you? How can I assist you today?
None

---

➡️ **Continúa en la Parte 4: personalización del estado y checkpoints**.

Seguir leyendo

Últimos posts -->

¿Has visto estos proyectos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicación móvil de entrenamiento personal con asistente de IA, biblioteca de ejercicios, seguimiento de rutinas, dieta y medidas corporales

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cocineros de hoteles y restaurantes. Un cocinero, jefe de cocina o camaeror de un hotel o restaurante puede hablar con el chatbot para obtener información de recetas y menús. Pero además implementa agentes, con los cuales puede editar o crear nuevas recetas o menús

Naviground

Naviground Naviground
Ver todos los proyectos -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres ver alguna charla?

Últimas charlas -->

¿Quieres mejorar con estos tips?

Últimos tips -->

Usa esto en local

Los espacios de Hugging Face nos permite ejecutar modelos con demos muy sencillas, pero ¿qué pasa si la demo se rompe? O si el usuario la elimina? Por ello he creado contenedores docker con algunos espacios interesantes, para poder usarlos de manera local, pase lo que pase. De hecho, es posible que si pinchas en alún botón de ver proyecto te lleve a un espacio que no funciona.

Flow edit

Flow edit Flow edit

Edita imágenes con este modelo de Flow. Basándose en SD3 o FLUX puedes editar cualquier imagen y generar nuevas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos los contenedores -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres entrenar tu modelo con estos datasets?

short-jokes-dataset

HuggingFace

Dataset de chistes en inglés

Uso: Fine-tuning de modelos de generación de texto humorístico

231K filas 2 columnas 45 MB
Ver en HuggingFace →

opus100

HuggingFace

Dataset con traducciones de inglés a español

Uso: Entrenamiento de modelos de traducción inglés-español

1M filas 2 columnas 210 MB
Ver en HuggingFace →

netflix_titles

HuggingFace

Dataset con películas y series de Netflix

Uso: Análisis de catálogo de Netflix y sistemas de recomendación

8.8K filas 12 columnas 3.5 MB
Ver en HuggingFace →
Ver más datasets -->