En la primera parte construimos un chatbot básico con LangGraph y le añadimos herramientas. El problema es que no recuerda nada de un mensaje al siguiente. En este capítulo le damos **memoria a corto plazo**: persistimos el estado del grafo dentro de un hilo con checkpointers.
⚠️ Este capítulo continúa el código de la parte anterior. Para ejecutarlo necesitas el entorno y el chatbot de la Parte 1.
📚 **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
Agregar memoria al chatbot - memoria a corto plazo, memoria dentro del hilo
Nuestro chatbot ahora puede usar herramientas para responder preguntas de los usuarios, pero no recuerda el contexto de las interacciones anteriores. Esto limita su capacidad de tener conversaciones coherentes y de múltiples turnos.
LangGraph resuelve este problema a través de puntos de control persistentes o checkpoints. Si le proporcionamos un checkpointer al compilar el grafo y un thread_id al llamar al grafo, LangGraph guarda automáticamente el estado después de cada iteración en la conversación.
Cuando invoquemos el grafo nuevamente usando el mismo thread_id, el grafo cargará su estado guardado, permitiendo que el chatbot continúe donde lo dejó.
Veremos más tarde que ese checkpointing es mucho más potente que la simple memoria de chat: le permite guardar y reanudar estados complejos en cualquier momento para la recuperación de errores, flujos de trabajo con human in the loop, interacciones en el tiempo y más. Pero antes de ver todo eso, vamos a agregar puntos de control para permitir conversaciones de varias iteraciones.
InputPythonimport osimport dotenvdotenv.load_dotenv()HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")TAVILY_API_KEY = os.getenv("TAVILY_LANGGRAPH_API_KEY")Copied
Para empezar, creamos un checkpointer MemorySaver.
InputPythonfrom langgraph.checkpoint.memory import MemorySavermemory = MemorySaver()Copied
**Aviso**
> Estamos usando un
checkpointeren memoria, es decir, se guarda en la RAM y cuando se termine de ejecutar el grafo se elimina. Esto nos vale para nuestro caso, ya que es un ejemplo para aprender a usarLangGraph. En una aplicación de producción, es probable que se necesite cambiar esto para usarlo conSqliteSaveroPostgresSavery conectarnos a nuestra propia base de datos.
A continuación, definimos el grafo.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesclass State(TypedDict):messages: Annotated[list, add_messages]graph_builder = StateGraph(State)Copied
Definimos la tool
InputPythonfrom langchain_community.utilities.tavily_search import TavilySearchAPIWrapperfrom langchain_community.tools.tavily_search import TavilySearchResultswrapper = TavilySearchAPIWrapper(tavily_api_key=TAVILY_API_KEY)tool = TavilySearchResults(api_wrapper=wrapper, max_results=2)tools_list = [tool]Copied
A continuación, el LLM con las bind_tools y lo añadimos al grafo
InputPythonfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom huggingface_hub import loginos.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Create the LLMlogin(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the modelMODEL = "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 modelllm = ChatHuggingFace(llm=model)# Modification: tell the LLM which tools it can callllm_with_tools = llm.bind_tools(tools_list)# Define the chatbot functiondef chatbot_function(state: State):return {"messages": [llm_with_tools.invoke(state["messages"])]}# Add the chatbot nodegraph_builder.add_node("chatbot_node", chatbot_function)Copied
<langgraph.graph.state.StateGraph at 0x1173534d0>
Antes construimos nuestro propio BasicToolNode para aprender cómo funciona, ahora lo reemplazaremos con el método de LangGraph ToolNode y tools_condition, ya que estos hacen algunas cosas buenas como la ejecución paralela de API. Aparte de eso, el resto es igual que antes.
InputPythonfrom langgraph.prebuilt import ToolNode, tools_conditiontool_node = ToolNode(tools=[tool])graph_builder.add_node("tools", tool_node)Copied
<langgraph.graph.state.StateGraph at 0x1173534d0>
Añadimos el nodo de tools_condition al grafo
InputPythongraph_builder.add_conditional_edges("chatbot_node",tools_condition,)Copied
<langgraph.graph.state.StateGraph at 0x1173534d0>
Añadimos el nodo de tools al grafo
InputPythongraph_builder.add_edge("tools", "chatbot_node")Copied
<langgraph.graph.state.StateGraph at 0x1173534d0>
Añadimos el nodo de START al grafo
InputPythongraph_builder.add_edge(START, "chatbot_node")Copied
<langgraph.graph.state.StateGraph at 0x1173534d0>
Compilamos el grafo añadiendo el checkpointer
InputPythongraph = graph_builder.compile(checkpointer=memory)Copied
Lo representamos gráficamente
InputPythonfrom IPython.display import Image, displaytry:display(Image(graph.get_graph().draw_mermaid_png()))except Exception as e:print(f"Error al visualizar el grafo: {e}")Copied
<IPython.core.display.Image object>
Creamos una configuración con un thread_id de un usuario
InputPythonUSER1_THREAD_ID = "1"config_USER1 = {"configurable": {"thread_id": USER1_THREAD_ID}}Copied
InputPythonuser_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
================================ Human Message =================================Hi there! My name is Maximo.================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args: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: 0Args:{"query": "Arguments["image={"}
InputPythonuser_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
================================ Human Message =================================Do you remember my name?================================== Ai Message ==================================Of course! You mentioned your name is Maximo.
Cómo se puede ver, no hemos pasado una lista con los mensajes, todo está siendo gestionado por el checkpointer.
Si ahora probamos con otro usuario, es decir, con otro thread_id, veremos que el grafo no recuerda la conversación anterior.
InputPythonUSER2_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
================================ Human Message =================================Do you remember my name?================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args: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: 0Args: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: 0Args: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?
Ahora que nuestro chatbot tiene herramientas de búsqueda y memoria, vamos a repetir el ejemplo anterior, donde le pregunto por el resultado del último partido del Real Madrid en la Liga y luego por qué jugadores jugaron.
InputPythonUSER3_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
================================ 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: 0Args: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.
Ahora le preguntamos por los jugadores que jugaron en el partido.
InputPythonuser_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
================================ Human Message =================================Which players played the match?================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args: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: 0Args: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: 0Args: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.
Tras mucho buscar, al final lo encuentra. Por lo que ya tenemos un chatbot con tools y memoria.
Por ahora, hemos creado unos checkpoints en tres hilos diferentes. Pero, ¿qué entra en cada checkpoint? Para inspeccionar el estado de un grafo para una configuración dada podemos usar el método get_state(config).
InputPythonsnapshot = graph.get_state(config_USER3)snapshotCopied
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=())
Si queremos ver el siguiente nodo a procesar, podemos usar el atributo next
InputPythonsnapshot.nextCopied
()
Dado que el grafo ha finalizado, next está vacío. Si obtienes un estado desde dentro de una invocación del grafo, next indica qué nodo se ejecutará a continuación.
La instantánea anterior (snapshot) contiene los valores de estado actuales, la configuración correspondiente y el siguiente nodo (next) a procesar. En nuestro caso, el grafo ha alcanzado el estado END, por eso next está vacío.
Vamos a volver a escribir todo el código para que sea más legible.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom huggingface_hub import loginfrom langchain_community.utilities.tavily_search import TavilySearchAPIWrapperfrom langchain_community.tools.tavily_search import TavilySearchResultsfrom langchain_core.messages import ToolMessagefrom langgraph.prebuilt import ToolNode, tools_conditionfrom langgraph.checkpoint.memory import MemorySaverfrom IPython.display import Image, displayimport jsonimport osos.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracingimport dotenvdotenv.load_dotenv()HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")TAVILY_API_KEY = os.getenv("TAVILY_LANGGRAPH_API_KEY")# Stateclass State(TypedDict):messages: Annotated[list, add_messages]# Toolswrapper = TavilySearchAPIWrapper(tavily_api_key=TAVILY_API_KEY)tool = TavilySearchResults(api_wrapper=wrapper, max_results=2)tools_list = [tool]# Create the LLM modellogin(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the modelMODEL = "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 modelllm = ChatHuggingFace(llm=model)# Create the LLM with toolsllm_with_tools = llm.bind_tools(tools_list)# Tool nodetool_node = ToolNode(tools=tools_list)# Functionsdef chatbot_function(state: State):return {"messages": [llm_with_tools.invoke(state["messages"])]}# Start to build the graphgraph_builder = StateGraph(State)# Add nodes to the graphgraph_builder.add_node("chatbot_node", chatbot_function)graph_builder.add_node("tools", tool_node)# Add edgesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_conditional_edges( "chatbot_node", tools_condition)graph_builder.add_edge("tools", "chatbot_node")# Compile the graphmemory = MemorySaver()graph = graph_builder.compile(checkpointer=memory)# Display the graphtry:display(Image(graph.get_graph().draw_mermaid_png()))except Exception as e:print(f"Error al visualizar el grafo: {e}")Copied
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 again2. 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)`
InputPythonUSER1_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
================================ 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.
InputPythonuser_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
================================ 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?
**¡Felicidades!** Nuestro chatbot ahora puede mantener el estado de conversación en todas las sesiones gracias al sistema de puntos de control (checkpoints) de LangGraph. Esto abre posibilidades para interacciones más naturales y contextuales. El control de LangGraph incluso maneja estados de grafos complejos.
Más
Chatbot con mensaje de resumen
Si vamos a manejar el contexto de la conversación para no gastar muchos tokens, una cosa que podemos hacer para mejorar la conversación es añadir un mensaje con el resumen de la conversación. Esto puede ser útil para el ejemplo anterior, en el que hemos filtrado tanto el estado que el LLM no tiene contexto suficiente.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom langchain_core.messages import RemoveMessage, trim_messages, SystemMessage, HumanMessage, AIMessage, RemoveMessagefrom langgraph.checkpoint.memory import MemorySaverfrom huggingface_hub import loginfrom IPython.display import Image, displayimport osimport dotenvdotenv.load_dotenv()HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")memory_saver = MemorySaver()class State(TypedDict):messages: Annotated[list, add_messages]summary: stros.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Create the LLM modellogin(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the modelMODEL = "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 modelllm = ChatHuggingFace(llm=model)# Print functionsdef print_message(m):if isinstance(m, HumanMessage):message_content = m.contentmessage_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.contentmessage_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.contentmessage_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.contentmessage_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.contentmessage_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")# Nodesdef 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 2if len(state["messages"]) > 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_nodeprint(" --- 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 parameterstrimmed_messages = trim_messages(state["messages"],max_tokens=100, # Maximum tokens allowed in the trimmed liststrategy="last", # Keep the latest messagestoken_counter=llm, # Use the LLM's tokenizer to count tokensallow_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 removedmessages_to_remove = [RemoveMessage(id=msg_id) for msg_id in ids_to_remove]# Print the result of the trimmingprint(" --- 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 existssummary = 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 messageif summary:# Add the summary to the system messagesystem_message = f"Summary of the conversation earlier: {summary}"# Add the system message to the messages at the beginningmessages = [SystemMessage(content=system_message)] + state["messages"]# If there is no summary, just return the messageselse: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 messagesresponse = 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 formatreturn {"messages": [response]}def summarize_conversation(state: State):# Get summary of the conversation if it existssummary = 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 messageif 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 oneelse: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 messagesmessages = 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 messagesresponse = llm.invoke(messages)print(" --- 4 messages (output of summarize_conversation) ---")print_message(response)print(" ------------------------------------------------")# Return the summary message in the correct state formatreturn {"summary": response.content}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_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 nodesgraph_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 graphgraph = graph_builder.compile(checkpointer=memory_saver)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Cómo podemos ver, tenemos:
- Función de filtrado de mensajes: Si en el estado hay más de 2 mensajes, se eliminan todos los mensajes excepto los 2 últimos.
- Función de trimado de mensajes: Se eliminan los mensajes que superan los 100 tokens.
- Función de chatbot: Se ejecuta el modelo con los mensajes filtrados y trimados. Además, si existe un resumen, se añade al mensaje de sistema.
- Función de resumen: Se crea un resumen de la conversación.
Creamos una función para imprimir los mensajes del grafo.
InputPython# Colors for the terminalCOLOR_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 printedassistant_response_printed = False# Print the user's input immediatelyprint(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")# Create the user's message with the HumanMessage classuser_message = HumanMessage(content=user_input)# Stream events from the graph executionfor 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 outputfor node_name, value in event.items():# Check if this event is from the chatbot node which should contain the assistant's replyif node_name == 'messages':# Ensure the output format is as expected (list of messages)if isinstance(value, list):# Get the messages from the eventmessages = value# Ensure 'messages' is a non-empty listif 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 AIMessageif isinstance(last_message, AIMessage):# Ensure the message has content to displayif hasattr(last_message, 'content'):# Print the assistant's message contentprint(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
Ahora ejecutamos el grafo
InputPythonUSER1_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!")breakevents = stream_graph_updates(user_input, config_USER1)Copied
User: Hello--- 1 messages (input to filter_messages) ---[HumanMessage]: HelloNo summary of the conversation--------------------------------------------------- 1 messages (output of filter_messages) ---No summary of the conversation--------------------------------------------------- 2 messages (input to trim_messages) ---[HumanMessage]: HelloNo summary of the conversation--------------------------------------------------- 2 messages (output of trim_messages - after trimming) ---[HumanMessage]: HelloNo summary of the conversation--------------------------------------------------- 3 messages (input to chat_model_node) ---[HumanMessage]: HelloNo 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!
Si nos vamos al final de la conversación, podemos ver
--- 2 messages (input to trim_messages) ---
[AIMessage]: Yes, I can help with information about LangGraph! LangGraph is a language model graph that represents the relationships and connections between different language models and their components. It can be used to visualize and understand the architecture, training processes, and performance characteristics of various language models.
LangGraph can be particularly useful for researchers and developers who are working on natural language processing (NLP) tasks. It helps in:
1. **Visualizing Model Architecture**: Provides a clear and detailed view of how different components of a language model are connected.
2. **Comparing Models**: Allows for easy comparison of different language models in terms of their structure, training data, and performance metrics.
3. **Understanding Training Processes**: Helps in understanding the training dynamics and the flow of data through the model.
4. **Identifying Bottlenecks**: Can help in identifying potential bottlenecks or areas for improvement in the model.
If you have specific questions or aspects of LangGraph you're interested in, feel free to let me know!
[HumanMessage]: I would like to know about using langsmith with huggingface llms, the integration of huggingface
Summary of the conversation: Sure! Here's an extended summary of the conversation:
---
**User:** Hello
**Qwen:** Hello! How can I assist you today? Whether you need help with information, a specific task, or just want to chat, I'm here to help.
**User:** I am studying about langgraph, do you know it?
**Qwen:** Yes, I can help with information about LangGraph! LangGraph is a language model graph that represents the relationships and connections between different language models and their components. It can be used to visualize and understand the architecture, training processes, and performance characteristics of various language models. LangGraph can be particularly useful for researchers and developers who are working on natural language processing (NLP) tasks. It helps in:
1. Visualizing Model Architecture
2. Comparing Models
3. Understanding Training Processes
4. Identifying Bottlenecks
If you have specific questions or aspects of LangGraph you're interested in, feel free to let me know!
**User:** This is a summary of the conversation to date: Sure! Here's a summary of the conversation above:
User: Hello
Qwen: Hello! How can I assist you today? Whether you need help with information, a specific task, or just want to chat, I'm here to help.
User: Create a summary of the conversation above.
Qwen: [Provided the summary you are now reading.]
Is there anything else you need assistance with?
**Qwen:** [Extended the summary you are now reading.]
---
Is there anything else you need assistance with?
------------------------------------------------Vemos que en los mensajes del estado solo se conservan
[AIMessage]: Yes, I can help with information about LangGraph! LangGraph is a language model graph that represents the relationships and connections between different language models and their components. It can be used to visualize and understand the architecture, training processes, and performance characteristics of various language models.
LangGraph can be particularly useful for researchers and developers who are working on natural language processing (NLP) tasks. It helps in:
1. **Visualizing Model Architecture**: Provides a clear and detailed view of how different components of a language model are connected.
2. **Comparing Models**: Allows for easy comparison of different language models in terms of their structure, training data, and performance metrics.
3. **Understanding Training Processes**: Helps in understanding the training dynamics and the flow of data through the model.
4. **Identifying Bottlenecks**: Can help in identifying potential bottlenecks or areas for improvement in the model.
If you have specific questions or aspects of LangGraph you're interested in, feel free to let me know!
[HumanMessage]: I would like to know about using langsmith with huggingface llms, the integration of huggingfaceEs decir, la función de filtrado solo mantiene los 2 últimos mensajes.
Pero después podemos ver
--- 2 messages (output of trim_messages - after trimming) ---
[HumanMessage]: I would like to know about using langsmith with huggingface llms, the integration of huggingface
Summary of the conversation: Sure! Here's an extended summary of the conversation:
---
**User:** Hello
**Qwen:** Hello! How can I assist you today? Whether you need help with information, a specific task, or just want to chat, I'm here to help.
**User:** I am studying about langgraph, do you know it?
**Qwen:** Yes, I can help with information about LangGraph! LangGraph is a language model graph that represents the relationships and connections between different language models and their components. It can be used to visualize and understand the architecture, training processes, and performance characteristics of various language models. LangGraph can be particularly useful for researchers and developers who are working on natural language processing (NLP) tasks. It helps in:
1. Visualizing Model Architecture
2. Comparing Models
3. Understanding Training Processes
4. Identifying Bottlenecks
If you have specific questions or aspects of LangGraph you're interested in, feel free to let me know!
**User:** This is a summary of the conversation to date: Sure! Here's a summary of the conversation above:
User: Hello
Qwen: Hello! How can I assist you today? Whether you need help with information, a specific task, or just want to chat, I'm here to help.
User: Create a summary of the conversation above.
Qwen: [Provided the summary you are now reading.]
Is there anything else you need assistance with?
**Qwen:** [Extended the summary you are now reading.]
---
Is there anything else you need assistance with?
------------------------------------------------Es decir, la función de trimado elimina el mensaje del asistente porque supera los 100 tokens.
Aun eliminando mensajes, por lo que no los tiene como contexto el LLM, podemos tener una conversación gracias al resumen de la conversación que vamos generando.
Guardar estado en SQLite
Hemos visto cómo guardar el estado del grafo en memoria, pero en cuanto terminamos el proceso, esa memoria se pierde, por lo que vamos a ver cómo guardarla en SQLite
Primero necesitamos instalar el paquete de sqlite para LangGraph.
pip install langgraph-checkpoint-sqliteImportamos las librerías de sqlite y langgraph-checkpoint-sqlite. Antes, cuando guardábamos el estado en memoria usábamos memory_saver, ahora usaremos SqliteSaver para guardar el estado en una base de datos SQLite.
InputPythonimport sqlite3from langgraph.checkpoint.sqlite import SqliteSaverimport os# Create the directory if it doesn't existos.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 a crear un chatbot básico para no añadir complejidad aparte de la funcionalidad que queremos probar.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom langchain_core.messages import HumanMessage, AIMessagefrom huggingface_hub import loginfrom IPython.display import Image, displayimport osimport dotenvdotenv.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 modellogin(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the modelMODEL = "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 modelllm = ChatHuggingFace(llm=model)# Nodesdef chat_model_node(state: State):# Return the LLM's response in the correct state formatreturn {"messages": [llm.invoke(state["messages"])]}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("chatbot_node", chat_model_node)# Connecto nodesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_edge("chatbot_node", END)# Compile the graphgraph = graph_builder.compile(checkpointer=memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Definimos la función para imprimir los mensajes del grafo.
InputPython# Colors for the terminalCOLOR_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 printedassistant_response_printed = False# Print the user's input immediatelyprint(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")# Create the user's message with the HumanMessage classuser_message = HumanMessage(content=user_input)# Stream events from the graph executionfor 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 outputfor node_name, value in event.items():# Check if this event is from the chatbot node which should contain the assistant's replyif node_name == 'messages':# Ensure the output format is as expected (list of messages)if isinstance(value, list):# Get the messages from the eventmessages = value# Ensure 'messages' is a non-empty listif 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 AIMessageif isinstance(last_message, AIMessage):# Ensure the message has content to displayif hasattr(last_message, 'content'):# Print the assistant's message contentprint(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
Ejecutamos el grafo
InputPythonUSER1_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!")breakevents = stream_graph_updates(user_input, config_USER1)Copied
User: Hello, my name is MáximoAssistant: Hello Máximo! It's a pleasure to meet you. How can I assist you today?User: Exiting...Assistant: Goodbye!
Como se puede ver, solo le he dicho cómo me llamo
Ahora reiniciamos el notebook para que se eliminen todos los datos guardados en RAM del notebook y volvemos a ejecutar el código anterior.
Volvemos a crear la memoria de sqlite con SqliteSaver
InputPythonimport sqlite3from langgraph.checkpoint.sqlite import SqliteSaverimport os# Create the directory if it doesn't existos.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
Volvemos a crear el grafo
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom langchain_core.messages import HumanMessage, AIMessagefrom huggingface_hub import loginfrom IPython.display import Image, displayimport osimport dotenvdotenv.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 modellogin(token=HUGGINGFACE_TOKEN) # Login to HuggingFace to use the modelMODEL = "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 modelllm = ChatHuggingFace(llm=model)# Nodesdef chat_model_node(state: State):# Return the LLM's response in the correct state formatreturn {"messages": [llm.invoke(state["messages"])]}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("chatbot_node", chat_model_node)# Connecto nodesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_edge("chatbot_node", END)# Compile the graphgraph = graph_builder.compile(checkpointer=memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Volvemos a definir la función para imprimir los mensajes del grafo.
InputPython# Colors for the terminalCOLOR_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 printedassistant_response_printed = False# Print the user's input immediatelyprint(f" {COLOR_GREEN}User: {COLOR_RESET}{user_input}")# Create the user's message with the HumanMessage classuser_message = HumanMessage(content=user_input)# Stream events from the graph executionfor 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 outputfor node_name, value in event.items():# Check if this event is from the chatbot node which should contain the assistant's replyif node_name == 'messages':# Ensure the output format is as expected (list of messages)if isinstance(value, list):# Get the messages from the eventmessages = value# Ensure 'messages' is a non-empty listif 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 AIMessageif isinstance(last_message, AIMessage):# Ensure the message has content to displayif hasattr(last_message, 'content'):# Print the assistant's message contentprint(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
Y lo volvemos a ejecutar
InputPythonUSER1_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!")breakevents = stream_graph_updates(user_input, config_USER1)Copied
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 puede ver, hemos podido recuperar el estado del grafo de la base de datos SQLite.
---
➡️ **Continúa en la Parte 3: memoria a largo plazo y human-in-the-loop**.