LangGraph (2/4): memória de curto prazo

LangGraph (2/4): memória de curto prazo

Na primeira parte construímos um chatbot básico com LangGraph e adicionámos ferramentas. O problema é que ele não se lembra de nada de uma mensagem para a seguinte. Neste capítulo damos-lhe **memória de curto prazo**: persistimos o estado do grafo dentro de uma thread com checkpointers.

⚠️ Este capítulo continua o código da parte anterior. Para executá-lo, você precisa do ambiente e do chatbot da Parte 1.

Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.

📚 **Esta entrada faz parte da série _Guia completo de LangGraph_**, dividida em quatro capítulos que se leem em ordem:

> * Parte 1: Chatbot básico y herramientas

* 👉 **Parte 2: Memória de curto prazo**

* Parte 3: Memória de longo prazo e human-in-the-loop

* Parte 4: Personalização do estado e checkpoints

Adicionar memória ao chatbot - memória de curto prazo, memória dentro do threadlink image 11

Nosso chatbot agora pode usar ferramentas para responder às perguntas dos usuários, mas não se lembra do contexto das interações anteriores. Isso limita sua capacidade de ter conversas coerentes e de múltiplas etapas.

LangGraph resolve esse problema por meio de pontos de controle persistentes ou checkpoints. Se fornecermos um checkpointer ao compilar o grafo e um thread_id ao chamar o grafo, LangGraph salva automaticamente o estado após cada iteração na conversa.

Quando invocarmos o grafo novamente usando o mesmo thread_id, o grafo carregará seu estado salvo, permitindo que o chatbot continue de onde parou.

Veremos mais tarde que esse checkpointing é muito mais poderoso do que a simples memória de chat: ele permite salvar e retomar estados complexos a qualquer momento para recuperação de erros, fluxos de trabalho com human in the loop, interações ao longo do tempo e mais. Mas antes de ver tudo isso, vamos adicionar pontos de controle para permitir conversas de várias iterações.

	
< > 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

Para começar, criamos um checkpointer MemorySaver.

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

**Aviso**

> Estamos usando um checkpointer em memória, ou seja, ele é salvo na RAM e, quando a execução do grafo termina, é removido. Isso nos serve para o nosso caso, já que é um exemplo para aprender a usar LangGraph. Em uma aplicação de produção, é provável que seja necessário बदलá-lo para usá-lo com SqliteSaver ou PostgresSaver e nos conectarmos ao nosso próprio banco de dados.

A seguir, definimos o 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 a tool

	
< > 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)
tool = TavilySearchResults(api_wrapper=wrapper, max_results=2)
tools_list = [tool]
Copied

A seguir, o LLM com as bind_tools e o adicionamos ao 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) # 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)
# 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):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# Add the chatbot node
graph_builder.add_node("chatbot_node", chatbot_function)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x1173534d0&gt;

Antes, construímos nosso próprio BasicToolNode para aprender como funciona; agora o substituiremos pelo método ToolNode e tools_condition do LangGraph, já que eles fazem algumas coisas boas, como a execução paralela de API. Fora isso, o resto é igual ao de antes.

	
< > Input
Python
from langgraph.prebuilt import ToolNode, tools_condition
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("tools", tool_node)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x1173534d0&gt;

Adicionamos o nó de tools_condition ao grafo

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

Adicionamos o nó de tools ao grafo

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

Adicionamos o nó de START ao grafo

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

Compilamos o grafo adicionando o checkpointer

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

Nós o representamos graficamente

	
< > 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;

Criamos uma configuração com um thread_id de um usuário

	
< > Input
Python
USER1_THREAD_ID = "1"
config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}
Copied
	
< > Input
Python
user_input = "Hi there! My name is Maximo."
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER1,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi there! My name is Maximo.
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: does not reside in any location,}},
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Determining an individual's tax residency status - IRS", "url": "https://www.irs.gov/individuals/international-taxpayers/determining-an-individuals-tax-residency-status", "content": "If you are not a U.S. citizen, you are considered a nonresident of the United States for U.S. tax purposes unless you meet one of two tests.", "score": 0.1508904}, {"title": "Fix "Location Is Not Available", C:\WINDOWS\system32 ... - YouTube", "url": "https://www.youtube.com/watch?v=QFD-Ptp0SJw", "content": "Fix Error "Location is not available" C:\WINDOWS\system32\config\systemprofile\Desktop is unavailable. If the location is on this PC,", "score": 0.07777658}]
================================== Ai Message ==================================
Invalid Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
{"query": "Arguments["image={"}
	
< > Input
Python
user_input = "Do you remember my name?"
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER1,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Do you remember my name?
================================== Ai Message ==================================
Of course! You mentioned your name is Maximo.

Como se pode ver, não passamos uma lista com as mensagens; tudo está sendo gerenciado pelo checkpointer.

Se agora testarmos com outro usuário, isto é, com outro thread_id, veremos que o grafo não se lembra da conversa anterior.

	
< > Input
Python
USER2_THREAD_ID = "2"
config_USER2 = {"configurable": {"thread_id": USER2_THREAD_ID}}
user_input = "Do you remember my name?"
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER2,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Do you remember my name?
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: Do you Remember My Name
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Sam Fender - Remember My Name (Official Video) - YouTube", "url": "https://www.youtube.com/watch?v=uaQm48G6IjY", "content": "Sam Fender - Remember My Name (Official Video) SamFenderVEVO 10743 likes 862209 views 14 Feb 2025 Remember My Name is a love song dedicated to my late Grandparents - they were always so fiercely proud of our family so I wrote the song in honour of them, from the perspective of my Grandad who was looking after my Grandma when she was suffering from dementia. This video is a really special one for me and I want to say thank you to everyone involved in making it. I hope you like it ❤️ [...] If I was wanting of anymore I’d be as greedy as those men on the hill But I remain forlorn In the memory of what once was Chasing a cross in from the wing Our boy’s a whippet, he’s faster than anything Remember the pride that we felt For the two of us made him ourselves Humour me Make my day I’ll tell you stories Kiss your face And I’ll pray You’ll remember My name I’m not sure of what awaits Wasn’t a fan of St Peter and his gates But by god I pray That I’ll see you in some way [...] Oh 11 Walk Avenue Something to behold To them it’s a council house To me it’s a home And a home that you made Where the grandkids could play But it’s never the same without you Humour me Make my day I’ll tell you stories I’ll kiss your face And I’ll pray You’ll remember My name And I’ll pray you remember my name And I’ll pray you remember my name ---", "score": 0.6609831}, {"title": "Do You Remember My Name? - Novel Updates", "url": "https://www.novelupdates.com/series/do-you-remember-my-name/", "content": "This is a Cute, Tender, and Heartwarming High School Romance. It's not Heavy. It's not so Emotional too, but it does have Emotional moments. It's story Full of", "score": 0.608897}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: do you remember my name
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Sam Fender - Remember My Name (Official Video) - YouTube", "url": "https://www.youtube.com/watch?v=uaQm48G6IjY", "content": "Sam Fender - Remember My Name (Official Video) SamFenderVEVO 10743 likes 862209 views 14 Feb 2025 Remember My Name is a love song dedicated to my late Grandparents - they were always so fiercely proud of our family so I wrote the song in honour of them, from the perspective of my Grandad who was looking after my Grandma when she was suffering from dementia. This video is a really special one for me and I want to say thank you to everyone involved in making it. I hope you like it ❤️ [...] Oh 11 Walk Avenue Something to behold To them it’s a council house To me it’s a home And a home that you made Where the grandkids could play But it’s never the same without you Humour me Make my day I’ll tell you stories I’ll kiss your face And I’ll pray You’ll remember My name And I’ll pray you remember my name And I’ll pray you remember my name --- [...] If I was wanting of anymore I’d be as greedy as those men on the hill But I remain forlorn In the memory of what once was Chasing a cross in from the wing Our boy’s a whippet, he’s faster than anything Remember the pride that we felt For the two of us made him ourselves Humour me Make my day I’ll tell you stories Kiss your face And I’ll pray You’ll remember My name I’m not sure of what awaits Wasn’t a fan of St Peter and his gates But by god I pray That I’ll see you in some way", "score": 0.7123327}, {"title": "Do you remember my name? - song and lyrics by Alea, Mama Marjas", "url": "https://open.spotify.com/track/3GVBn3rEQLxZl4zJ4dG8UJ", "content": "Listen to Do you remember my name? on Spotify. Song · Alea, Mama Marjas · 2023.", "score": 0.6506676}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: do you remember my name
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Sam Fender - Remember My Name (Official Video) - YouTube", "url": "https://www.youtube.com/watch?v=uaQm48G6IjY", "content": "Sam Fender - Remember My Name (Official Video) SamFenderVEVO 10743 likes 862209 views 14 Feb 2025 Remember My Name is a love song dedicated to my late Grandparents - they were always so fiercely proud of our family so I wrote the song in honour of them, from the perspective of my Grandad who was looking after my Grandma when she was suffering from dementia. This video is a really special one for me and I want to say thank you to everyone involved in making it. I hope you like it ❤️ [...] Oh 11 Walk Avenue Something to behold To them it’s a council house To me it’s a home And a home that you made Where the grandkids could play But it’s never the same without you Humour me Make my day I’ll tell you stories I’ll kiss your face And I’ll pray You’ll remember My name And I’ll pray you remember my name And I’ll pray you remember my name --- [...] If I was wanting of anymore I’d be as greedy as those men on the hill But I remain forlorn In the memory of what once was Chasing a cross in from the wing Our boy’s a whippet, he’s faster than anything Remember the pride that we felt For the two of us made him ourselves Humour me Make my day I’ll tell you stories Kiss your face And I’ll pray You’ll remember My name I’m not sure of what awaits Wasn’t a fan of St Peter and his gates But by god I pray That I’ll see you in some way", "score": 0.7123327}, {"title": "Do you remember my name? - song and lyrics by Alea, Mama Marjas", "url": "https://open.spotify.com/track/3GVBn3rEQLxZl4zJ4dG8UJ", "content": "Listen to Do you remember my name? on Spotify. Song · Alea, Mama Marjas · 2023.", "score": 0.6506676}]
================================== Ai Message ==================================
I'm here to assist you, but I don't actually have the ability to remember names or personal information from previous conversations. How can I assist you today?

Agora que o nosso chatbot tem ferramentas de busca e memória, vamos repetir o exemplo anterior, em que pergunto pelo resultado do último jogo do Real Madrid na Liga e, depois, quais jogadores jogaram.

	
< > Input
Python
USER3_THREAD_ID = "3"
config_USER3 = {"configurable": {"thread_id": USER3_THREAD_ID}}
user_input = "How did Real Madrid fare this weekend against Leganes in La Liga?"
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER3,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
How did Real Madrid fare this weekend against Leganes in La Liga?
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: Real Madrid vs Leganes La Liga this weekend
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Real Madrid 3-2 Leganes: Goals and highlights - LaLiga 24/25 | Marca", "url": "https://www.marca.com/en/soccer/laliga/r-madrid-leganes/2025/03/29/01_0101_20250329_186_957-live.html", "content": "While their form has varied throughout the campaign there is no denying Real Madrid are a force at home in LaLiga this season, as they head into Saturday's match having picked up 34 points from 13 matches. As for Leganes they currently sit 18th in the table, though they are level with Alaves for 17th as both teams look to stay in the top flight. [...] The two teams have already played twice this season, with Real Madrid securing a 3-0 win in the reverse league fixture. They also met in the quarter-finals of the Copa del Rey, a game Real won 3-2. Real Madrid vs Leganes LIVE - Latest Updates Match ends, Real Madrid 3, Leganes 2. Second Half ends, Real Madrid 3, Leganes 2. Foul by Vinícius Júnior (Real Madrid). Seydouba Cissé (Leganes) wins a free kick in the defensive half. [...] Goal! Real Madrid 1, Leganes 1. Diego García (Leganes) left footed shot from very close range. Attempt missed. Óscar Rodríguez (Leganes) left footed shot from the centre of the box. Goal! Real Madrid 1, Leganes 0. Kylian Mbappé (Real Madrid) converts the penalty with a right footed shot. Penalty Real Madrid. Arda Güler draws a foul in the penalty area. Penalty conceded by Óscar Rodríguez (Leganes) after a foul in the penalty area. Delay over. They are ready to continue.", "score": 0.8548001}, {"title": "Real Madrid 3-2 Leganés (Mar 29, 2025) Game Analysis - ESPN", "url": "https://www.espn.com/soccer/report/_/gameId/704946", "content": "Real Madrid Leganés Mbappé nets twice to keep Real Madrid's title hopes alive Real Madrid vs. Leganés - Game Highlights Watch the Game Highlights from Real Madrid vs. Leganés, 03/30/2025 Real Madrid's Kylian Mbappé struck twice to help his side come from behind to claim a hard-fought 3-2 home win over relegation-threatened Leganes on Saturday to move the second-placed reigning champions level on points with leaders Barcelona. [...] Leganes pushed for an equaliser but fell to a third consecutive defeat to sit 18th on 27 points, level with Alaves who are one place higher in the safety zone on goal difference. "We have done a tremendous job. We leave with our heads held high because we were fighting until the end to score here," Leganes striker Garcia said. "Ultimately, it was down to the details that they took it. We played a very serious game and now we have to think about next week." Game Information", "score": 0.82220376}]
================================== Ai Message ==================================
Real Madrid secured a 3-2 victory against Leganes this weekend in their La Liga match. Kylian Mbappé scored twice, including a penalty, to help his team come from behind and claim the win, keeping Real Madrid's title hopes alive. Leganes, now sitting 18th in the table, continues to face challenges in their fight against relegation.

Agora perguntamos sobre os jogadores que jogaram na partida.

	
< > Input
Python
user_input = "Which players played the match?"
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER3,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Which players played the match?
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: Real Madrid vs Leganes match report players lineup
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Real Madrid vs. Leganes final score: La Liga result, updates, stats ...", "url": "https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-score-result-updates-stats-la-liga/8ecf730cfcb9b6c5f6693a0d", "content": "Real Madrid came through a topsy-turvy game with Leganes to claim a 3-2 victory and put pressure back on Barcelona in La Liga's title race. Kylian Mbappe scored in each half either side of a Jude Bellingham goal — his first in the league since January 3 — to seal all three points for the champions after Leganes had come from behind to lead at the interval. Rodrygo won back the ball in the Leganes half and earned a free-kick on the edge of the box, and Mbappe found the bottom corner after rolling the ball short to Fran Garcia to work an angle. Leganes lead Real Madrid at the Bernabeu for the very first time! *Real Madrid starting lineup (4-3-3, right to left):* Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B.", "score": 0.88372874}, {"title": "CONFIRMED lineups: Real Madrid vs Leganés, 2025 La Liga", "url": "https://www.managingmadrid.com/2025/3/29/24396638/real-madrid-vs-leganes-2025-la-liga-live-online-stream", "content": "Real Madrid starting XI: Lunin, Vazquez, Rudiger, Asencio, Fran Garcia, Camavinga, Guler, Modric, Bellingham, Brahim, Mbappe. Leganes starting", "score": 0.83452857}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: Real Madrid vs Leganes players 2025
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga", "url": "https://www.managingmadrid.com/2025/3/30/24396688/player-ratings-real-madrid-3-2-leganes-2025-la-liga", "content": "Raúl Asencio—7: Applauded by the Bernabeu on multiple occasions with good sweeping up defensively. Fran García—6: Better on the offensive end, getting into the final third and playing some dagger crosses. Eduardo Camavinga—6: Modric and Camavinga struggled to deal with Leganes counter attacks and Diego, playing as a #10 for Leganes, got the better of both of them. [...] Follow Managing Madrid online: Site search Managing Madrid main menu Filed under: Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga Kylian Mbappe scores a brace to help Madrid secure a nervy 3-2 victory. Share this story Share All sharing options for: Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga Full match player ratings below: Andriy Lunin—7: Not at fault for the goals, was left with the opposition taking a shot from near the six yard box. [...] Lucas Vázquez—4: Exposed in transition and lacking the speed and athleticism to cover the gaps he leaves when venturing forward. Needs a more “pessimistic” attitude when the ball is on the opposite flank, occupying better spots in ““rest defense”. Antonio Rudiger—5: Several unnecessary long distance shots to hurt Madrid’s rhythm and reinforce Leganes game plan. Playing with too many matches in his legs and it’s beginning to show.", "score": 0.8832463}, {"title": "Real Madrid vs. Leganés (Mar 29, 2025) Live Score - ESPN", "url": "https://www.espn.com/soccer/match/_/gameId/704946", "content": "Match Formations · 13. Lunin · 20. García · 22. Rüdiger · 35. Asencio · 17. Vázquez · 5. Bellingham · 10. Modric · 6. Camavinga.", "score": 0.86413884}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (0)
Call ID: 0
Args:
query: Real Madrid vs Leganes starting lineup
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "Starting lineups of Real Madrid and Leganés", "url": "https://www.realmadrid.com/en-US/news/football/first-team/latest-news/once-inicial-del-real-madrid-contra-el-leganes-29-03-2025", "content": "Starting lineups of Real Madrid and Leganés The Whites’ team is: Lunin, Lucas V., Asencio, Rüdiger, Fran García, Arda Güler, Modrić, Camavinga, Bellingham, Brahim and Mbappé. Real Madrid have named their starting line-up for the game against Leganés on matchday 29 of LaLiga, which will be played at the Santiago Bernabéu (9 pm CET). [...] Real Madrid starting line-up: 13. Lunin 17. Lucas V. 35. Asencio 22. Rüdiger 20. Fran García 15. Arda Güler 10. Modrić 6. Camavinga 5. Bellingham 21. Brahim 9. Mbappé. Substitutes: 26. Fran González 34. Sergio Mestre 4. Alaba 7. Vini Jr. 8. Valverde 11. Rodrygo 14. Tchouameni 16. Endrick 18. Vallejo 43. Diego Aguado. Leganés starting line-up: 13. Dmitrovic 5. Tapia 6. Sergio G. 7. Óscar 10. Raba 11. Cruz 12. V. Rosier 17. Neyou 19. Diego G. 20. Javi Hernández 22. Nastasic. [...] Suplentes: 1. Juan Soriano 36. Abajas 2. A. Alti 3. Jorge Sáenz 8. Cisse 9. Miguel 14. Darko 18. Duk 21. R. López 23. Munir 24. Chicco 30. I. Diomande. Download Now Official App Fan Real Madrid © 2025 All rights reserved", "score": 0.9465623}, {"title": "Real Madrid vs. Leganes lineups, confirmed starting 11, team news ...", "url": "https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-lineups-starting-11-team-news-injuries/aac757d10cc7b9a084995b4d", "content": "Real Madrid starting lineup (4-3-3, right to left): Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B. Diaz,", "score": 0.9224337}]
================================== Ai Message ==================================
The starting lineup for Real Madrid in their match against Leganés was: Lunin (GK), Vázquez, Rüdiger, Asencio, Fran García, Modric, Bellingham, Camavinga, Brahim, Arda Güler, and Mbappé. Notable players like Vini Jr., Rodrygo, and Valverde were on the bench.

Depois de procurar muito, no final ele encontra. Portanto, já temos um chatbot com tools e memória.

Por enquanto, criámos uns checkpoints em três threads diferentes. Mas o que entra em cada checkpoint? Para inspecionar o estado de um grafo para uma configuração dada podemos usar o método get_state(config).

	
< > Input
Python
snapshot = graph.get_state(config_USER3)
snapshot
Copied
>_ Output
			
StateSnapshot(values={'messages': [HumanMessage(content='How did Real Madrid fare this weekend against Leganes in La Liga?', additional_kwargs={}, response_metadata={}, id='a33f5825-1ae4-4717-ad17-8e306f35b027'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': {'query': 'Real Madrid vs Leganes La Liga this weekend'}, 'name': 'tavily_search_results_json', 'description': None}, 'id': '0', 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 296, 'total_tokens': 321}, 'model': '', 'finish_reason': 'stop'}, id='run-7905b5ae-5dee-4641-b012-396affde984c-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'Real Madrid vs Leganes La Liga this weekend'}, 'id': '0', 'type': 'tool_call'}]), ToolMessage(content='[{"title": "Real Madrid 3-2 Leganes: Goals and highlights - LaLiga 24/25 | Marca", "url": "https://www.marca.com/en/soccer/laliga/r-madrid-leganes/2025/03/29/01_0101_20250329_186_957-live.html", "content": "While their form has varied throughout the campaign there is no denying Real Madrid are a force at home in LaLiga this season, as they head into Saturday's match having picked up 34 points from 13 matches.\n\nAs for Leganes they currently sit 18th in the table, though they are level with Alaves for 17th as both teams look to stay in the top flight. [...] The two teams have already played twice this season, with Real Madrid securing a 3-0 win in the reverse league fixture. They also met in the quarter-finals of the Copa del Rey, a game Real won 3-2.\n\nReal Madrid vs Leganes LIVE - Latest Updates\n\nMatch ends, Real Madrid 3, Leganes 2.\n\nSecond Half ends, Real Madrid 3, Leganes 2.\n\nFoul by Vinícius Júnior (Real Madrid).\n\nSeydouba Cissé (Leganes) wins a free kick in the defensive half. [...] Goal! Real Madrid 1, Leganes 1. Diego García (Leganes) left footed shot from very close range.\n\nAttempt missed. Óscar Rodríguez (Leganes) left footed shot from the centre of the box.\n\nGoal! Real Madrid 1, Leganes 0. Kylian Mbappé (Real Madrid) converts the penalty with a right footed shot.\n\nPenalty Real Madrid. Arda Güler draws a foul in the penalty area.\n\nPenalty conceded by Óscar Rodríguez (Leganes) after a foul in the penalty area.\n\nDelay over. They are ready to continue.", "score": 0.8548001}, {"title": "Real Madrid 3-2 Leganés (Mar 29, 2025) Game Analysis - ESPN", "url": "https://www.espn.com/soccer/report/_/gameId/704946", "content": "Real Madrid\n\nLeganés\n\nMbappé nets twice to keep Real Madrid's title hopes alive\n\nReal Madrid vs. Leganés - Game Highlights\n\nWatch the Game Highlights from Real Madrid vs. Leganés, 03/30/2025\n\nReal Madrid's Kylian Mbappé struck twice to help his side come from behind to claim a hard-fought 3-2 home win over relegation-threatened Leganes on Saturday to move the second-placed reigning champions level on points with leaders Barcelona. [...] Leganes pushed for an equaliser but fell to a third consecutive defeat to sit 18th on 27 points, level with Alaves who are one place higher in the safety zone on goal difference.\n\n\"We have done a tremendous job. We leave with our heads held high because we were fighting until the end to score here,\" Leganes striker Garcia said.\n\n\"Ultimately, it was down to the details that they took it. We played a very serious game and now we have to think about next week.\"\n\nGame Information", "score": 0.82220376}]', name='tavily_search_results_json', id='0e02fce3-a6f0-4cce-9217-04c8c3219265', tool_call_id='0', artifact={'query': 'Real Madrid vs Leganes La Liga this weekend', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.marca.com/en/soccer/laliga/r-madrid-leganes/2025/03/29/01_0101_20250329_186_957-live.html', 'title': 'Real Madrid 3-2 Leganes: Goals and highlights - LaLiga 24/25 | Marca', 'content': "While their form has varied throughout the campaign there is no denying Real Madrid are a force at home in LaLiga this season, as they head into Saturday's match having picked up 34 points from 13 matches. As for Leganes they currently sit 18th in the table, though they are level with Alaves for 17th as both teams look to stay in the top flight. [...] The two teams have already played twice this season, with Real Madrid securing a 3-0 win in the reverse league fixture. They also met in the quarter-finals of the Copa del Rey, a game Real won 3-2. Real Madrid vs Leganes LIVE - Latest Updates Match ends, Real Madrid 3, Leganes 2. Second Half ends, Real Madrid 3, Leganes 2. Foul by Vinícius Júnior (Real Madrid). Seydouba Cissé (Leganes) wins a free kick in the defensive half. [...] Goal! Real Madrid 1, Leganes 1. Diego García (Leganes) left footed shot from very close range. Attempt missed. Óscar Rodríguez (Leganes) left footed shot from the centre of the box. Goal! Real Madrid 1, Leganes 0. Kylian Mbappé (Real Madrid) converts the penalty with a right footed shot. Penalty Real Madrid. Arda Güler draws a foul in the penalty area. Penalty conceded by Óscar Rodríguez (Leganes) after a foul in the penalty area. Delay over. They are ready to continue.", 'score': 0.8548001, 'raw_content': None}, {'url': 'https://www.espn.com/soccer/report/_/gameId/704946', 'title': 'Real Madrid 3-2 Leganés (Mar 29, 2025) Game Analysis - ESPN', 'content': 'Real Madrid Leganés Mbappé nets twice to keep Real Madrid's title hopes alive Real Madrid vs. Leganés - Game Highlights Watch the Game Highlights from Real Madrid vs. Leganés, 03/30/2025 Real Madrid's Kylian Mbappé struck twice to help his side come from behind to claim a hard-fought 3-2 home win over relegation-threatened Leganes on Saturday to move the second-placed reigning champions level on points with leaders Barcelona. [...] Leganes pushed for an equaliser but fell to a third consecutive defeat to sit 18th on 27 points, level with Alaves who are one place higher in the safety zone on goal difference. "We have done a tremendous job. We leave with our heads held high because we were fighting until the end to score here," Leganes striker Garcia said. "Ultimately, it was down to the details that they took it. We played a very serious game and now we have to think about next week." Game Information', 'score': 0.82220376, 'raw_content': None}], 'response_time': 1.47}), AIMessage(content="Real Madrid secured a 3-2 victory against Leganes this weekend in their La Liga match. Kylian Mbappé scored twice, including a penalty, to help his team come from behind and claim the win, keeping Real Madrid's title hopes alive. Leganes, now sitting 18th in the table, continues to face challenges in their fight against relegation.", additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 92, 'prompt_tokens': 1086, 'total_tokens': 1178}, 'model': '', 'finish_reason': 'stop'}, id='run-22226dda-0475-49b7-882f-fe7bd63ef025-0'), HumanMessage(content='Which players played the match?', additional_kwargs={}, response_metadata={}, id='3e6d9f84-06a2-4148-8f2b-d8ef42c3bea1'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': {'query': 'Real Madrid vs Leganes match report players lineup'}, 'name': 'tavily_search_results_json', 'description': None}, 'id': '0', 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 1178, 'total_tokens': 1207}, 'model': '', 'finish_reason': 'stop'}, id='run-025d3235-61b9-4add-8e1b-5b1bc795a9d3-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'Real Madrid vs Leganes match report players lineup'}, 'id': '0', 'type': 'tool_call'}]), ToolMessage(content='[{"title": "Real Madrid vs. Leganes final score: La Liga result, updates, stats ...", "url": "https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-score-result-updates-stats-la-liga/8ecf730cfcb9b6c5f6693a0d", "content": "Real Madrid came through a topsy-turvy game with Leganes to claim a 3-2 victory and put pressure back on Barcelona in La Liga's title race. Kylian Mbappe scored in each half either side of a Jude Bellingham goal — his first in the league since January 3 — to seal all three points for the champions after Leganes had come from behind to lead at the interval. Rodrygo won back the ball in the Leganes half and earned a free-kick on the edge of the box, and Mbappe found the bottom corner after rolling the ball short to Fran Garcia to work an angle. Leganes lead Real Madrid at the Bernabeu for the very first time! *Real Madrid starting lineup (4-3-3, right to left):* Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B.", "score": 0.88372874}, {"title": "CONFIRMED lineups: Real Madrid vs Leganés, 2025 La Liga", "url": "https://www.managingmadrid.com/2025/3/29/24396638/real-madrid-vs-leganes-2025-la-liga-live-online-stream", "content": "Real Madrid starting XI: Lunin, Vazquez, Rudiger, Asencio, Fran Garcia, Camavinga, Guler, Modric, Bellingham, Brahim, Mbappe. Leganes starting", "score": 0.83452857}]', name='tavily_search_results_json', id='2dbc1324-2c20-406a-b2d7-a3d6fc609537', tool_call_id='0', artifact={'query': 'Real Madrid vs Leganes match report players lineup', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-score-result-updates-stats-la-liga/8ecf730cfcb9b6c5f6693a0d', 'title': 'Real Madrid vs. Leganes final score: La Liga result, updates, stats ...', 'content': "Real Madrid came through a topsy-turvy game with Leganes to claim a 3-2 victory and put pressure back on Barcelona in La Liga's title race. Kylian Mbappe scored in each half either side of a Jude Bellingham goal — his first in the league since January 3 — to seal all three points for the champions after Leganes had come from behind to lead at the interval. Rodrygo won back the ball in the Leganes half and earned a free-kick on the edge of the box, and Mbappe found the bottom corner after rolling the ball short to Fran Garcia to work an angle. Leganes lead Real Madrid at the Bernabeu for the very first time! *Real Madrid starting lineup (4-3-3, right to left):* Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B.", 'score': 0.88372874, 'raw_content': None}, {'url': 'https://www.managingmadrid.com/2025/3/29/24396638/real-madrid-vs-leganes-2025-la-liga-live-online-stream', 'title': 'CONFIRMED lineups: Real Madrid vs Leganés, 2025 La Liga', 'content': 'Real Madrid starting XI: Lunin, Vazquez, Rudiger, Asencio, Fran Garcia, Camavinga, Guler, Modric, Bellingham, Brahim, Mbappe. Leganes starting', 'score': 0.83452857, 'raw_content': None}], 'response_time': 3.36}), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': {'query': 'Real Madrid vs Leganes players 2025'}, 'name': 'tavily_search_results_json', 'description': None}, 'id': '0', 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 31, 'prompt_tokens': 1630, 'total_tokens': 1661}, 'model': '', 'finish_reason': 'stop'}, id='run-d6b4c4ff-0923-4082-9dea-7c51b2a4fc60-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'Real Madrid vs Leganes players 2025'}, 'id': '0', 'type': 'tool_call'}]), ToolMessage(content='[{"title": "Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga", "url": "https://www.managingmadrid.com/2025/3/30/24396688/player-ratings-real-madrid-3-2-leganes-2025-la-liga", "content": "Raúl Asencio—7: Applauded by the Bernabeu on multiple occasions with good sweeping up defensively.\n\nFran García—6: Better on the offensive end, getting into the final third and playing some dagger crosses.\n\nEduardo Camavinga—6: Modric and Camavinga struggled to deal with Leganes counter attacks and Diego, playing as a #10 for Leganes, got the better of both of them. [...] Follow Managing Madrid online:\n\nSite search\n\nManaging Madrid main menu\n\nFiled under:\n\nPlayer Ratings: Real Madrid 3-2 Leganes; 2025 La Liga\n\nKylian Mbappe scores a brace to help Madrid secure a nervy 3-2 victory.\n\nShare this story\n\nShare\nAll sharing options for:\nPlayer Ratings: Real Madrid 3-2 Leganes; 2025 La Liga\n\nFull match player ratings below:\n\nAndriy Lunin—7: Not at fault for the goals, was left with the opposition taking a shot from near the six yard box. [...] Lucas Vázquez—4: Exposed in transition and lacking the speed and athleticism to cover the gaps he leaves when venturing forward. Needs a more “pessimistic” attitude when the ball is on the opposite flank, occupying better spots in ““rest defense”.\n\nAntonio Rudiger—5: Several unnecessary long distance shots to hurt Madrid’s rhythm and reinforce Leganes game plan. Playing with too many matches in his legs and it’s beginning to show.", "score": 0.8832463}, {"title": "Real Madrid vs. Leganés (Mar 29, 2025) Live Score - ESPN", "url": "https://www.espn.com/soccer/match/_/gameId/704946", "content": "Match Formations · 13. Lunin · 20. García · 22. Rüdiger · 35. Asencio · 17. Vázquez · 5. Bellingham · 10. Modric · 6. Camavinga.", "score": 0.86413884}]', name='tavily_search_results_json', id='ac15dd6e-09b1-4075-834e-d869f4079285', tool_call_id='0', artifact={'query': 'Real Madrid vs Leganes players 2025', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.managingmadrid.com/2025/3/30/24396688/player-ratings-real-madrid-3-2-leganes-2025-la-liga', 'title': 'Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga', 'content': 'Raúl Asencio—7: Applauded by the Bernabeu on multiple occasions with good sweeping up defensively. Fran García—6: Better on the offensive end, getting into the final third and playing some dagger crosses. Eduardo Camavinga—6: Modric and Camavinga struggled to deal with Leganes counter attacks and Diego, playing as a #10 for Leganes, got the better of both of them. [...] Follow Managing Madrid online: Site search Managing Madrid main menu Filed under: Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga Kylian Mbappe scores a brace to help Madrid secure a nervy 3-2 victory. Share this story Share All sharing options for: Player Ratings: Real Madrid 3-2 Leganes; 2025 La Liga Full match player ratings below: Andriy Lunin—7: Not at fault for the goals, was left with the opposition taking a shot from near the six yard box. [...] Lucas Vázquez—4: Exposed in transition and lacking the speed and athleticism to cover the gaps he leaves when venturing forward. Needs a more “pessimistic” attitude when the ball is on the opposite flank, occupying better spots in ““rest defense”. Antonio Rudiger—5: Several unnecessary long distance shots to hurt Madrid’s rhythm and reinforce Leganes game plan. Playing with too many matches in his legs and it’s beginning to show.', 'score': 0.8832463, 'raw_content': None}, {'url': 'https://www.espn.com/soccer/match/_/gameId/704946', 'title': 'Real Madrid vs. Leganés (Mar 29, 2025) Live Score - ESPN', 'content': 'Match Formations · 13. Lunin · 20. García · 22. Rüdiger · 35. Asencio · 17. Vázquez · 5. Bellingham · 10. Modric · 6. Camavinga.', 'score': 0.86413884, 'raw_content': None}], 'response_time': 0.89}), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': {'query': 'Real Madrid vs Leganes starting lineup'}, 'name': 'tavily_search_results_json', 'description': None}, 'id': '0', 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 2212, 'total_tokens': 2239}, 'model': '', 'finish_reason': 'stop'}, id='run-68867df1-2012-47ac-9f01-42b071ef3a1f-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'Real Madrid vs Leganes starting lineup'}, 'id': '0', 'type': 'tool_call'}]), ToolMessage(content='[{"title": "Starting lineups of Real Madrid and Leganés", "url": "https://www.realmadrid.com/en-US/news/football/first-team/latest-news/once-inicial-del-real-madrid-contra-el-leganes-29-03-2025", "content": "Starting lineups of Real Madrid and Leganés\n\n\n\nThe Whites’ team is: Lunin, Lucas V., Asencio, Rüdiger, Fran García, Arda Güler, Modrić, Camavinga, Bellingham, Brahim and Mbappé.\n\n\n\n\n\nReal Madrid have named their starting line-up for the game against Leganés on matchday 29 of LaLiga, which will be played at the Santiago Bernabéu (9 pm CET). [...] Real Madrid starting line-up:\n13. Lunin\n17. Lucas V.\n35. Asencio\n22. Rüdiger\n20. Fran García\n15. Arda Güler\n10. Modrić\n6. Camavinga\n5. Bellingham\n21. Brahim\n9. Mbappé.\n\nSubstitutes:\n26. Fran González\n34. Sergio Mestre\n4. Alaba\n7. Vini Jr.\n8. Valverde\n11. Rodrygo\n14. Tchouameni\n16. Endrick\n18. Vallejo\n43. Diego Aguado.\n\nLeganés starting line-up:\n13. Dmitrovic\n5. Tapia\n6. Sergio G.\n7. Óscar\n10. Raba\n11. Cruz\n12. V. Rosier\n17. Neyou\n19. Diego G.\n20. Javi Hernández\n22. Nastasic. [...] Suplentes:\n1. Juan Soriano\n36. Abajas\n2. A. Alti\n3. Jorge Sáenz\n8. Cisse\n9. Miguel\n14. Darko\n18. Duk\n21. R. López\n23. Munir\n24. Chicco\n30. I. Diomande.\n\n\n\nDownload Now\n\nOfficial App Fan\n\nReal Madrid © 2025 All rights reserved", "score": 0.9465623}, {"title": "Real Madrid vs. Leganes lineups, confirmed starting 11, team news ...", "url": "https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-lineups-starting-11-team-news-injuries/aac757d10cc7b9a084995b4d", "content": "Real Madrid starting lineup (4-3-3, right to left): Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B. Diaz,", "score": 0.9224337}]', name='tavily_search_results_json', id='46721f2b-2df2-4da2-831a-ce94f6b4ff8f', tool_call_id='0', artifact={'query': 'Real Madrid vs Leganes starting lineup', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.realmadrid.com/en-US/news/football/first-team/latest-news/once-inicial-del-real-madrid-contra-el-leganes-29-03-2025', 'title': 'Starting lineups of Real Madrid and Leganés', 'content': 'Starting lineups of Real Madrid and Leganés The Whites’ team is: Lunin, Lucas V., Asencio, Rüdiger, Fran García, Arda Güler, Modrić, Camavinga, Bellingham, Brahim and Mbappé. Real Madrid have named their starting line-up for the game against Leganés on matchday 29 of LaLiga, which will be played at the Santiago Bernabéu (9 pm CET). [...] Real Madrid starting line-up: 13. Lunin 17. Lucas V. 35. Asencio 22. Rüdiger 20. Fran García 15. Arda Güler 10. Modrić 6. Camavinga 5. Bellingham 21. Brahim 9. Mbappé. Substitutes: 26. Fran González 34. Sergio Mestre 4. Alaba 7. Vini Jr. 8. Valverde 11. Rodrygo 14. Tchouameni 16. Endrick 18. Vallejo 43. Diego Aguado. Leganés starting line-up: 13. Dmitrovic 5. Tapia 6. Sergio G. 7. Óscar 10. Raba 11. Cruz 12. V. Rosier 17. Neyou 19. Diego G. 20. Javi Hernández 22. Nastasic. [...] Suplentes: 1. Juan Soriano 36. Abajas 2. A. Alti 3. Jorge Sáenz 8. Cisse 9. Miguel 14. Darko 18. Duk 21. R. López 23. Munir 24. Chicco 30. I. Diomande. Download Now Official App Fan Real Madrid © 2025 All rights reserved', 'score': 0.9465623, 'raw_content': None}, {'url': 'https://www.sportingnews.com/us/soccer/news/real-madrid-leganes-lineups-starting-11-team-news-injuries/aac757d10cc7b9a084995b4d', 'title': 'Real Madrid vs. Leganes lineups, confirmed starting 11, team news ...', 'content': 'Real Madrid starting lineup (4-3-3, right to left): Lunin (GK) — Vazquez, Rudiger, Asencio, Garcia — Modric, Bellingham, Camavinga — B. Diaz,', 'score': 0.9224337, 'raw_content': None}], 'response_time': 2.3}), AIMessage(content='The starting lineup for Real Madrid in their match against Leganés was: Lunin (GK), Vázquez, Rüdiger, Asencio, Fran García, Modric, Bellingham, Camavinga, Brahim, Arda Güler, and Mbappé. Notable players like Vini Jr., Rodrygo, and Valverde were on the bench.', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 2954, 'total_tokens': 3052}, 'model': '', 'finish_reason': 'stop'}, id='run-0bd921c6-1d94-4a4c-9d9c-d255d301e2d5-0')]}, next=(), config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1f010a50-49f2-6904-800c-ec8d67fe5b92'}}, metadata={'source': 'loop', 'writes': {'chatbot_node': {'messages': [AIMessage(content='The starting lineup for Real Madrid in their match against Leganés was: Lunin (GK), Vázquez, Rüdiger, Asencio, Fran García, Modric, Bellingham, Camavinga, Brahim, Arda Güler, and Mbappé. Notable players like Vini Jr., Rodrygo, and Valverde were on the bench.', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 2954, 'total_tokens': 3052}, 'model': '', 'finish_reason': 'stop'}, id='run-0bd921c6-1d94-4a4c-9d9c-d255d301e2d5-0')]}}, 'thread_id': '3', 'step': 12, 'parents': {}}, created_at='2025-04-03T16:02:18.167222+00:00', parent_config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1f010a50-1feb-6534-800b-079c102aaa71'}}, tasks=())

Se quisermos ver o próximo nó a processar, podemos usar o atributo next

	
< > Input
Python
snapshot.next
Copied
>_ Output
			
()

Como o grafo terminou, next está vazio. Se você obtiver um estado de dentro de uma invocação do grafo, next indica qual nó será executado a seguir.

O snapshot anterior (snapshot) contém os valores de estado atuais, a configuração correspondente e o próximo nó (next) a processar. No nosso caso, o grafo atingiu o estado END, por isso next está vazio.

Vamos reescrever todo o código para que fique mais legível.

	
< > 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.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 = TavilySearchResults(api_wrapper=wrapper, max_results=2)
tools_list = [tool]
# 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):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# 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)`
	
< > Input
Python
USER1_THREAD_ID = "1"
config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}
user_input = "Hi there! My name is Maximo."
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER1,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Hi there! My name is Maximo.
================================== Ai Message ==================================
Hello Maximo! It's nice to meet you. How can I assist you today? Feel free to ask me any questions or let me know if you need help with anything specific.
	
< > Input
Python
user_input = "Do you remember my name?"
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config_USER1,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
Copied
>_ Output
			
================================ Human Message =================================
Do you remember my name?
================================== Ai Message ==================================
Yes, I remember your name! You mentioned it's Maximo. It's nice to chat with you, Maximo. How can I assist you today?

**Parabéns!** Nosso chatbot agora pode manter o estado da conversa em todas as sessões graças ao sistema de pontos de controle (checkpoints) do LangGraph. Isso abre possibilidades para interações mais naturais e contextuais. O controle do LangGraph até mesmo lida com estados de grafos complexos.

Maislink image 12

Chatbot com mensagem de resumolink image 13

Se vamos a gerir o contexto da conversa para não gastar muitos tokens, uma coisa que podemos fazer para melhorar a conversa é adicionar uma mensagem com o resumo da conversa. Isso pode ser útil para o exemplo anterior, em que filtramos tanto o estado que o LLM não tem contexto suficiente.

	
< > 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 RemoveMessage, trim_messages, SystemMessage, HumanMessage, AIMessage, RemoveMessage
from langgraph.checkpoint.memory import MemorySaver
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]
summary: str
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)
# Print functions
def print_message(m):
if isinstance(m, HumanMessage):
message_content = m.content
message_lines = message_content.split(" ")
for i, line in enumerate(message_lines):
if i == 0:
print(f" [HumanMessage]: {line}")
else:
print(f" {line}")
elif isinstance(m, SystemMessage):
message_content = m.content
message_lines = message_content.split(" ")
for i, line in enumerate(message_lines):
if i == 0:
print(f" [SystemMessage]: {line}")
else:
print(f" {line}")
elif isinstance(m, AIMessage):
message_content = m.content
message_lines = message_content.split(" ")
for i, line in enumerate(message_lines):
if i == 0:
print(f" [AIMessage]: {line}")
else:
print(f" {line}")
elif isinstance(m, RemoveMessage):
message_content = m.content
message_lines = message_content.split(" ")
for i, line in enumerate(message_lines):
if i == 0:
print(f" [RemoveMessage]: {line}")
else:
print(f" {line}")
else:
message_content = m.content
message_lines = message_content.split(" ")
for i, line in enumerate(message_lines):
if i == 0:
print(f" [{type(m)}]: {line}")
else:
print(f" {line}")
def print_state_summary(state: State):
if state.get("summary"):
summary_lines = state["summary"].split(" ")
for i, line in enumerate(summary_lines):
if i == 0:
print(f" Summary of the conversation: {line}")
else:
print(f" {line}")
else:
print(" No summary of the conversation")
def print_summary(summary: str):
if summary:
summary_lines = summary.split(" ")
for i, line in enumerate(summary_lines):
if i == 0:
print(f" Summary of the conversation: {line}")
else:
print(f" {line}")
else:
print(" No summary of the conversation")
# Nodes
def filter_messages(state: State):
print(" --- 1 messages (input to filter_messages) ---")
for m in state["messages"]:
print_message(m)
print_state_summary(state)
print(" ------------------------------------------------")
# Delete all but the 2 most recent messages if there are more than 2
if len(state["messages"]) &gt; 2:
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
else:
delete_messages = []
print(" --- 1 messages (output of filter_messages) ---")
for m in delete_messages:
print_message(m)
print_state_summary(state)
print(" ------------------------------------------------")
return {"messages": delete_messages}
def trim_messages_node(state: State):
# print the messages received from filter_messages_node
print(" --- 2 messages (input to trim_messages) ---")
for m in state["messages"]:
print_message(m)
print_state_summary(state)
print(" ------------------------------------------------")
# Trim the messages based on the specified parameters
trimmed_messages = trim_messages(
state["messages"],
max_tokens=100, # Maximum tokens allowed in the trimmed list
strategy="last", # Keep the latest messages
token_counter=llm, # Use the LLM's tokenizer to count tokens
allow_partial=True, # Allow cutting messages mid-way if needed
)
# Identify the messages that must be removed
# This is crucial: determine which messages are in 'state["messages"]' but not in 'trimmed_messages'
original_ids = {m.id for m in state["messages"]}
trimmed_ids = {m.id for m in trimmed_messages}
ids_to_remove = original_ids - trimmed_ids
# Create a RemoveMessage for each message that must be removed
messages_to_remove = [RemoveMessage(id=msg_id) for msg_id in ids_to_remove]
# Print the result of the trimming
print(" --- 2 messages (output of trim_messages - after trimming) ---")
if trimmed_messages:
for m in trimmed_messages:
print_message(m)
else:
print("[Empty list - No messages after trimming]")
print_state_summary(state)
print(" ------------------------------------------------")
return {"messages": messages_to_remove}
def chat_model_node(state: State):
# Get summary of the conversation if it exists
summary = state.get("summary", "")
print(" --- 3 messages (input to chat_model_node) ---")
for m in state["messages"]:
print_message(m)
print_state_summary(state)
print(" ------------------------------------------------")
# If there is a summary, add it to the system message
if summary:
# Add the summary to the system message
system_message = f"Summary of the conversation earlier: {summary}"
# Add the system message to the messages at the beginning
messages = [SystemMessage(content=system_message)] + state["messages"]
# If there is no summary, just return the messages
else:
messages = state["messages"]
print(f" --- 3 messages (input to chat_model_node) ---")
for m in messages:
print_message(m)
print_summary(summary)
print(" ------------------------------------------------")
# Invoke the LLM with the messages
response = llm.invoke(messages)
print(" --- 3 messages (output of chat_model_node) ---")
print_message(response)
print_summary(summary)
print(" ------------------------------------------------")
# Return the LLM's response in the correct state format
return {"messages": [response]}
def summarize_conversation(state: State):
# Get summary of the conversation if it exists
summary = state.get("summary", "")
print(" --- 4 messages (input to summarize_conversation) ---")
for m in state["messages"]:
print_message(m)
print_summary(summary)
print(" ------------------------------------------------")
# If there is a summary, add it to the system message
if summary:
summary_message = (
f"This is a summary of the conversation to date: {summary} "
"Extend the summary by taking into account the new messages above."
)
# If there is no summary, create a new one
else:
summary_message = "Create a summary of the conversation above."
print(f" --- 4 summary message ---")
summary_lines = summary_message.split(" ")
for i, line in enumerate(summary_lines):
if i == 0:
print(f" {line}")
else:
print(f" {line}")
print_summary(summary)
print(" ------------------------------------------------")
# Add prompt to the messages
messages = state["messages"] + [HumanMessage(summary_message)]
print(" --- 4 messages (input to summarize_conversation with summary) ---")
for m in messages:
print_message(m)
print(" ------------------------------------------------")
# Invoke the LLM with the messages
response = llm.invoke(messages)
print(" --- 4 messages (output of summarize_conversation) ---")
print_message(response)
print(" ------------------------------------------------")
# Return the summary message in the correct state format
return {"summary": response.content}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("filter_messages_node", filter_messages)
graph_builder.add_node("trim_messages_node", trim_messages_node)
graph_builder.add_node("chatbot_node", chat_model_node)
graph_builder.add_node("summarize_conversation_node", summarize_conversation)
# Connecto nodes
graph_builder.add_edge(START, "filter_messages_node")
graph_builder.add_edge("filter_messages_node", "trim_messages_node")
graph_builder.add_edge("trim_messages_node", "chatbot_node")
graph_builder.add_edge("chatbot_node", "summarize_conversation_node")
graph_builder.add_edge("summarize_conversation_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 podemos ver, temos:

  • Função de filtragem de mensagens: Se houver mais de 2 mensagens no estado, são eliminadas todas as mensagens exceto as 2 últimas.
  • Função de corte de mensagens: São eliminadas as mensagens que ultrapassam 100 tokens.* Função de chatbot: Executa-se o modelo com as mensagens filtradas e aparadas. Além disso, se existir um resumo, ele é adicionado à mensagem de sistema.
  • Função de resumo: É criado um resumo da conversa.

Criamos uma função para imprimir as mensagens do grafo.

	
< > Input
Python
# Colors for the terminal
COLOR_GREEN = "\033[32m"
COLOR_YELLOW = "\033[33m"
COLOR_RESET = "\033[0m"
def stream_graph_updates(user_input: str, config: dict):
# Initialize a flag to track if an assistant response has been printed
assistant_response_printed = False
# Print the user's input immediately
print(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")
# Create the user's message with the HumanMessage class
user_message = HumanMessage(content=user_input)
# Stream events from the graph execution
for event in graph.stream({"messages": [user_message]}, config, stream_mode="values"):
# event is a dictionary mapping node names to their output
# Example: {'chatbot_node': {'messages': [...]}} or {'summarize_conversation_node': {'summary': '...'}}
# Iterate through node name and its output
for node_name, value in event.items():
# Check if this event is from the chatbot node which should contain the assistant's reply
if node_name == 'messages':
# Ensure the output format is as expected (list of messages)
if isinstance(value, list):
# Get the messages from the event
messages = value
# Ensure 'messages' is a non-empty list
if isinstance(messages, list) and messages:
# Get the last message (presumably the assistant's reply)
last_message = messages[-1]
# Ensure the message is an instance of AIMessage
if isinstance(last_message, AIMessage):
# Ensure the message has content to display
if hasattr(last_message, 'content'):
# Print the assistant's message content
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}{last_message.content}")
assistant_response_printed = True # Mark that we've printed the response
# Fallback if no assistant response was printed (e.g., graph error before chatbot_node)
if not assistant_response_printed:
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}[No response generated or error occurred]")
Copied

Agora executamos o grafo

	
< > Input
Python
USER1_THREAD_ID = "1"
config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}
while True:
user_input = input(f" User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print(f"{COLOR_GREEN}User: {COLOR_RESET}Exiting...")
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}Goodbye!")
break
events = stream_graph_updates(user_input, config_USER1)
Copied
>_ Output
			
User: Hello
--- 1 messages (input to filter_messages) ---
[HumanMessage]: Hello
No summary of the conversation
------------------------------------------------
--- 1 messages (output of filter_messages) ---
No summary of the conversation
------------------------------------------------
--- 2 messages (input to trim_messages) ---
[HumanMessage]: Hello
No summary of the conversation
------------------------------------------------
--- 2 messages (output of trim_messages - after trimming) ---
[HumanMessage]: Hello
No summary of the conversation
------------------------------------------------
--- 3 messages (input to chat_model_node) ---
[HumanMessage]: Hello
No summary of the conversation
------------------------------------------------
...
- Use LangSmith’s web interface to visualize the evaluation results.
- Compare different models and versions to identify the best performing model.
### Additional Tips
- **Model Tuning**: Use Hugging Face’s `Trainer` class to fine-tune models on your datasets and then evaluate them using LangSmith.
- **Custom Metrics**: Define custom evaluation metrics and use them to assess model performance.
- **Collaboration**: Share datasets and models with team members using the Hugging Face Hub and LangSmith.
By following these steps, you can effectively integrate Hugging Face models with LangSmith, leveraging the strengths of both platforms to build and evaluate robust NLP applications.
If you have any specific questions or need further assistance, feel free to ask!
User: Exiting...
Assistant: Goodbye!

Se formos para o final da conversa, podemos ver

--- 2 mensagens (entrada para trim_messages) ---
[AIMessage]: Sim, posso ajudar com informações sobre o LangGraph! O LangGraph é um grafo de modelo de linguagem que representa as relações e conexões entre diferentes modelos de linguagem e seus componentes. Ele pode ser usado para visualizar e entender a arquitetura, os processos de treinamento e as características de desempenho de vários modelos de linguagem.

LangGraph puede ser especialmente útil para investigadores y desarrolladores que trabajan en tareas de procesamiento de lenguaje natural (NLP). Ayuda en:

1. **Visualização da Arquitetura do Modelo**: Fornece uma visão clara e detalhada de como diferentes componentes de um modelo de linguagem estão conectados.
2. **Comparando Modelos**: Permite uma comparação fácil de diferentes modelos de linguagem em termos de sua estrutura, dados de treinamento e métricas de desempenho.
3. **Compreendendo os Processos de Treinamento**: Ajuda a entender a dinâmica do treinamento e o fluxo de dados através do modelo.
4. **Identificando Gargalos**: Pode ajudar a identificar potenciais gargalos ou áreas de melhoria no modelo.

Se você tiver perguntas específicas ou aspectos do LangGraph nos quais esteja interessado, sinta-se à vontade para me avisar!
[HumanMessage]: Gostaria de saber sobre o uso do langsmith com llms da huggingface, a integração da huggingface
Resumo da conversa: Claro! Aqui está um resumo विस्तendido da conversa:

---

**Usuário:** Olá

**Qwen:** Olá! Como posso ajudar você hoje? Se você precisa de ajuda com informações, uma tarefa específica ou apenas quer conversar, estou aqui para ajudar.

**Usuário:** Estou estudando sobre langgraph, você conhece?

**Qwen:** Sim, posso ajudar com informações sobre LangGraph! LangGraph é um grafo de modelos de linguagem que representa os relacionamentos e conexões entre diferentes modelos de linguagem e seus componentes. Ele pode ser usado para visualizar e entender a arquitetura, os processos de treinamento e as características de desempenho de vários modelos de linguagem. LangGraph pode ser particularmente útil para pesquisadores e desenvolvedores que estão trabalhando em tarefas de processamento de linguagem natural (NLP). Ele ajuda em:
1. Visualizando la Arquitetura do Modelo
2. Comparando Modelos
3. Entendendo os Processos de Treinamento
4. Identificando Gargalos

Se você tiver perguntas específicas ou aspectos do LangGraph nos quais esteja interessado, fique à vontade para me dizer!

**Usuário:** Este é um resumo da conversa até o momento: Claro! Aqui está um resumo da conversa acima:
Usuário: Olá
Qwen: Olá! Como posso ajudá-lo hoje? Se você precisar de ajuda com informações, uma tarefa específica ou apenas quiser conversar, estou aqui para ajudar.
Usuário: Crie um resumo da conversa acima.
Qwen: [Fornecido o resumo que você está lendo agora.]

Há mais alguma coisa em que você precise de ajuda?
**Qwen:** [Estendeu o resumo que você está lendo agora.]

---

Há mais alguma coisa com que você precise de ajuda?
------------------------------------------------

Vemos que nas mensagens de estado apenas se conservam

		[AIMessage]: Sim, posso ajudar com informações sobre LangGraph! LangGraph é um grafo de modelo de linguagem que representa as relações e conexões entre diferentes modelos de linguagem e seus componentes. Ele pode ser usado para visualizar e compreender a arquitetura, os processos de treinamento e as características de desempenho de vários modelos de linguagem.

LangGraph pode ser particularmente útil para pesquisadores e desenvolvedores que estão trabalhando em tarefas de processamento de linguagem natural (NLP). Ele ajuda em:

1. **Visualizando a Arquitetura do Modelo**: Fornece uma visão clara e detalhada de como os diferentes componentes de um modelo de linguagem estão conectados.
2. **Comparando Modelos**: Permite comparar facilmente diferentes modelos de linguagem em termos de sua estrutura, dados de treinamento e métricas de desempenho.
3. **Entendendo os Processos de Treinamento**: Ajuda a compreender a dinâmica de treinamento e o fluxo de dados através do modelo.
4. **Identificando Gargalos**: Pode ajudar a identificar possíveis gargalos ou áreas de melhoria no modelo.

Se você tiver perguntas específicas ou aspectos do LangGraph nos quais esteja interessado, sinta-se à vontade para me dizer!
[HumanMessage]: Eu gostaria de saber sobre o uso do langsmith com llms da huggingface, a integração da huggingface

Isto é, a função de filtragem mantém apenas as 2 últimas mensagens.

Mas depois podemos ver

--- 2 mensagens (saída de trim_messages - após o corte) ---
[HumanMessage]: Gostaria de saber sobre o uso do langsmith com LLMs da Hugging Face, a integração da Hugging Face
Resumo da conversa: Claro! Aqui está um resumo विस्तारido da conversa:

---

**Usuário:** Olá

**Qwen:** Olá! Como posso ajudá-lo hoje? Se você precisa de ajuda com informações, uma tarefa específica ou apenas quer conversar, estou aqui para ajudar.

**Usuário:** Estou estudando sobre langgraph, você conhece?

**Qwen:** Sim, posso ajudar com informações sobre o LangGraph! O LangGraph é um grafo de modelo de linguagem que representa as relações e conexões entre diferentes modelos de linguagem e seus componentes. Ele pode ser usado para visualizar e entender a arquitetura, os processos de treinamento e as características de desempenho de vários modelos de linguagem. O LangGraph pode ser particularmente útil para pesquisadores e desenvolvedores que estão trabalhando em tarefas de processamento de linguagem natural (NLP). Ele ajuda em:
1. Visualizando a Arquitetura do Modelo
2. Comparando Modelos
3. Compreendendo os Processos de Treinamento
4. Identificando Gargalos

Se você tiver perguntas específicas ou aspectos do LangGraph nos quais esteja interessado, sinta-se à vontade para me dizer!

**Usuário:** Este é um resumo da conversa até agora: Claro! Aqui está um resumo da conversa acima:
Usuário: Olá Qwen: Olá! Como posso ajudá-lo hoje? Seja com informações, uma tarefa específica ou apenas para conversar, estou aqui para ajudar.
Usuário: Crie um resumo da conversa acima.
Qwen: [Fornecida o resumo que você está lendo agora.]

Há mais alguma coisa com que você precise de ajuda?

**Qwen:** [Estendeu o resumo que você está lendo agora.]

---

Há mais alguma coisa em que você precise de ajuda?
------------------------------------------------

Ou seja, a função de corte remove a mensagem do assistente porque ela ultrapassa 100 tokens.

Mesmo excluindo mensagens, de modo que o LLM não as tenha como contexto, podemos manter uma conversa graças ao resumo da conversa que vamos gerando.

Salvar estado em SQLitelink image 14

Vimos como guardar o estado do grafo na memória, mas assim que terminamos o processo, essa memória é perdida, então vamos ver como salvá-la em SQLite

Primeiro precisamos instalar o pacote sqlite para o LangGraph.

pip install langgraph-checkpoint-sqlite

Importamos as bibliotecas de sqlite e langgraph-checkpoint-sqlite. Antes, quando guardávamos o estado na memória, usávamos memory_saver; agora usaremos SqliteSaver para guardar o estado em um banco de dados SQLite.

	
< > Input
Python
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
import os
# Create the directory if it doesn't exist
os.makedirs("state_db", exist_ok=True)
db_path = "state_db/langgraph_sqlite.db"
conn = sqlite3.connect(db_path, check_same_thread=False)
memory = SqliteSaver(conn)
Copied

Vamos criar um chatbot básico para não adicionar complexidade além da funcionalidade que queremos testar.

	
< > 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 HumanMessage, AIMessage
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)
# Nodes
def chat_model_node(state: State):
# Return the LLM's response in the correct state format
return {"messages": [llm.invoke(state["messages"])]}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("chatbot_node", chat_model_node)
# 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)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Definimos a função para imprimir as mensagens do grafo.

	
< > Input
Python
# Colors for the terminal
COLOR_GREEN = "\033[32m"
COLOR_YELLOW = "\033[33m"
COLOR_RESET = "\033[0m"
def stream_graph_updates(user_input: str, config: dict):
# Initialize a flag to track if an assistant response has been printed
assistant_response_printed = False
# Print the user's input immediately
print(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")
# Create the user's message with the HumanMessage class
user_message = HumanMessage(content=user_input)
# Stream events from the graph execution
for event in graph.stream({"messages": [user_message]}, config, stream_mode="values"):
# event is a dictionary mapping node names to their output
# Example: {'chatbot_node': {'messages': [...]}} or {'summarize_conversation_node': {'summary': '...'}}
# Iterate through node name and its output
for node_name, value in event.items():
# Check if this event is from the chatbot node which should contain the assistant's reply
if node_name == 'messages':
# Ensure the output format is as expected (list of messages)
if isinstance(value, list):
# Get the messages from the event
messages = value
# Ensure 'messages' is a non-empty list
if isinstance(messages, list) and messages:
# Get the last message (presumably the assistant's reply)
last_message = messages[-1]
# Ensure the message is an instance of AIMessage
if isinstance(last_message, AIMessage):
# Ensure the message has content to display
if hasattr(last_message, 'content'):
# Print the assistant's message content
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}{last_message.content}")
assistant_response_printed = True # Mark that we've printed the response
# Fallback if no assistant response was printed (e.g., graph error before chatbot_node)
if not assistant_response_printed:
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}[No response generated or error occurred]")
Copied

Executamos o grafo

	
< > Input
Python
USER1_THREAD_ID = "USER1"
config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}
while True:
user_input = input(f" User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print(f"{COLOR_GREEN}User: {COLOR_RESET}Exiting...")
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}Goodbye!")
break
events = stream_graph_updates(user_input, config_USER1)
Copied
>_ Output
			
User: Hello, my name is Máximo
Assistant: Hello Máximo! It's a pleasure to meet you. How can I assist you today?
User: Exiting...
Assistant: Goodbye!

Como se pode ver, apenas lhe disse como me chamo

Agora reiniciamos o notebook para que sejam eliminados todos os dados guardados na RAM do notebook e voltamos a executar o código anterior.

Vamos recriar a memória de sqlite com SqliteSaver

	
< > Input
Python
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
import os
# Create the directory if it doesn't exist
os.makedirs("state_db", exist_ok=True)
db_path = "state_db/langgraph_sqlite.db"
conn = sqlite3.connect(db_path, check_same_thread=False)
memory = SqliteSaver(conn)
Copied

Voltamos a criar o 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
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from langchain_core.messages import HumanMessage, AIMessage
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)
# Nodes
def chat_model_node(state: State):
# Return the LLM's response in the correct state format
return {"messages": [llm.invoke(state["messages"])]}
# Create graph builder
graph_builder = StateGraph(State)
# Add nodes
graph_builder.add_node("chatbot_node", chat_model_node)
# 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)
display(Image(graph.get_graph().draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Voltamos a definir a função para imprimir as mensagens do grafo.

	
< > Input
Python
# Colors for the terminal
COLOR_GREEN = "\033[32m"
COLOR_YELLOW = "\033[33m"
COLOR_RESET = "\033[0m"
def stream_graph_updates(user_input: str, config: dict):
# Initialize a flag to track if an assistant response has been printed
assistant_response_printed = False
# Print the user's input immediately
print(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")
# Create the user's message with the HumanMessage class
user_message = HumanMessage(content=user_input)
# Stream events from the graph execution
for event in graph.stream({"messages": [user_message]}, config, stream_mode="values"):
# event is a dictionary mapping node names to their output
# Example: {'chatbot_node': {'messages': [...]}} or {'summarize_conversation_node': {'summary': '...'}}
# Iterate through node name and its output
for node_name, value in event.items():
# Check if this event is from the chatbot node which should contain the assistant's reply
if node_name == 'messages':
# Ensure the output format is as expected (list of messages)
if isinstance(value, list):
# Get the messages from the event
messages = value
# Ensure 'messages' is a non-empty list
if isinstance(messages, list) and messages:
# Get the last message (presumably the assistant's reply)
last_message = messages[-1]
# Ensure the message is an instance of AIMessage
if isinstance(last_message, AIMessage):
# Ensure the message has content to display
if hasattr(last_message, 'content'):
# Print the assistant's message content
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}{last_message.content}")
assistant_response_printed = True # Mark that we've printed the response
# Fallback if no assistant response was printed (e.g., graph error before chatbot_node)
if not assistant_response_printed:
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}[No response generated or error occurred]")
Copied

E o executamos novamente

	
< > Input
Python
USER1_THREAD_ID = "USER1"
config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}
while True:
user_input = input(f" User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print(f"{COLOR_GREEN}User: {COLOR_RESET}Exiting...")
print(f"{COLOR_YELLOW}Assistant: {COLOR_RESET}Goodbye!")
break
events = stream_graph_updates(user_input, config_USER1)
Copied
>_ Output
			
User: What's my name?
Assistant: Your name is Máximo. It's nice to know and use your name as we chat. How can I assist you today, Máximo?
User: Exiting...
Assistant: Goodbye!

Como se pode ver, conseguimos recuperar o estado do grafo da base de dados SQLite.

---

➡️ **Continua na Parte 3: memória de longo prazo e human-in-the-loop**.

Continuar lendo

Últimos posts -->

Você viu esses projetos?

Gymnasia

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

Aplicativo móvel de treino pessoal com assistente de IA, biblioteca de exercícios, acompanhamento de rotinas, dieta e medidas corporais

Horeca chatbot

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

Chatbot conversacional para cozinheiros de hotéis e restaurantes. Um cozinheiro, gerente de cozinha ou serviço de quarto de um hotel ou restaurante pode falar com o chatbot para obter informações sobre receitas e menus. Mas também implementa agentes, com os quais pode editar ou criar novas receitas ou menus

Naviground

Naviground Naviground
Ver todos os projetos -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Quer assistir alguma palestra?

Últimas palestras -->

Quer melhorar com essas dicas?

Últimos tips -->

Use isso localmente

Os espaços do Hugging Face nos permitem executar modelos com demos muito simples, mas e se a demo quebrar? Ou se o usuário a deletar? Por isso, criei contêineres docker com alguns espaços interessantes, para poder usá-los localmente, aconteça o que acontecer. Na verdade, se você clicar em qualquer botão de visualização de projeto, ele pode levá-lo a um espaço que não funciona.

Flow edit

Flow edit Flow edit

Edite imagens com este modelo de Flow. Baseado em SD3 ou FLUX, você pode editar qualquer imagem e gerar novas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos os contêineres -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Você quer treinar seu modelo com esses datasets?

short-jokes-dataset

HuggingFace

Dataset com piadas em inglês

Uso: Fine-tuning de modelos de geração de texto humorístico

231K linhas 2 colunas 45 MB
Ver no HuggingFace →

opus100

HuggingFace

Dataset com traduções de inglês para espanhol

Uso: Treinamento de modelos de tradução inglês-espanhol

1M linhas 2 colunas 210 MB
Ver no HuggingFace →

netflix_titles

HuggingFace

Dataset com filmes e séries da Netflix

Uso: Análise de catálogo Netflix e sistemas de recomendação

8.8K linhas 12 colunas 3.5 MB
Ver no HuggingFace →
Ver mais datasets -->